Skip to content

T-W6NY-apps-imports-check-scopes-to-staged

Status: planning/proposed · Impact: medium · Complexity: small

AUTO-DEFINED: this spec was best-effort machine-authored by /sdlc:task-auto-define on 2026-07-19 because the task is autonomy: autonomous/pr. Review the Goal, Approach, Today, Files-to-touch, and Acceptance-criteria carefully before trusting it.

The project-check-apps-imports pre-commit gate re-scans every app in the monorepo on every commit, making commits slow enough to time out a foreground commit at the 2-minute tool limit. This task scopes that check to the staged paths so it only re-checks changed files, closing a friction gap surfaced by T-NLKF-converge-obsidian-bases in https://github.com/sksizer/dev.

The pre-commit hook is slow (~2-3 min) because .claude/skills/project-check/check_apps_imports.ts (wired as project-check-apps-imports in the root lefthook.yml) scans all monorepo apps on every commit, timing out a foreground commit at the 2-min tool limit during T-NLKF. Scope the apps-imports check to staged paths so it only re-checks changed files instead of the whole monorepo. Sibling: open/ready T-SGIN does the same staged-scoping for the check_entities pre-commit gate; follow the same pattern here.

T-NLKF-converge-obsidian-bases

LocationRole today
.claude/skills/project-check/check_apps_imports.tsS-0008 apps-import-discipline checker; wired as the project-check-apps-imports pre-commit gate
.claude/skills/project-check/check_apps_imports.ts#mainCollects EVERY source file under apps/** + packages/** from the working tree (via walkSourceFiles) and scans each with scanOneFile — re-walks the whole monorepo on every invocation, with no staged scoping
.claude/skills/project-check/check_apps_imports.ts#parseArgsParses only --root; ParsedArgs carries a single root field
lefthook.ymlWires project-check-apps-imports to run .claude/skills/project-check/check_apps_imports.ts (via bun run) with no staged scoping, so every commit re-scans the whole tree
apps/sdlc/lib/services/docs/generate.ts#checkStagedRepo precedent for index-scoped gating: materializes the git index into a temp tree via git checkout-index and runs the unchanged check against it, leaving the working tree untouched
.claude/skills/project-check/tests/check_apps_imports.test.tsFixture-driven harness shelling the checker with --root <fixture>; no staged-mode coverage

Teach check_apps_imports.ts a --staged mode and switch the lefthook.yml hook to it. In --staged mode the checker gates the git index: it exits 0 immediately when no staged path under apps/** or packages/** is a source file, and otherwise scans only the staged copies of those files (materialized from the index) instead of re-walking the whole monorepo — so a commit no longer pays to re-scan every app, and unstaged WIP edits cannot fail an unrelated commit. Bare invocations — CI, direct runs, and the fixture-driven tests — keep today’s --root working-tree behavior.

  1. Add a --staged boolean flag to parseArgs in .claude/skills/project-check/check_apps_imports.ts (default off; ParsedArgs gains a staged field). Without the flag, behavior is unchanged — the existing --root working-tree walk still runs.
  2. In main, when staged is set, resolve the git repo governing root via git rev-parse --show-toplevel run with -C at the resolved root. If root is not inside a git repo, print an error naming the conflict and exit 2 (the existing script-failure exit class).
  3. Probe the index with git diff --cached --name-only -z -- apps packages and keep only paths whose extension is in SOURCE_EXTS, applying the same EXCLUDE_PREFIXES / REINCLUDE_PREFIXES scoping the working-tree walk uses. Empty result means nothing relevant is staged: print a one-line skip note and exit 0 without reading any file.
  4. Otherwise materialize the staged copies of those files into a mkdtempSync temp dir via git checkout-index -z --stdin -f with --prefix pointing at the temp dir (feeding the staged path list), then run the existing scanOneFile over each materialized file with root set to the temp dir. Rebase reported paths onto the real root for readable citations and remove the temp dir in a finally block. This is the same no-stash, working-tree-untouched materialization pattern as checkStaged in apps/sdlc/lib/services/docs/generate.ts.
  5. Point the project-check-apps-imports command in lefthook.yml at bun run .claude/skills/project-check/check_apps_imports.ts --staged, with a comment carrying the index-vs-working-tree rationale (mirror the project-check-docs-drift comment).
  6. Extend .claude/skills/project-check/tests/check_apps_imports.test.ts with staged-mode tests that build a throwaway git repo in a temp dir around a copied fixture: (a) a forbidden import unstaged with nothing staged under apps/** / packages/** exits 0 via the skip path; (b) a compliant file staged while a forbidden-import edit sits unstaged exits 0; (c) the forbidden import staged exits 1 with the usual S-0008 citation; (d) --staged against a copied fixture outside any git repo exits 2.
LocationKindChange
.claude/skills/project-check/check_apps_imports.tsmodifyAdd --staged flag: resolve the repo, fast-skip when no staged source file falls under apps/** / packages/**, else materialize the staged files (git checkout-index) and run the existing scanOneFile over them
lefthook.ymlmodifyRun project-check-apps-imports with --staged plus an index-vs-working-tree rationale comment
.claude/skills/project-check/tests/check_apps_imports.test.tsmodifyAdd staged-mode cases: skip-fast, staged-clean vs unstaged-leak, staged-leak fails, non-repo root errors
  • AC-1: With --staged and no staged source file under apps/** or packages/**, the checker exits 0 with a skip note even when a working-tree file carries a forbidden import that fails the bare --root run.
  • AC-2: With --staged and a forbidden-import-free staged copy of an apps/** file whose working-tree copy carries an unstaged forbidden import, the checker exits 0; staging the forbidden import instead makes it exit 1 with the same S-0008 file citation the working-tree mode prints.
  • AC-3: Without --staged the working-tree behavior is unchanged: the existing cases in .claude/skills/project-check/tests/check_apps_imports.test.ts pass unmodified.
  • AC-4: The project-check-apps-imports command in lefthook.yml invokes check_apps_imports.ts with --staged.
  • AC-5: --staged against a --root outside any git repo exits 2 with a message naming the problem.
  • AC-6: running .claude/skills/project-check/tests/check_apps_imports.test.ts under bun test passes, including new staged-mode tests covering AC-1, AC-2, and AC-5.
  • Converting the other working-tree project-check hooks wired in lefthook.yml (identifier-shape, entities, pipe-tail, no-legacy-script-paths, util-redeclarations, op-modules, skill-docs) to index gating — same failure class, separate follow-ups (check_entities is the sibling T-SGIN).
  • Repointing check_apps_imports.ts at the sdlc CLI as a gate op (the T-WC1T repointing pattern) — the script stays a thin project-check adapter here.
  • Any change to the S-0008 detection itself (forbidden trees, @lib/ alias handling, specifier extraction) or to the existing fixtures’ semantics.
  • none

Spawned by /sdlc:spawn-task-pr on 2026-07-19 UTC from T-NLKF-converge-obsidian-bases in https://github.com/sksizer/dev.


← Back to Tasks