Skip to content

T-LX04-activate-s0008-apps-adapter-validator

Status: closed/done · Impact: medium · Complexity: medium

S-0008-apps-consume-substrate-through-published-surfaces forbids apps/** and packages/** from importing the substrate (plugin/lib/**) or the harness surface (plugin/skills/**, plugin/.claude-plugin/**) directly — but it is open/draft with no enforcement, so the rule is honor-system only. Now that apps/ exists (T-CW4K), this task lands a deterministic import-discipline validator and promotes S-0008 to open/active, turning a documented contract into a pre-commit gate that blocks substrate-coupling imports with file:line citations.

LocationRole today
docs/planning/standards/S-0008-apps-consume-substrate-through-published-surfaces.mdThe standard, status: open/draft; its “How to apply” says to promote and name a validator “when the first application lands” — but no validator exists
.claude/skills/project-check/Home of the deterministic check_*.ts gates; greps/scans for forbidden patterns and exits non-zero with file:line citations
.claude/skills/project-check/check_no_legacy_script_paths.tsThe closest pattern to copy: regex-scans a file set per line, prints OK/FAIL per unit, exits 1 with file:line citations on any hit (2 on its own error)
.claude/skills/project-check/tests/Sibling Bun test suite — one check_*.test.ts per gate, with fixtures/ dirs
lefthook.yml#pre-commitWires each gate as a project-check-* command (bun run .claude/skills/project-check/check_*.ts); aborts on first non-zero exit, surfaces citations verbatim
plugin/lib/model/ops/_update.tsThe entity-agnostic frontmatter-update op behind sdlc standard update; no dedicated standard activate verb exists — the open/draft → open/active flip is sdlc standard update --set '{"status":"open/active"}'

apps/ is created by T-CW4K (apps/dashboard/); this validator scopes over it alongside packages/**.

A new check_apps_imports.ts gate under .claude/skills/project-check/ scans every file under apps/** and packages/** for imports of the substrate (plugin/lib/**, and src/** post-lift) or the harness surface (plugin/skills/**, plugin/.claude-plugin/**). It prints OK/FAIL per scanned file, exits non-zero with file:line citations on any forbidden import, and is wired into lefthook.yml as a project-check-apps-imports pre-commit command alongside the existing gates. A sibling check_apps_imports.test.ts with fixtures covers a violating file and a compliant file. S-0008 is flipped to status: open/active and its “How to apply” bullet names this validator.

  1. Author .claude/skills/project-check/check_apps_imports.ts, mirroring check_no_legacy_script_paths.ts (repo-root resolution via import.meta.url, pyResolve, splitlines, displayPath, OK/FAIL per-unit output, exit 0/1/2). It walks apps/** and packages/** (skip node_modules/, dist/, .git/) and line-scans source files (.ts/.tsx/.js/.jsx/.vue — Vue SFC <script> blocks carry imports too) for import/require/dynamic-import() statements whose specifier resolves into a forbidden tree.
  2. Define the forbidden-specifier match: any import/export … from, require(…), or import(…) whose module string points at plugin/lib/**, plugin/skills/**, plugin/.claude-plugin/**, or top-level src/** — whether written as a relative path that climbs into those trees or via a path alias (e.g. @lib/…). Resolve relative specifiers against the importing file before matching so an alias-free ../../plugin/lib/x is caught; treat the @lib/ alias (used across the substrate) as a plugin/lib/** reference. Emit one citation per hit: apps/.../file.ts:NN: apps/** may not import the substrate (plugin/lib/**) — reach it through a defined API (sdlc CLI / JSON API / packages/<eco> API library) per S-0008.
  3. Add check_apps_imports.test.ts under .claude/skills/project-check/tests/ with a fixtures/ dir holding a violating file (e.g. an apps/-shaped file importing @lib/… or plugin/lib/…) and a compliant file (shells the CLI / imports only a packages/<eco> API library). Assert exit 1 + citation on the former, exit 0 on the latter, mirroring check_no_legacy_script_paths.test.ts.
  4. Wire the gate into lefthook.yml pre-commit.commands as project-check-apps-imports: running bun run .claude/skills/project-check/check_apps_imports.ts, placed alongside the other project-check-* gates with a comment citing S-0008.
  5. Promote the standard: bun run plugin/cli/sdlc.ts standard update S-0008-apps-consume-substrate-through-published-surfaces --set '{"status":"open/active"}', then Edit the “How to apply” open/draft bullet in the standard to state the rule is now enforced by .claude/skills/project-check/check_apps_imports.ts.
LocationKindChange
.claude/skills/project-check/check_apps_imports.tsnewThe import-discipline validator: scans apps/** + packages/**, exits non-zero with file:line citations on substrate/harness imports
.claude/skills/project-check/tests/check_apps_imports.test.tsnewBun test: violating file fails with a citation, compliant file passes
lefthook.ymlmodifyAdd project-check-apps-imports to pre-commit.commands, running the new gate
docs/planning/standards/S-0008-apps-consume-substrate-through-published-surfaces.mdmodifystatus: open/draft → open/active (via standard update); name the validator in “How to apply”
  • AC-1: An apps/** file that imports plugin/lib/** (or via the @lib/ alias) fails bun run .claude/skills/project-check/check_apps_imports.ts with exit code 1 and a file:line citation naming the offending import.
  • AC-2: A packages/** file importing plugin/skills/** or plugin/.claude-plugin/** likewise fails with a file:line citation.
  • AC-3: A compliant apps/** file (no substrate/harness import — reaching the substrate only through the sdlc CLI or a packages/<eco> API library) passes the gate with exit code 0.
  • AC-4: docs/planning/standards/S-0008-apps-consume-substrate-through-published-surfaces.md has status: open/active and its “How to apply” section names check_apps_imports.ts as the enforcing validator.
  • AC-5: lefthook.yml runs project-check-apps-imports in pre-commit, so staging a violating apps/** import aborts the commit.
  • The applications and packages themselves (apps/dashboard/, any packages/<eco>/ API library) — created by T-CW4K and follow-on tasks, not here. This task only validates their import discipline.
  • Enforcing the inverse arrow (substrate plugin/lib/** not importing harness surface) — that is the future Standard flagged in S-0008’s Notes, separate work.
  • A dedicated sdlc standard activate verb — the flip rides the existing generic standard update --set op; minting a bespoke verb is not in scope.
  • T-CW4Kapps/ (and the dashboard app under it) must exist before there is anything for the validator to scope over and test against.

Captured by /sdlc:task-work on 2026-06-20. PR: pending.

  • AC-1: auto — check_apps_imports.test.ts asserts an apps/** file importing plugin/lib/** (relative ../../../plugin/lib/…) and via the @lib/ alias each fail with exit 1 + a file:line citation.
  • AC-2: auto — the test asserts a packages/** file importing plugin/skills/** and plugin/.claude-plugin/** (dynamic import()) each fail with exit 1 + a citation.
  • AC-3: auto + agent-manual — the test asserts a compliant apps/** fixture passes (exit 0); separately, bun run .claude/skills/project-check/check_apps_imports.ts against the live tree exits 0 over the real apps/dashboard/ (4 files: env.d.ts, App.vue, main.ts, vite.config.ts — all npm/relative, no substrate import).
  • AC-4: agent-manual — docs/planning/standards/S-0008-…md frontmatter is status: open/active and its “How to apply” names check_apps_imports.ts as the enforcing validator (verified by reading the committed file).
  • AC-5: agent-manual — lefthook.yml runs project-check-apps-imports in pre-commit; confirmed the gate fired and passed (✔️ project-check-apps-imports) during the post-mortem commit.
  • Mirroring check_no_legacy_script_paths.ts gave a clean, proven skeleton (repo-root resolution, per-unit OK/FAIL, exit 0/1/2) — the new validator only had to add specifier extraction + forbidden-tree matching.
  • The real apps/dashboard/ is genuinely compliant (relative /api fetch, npm imports only), so promoting S-0008 to open/active and arming the pre-commit gate is safe day one — the live gate passes on the first commit.
  • The @cli/ vs @lib/ distinction was correct out of the box: the CLI is a defined API (allowed), @lib/ is substrate internals (forbidden).
  • The verify/start task-state commits landed on the task BRANCH (stale SHAs) instead of only on main: start_task.ts reset the branch to local main’s tip, then the parent had to git pull --rebase main (origin/main had diverged under parallel WIP) which rewrote those commits’ SHAs — leaving the pre-rebase copies stranded on the branch. They tripped project-check-task-state-origin on every branch commit, forcing the implementation sub-agent to commit with LEFTHOOK=0 (bypassing ALL gates). Resolved by rebasing the branch onto origin/main (git dropped the stale commits as already-applied), after which the parent re-verified every gate independently. Gap: when origin/main moves between start_task’s branch-reset and the parent’s main-push, the branch carries stale state commits; task-work Step 5b should rebase the task branch onto origin/main after the main-push (or start_task should reset against the post-push tip), so the implementer never sees the lint-state-origin false positive and never needs LEFTHOOK=0. → T-6R73-pre-commit-drift-hooks-gate-unconditionally-forcing-no
  • A sub-agent reaching for LEFTHOOK=0 to get unblocked silently skips the new validator + docs-drift + markdown-fmt gates; the parent then has to re-run the whole gate set by hand. The implementer brief should forbid LEFTHOOK=0 and instead surface a hook block to the parent for resolution. → T-6R73-pre-commit-drift-hooks-gate-unconditionally-forcing-no
  • The baseline-diff quality gate again surfaced spurious new-drift (parallel work’s T-44OO/T-BQRU markdown landed on origin/main after the Step 3a baseline was captured at a now-stale SHA, plus rumdl’s issue-count summary line) — the same gap tracked by T-BCNP-quality-gate-ignores-summary-and-corpus-lines. → T-BCNP-quality-gate-ignores-summary-and-corpus-lines
  • The S-0008 promotion and validator landed once T-CW4K shipped apps/dashboard/; the validator’s compliant-case is exercised against that real app.

T-CW4K


← Back to Tasks