Skip to content

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.

LocationRole today
tsconfig.jsonnoEmit: true, moduleResolution: "bundler", allowImportingTsExtensions, verbatimModuleSyntax, paths: {"@lib/*","@cli/*"}; TS 5.9.3 installed (rewriteRelativeImportExtensions available)
plugin/lib/registry.ts#isModuleFileop 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.tsmigration-transform loader: computed-path dynamic import() of per-entity modules (same .ts assumption)
plugin/lib/services/docs/site.tsspawns sibling helper by literal list-ops.ts path
plugin/lib/services/dashboard/server.tsdynamic 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.tgzfile: tarball dep imported by 18 shipped files — unresolvable for consumers unless the tarball ships inside the package
plugin/.claude-plugin/plugin.jsonversion 0.2.0 — disagrees with marketplace.json (0.1.1) and package.json (0.0.0)
plugin/cli/sdlc.tsCLI entry; emitted sdlc.js must carry a #!/usr/bin/env node shebang for the bin entry
plugin/conventions/typescript-runtime.mdteaches “Bun is a hard dependency … no transpile-to-JS phase, no dist/” — contradicts the published-artifact story
moon.ymlroot 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.

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.

  1. Registry discovery becomes extension-aware: derive the accepted extension from the running module’s own (import.meta.url ends .js → scan .js/*-service.js; else .ts) in plugin/lib/registry.ts (isModuleFile, serviceModuleFiles) and the migration-transform loader in plugin/lib/model/ops/migrate.ts; same treatment for site.ts’s list-ops.ts sibling-helper path. Unit-test by parameterizing the extension.
  2. Add tsconfig.build.json: extends root; noEmit: false, outDir (staging), module/moduleResolution: "nodenext", rewriteRelativeImportExtensions: true, sourceMap: true, declaration: false; include only plugin/lib + plugin/cli; exclude tests/, *.test.ts, _example/, plugin/site-template.
  3. Resolve the 321 @lib//@cli/ alias imports in emitted output with tsc-alias (new devDep) as a post-emit pass — it also rewrites the two dynamic alias imports in dashboard/server.ts.
  4. Give plugin/cli/sdlc.ts a #!/usr/bin/env node shebang (tsc preserves it into sdlc.js; dev invocation goes through the launcher and ignores it).
  5. Add op sdlc plugin build-artifact (path: ['plugin','build-artifact']): clean staging dir → tsc build → tsc-alias → copy runtime assets (25 .eta under plugin/lib, 11 entities/*/definition.md, plugin/site-template/ wholesale) → copy harness surface (plugin/skills/, plugin/conventions/, plugin/.claude-plugin/, plugin/cli/sdlc launcher, executable bits preserved) → copy vendor/markdown-contract-0.2.0.tgz, README.md → write the publish package.json (see step 6) → verify step: every source op module has a staged .js twin, and staged op count equals dev op count.
  6. Reshape package.json: rename @sksizer/sdlc; add bin: {"sdlc": "plugin/cli/sdlc.js"}; add files: allowlist; prepacksdlc plugin build-artifact; remove dead deps pathe, proper-lockfile, @types/proper-lockfile; keep engines.node >=20. Unify the version triple: root package.json becomes the single source; build stamps plugin/.claude-plugin/plugin.json and marketplace.json from it.
  7. Wire moon.yml root build task running the op; leave justfile alone.
  8. Rewrite plugin/conventions/typescript-runtime.md in place for the two-runtime model: Bun + source in the repo; Node + built JS in the artifact; the launcher as the seam.
  9. 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 validate against a fixture project; then npm pack the staged tree and install the tarball into a scratch project — npx sdlc --help works with Bun absent from PATH (markdown-contract’s file:./vendor/… resolving relative to the installed package root is part of this check).
LocationKindChange
plugin/lib/registry.tsmodifyextension-aware isModuleFile / serviceModuleFiles (derive from own module extension)
plugin/lib/model/ops/migrate.tsmodifyextension-aware migration-transform module resolution
plugin/lib/services/docs/site.tsmodifysibling helper path derives extension from running module
plugin/lib/tests/modifyregistry-discovery tests parameterized over .ts/.js
tsconfig.build.jsonnewemit config: nodenext, rewriteRelativeImportExtensions, sourcemaps, staging outDir
plugin/lib/services/plugin/ops/build-artifact.tsnewthe staging op: build + tsc-alias + asset/harness copy + package.json synthesis + self-verify
plugin/lib/services/plugin/tests/modifybuild-artifact op test (staged tree shape, op-count parity, version stamping)
plugin/cli/sdlc.tsmodify#!/usr/bin/env node shebang
package.jsonmodifyrename @sksizer/sdlc; bin; files; prepack; drop pathe/proper-lockfile/@types/proper-lockfile; add tsc-alias devDep
plugin/.claude-plugin/plugin.jsonmodifyversion stamped from root package.json by the build
plugin/.claude-plugin/marketplace.jsonmodifyversion stamped from root package.json by the build
moon.ymlmodifyroot build task → sdlc plugin build-artifact
plugin/conventions/typescript-runtime.mdmodifyrewrite in place for the two-runtime model
  • 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, and entities validate against a fixture project all succeed.
  • AC-3: npm pack of the staged tree installs into a scratch project where npx sdlc --help exits 0 with Bun absent, and markdown-contract resolves from the packaged vendor/ 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/sdlc launcher all present, launcher executable).
  • AC-5: root package.json is named @sksizer/sdlc with bin/files/prepack; pathe and proper-lockfile are gone; bun install && bun test && bunx tsc --noEmit stay green.
  • AC-6: built plugin.json/marketplace.json versions equal the root package.json version in the staged tree.
  • 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.js branch from that task).
  • Project-root / consumer-path semantics — T-T0O9.
  • Pushing the staged tree anywhere — T-75UD owns 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-contract as a standalone package — the in-tree vendor/ tarball ships inside the artifact; revisit only if the pilot shows file: resolution failing in a consumer installer.
  • T-WGV4 — built output must be runtime-agnostic before plain-Node smoke tests can pass (hard, recorded in depends_on).
  • T-ITMN’s launcher branch is what makes the staged cli/sdlc pick Node; either order works, but both must land before T-75UD.
  • D-0014 Migration step 3; layout invariant motivated by the path audit (2026-07-02): ../×4 hops in info-report.ts/setup.ts/generate.ts and ../×2 in backlog_cli/create.ts survive only if the published tree preserves the plugin/ subtree depth.
  • Build-facts survey 2026-07-02: 458 .ts-suffixed relative imports, 321 alias imports, 25+2 .eta, 11 definition.md, schema.json no longer a live runtime asset (Zod modules are), dashboard serves inline HTML (no SPA build output involved).

T-WGV4-runtime-agnostic-shipped-code


← Back to Tasks