T-T0O9-package-root-path-assumptions
Status: closed/done · Impact: high · Complexity: medium
Installed as a package, the code root (inside node_modules or a dist
checkout) and the project root (the consumer repo holding docs/planning/
and sdlc.yaml) diverge for the first time. The audit found the core
already clean — project root is flag-or-cwd, never derived from the code
tree — but a handful of fallbacks and the shipped site scaffold still
assume code root == repo root. This task removes those assumptions so
D-0014’s consumer install cannot silently read or write the wrong tree.
| Location | Role today |
|---|---|
plugin/cli/registry_adapter.ts | project-root resolution: --project-root <path> else process.cwd(); ops with projectRoot in their input schema get it threaded — correct by construction |
plugin/lib/config/load.ts#loadConfig | reads <projectRoot>/sdlc.yaml; lexical path, no ancestor walk-up — running from a consumer subdirectory silently degrades to all-defaults |
plugin/lib/services/docs/generate.ts#repoRoot | no-arg main() fallback: join(import.meta.dir, "..","..","..","..") — under node_modules that is the package, not the consumer project |
plugin/lib/services/plugin/info-report.ts | plugin root: CLAUDE_PLUGIN_ROOT first, else ../×4 from module — hop count is a layout assumption |
plugin/lib/services/project/ops/setup.ts | pluginRoot = ../×4 from services/project/ops/ to reach its own lib/model/entities |
plugin/cli/backlog_cli/create.ts | pluginRoot() = ../×2 from cli/backlog_cli/; reaches cli/sdlc.ts and lib/model/entities |
plugin/site-template/package.json | generate script: bun run "$(git rev-parse --show-toplevel)/plugin/cli/sdlc.ts" docs generate site --project-root "$(git rev-parse --show-toplevel)" — assumes the CLI lives at <git-toplevel>/plugin/ and project root == git toplevel; false in every consumer |
plugin/lib/util/paths.ts | documents the “plain lexical absolute path (no walk-up)” intent |
Correct today and untouched: all docs/planning reads are
join(projectRoot, …); git rev-parse uses in cleanup/, scan-todos,
and the pr/task skills resolve the consumer repo (their own project).
Proposed
Section titled “Proposed”No shipped code derives a project location from the code tree. The
../×N self-locating hops are annotated as layout-invariant-dependent
(the T-XTGT staged tree preserves the plugin/ subtree, so they
survive) and covered by a test that pins the hop count to the tree
shape. loadConfig finds sdlc.yaml by ancestor walk-up when invoked
from a consumer subdirectory. The site scaffold’s scripts invoke the
installed sdlc bin, not a git-toplevel-relative dev path.
Approach
Section titled “Approach”plugin/lib/services/docs/generate.ts: delete therepoRoot()code-tree fallback; the no-argmain()falls back toprocess.cwd()(matching the CLI adapter’s rule) and the doc-comment says why.plugin/lib/config/load.ts: when the caller did not pass an explicit--project-rootand<cwd>/sdlc.yamlis absent, walk ancestors to the first directory containingsdlc.yaml(stop at the filesystem root); fall back to the current lexical behavior when none is found. Updateplugin/lib/util/paths.ts’s no-walk-up doc note. Test: fixture tree withsdlc.yamlat root, invocation from a nested dir resolves the root config; absentsdlc.yamlkeeps today’s defaults.- Pin the self-location hops: a unit test asserting
info-report.ts,setup.ts, andbacklog_cli/create.tsresolve the realplugin/root from their on-disk module locations (the test breaks if a future refactor moves a module without updating its hop count); add a one-line comment at each hop naming theT-XTGTlayout invariant it relies on. plugin/site-template/package.json: replace thegeneratescript withsdlc docs generate site --project-root ..(the site dir is a child of the project root;npm run/bun runput the installedsdlcbin on PATH in consumers). Keep the dev repo working: its generation is driven by the rootbuild:doc-sitescript, not the scaffold script.- Sweep for stragglers: re-run the audit greps (
git rev-parse --show-toplevel,import.meta+..hops,join(.*projectRootmisuse) overplugin/lib+plugin/cliand confirm every remaining hit is either project-scoped (correct) or annotated by step 3. - Full quality suite; regenerate docs if any entity prose changed.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
plugin/lib/services/docs/generate.ts | modify | drop repoRoot() code-tree fallback; cwd fallback + rationale comment |
plugin/lib/config/load.ts | modify | ancestor walk-up for sdlc.yaml when no explicit root |
plugin/lib/config/tests/ | modify | walk-up tests (nested-dir resolution; no-sdlc.yaml default path) |
plugin/lib/util/paths.ts | modify | update the no-walk-up doc note to describe the new rule |
plugin/lib/services/plugin/info-report.ts | modify | layout-invariant comment on the ../×4 fallback |
plugin/lib/services/project/ops/setup.ts | modify | layout-invariant comment on the ../×4 hop |
plugin/cli/backlog_cli/create.ts | modify | layout-invariant comment on the ../×2 hop |
plugin/lib/tests/ | modify | hop-count pinning test for the three self-locating modules |
plugin/site-template/package.json | modify | generate script → installed sdlc bin, no git rev-parse/plugin/ path |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
grep -rn 'git rev-parse --show-toplevel' plugin/site-template plugin/lib plugin/clireturns only project-scoped uses (cleanup/,scan-todos,pluginself-diagnostics) — zero insite-template/and zero deriving a consumer path from the code tree. - AC-2:
sdlc docs generateinvoked with no--project-rootfrom a fixture project’s nested subdirectory writes into that project’s tree, not the package’s (walk-up test). - AC-3: the hop-count pinning test fails when any of the three self-locating modules is moved without updating its resolution (demonstrated in the test by a simulated wrong hop).
- AC-4: existing suites green (
bun test,bunx tsc --noEmit);sdlc docs generate --checkreports no drift.
Out of scope
Section titled “Out of scope”- The staged-tree layout itself (
T-XTGTowns the invariant; this task annotates and pins against it). update-skill-docand other maintainer-only skills that assume the dev repo layout — they never run in consumers.- Replacing the
../×Nhops with a manifest/anchor-file mechanism — worth considering only if the layout invariant breaks later.
Dependencies
Section titled “Dependencies”- none hard; runs parallel to
T-WGV4. End-to-end confirmation in an installed package comes withT-521G(pilot).
Discovery context
Section titled “Discovery context”- D-0014 Migration step 4; path audit 2026-07-02. Highest-severity
findings addressed here: site-template generate script (breaks in
every consumer),
generate.tscode-tree fallback, missingsdlc.yamlwalk-up. The registry.ts-filter finding from the same audit isT-XTGTstep 1, not this task.