T-XTGT-publish-build-and-package-reshape
Status: closed/done · Impact: high · Complexity: large
D-0014’s npm artifact must ship built JS (Node refuses to type-strip
under node_modules), but the repo has no emit path at all: noEmit
tsconfig, no bin, no files:, and — most severely — a registry
op-discovery walk that filters on .ts filenames, so a naive compile
yields a CLI that discovers zero ops. This task builds the publishable
tree: sdlc plugin build-artifact producing a package that plain Node
can run end to end.
| Location | Role today |
|---|---|
tsconfig.json | noEmit: true, moduleResolution: "bundler", allowImportingTsExtensions, verbatimModuleSyntax, paths: {"@lib/*","@cli/*"}; TS 5.9.3 installed (rewriteRelativeImportExtensions available) |
plugin/lib/registry.ts#isModuleFile | op discovery accepts only entry.endsWith(".ts"); services matched as *-service.ts; discovered files loaded via dynamic import() — a compiled tree discovers nothing |
plugin/lib/model/ops/migrate.ts | migration-transform loader: computed-path dynamic import() of per-entity modules (same .ts assumption) |
plugin/lib/services/docs/site.ts | spawns sibling helper by literal list-ops.ts path |
plugin/lib/services/dashboard/server.ts | dynamic alias imports import("@lib/services/lease"), import("@lib/config") |
package.json | @sksizer/sdlc-plugin 0.0.0, private: true, no bin/files/prepack; dead deps pathe, proper-lockfile (+@types/proper-lockfile) with zero import sites |
vendor/markdown-contract-0.2.0.tgz | file: tarball dep imported by 18 shipped files — unresolvable for consumers unless the tarball ships inside the package |
plugin/.claude-plugin/plugin.json | version 0.2.0 — disagrees with marketplace.json (0.1.1) and package.json (0.0.0) |
plugin/cli/sdlc.ts | CLI entry; emitted sdlc.js must carry a #!/usr/bin/env node shebang for the bin entry |
plugin/conventions/typescript-runtime.md | teaches “Bun is a hard dependency … no transpile-to-JS phase, no dist/” — contradicts the published-artifact story |
moon.yml | root moon project has test and check tasks; no build |
Import shape (relevant to emit): 458 relative imports, 100% .ts-suffixed
(rewritable by TS ≥5.7); 321 @lib//@cli/ alias imports that tsc
does not rewrite in emit.
Proposed
Section titled “Proposed”sdlc plugin build-artifact --out <dir> deterministically stages the
publishable tree. Layout invariant (shared with T-ITMN and
T-T0O9): the staged tree mirrors the repo layout — package.json at
root, the plugin/ subtree preserved with .js+.map in place of
.ts, runtime assets and the harness surface copied in place — so
every module-relative read, ../×N hop, and ${CLAUDE_PLUGIN_ROOT}
reference survives unchanged. Registry discovery is extension-aware.
package.json is publish-shaped (@sksizer/sdlc, bin, files,
prepack). Plain Node runs the staged CLI end to end.
Approach
Section titled “Approach”- Registry discovery becomes extension-aware: derive the accepted
extension from the running module’s own (
import.meta.urlends.js→ scan.js/*-service.js; else.ts) inplugin/lib/registry.ts(isModuleFile,serviceModuleFiles) and the migration-transform loader inplugin/lib/model/ops/migrate.ts; same treatment forsite.ts’slist-ops.tssibling-helper path. Unit-test by parameterizing the extension. - Add
tsconfig.build.json: extends root;noEmit: false,outDir(staging),module/moduleResolution: "nodenext",rewriteRelativeImportExtensions: true,sourceMap: true,declaration: false; include onlyplugin/lib+plugin/cli; excludetests/,*.test.ts,_example/,plugin/site-template. - Resolve the 321
@lib//@cli/alias imports in emitted output withtsc-alias(new devDep) as a post-emit pass — it also rewrites the two dynamic alias imports indashboard/server.ts. - Give
plugin/cli/sdlc.tsa#!/usr/bin/env nodeshebang (tsc preserves it intosdlc.js; dev invocation goes through the launcher and ignores it). - Add op
sdlc plugin build-artifact(path: ['plugin','build-artifact']): clean staging dir → tsc build → tsc-alias → copy runtime assets (25.etaunderplugin/lib, 11entities/*/definition.md,plugin/site-template/wholesale) → copy harness surface (plugin/skills/,plugin/conventions/,plugin/.claude-plugin/,plugin/cli/sdlclauncher, executable bits preserved) → copyvendor/markdown-contract-0.2.0.tgz,README.md→ write the publishpackage.json(see step 6) → verify step: every source op module has a staged.jstwin, and staged op count equals dev op count. - Reshape
package.json: rename@sksizer/sdlc; addbin: {"sdlc": "plugin/cli/sdlc.js"}; addfiles:allowlist;prepack→sdlc plugin build-artifact; remove dead depspathe,proper-lockfile,@types/proper-lockfile; keepengines.node >=20. Unify the version triple: rootpackage.jsonbecomes the single source; build stampsplugin/.claude-plugin/plugin.jsonandmarketplace.jsonfrom it. - Wire
moon.ymlrootbuildtask running the op; leavejustfilealone. - Rewrite
plugin/conventions/typescript-runtime.mdin place for the two-runtime model: Bun + source in the repo; Node + built JS in the artifact; the launcher as the seam. - Verify: stage the artifact, then from the staged dir under plain Node
run
node plugin/cli/sdlc.js --help,… project doctor --output json,… task create --dry-run x,… entities validateagainst a fixture project; thennpm packthe staged tree and install the tarball into a scratch project —npx sdlc --helpworks with Bun absent from PATH (markdown-contract’sfile:./vendor/…resolving relative to the installed package root is part of this check).
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
plugin/lib/registry.ts | modify | extension-aware isModuleFile / serviceModuleFiles (derive from own module extension) |
plugin/lib/model/ops/migrate.ts | modify | extension-aware migration-transform module resolution |
plugin/lib/services/docs/site.ts | modify | sibling helper path derives extension from running module |
plugin/lib/tests/ | modify | registry-discovery tests parameterized over .ts/.js |
tsconfig.build.json | new | emit config: nodenext, rewriteRelativeImportExtensions, sourcemaps, staging outDir |
plugin/lib/services/plugin/ops/build-artifact.ts | new | the staging op: build + tsc-alias + asset/harness copy + package.json synthesis + self-verify |
plugin/lib/services/plugin/tests/ | modify | build-artifact op test (staged tree shape, op-count parity, version stamping) |
plugin/cli/sdlc.ts | modify | #!/usr/bin/env node shebang |
package.json | modify | rename @sksizer/sdlc; bin; files; prepack; drop pathe/proper-lockfile/@types/proper-lockfile; add tsc-alias devDep |
plugin/.claude-plugin/plugin.json | modify | version stamped from root package.json by the build |
plugin/.claude-plugin/marketplace.json | modify | version stamped from root package.json by the build |
moon.yml | modify | root build task → sdlc plugin build-artifact |
plugin/conventions/typescript-runtime.md | modify | rewrite in place for the two-runtime model |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
sdlc plugin build-artifact --out <dir>exits 0 and the staged tree’s registry discovers the same op count as the dev tree (asserted by the op’s self-verify and its test). - AC-2: from the staged tree under plain Node (Bun not on PATH):
node plugin/cli/sdlc.js --help,project doctor --output json,task create --dry-run x, andentities validateagainst a fixture project all succeed. - AC-3:
npm packof the staged tree installs into a scratch project wherenpx sdlc --helpexits 0 with Bun absent, andmarkdown-contractresolves from the packagedvendor/tarball. - AC-4: staged
plugin/layout mirrors the source layout (spot-assert:plugin/lib/model/entities/task/body-template.eta,plugin/skills/task-work/SKILL.md,plugin/site-template/package.json,plugin/cli/sdlclauncher all present, launcher executable). - AC-5: root
package.jsonis named@sksizer/sdlcwithbin/files/prepack;patheandproper-lockfileare gone;bun install && bun test && bunx tsc --noEmitstay green. - AC-6: built
plugin.json/marketplace.jsonversions equal the rootpackage.jsonversion in the staged tree.
Out of scope
Section titled “Out of scope”- Removing the
Bun.*call sites —T-WGV4(this task depends on it). - The skill-corpus invocation rewrite —
T-ITMN(the staged launcher picks Node via its sibling-sdlc.jsbranch from that task). - Project-root / consumer-path semantics —
T-T0O9. - Pushing the staged tree anywhere —
T-75UDowns release CI and the dist repo. - Bundling (esbuild/bun build): the registry’s walk-then-import pattern is deliberately kept; the build is transpile-in-place, not a bundle.
- Publishing
markdown-contractas a standalone package — the in-treevendor/tarball ships inside the artifact; revisit only if the pilot showsfile:resolution failing in a consumer installer.
Dependencies
Section titled “Dependencies”T-WGV4— built output must be runtime-agnostic before plain-Node smoke tests can pass (hard, recorded independs_on).T-ITMN’s launcher branch is what makes the stagedcli/sdlcpick Node; either order works, but both must land beforeT-75UD.
Discovery context
Section titled “Discovery context”- D-0014 Migration step 3; layout invariant motivated by the path audit
(2026-07-02):
../×4hops ininfo-report.ts/setup.ts/generate.tsand../×2inbacklog_cli/create.tssurvive only if the published tree preserves theplugin/subtree depth. - Build-facts survey 2026-07-02: 458
.ts-suffixed relative imports, 321 alias imports, 25+2.eta, 11definition.md,schema.jsonno longer a live runtime asset (Zod modules are), dashboard serves inline HTML (no SPA build output involved).
Depends on
Section titled “Depends on”T-WGV4-runtime-agnostic-shipped-code