- Status: Proposed simplification.
- Sources checked: V3 runtime page in kai-chattr docs,
E:/hooks/depcruiser-migrations,E:/hooks/_docs/depcruiser-migrations-design-and-verification*.md,E:/hooks/config.json,E:/hooks/_core/config-model.mjs,E:/hooks/quality-completion-gate/quality-verify-manifest.json,E:/hooks/_db/hooks.db, andE:/hooks/.state.
Files the Proposed Plan Would Create
This implementation proposes to create the following hook-side files:
| # | File | Purpose |
|---|---|---|
| 1 | README.md | Documents the hook bundle, operator workflow, local commands, and recovery behavior. |
| 2 | dependency-cruiser.cjs | Provides the dependency-cruiser rules used by target verification. |
| 3 | depcruiser-migrations.mjs | Runs the PreToolUse hook gate, reads active state, and permits or blocks guarded edits. |
| 4 | depcruiser-migrations-cli.mjs | Gives the worker the command surface for initialize, arm, validate-state, and disarm. |
| 5 | graph.contract.json | Stores the authored migration graph as data. |
| 6 | generate-closure-manifest.mjs | Converts dependency-cruiser donor output into the source manifest used for adjudication. |
| 7 | validate-closure-manifest.mjs | Performs strict reconcile checks against approved port, adapter, cut, and target_extras decisions. |
| 8 | verify-active-closure.mjs | Runs target dependency-cruiser, typecheck, and manifest closure verification for closure approval. |
| 9 | initialize-depcruiser-migrations/SKILL.md | Describes the worker-first workflow and tells the agent which CLI commands to run first. |
| 10 | lib/path-scope.mjs | Parses edit paths, including apply_patch, and enforces guarded prefixes and files. |
| 11 | lib/state-store.mjs | Owns atomic .state reads, writes, event appends, and active migration validation. |
| 12 | tests/depcruiser-migrations.test.mjs | Covers the gate, path extraction, state transitions, and strict reconcile behavior. |
Runtime Behavior
The user starts the workflow by invoking the initialize-depcruiser-migrations skill.
The skill should work in two packaging modes:
- as the internal
E:/hooks/depcruiser-migrations/initialize-depcruiser-migrations/SKILL.mdskill - as a self-contained skill plus hook plus script plugin with the same command behavior
The graph contract is available before the workflow starts. The proposed P0 graph has these nodes:
initialize -> adjudication -> implementation -> closure_verification -> disarmNode 1: initialize
Initiated by: The user invokes initialize-depcruiser-migrations.
What happens: The worker gathers or confirms only the variable migration inputs:
- migration name
- donor repo absolute path
- donor entry file, repo-relative
- donor tsconfig, repo-relative
- target repo absolute path
- target entry file, repo-relative
- guarded target prefixes and guarded target files
- manifest directory, normally
E:/hooks/depcruiser-migrations/manifests/<name>/
If the user gives only a page, route, or repo pair, the worker inspects the repos and asks only for missing values that cannot be inferred.
After the inputs are known, the worker runs the deterministic initialize command:
node E:/hooks/depcruiser-migrations/depcruiser-migrations-cli.mjs initialize --name <name> --donor-root <donorRepo> --donor-entry <donorEntry> --donor-tsconfig <donorTsconfig> --target-root <targetRepo> --target-entry <targetEntry> --guarded-prefix <prefix> --guarded-file <file>Repeat --guarded-prefix and --guarded-file as needed.
The script, not the worker, owns the setup procedure:
- detect package managers from lockfiles
- parse donor and target
package.json - check whether
dependency-cruiseris already installed - install
dependency-cruiseronly where missing - create the manifest directory
- run the donor dependency-cruiser trace
- generate the source manifest
- check for unresolved donor imports
- write setup artifacts
- confirm that
graph.contract.jsoncan be read
Donor trace command run by the script:
npx depcruise --no-config --output-type json --ts-config <donorTsconfig> --ts-pre-compilation-deps <donorEntry> > E:/hooks/depcruiser-migrations/manifests/<name>/before-edit.donor.jsonSource-manifest command run by the script:
node E:/hooks/depcruiser-migrations/generate-closure-manifest.mjs E:/hooks/depcruiser-migrations/manifests/<name>/before-edit.donor.json E:/hooks/depcruiser-migrations/manifests/<name>/<name>.source-manifest.json --entry <donorEntry>generate-closure-manifest.mjs writes each donor module with verdict: "TBD" and target_path: null.
Files saved:
E:/hooks/depcruiser-migrations/manifests/<name>/E:/hooks/depcruiser-migrations/manifests/<name>/inputs.jsonE:/hooks/depcruiser-migrations/manifests/<name>/setup-result.jsonE:/hooks/depcruiser-migrations/manifests/<name>/before-edit.donor.jsonE:/hooks/depcruiser-migrations/manifests/<name>/<name>.source-manifest.json- dependency changes in donor or target
package.jsonand lockfile only when dependency-cruiser was missing
Worker/AI does: Resolves inputs, asks for missing user decisions, runs the initialize command with those inputs, and reads the script result.
User does: Supplies missing source or target information and confirms any ambiguous repo, entry, or guarded path decision.
Completed when: Required inputs are known, the initialize command exits successfully, both repos have dependency-cruiser available, the manifest directory exists, setup artifacts are saved, the donor trace exists, the source manifest exists, unresolved donor imports have either been resolved or explicitly reported, and the graph contract loads successfully. Completion starts adjudication.
Node 2: adjudication
Initiated by: initialize completes.
What happens: The worker classifies every module in the source manifest, resolves the human decision loop, and asks the user to approve the adjudicated manifest. User approval is the trigger that completes this node.
Allowed verdicts:
port: bring the donor source file into the target and adapt only required runtime seamsadapter: replace a donor runtime seam with a target-native adaptercut: exclude the donor module intentionally
For port and adapter, the worker sets target_path to the repo-relative target path.
For target-native files that are required but not present in the donor closure, the worker records target_extras[] with reasons.
Files saved:
- updated
E:/hooks/depcruiser-migrations/manifests/<name>/<name>.source-manifest.json - optional
E:/hooks/depcruiser-migrations/manifests/<name>/adjudication-notes.mdwhen decisions need explanation E:/hooks/depcruiser-migrations/manifests/<name>/adjudication-approval.json
Worker/AI does: Walks the manifest in port_order, proposes verdicts, fills target_path, identifies adapters, identifies cuts, records target extras, presents the decision summary, and waits for user approval.
User does: Approves or corrects ambiguous verdicts, adapter decisions, cuts, target paths, and target extras before any guarded target edit occurs.
Completed when: No manifest module has verdict: "TBD", every port and adapter has a repo-relative target_path, cuts are intentional, target extras have reasons, and the user explicitly approves the adjudicated manifest. That approval triggers implementation.
Node 3: implementation
Initiated by: User approval completes adjudication.
What happens: The worker first arms and validates the migration, then edits the target repo under the active hook gate.
The worker arms the migration through the proposed CLI:
node E:/hooks/depcruiser-migrations/depcruiser-migrations-cli.mjs arm --name <name> --target-root <targetRepo> --target-entry <targetEntry> --manifest-dir E:/hooks/depcruiser-migrations/manifests/<name> --source-manifest E:/hooks/depcruiser-migrations/manifests/<name>/<name>.source-manifest.json --donor-trace E:/hooks/depcruiser-migrations/manifests/<name>/before-edit.donor.json --target-trace E:/hooks/depcruiser-migrations/manifests/<name>/after-edit.target.json --guarded-prefix <prefix> --guarded-file <file>Repeat --guarded-prefix and --guarded-file as needed.
The worker validates active state:
node E:/hooks/depcruiser-migrations/depcruiser-migrations-cli.mjs validate-stateThe hook gate behavior is:
- unarmed state allows edits
- malformed active state denies guarded edits with repair instructions
- missing source manifest denies guarded edits
- any manifest module with
verdict: "TBD"denies guarded edits - all verdicts valid allows guarded edits
The worker ports files in port_order, handles leaves first, writes adapters where approved, and does not create screenshot lookalikes or unrelated target files.
Files saved:
- target repo files listed by approved
target_path - target repo files listed by approved
target_extras[] E:/hooks/.state/depcruiser-migrations/active.json- appended
E:/hooks/.state/depcruiser-migrations/events.jsonl
Worker/AI does: Arms through the CLI, validates active state, copies real donor source for port modules, adapts only target runtime seams, writes approved adapters, skips approved cuts, and keeps edits inside guarded prefixes or files.
User does: Answers migration questions that change approved intent. The user does not need to manually drive each file edit.
Completed when: active.json is written atomically, events.jsonl records the arm event, active state validation passes, all approved port, adapter, and target_extras[] files exist in the target repo, and the worker is ready to run closure verification. Completion starts closure_verification.
Node 4: closure_verification
Initiated by: implementation completes.
What happens: The worker runs the proposed verifier:
node E:/hooks/depcruiser-migrations/verify-active-closure.mjsThe verifier runs target dependency-cruiser from the target repo root:
npx depcruise --config E:/hooks/depcruiser-migrations/dependency-cruiser.cjs --output-type json --ts-pre-compilation-deps <targetEntry> > E:/hooks/depcruiser-migrations/manifests/<name>/after-edit.target.jsonThe verifier runs the target typecheck with the target package manager:
pnpm exec tsc --noEmit
npx tsc --noEmit
yarn tsc --noEmitThe verifier then runs strict reconcile through validate-closure-manifest.mjs.
Strict reconcile must fail on:
- missing ports
- leftover cuts
- unexpected target nodes
- undeclared target-native files
- duplicate target paths
- path traversal
- dependency-cruiser JSON errors
- target typecheck failure
If verification fails because implementation is incomplete, the workflow returns to implementation. If verification fails because the adjudication decision was wrong, the workflow returns to adjudication.
After verification passes, the worker presents the closure evidence to the user:
- donor trace path
- source manifest path
- target trace path
- verification report path
- target typecheck result
- strict reconcile result
Files saved:
E:/hooks/depcruiser-migrations/manifests/<name>/after-edit.target.jsonE:/hooks/depcruiser-migrations/manifests/<name>/verification-report.jsonE:/hooks/depcruiser-migrations/manifests/<name>/closure-approval.json- appended
E:/hooks/.state/depcruiser-migrations/events.jsonl
Worker/AI does: Runs verification, reads failures, debugs the target implementation, updates only approved manifest decisions when required, reruns verification until closure passes, summarizes the passing evidence, and waits for explicit final closure approval.
User does: Gives direction only when verification requires changing an approved adjudication decision or accepting a new target_extras[] entry. After verification passes, the user approves final closure or sends the workflow back for more implementation or adjudication work.
Completed when: Target dependency-cruiser passes, target tsc --noEmit passes, strict reconcile passes, verification-report.json records the passing evidence, the user explicitly approves final closure, and closure-approval.json is saved. Completion starts disarm.
Node 5: disarm
Initiated by: closure_verification completes.
What happens: The skill instructs the worker to run the deterministic disarm command:
node E:/hooks/depcruiser-migrations/depcruiser-migrations-cli.mjs disarm --name <name>The worker does not hand-edit .state. The CLI removes or marks inactive the active state atomically, appends an audit event, confirms the hook is unarmed, and returns the final artifact paths.
Files saved:
- updated or removed
E:/hooks/.state/depcruiser-migrations/active.json - appended
E:/hooks/.state/depcruiser-migrations/events.jsonl - optional final run summary or artifact index returned by the CLI
Worker/AI does: Runs the disarm command, reads the CLI result, and reports the final artifact paths.
User does: No action after final approval unless disarm fails.
Completed when: No active depcruiser migration remains armed, the disarm event is recorded, and the worker reports the artifact paths.
State
Use .state first:
E:/hooks/.state/depcruiser-migrations/
active.json
events.jsonlactive.json should hold the active migration:
{
"version": 1,
"migrationId": "conversation-page",
"currentNode": "adjudication",
"targetRoot": "E:/kai-chattr",
"targetEntry": "apps/web/src/routes/conversation.tsx",
"guardedPrefixes": ["apps/web/src/"],
"guardedFiles": [],
"evidenceDir": "E:/hooks/depcruiser-migrations/manifests/conversation-page",
"sourceManifest": "E:/hooks/depcruiser-migrations/manifests/conversation-page/conversation-page.source-manifest.json",
"status": "active"
}Do not use E:/hooks/_db/hooks.db for depcruiser run state in P0. That DB supports hook telemetry and gate behavior. If JSON state becomes insufficient, add exactly one DB after approval:
E:/hooks/.state/depcruiser-migrations/runs.sqliteGraph Contract
Use one graph contract file:
{
"version": 1,
"nodes": [
"initialize",
"adjudication",
"implementation",
"closure_verification",
"disarm"
],
"transitions": [
["initialize", "adjudication"],
["adjudication", "implementation"],
["implementation", "closure_verification"],
["closure_verification", "disarm"],
["closure_verification", "implementation"],
["closure_verification", "adjudication"]
]
}Config And Schema
Add the hook entry only after depcruiser-migrations.mjs exists:
{
"id": "depcruiser-migrations",
"name": "Depcruiser Migrations",
"description": "PreToolUse gate and CLI for governed dependency-cruiser migrations.",
"category": "gate",
"event": "PreToolUse",
"match": { "tools": ["Edit", "Write", "MultiEdit", "NotebookEdit", "apply_patch"] },
"script": { "path": "depcruiser-migrations/depcruiser-migrations.mjs", "runtime": "node" },
"scope": { "projects": ["*"], "paths": ["**"] },
"enabled": false,
"failPolicy": "open",
"settings": {
"stateDir": "E:/hooks/.state/depcruiser-migrations",
"editTools": ["Edit", "Write", "MultiEdit", "NotebookEdit", "apply_patch"]
}
}Do not put active migration data in config.json. Active migration data belongs in .state.
config.schema.json is generated output. Do not hand-edit it. P0 can use the existing config model because hook settings is a plain object unless a hook-specific schema is added.
Quality Gate
After the replacement files exist, add syntax and focused test coverage to the hooks runtime commands:
node --check depcruiser-migrations/depcruiser-migrations.mjs
node --check depcruiser-migrations/depcruiser-migrations-cli.mjs
node --check depcruiser-migrations/verify-active-closure.mjs
node depcruiser-migrations/tests/depcruiser-migrations.test.mjsDo not add verify-active-closure.mjs to target repo completion gates until it exists and passes local focused tests.
Skill Discovery
Current hook config indexes skills from E:/__skills, not from E:/hooks/depcruiser-migrations.
For the first implementation:
- keep the nested skill path in the hook bundle
- invoke it by explicit path/name during development
- do not change
skill-indexer.settings.scanRootswithout approval
If the skill must become globally discoverable, publish it through the existing skills warehouse path rather than quietly adding a second scan root.
Implementation Build Order
- Create
depcruiser-migrations.mjs,depcruiser-migrations-cli.mjs,lib/state-store.mjs, andlib/path-scope.mjs. - Implement unarmed allow, invalid-state deny, guarded-path matching, and
apply_patchpath extraction. - Add
graph.contract.jsonwith the five P0 nodes. - Strengthen
validate-closure-manifest.mjsto use repo-relative paths and blocking reconcile. - Add
verify-active-closure.mjs. - Update
initialize-depcruiser-migrations/SKILL.mdso the worker runs the CLI first. - Add one focused test file.
- Add the disabled
config.jsonhook entry. - Regenerate
config.schema.json. - Run
_core/validate-runtime-hooks.mjsand focused depcruiser tests. - Retire old
closure-gate*files only after the new runtime passes.
P0 Scope Boundary
Browser verification is not part of P0. P0 proves source closure, target typecheck, strict reconcile, and user-approved closure completion.