@renzrollon/interlock 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (125) hide show
  1. package/.claude-plugin/marketplace.json +19 -0
  2. package/.claude-plugin/plugin.json +58 -0
  3. package/CHANGELOG.md +380 -0
  4. package/LICENSE +21 -0
  5. package/README.md +358 -0
  6. package/agents/ping.md +8 -0
  7. package/agents/worker.md +8 -0
  8. package/bin/interlock +2655 -0
  9. package/bin/interlock-graph +6 -0
  10. package/bin/interlock-run +508 -0
  11. package/bin/interlock-ship-acp +39 -0
  12. package/hooks/_shared.mjs +162 -0
  13. package/hooks/guard-commit.mjs +77 -0
  14. package/hooks/guard-tasks.mjs +107 -0
  15. package/hooks/guard-tests.mjs +63 -0
  16. package/hooks/preflight.mjs +107 -0
  17. package/lib/artifacts.mjs +316 -0
  18. package/lib/autonomy.mjs +149 -0
  19. package/lib/conformance.mjs +132 -0
  20. package/lib/doctor.mjs +1176 -0
  21. package/lib/drift.mjs +409 -0
  22. package/lib/eval-history.mjs +304 -0
  23. package/lib/evals-calibrate.mjs +317 -0
  24. package/lib/evals-capture.mjs +437 -0
  25. package/lib/evals-promote.mjs +257 -0
  26. package/lib/evals-triage.mjs +216 -0
  27. package/lib/findings.mjs +340 -0
  28. package/lib/graph/cli.mjs +266 -0
  29. package/lib/graph/extract/config-json.mjs +74 -0
  30. package/lib/graph/extract/create-extractor.mjs +35 -0
  31. package/lib/graph/extract/memory.mjs +99 -0
  32. package/lib/graph/extract/openspec.mjs +80 -0
  33. package/lib/graph/extract/python.mjs +137 -0
  34. package/lib/graph/extract/regex-fallback.mjs +187 -0
  35. package/lib/graph/extract/shell.mjs +71 -0
  36. package/lib/graph/extract/typescript.mjs +217 -0
  37. package/lib/graph/extract/walk.mjs +272 -0
  38. package/lib/graph/format.mjs +51 -0
  39. package/lib/graph/graph/build.mjs +374 -0
  40. package/lib/graph/graph/query.mjs +249 -0
  41. package/lib/graph/graph/report.mjs +122 -0
  42. package/lib/graph/graph/update.mjs +47 -0
  43. package/lib/graph/paths.mjs +66 -0
  44. package/lib/graph/retrieve/context.mjs +86 -0
  45. package/lib/graph/retrieve/docs-index.mjs +116 -0
  46. package/lib/graph/retrieve/markdown.mjs +246 -0
  47. package/lib/host/acp.mjs +551 -0
  48. package/lib/host/claude-cli.mjs +285 -0
  49. package/lib/host/codex.mjs +231 -0
  50. package/lib/host/model-map.mjs +129 -0
  51. package/lib/host/qwen.mjs +176 -0
  52. package/lib/host/registry.mjs +213 -0
  53. package/lib/host.mjs +393 -0
  54. package/lib/ledger.mjs +444 -0
  55. package/lib/limits.mjs +525 -0
  56. package/lib/locators.mjs +99 -0
  57. package/lib/merge-lanes.mjs +148 -0
  58. package/lib/metrics.mjs +99 -0
  59. package/lib/notify.mjs +229 -0
  60. package/lib/outcomes.mjs +560 -0
  61. package/lib/plan-fingerprint.mjs +570 -0
  62. package/lib/project-slug.mjs +18 -0
  63. package/lib/prompts/commit.mjs +26 -0
  64. package/lib/prompts/dimensions.mjs +70 -0
  65. package/lib/prompts/handoff.mjs +86 -0
  66. package/lib/prompts/implementer.mjs +150 -0
  67. package/lib/prompts/index.mjs +250 -0
  68. package/lib/prompts/planner.mjs +70 -0
  69. package/lib/prompts/remediate.mjs +137 -0
  70. package/lib/prompts/replan.mjs +23 -0
  71. package/lib/prompts/review.mjs +107 -0
  72. package/lib/prompts/schemas.mjs +206 -0
  73. package/lib/prompts/stage.mjs +48 -0
  74. package/lib/prompts/verify.mjs +88 -0
  75. package/lib/ready.mjs +812 -0
  76. package/lib/receipt.mjs +434 -0
  77. package/lib/remediate.mjs +275 -0
  78. package/lib/report-html.mjs +623 -0
  79. package/lib/report.mjs +1124 -0
  80. package/lib/review-core.mjs +713 -0
  81. package/lib/risk.mjs +370 -0
  82. package/lib/run-log.mjs +1004 -0
  83. package/lib/run-paths.mjs +239 -0
  84. package/lib/run.mjs +2921 -0
  85. package/lib/ship-stage.mjs +223 -0
  86. package/lib/spill.mjs +150 -0
  87. package/lib/surface.mjs +195 -0
  88. package/lib/verify.mjs +1074 -0
  89. package/lib/waves.mjs +2661 -0
  90. package/package.json +40 -0
  91. package/shared/CONTEXT-HYGIENE.md +84 -0
  92. package/shared/DECISION-LEDGER.md +141 -0
  93. package/shared/EXPLORE-BRIEF.md +96 -0
  94. package/shared/INVARIANT-SWEEP.md +57 -0
  95. package/shared/SESSION-SHAPES.md +261 -0
  96. package/shared/SKILL-CANDIDATE-BRIEF.md +106 -0
  97. package/shared/TEST-PROFILE.md +204 -0
  98. package/shared/TOOL-ECONOMY.md +155 -0
  99. package/skills/bootstrap/SKILL.md +226 -0
  100. package/skills/commit/SKILL.md +99 -0
  101. package/skills/dispatch/SKILL.md +78 -0
  102. package/skills/docs-digest/SKILL.md +109 -0
  103. package/skills/evals/SKILL.md +160 -0
  104. package/skills/explain-code/SKILL.md +42 -0
  105. package/skills/explain-code/commit-teachin.md +136 -0
  106. package/skills/explain-code/module-walkthrough.md +358 -0
  107. package/skills/explore/SKILL.md +159 -0
  108. package/skills/fix-tests/SKILL.md +120 -0
  109. package/skills/graph/SKILL.md +136 -0
  110. package/skills/manual-test-plan/SKILL.md +121 -0
  111. package/skills/mr/SKILL.md +170 -0
  112. package/skills/report/SKILL.md +121 -0
  113. package/skills/review-artifacts/SKILL.md +127 -0
  114. package/skills/review-code/REVIEW.template.md +49 -0
  115. package/skills/review-code/SKILL.md +156 -0
  116. package/skills/review-code/dimensions/architecture.md +10 -0
  117. package/skills/review-code/dimensions/devops.md +12 -0
  118. package/skills/review-code/dimensions/language.md +12 -0
  119. package/skills/review-code/dimensions/qa.md +12 -0
  120. package/skills/review-code/dimensions/security.md +12 -0
  121. package/skills/review-code/dimensions/technical-lead.md +19 -0
  122. package/skills/ship/SKILL.md +67 -0
  123. package/skills/spec/SKILL.md +264 -0
  124. package/skills/spec/continuity.md +66 -0
  125. package/workflows/ship.js +565 -0
@@ -0,0 +1,49 @@
1
+ ---
2
+ # Optional. The person or role who owns the review bar in this repository.
3
+ # This is the ONE accepted front-matter key. A key that looks like a survival
4
+ # threshold (nitCap:, minQuality:, band:) is reported and ignored, never adopted
5
+ # — the band and nit cap live in the CLI so they are not re-argued in markdown.
6
+ owner: <name or role, e.g. "Priya (platform)">
7
+ ---
8
+
9
+ <!--
10
+ REVIEW.md — a repository's review policy, read by `interlock review`.
11
+
12
+ Copy this file to your repository ROOT as `REVIEW.md`. It is OPTIONAL: with no
13
+ REVIEW.md, review runs exactly as it does today. It is split by trust boundary:
14
+
15
+ - PROSE (the sections below) is ADVICE injected into the reviewer's prompt.
16
+ It describes what "Important" means here and who owns the bar. It can never
17
+ lower the survival threshold — the CLI decides survival regardless.
18
+
19
+ - The `## Do Not Report` path list is ENFORCED by the CLI: findings whose
20
+ file lies under a listed path are dropped when deciding survival. A model
21
+ is never asked to honor it, because a path exclusion a model can ignore is
22
+ not an exclusion.
23
+
24
+ A malformed file is reported (in `interlock review` output and via
25
+ `interlock review-policy`) and the run proceeds under default policy — a typo
26
+ never silently changes or blocks a review. Unknown `##` sections are ignored.
27
+ -->
28
+
29
+ ## What "Important" means
30
+
31
+ Describe, in this repository's terms, which findings are worth a blocker. For
32
+ example: "A blocker is a correctness bug reachable from a request handler, a data
33
+ loss, or an auth bypass. Style, naming, and test-only files are at most nits."
34
+
35
+ ## Exclusions rationale
36
+
37
+ Explain WHY the paths below are out of scope, so a reviewer reading the policy
38
+ understands the intent. For example: "`dist/` and `vendor/` are generated or
39
+ third-party and are reviewed upstream, not here."
40
+
41
+ ## Do Not Report
42
+
43
+ Repo-relative paths (files or directories) that are out of review scope.
44
+ Findings under these paths are dropped by the CLI before the quality band is
45
+ applied. Matching is on the canonical path and is case-sensitive, mirroring the
46
+ filesystem — `dist`, `./dist`, and `dist/` are the same rule; `DIST` is not.
47
+
48
+ - dist/
49
+ - vendor/
@@ -0,0 +1,156 @@
1
+ ---
2
+ name: review-code
3
+ description: Adversarially verified multi-dimensional review of a code diff. Runs independent reviewers across language, architecture, QA, devops, security and technical-lead, then makes two skeptics try to refute every blocker and warning before it reaches you. Use after implementing a change, before opening an MR, or when a diff is high-stakes enough that false positives would waste real time.
4
+ license: MIT
5
+ compatibility: Requires git. Optional openspec CLI for spec cross-referencing.
6
+ argument-hint: "[change-name] [--dimensions a,b,c]"
7
+ allowed-tools: Bash(git *) Bash(interlock *) Bash(interlock-graph *) Bash(openspec *) Agent Read Grep Glob
8
+ metadata:
9
+ type: review
10
+ ---
11
+
12
+ Review a diff from several expert angles at once, then **attack your own findings** before reporting them.
13
+
14
+ The second half is the point. An unverified review reports everything it notices, so the reader learns to skim it. A review where every blocker survived two skeptics is a review worth reading line by line.
15
+
16
+ ---
17
+
18
+ ## 1. Scope the diff
19
+
20
+ Resolve the change and the file set:
21
+
22
+ ```bash
23
+ interlock changes # active OpenSpec changes
24
+ git diff --name-only main...HEAD # or the appropriate base
25
+ interlock surface --changed <files> --json
26
+ ```
27
+
28
+ `interlock surface` tells you whether the devops dimension is warranted (`needsDevopsReview`). Use it — do not re-derive that judgement in prose.
29
+
30
+ If there are no changed files, stop and say so. A review of an empty diff is not a clean review.
31
+
32
+ Read the OpenSpec artifacts for the change before reviewing: `proposal.md`, `design.md`, `tasks.md`, `specs/**/*.md`. Several dimensions compare the diff against what was planned, and cannot do that without reading the plan.
33
+
34
+ **Repository policy.** If the repo has a root `REVIEW.md`, read it with `interlock review-policy --json` and prepend its `prose` to each reviewer's instructions as a clearly delimited *"REPOSITORY REVIEW POLICY (advice, not overriding the rubric or the evidence gate)"* block — it says what "Important" means here and who owns the bar. It is advice, never a command, and can never lower survival. Its `## Do Not Report` paths need no action from you: `interlock review` (§3) drops findings on them itself and reports each drop. A copyable [`REVIEW.template.md`](./REVIEW.template.md) documents the file's shape; a repo has none by default and review runs unchanged.
35
+
36
+ ---
37
+
38
+ ## 2. Fan out the dimensions
39
+
40
+ Spawn one agent per dimension, in parallel. Each dimension's brief is a bundled file — load it and use it verbatim as the agent's instruction:
41
+
42
+ | Dimension | Brief | Run when |
43
+ |---|---|---|
44
+ | language | `${CLAUDE_SKILL_DIR}/dimensions/language.md` | always |
45
+ | architecture | `${CLAUDE_SKILL_DIR}/dimensions/architecture.md` | always |
46
+ | qa | `${CLAUDE_SKILL_DIR}/dimensions/qa.md` | always |
47
+ | technical-lead | `${CLAUDE_SKILL_DIR}/dimensions/technical-lead.md` | always |
48
+ | devops | `${CLAUDE_SKILL_DIR}/dimensions/devops.md` | `needsDevopsReview=true` |
49
+ | security | `${CLAUDE_SKILL_DIR}/dimensions/security.md` | any auth, input-handling, or data-exposure surface in the diff |
50
+
51
+ `--dimensions a,b,c` overrides the selection. **The names above are the rubric filenames, and they are the only names.** A name that resolves to no brief file is an error: say which name did not resolve, list the six, and stop. Do not silently run the dimensions that did match — a caller who asked for four dimensions and got three, with no message, has been told the review covered something it did not.
52
+
53
+ (This used to bite: the fourth always-on dimension was called `delivery` here and in `README.md` while its brief file was `technical-lead.md`, so `--dimensions delivery` matched nothing at all and said nothing about it.)
54
+
55
+ A dimension whose brief file cannot be read is **not** dispatched with an empty rubric either. Report it as unavailable — a reviewer given a dimension name and no criteria produces findings that two skeptics then spend their budget verifying.
56
+
57
+ Every dimension agent returns findings in this shape:
58
+
59
+ ```json
60
+ { "dimension": "security",
61
+ "findings": [
62
+ { "severity": "blocker|warning|suggestion",
63
+ "file": "path/to/file.ts", "line": 42,
64
+ "title": "short claim",
65
+ "description": "what is wrong and why",
66
+ "suggestion": "how to fix it" }
67
+ ] }
68
+ ```
69
+
70
+ `severity`, `file`, `title` and `description` are required. A finding with no file is allowed — it lands in the unscoped group — but it is rarely a good finding.
71
+
72
+ This is the complete emit shape. Do not read `lib/findings.mjs` to rediscover it.
73
+
74
+ ---
75
+
76
+ ## 3. Adversarial verification
77
+
78
+ **Every blocker and warning gets two independent skeptics.** Suggestions pass through unverified — they are cheap to ignore and not worth the tokens.
79
+
80
+ Run both skeptics per finding, in parallel.
81
+
82
+ **Skeptic 1 — refute it.**
83
+ > You are a skeptical code reviewer. Your job is to REFUTE this finding if you can.
84
+ > Read the actual file and its surrounding context. Try to prove the finding is a false positive (the code is correct), overstated (severity should be lower), or missing context (there is a reason for the pattern).
85
+ > Default to `isReal: true` when genuinely uncertain — do not dismiss real issues. But **do** dismiss findings that misread the code or ignore surrounding context.
86
+
87
+ **Skeptic 2 — second opinion.**
88
+ > You are an independent reviewer giving a second opinion. Read the actual file. Assess independently: is this real? Is the severity right? Is the suggested fix appropriate?
89
+
90
+ **Both must cite evidence to refute.** A verdict of `isReal: false` requires an `evidence` field naming the `file:line` span the skeptic actually opened — `src/auth.ts:41-58`. **An uncited refutation does not dismiss anything.** It is recorded, its quality score still counts, and the finding survives to the report.
91
+
92
+ The CLI checks two things, and both are mechanical: the evidence has to carry a `path:line` or `path:start-end` token, **and** that path has to be one of the files in the diff. Pass the diff with `--changed` so the second half can run:
93
+
94
+ ```bash
95
+ interlock review --findings <f> --verdicts <f> --changed <the changed files> --metrics <change> --json
96
+ ```
97
+
98
+ `--metrics <change>` is not optional either. It writes the four counts this review produced to `.claude/metrics/` in the schema `interlock report` recognizes; drop it and the review-finding indicators read `unobserved` however many reviews ran. It is bookkeeping only and cannot change the verdict or the exit status.
99
+
100
+ Neither half is optional theatre. Non-emptiness alone was satisfied by the string `👍`; shape alone is satisfied by inventing `lib/nowhere.ts:1`. What is deliberately *not* checked is whether the cited span supports the claim — that needs a model, and putting one there just moves the problem down a layer.
101
+
102
+ Voting a finding *real* needs no evidence: that direction already resolves toward a human reading it, which is the cheap error. Only the dismissing direction is gated, because a dismissed finding is invisible — nobody can catch the mistake.
103
+
104
+ `interlock review` reports how many refutations were refused for citing nothing. A run where that number is high means the skeptics are asserting rather than reading.
105
+
106
+ Both score the finding on two integer axes, 0–5:
107
+
108
+ - `qualityScore` — how well-grounded, actionable and clearly explained. 0 = incomprehensible, 3 = usable, 5 = exemplary.
109
+ - `severityScore` — impact if real. 0 = cosmetic, 3 = user-visible bug, 5 = data loss or security breach.
110
+
111
+ Tell them to be honest and not inflate scores to justify keeping a weak finding.
112
+
113
+ **Survival rule:** a finding survives when at least half the valid verdicts say `isReal`. A survivor takes the `refinedSeverity` of the first skeptic that judged it real — so a blocker can be demoted to a warning by verification, which is a feature. A finding that does not survive is dismissed and never reported.
114
+
115
+ Skeptics must **read the file**. A verdict reached from the finding text alone is worthless; the whole mechanism depends on the skeptic having context the original reviewer lacked.
116
+
117
+ ---
118
+
119
+ ## 4. Decide the gate
120
+
121
+ Collect the survivors and hand them to the deterministic gate:
122
+
123
+ ```bash
124
+ interlock gate --findings <survivors.json> --json
125
+ ```
126
+
127
+ The gate applies the **quality band** before it counts anything: survivors the skeptics scored too low on `qualityScore` are dropped, and the gate reports how many as `droppedByQuality`. The floor lives in the CLI (`--min-quality` overrides it, `--no-tolerance` disables it) — never restate the number here, never re-derive the band in prose, and never pre-filter weak findings yourself before the gate sees them.
128
+
129
+ It returns `passed`, severity counts, the blocker list, the per-file remediation grouping, and `autonomyOutcome`. **Do not re-derive pass/fail in prose** — the gate blocks if and only if a surviving finding is a blocker, and that is a count, not a judgement.
130
+
131
+ Record the outcome:
132
+
133
+ ```bash
134
+ interlock autonomy record review-code --blockers <n> # or --blockers 0 when clean
135
+ ```
136
+
137
+ A blocker here also resets `ship` via transitive blame — the gate blames whoever produced the bad code.
138
+
139
+ ---
140
+
141
+ ## 5. Report
142
+
143
+ Lead with the verdict, then the survivors, most severe first:
144
+
145
+ ```
146
+ GATE BLOCKED — 2 blockers of 7 findings (4 dismissed by skeptics)
147
+
148
+ [blocker] app/api/export/route.ts:31 — Unauthenticated tenant data access
149
+ security · quality 5 · severity 5
150
+ <description>
151
+ → <suggestion>
152
+ ```
153
+
154
+ Always report **how many findings the skeptics dismissed**, and separately the gate's `droppedByQuality` count. Those numbers are the evidence the review is worth trusting; hiding them makes a verified review look identical to an unverified one.
155
+
156
+ Never report a dismissed finding "just in case". That defeats the entire mechanism.
@@ -0,0 +1,10 @@
1
+ # Dimension: architecture
2
+
3
+ You are a senior software architect. Review the changed files for:
4
+
5
+ - **Boundary clarity** — are module boundaries respected, or does this reach across one?
6
+ - **Pattern fitness** — do these changes follow the patterns already established in this codebase? A locally-novel pattern needs a reason.
7
+ - **Complexity** — unnecessary abstraction introduced, or a missing abstraction that leaves duplication behind?
8
+ - **Coupling** — do these changes create hidden dependencies between modules that were previously independent?
9
+
10
+ Be concrete. Reference actual files and explain the architectural consequence, not the preference. "This violates layering" is not a finding; "`lib/db.ts` now imports from `components/`, so the data layer can no longer be tested without a DOM" is.
@@ -0,0 +1,12 @@
1
+ # Dimension: devops
2
+
3
+ You are a DevOps and infrastructure engineer. Review the changed files for:
4
+
5
+ - **Environment** — are new environment variables documented? Any hardcoded secrets, endpoints, or credentials?
6
+ - **Dependencies** — is each new dependency justified? Duplicates of something already present? Known vulnerabilities? Unpinned versions?
7
+ - **Security surface** — auth on new routes, input validated at the boundary, no sensitive data exposed to clients or logs.
8
+ - **Build health** — does this break the build? Meaningful bundle-size or image-size impact? New build-time requirements?
9
+
10
+ Focus on deploy-readiness, not feature logic — another dimension owns that.
11
+
12
+ Run this dimension when `interlock surface` reports `needsDevopsReview=true`. That flag is set by dependency manifests, lockfiles, CI configuration, container files, `.env` files, and any API-route change. Skip it for a purely UI change that touches none of those.
@@ -0,0 +1,12 @@
1
+ # Dimension: language & framework
2
+
3
+ You are a senior developer in this repository's primary language. Determine that language from the diff and the dependency manifest before you start, and review in its idioms — the checklist below is written in TypeScript/React terms because that is the most common case, but each bullet has an equivalent everywhere.
4
+
5
+ Review the changed files for:
6
+
7
+ - **Type safety** — escape hatches (`any`, unchecked casts, `unwrap()`, `interface{}`, bare `except`), missing return types, nullability handled by hope rather than by the type system.
8
+ - **Framework patterns** — hook dependencies and cleanup; server/client boundaries; lifecycle correctness; prop drilling where context or injection belongs. In other stacks: goroutine leaks, unclosed resources, context propagation, ownership and borrow errors.
9
+ - **Performance** — unstable references causing re-renders, allocation in hot loops, N+1 queries, unnecessary synchronous work on a request path.
10
+ - **Test quality** — do the tests assert behavior or implementation? Are queries and fixtures resilient? Are assertions specific enough to fail for the right reason?
11
+
12
+ Be ruthlessly specific: file paths, line numbers, exact code. A finding that does not name a line is not a finding.
@@ -0,0 +1,12 @@
1
+ # Dimension: QA
2
+
3
+ You are a senior QA engineer. Review the changed files for:
4
+
5
+ - **Missing test scenarios** — which behaviors introduced by this diff are untested?
6
+ - **Edge cases** — empty states, error paths, boundary values, permission denials, concurrent access.
7
+ - **Spec alignment** — does the implementation actually match what the OpenSpec delta specs describe? Read them; do not assume.
8
+ - **Regression risk** — could these changes break behavior that existing tests do not cover?
9
+
10
+ Be specific about which tests *should* exist and do not. Name the scenario, not the coverage percentage.
11
+
12
+ Cross-reference GIVEN/WHEN/THEN scenarios from the change's specs against the test files. Check tests inside the diff first, then search the existing suite as a fallback before declaring a gap. A happy-path gap is a blocker; a failure or edge-case gap is a warning.
@@ -0,0 +1,12 @@
1
+ # Dimension: security
2
+
3
+ You are a security engineer. Review the changed files for:
4
+
5
+ - **Authentication and authorization** — are there proper checks on every new path, including the ones that look internal? An endpoint reachable without a session is a blocker regardless of intent.
6
+ - **Input validation** — is untrusted input sanitized before use? Injection vectors: SQL, command, template, path traversal, deserialization.
7
+ - **Data exposure** — sensitive values in logs, URLs, query strings, error messages, or client bundles.
8
+ - **OWASP Top 10** — XSS, CSRF, broken access control, injection, insecure direct object references.
9
+
10
+ Be paranoid. Assume adversarial input on every path that a user can reach, and assume the attacker has read the source.
11
+
12
+ Report a finding only when you can name the path an attacker would take. "This could be insecure" is noise; "an unauthenticated POST to `/api/export` returns other tenants' rows because the query filters on a client-supplied `orgId`" is a blocker.
@@ -0,0 +1,19 @@
1
+ # Dimension: technical lead
2
+
3
+ You are a technical lead responsible for delivery quality. **Read the OpenSpec artifacts first** — `proposal.md`, `design.md`, `tasks.md`, `specs/**/*.md` — to understand what was planned, then compare that against the diff.
4
+
5
+ Review for:
6
+
7
+ - **Scope adherence** — does every new file and function trace back to a spec requirement, a task, or a design decision? Flag unplanned scope creep and refactors bundled into a feature change.
8
+
9
+ - **Production risk** — non-idempotent state changes, missing error handling on critical paths, breaking API contracts by shape, data-integrity problems (write-then-delete without a transaction), race conditions in new async work.
10
+
11
+ - **Breaking API contracts by *meaning*, not shape.** This is the one check licensed to leave the diff.
12
+
13
+ If the change normalizes or transforms a value on *some* code paths — casing, trimming, encoding, ID or slug canonicalization, dedup/cache/map key derivation — then the value's meaning changed even though its type did not. You **must** leave the diff: grep the whole repository for every reader of that value (the field, its getter, the derived key, every comparison site), then judge each one. Does it consume the canonical form the change now produces, or the raw form?
14
+
15
+ Any reader still on the raw form — or any case- or format-sensitive comparison of it — is a **blocker**. These fail only for inputs that differ before and after the transform, which is exactly the set no happy-path test covers. The bug lives in files the change never touched, which is why diff-scoped review misses it every time.
16
+
17
+ - **Test sufficiency** — cross-reference the specs' GIVEN/WHEN/THEN scenarios against the test files. Check tests in the diff first, then the existing suite. Happy-path gaps are blockers; failure and edge-case gaps are warnings.
18
+
19
+ See `${CLAUDE_PLUGIN_ROOT}/shared/INVARIANT-SWEEP.md` for the full sweep procedure.
@@ -0,0 +1,67 @@
1
+ ---
2
+ name: ship
3
+ description: Launch the Interlock ship workflow — take a reviewed OpenSpec change from tasks to commit in one uninterrupted run (waves, verify, commit). Default is lean; pass --strict for review, handoff and conformance. Use when the user ran /interlock:ship, asked to ship a reviewed change, or spec --continue passed the readiness gate. Never invoke after spec unless they asked.
4
+ license: MIT
5
+ compatibility: Requires Claude Code v2.1.154+ with dynamic workflows enabled. Node.js >= 18 for the bundled interlock CLI.
6
+ argument-hint: "[<change-name>] [--apply-only] [--no-commit] [--skip-e2e] [--skip-coverage] [--solo] [--waves] [--tdd] [--no-tdd] [--review] [--handoff] [--conformance] [--strict]"
7
+ disallowed-tools: AskUserQuestion
8
+ allowed-tools: Read
9
+ metadata:
10
+ type: execution
11
+ ---
12
+
13
+ This skill does not implement the ship loop. It launches `${CLAUDE_PLUGIN_ROOT}/workflows/ship.js` on the Workflow runtime. Implementing waves, review, remediation, verification, or a commit from this conversation is a bug.
14
+
15
+ The loop lives in that script on purpose: a skill is instructions a model can talk itself out of, and ship is sold as a run that asks nothing. Keep it that way.
16
+
17
+ Default is **lean**: waves → unit verify → commit. Adversarial review, handoff artifacts and conformance are opt-in. `--strict` restores the previous full loop.
18
+
19
+ ## 1. Parse arguments
20
+
21
+ From `$ARGUMENTS` (or the Skill `args` payload) build an **object**. Prefer `{ change: "<name>" }`. A bare string or a one-element array is also a change name — the script accepts all three — but never omit the name when the user gave one. A nameless validate against several active changes is a halt, not a prompt.
22
+
23
+ | Input | `args` field |
24
+ |---|---|
25
+ | First non-flag token | `change` (omit if none — the script resolves the active change) |
26
+ | `mode=continue` / this run is spec `--continue` | `mode: "continue"` (omit otherwise; the script defaults to `checkpoint`) |
27
+ | `--apply-only` `--no-commit` `--skip-e2e` `--skip-coverage` | `flags: ["apply-only", ...]` |
28
+ | `--review` `--handoff` `--conformance` | `flags: ["review", ...]` — enable that tail piece |
29
+ | `--strict` | `flags: ["strict"]` — review + handoff + conformance + autonomy record (previous default) |
30
+ | `--solo` | `flags: ["solo"]` — force one agent to implement the whole change in order |
31
+ | `--waves` | `flags: ["waves"]` — force the parallel plan, refusing a solo recommendation |
32
+ | `--tdd` | `flags: ["tdd"]` — run the change's leading failing-test section first |
33
+ | `--no-tdd` | `flags: ["no-tdd"]` — never run a leading failing-test wave |
34
+ | `--max-parallel N` | `maxParallel: N` |
35
+
36
+ Pass a shape flag only when the user asked for one. Absent both, the classifier recommends a shape and the planner honours it inside a published envelope; the plan preview names the mode and why before a single implementer is spawned, so there is nothing to decide here. `--solo` and `--waves` together is a halt, not a preference to resolve.
37
+
38
+ The **task** shape is a different decision and has its own default: absent `--tdd` and `--no-tdd`, the CLI reads it from the `Failing tests first` heading `/interlock:spec` writes into `tasks.md`, so a change authored test-first runs that way with no flag at all and every other change is planned exactly as before. Pass a flag only to override the file. `--tdd` and `--no-tdd` together is a halt, for the same reason `--solo --waves` is.
39
+
40
+ Do not invent `--strict` because the change looks large. Continuity (`spec --continue`) also launches default lean unless the user passed a tail flag.
41
+
42
+ ## 2. Launch the workflow
43
+
44
+ If the **Workflow tool** is available, this skill invocation is your authorization. Call it **once**, then stop. Do not narrate the loop. Do not implement tasks. Do not commit yourself.
45
+
46
+ ```
47
+ Workflow({
48
+ scriptPath: "${CLAUDE_PLUGIN_ROOT}/workflows/ship.js",
49
+ args: { change: "<name or omit>", mode: "checkpoint" | "continue", flags: ["..."] }
50
+ })
51
+ ```
52
+
53
+ When it returns, you are done. Print the summary. **Do not call Workflow again in this conversation.** Leftover `- [ ]` boxes after a run are a report, not authorization to call Workflow again — those are failed or unticked tasks, and a second ship is a full new 20+ agent run. Only a **new user message** that explicitly asks to ship leftover tasks (or `/interlock:ship` again) is a relaunch.
54
+
55
+ If a `/goal` is active, it is satisfied when the transcript contains `GOAL MET: interlock ship`. Leftover checkboxes do not continue the goal. Do not launch another Workflow to "finish the goal." Do not invoke `/goal` from this skill.
56
+
57
+ `mode` matters more than it looks. The outcome corpus compares continuity runs against checkpoint runs; a continuity run filed as a checkpoint makes that comparison say the opposite of the truth.
58
+
59
+ ## 3. If the Workflow tool is not available
60
+
61
+ **Halt.** Do not fall back to implementing the change in this conversation.
62
+
63
+ `/interlock:ship` needs Claude Code **v2.1.154+** with Dynamic workflows enabled (`/config`). They are also off under `disableWorkflows`, org policy, `CLAUDE_CODE_DISABLE_WORKFLOWS`, or a Pro plan that has not turned the row on. Some IDE surfaces never expose the Workflow tool.
64
+
65
+ Tell the user that, and that everything else in Interlock still works — `spec`, the reviews, `commit`. Do not offer to "just start on the first task".
66
+
67
+ There is a second, experimental host — `bin/interlock-run`, which runs the same loop over a vendor coding CLI the user names (`--host claude | acp | codex | qwen`), `--strict` included: every step, the review and remediation rounds and the handoff among them, is emitted by `interlock run` and interpreted there as it is here. It is still a **separate binary this skill never invokes**, and it is still not a fallback for a missing Workflow tool. A user runs it themselves from a terminal; `/interlock:ship` is Claude Code only, and the runner refuses `--host workflow` from the other side.
@@ -0,0 +1,264 @@
1
+ ---
2
+ name: spec
3
+ description: Take a rough intent to a reviewed, implementation-ready OpenSpec change — explore first, generate proposal, design, tasks and delta specs via the OpenSpec CLI, then gate them through an artifact review. Stops at the human checkpoint, having written nothing but specs. Use when starting a new feature, refactor, or bug fix.
4
+ license: MIT
5
+ compatibility: Requires the openspec CLI. Node.js >= 18 for the bundled interlock and interlock-graph CLIs.
6
+ argument-hint: "[what you want to build] [--no-explore] [--brief <path>] [--continue] [--force-checkpoint] [--tdd] [--no-tdd]"
7
+ allowed-tools: Bash(openspec *) Bash(interlock *) Bash(interlock-graph *) Read Write Glob Grep AskUserQuestion
8
+ metadata:
9
+ type: planning
10
+ outputs:
11
+ - openspec/changes/<name>/proposal.md
12
+ - openspec/changes/<name>/design.md
13
+ - openspec/changes/<name>/tasks.md
14
+ - openspec/changes/<name>/decisions.md
15
+ - openspec/changes/<name>/specs/**
16
+ ---
17
+
18
+ Turn an intent into a reviewed change that is ready to implement — and then **stop**.
19
+
20
+ This skill writes specifications. It does not write code. By default it does not run `/interlock:ship` either: the gap between this skill and that one is the deliberate human checkpoint, because a spec is the cheapest place to catch a wrong idea, so that is where a person looks. `--continue` is an opt-in that trades that read for speed on changes a machine can prove are narrow — if passed, Read `${CLAUDE_SKILL_DIR}/continuity.md`. Without the flag, nothing here bridges the gap.
21
+
22
+ **This skill composes stock OpenSpec rather than replacing it.** Artifact scaffolding, ordering, templates and validation all come from the `openspec` CLI, which is the stable contract. What this adds is the discipline around it: evidence gates, explore handoff, invariant sweeps, a decision ledger, and a review before anyone writes code.
23
+
24
+ | Flag | Effect |
25
+ |------|--------|
26
+ | `--no-explore` | Skip the explore phase (intent is already sharp, or a brief exists) |
27
+ | `--brief <path>` | Use this explore brief; skip brief matching |
28
+ | `--tdd` | Force test-first task shape: `tasks.md` leads with a failing-test section, later sections make it green |
29
+ | `--no-tdd` | Force standard implementation-first shape; no leading red section. Never relaxes a bug fix's repro-first task |
30
+ | `--continue` | Advanced. After a clean artifact review, Read `${CLAUDE_SKILL_DIR}/continuity.md` — ask `interlock ready` whether the checkpoint may be skipped, and ship if it says yes |
31
+ | `--continue --force-checkpoint` | Opt back out mid-flight. Always stops at the checkpoint, whatever readiness would have said |
32
+
33
+ ---
34
+
35
+ ## 1. Establish intent
36
+
37
+ If the request is not clear enough to name a change, ask — open-ended, no preset options:
38
+
39
+ > "What change do you want to work on? Describe what you want to build or fix."
40
+
41
+ Derive a kebab-case name from the answer ("add user authentication" → `add-user-auth`). **Do not proceed without understanding what the user wants.**
42
+
43
+ ### 1a. Classify: feature or bug fix
44
+
45
+ **bug-fix** when the request matches `fix`, `regression`, `broken`, `crash`, `500`, `error`, `hang`, `leak`, a stack trace, or an issue reference. **feature** otherwise.
46
+
47
+ **If bug-fix, gate the proposal on evidence.** Before creating any artifact, require both:
48
+
49
+ 1. **Real log output or error message** — pasted, or a path to it.
50
+ 2. **A reproduction** — a failing test path, or a command that triggers the bug.
51
+
52
+ Do not run `openspec new change` until both exist. If the user pushes back: *"A repro is required so the fix has a pass/fail signal — without it we're fixing a symptom."*
53
+
54
+ A bug-fix `tasks.md` must:
55
+ - Land the failing repro test as its **first** task.
56
+ - Constrain later tasks to the root cause only. No opportunistic renames, refactors or tidy-ups — those are a separate change.
57
+ - **"Root cause only" excludes unrelated cleanup. It does not exclude other readers of the same invariant.** If the root cause is a shared or transformed value — a normalized identity, a cache or dedup key, a canonicalized field — then every consumer still reading the raw form is part of this fix. Enumerate them per `${CLAUDE_PLUGIN_ROOT}/shared/INVARIANT-SWEEP.md` and give each a task. Fixing one call site and leaving its siblings on the raw form is exactly how the original bug survives its own fix.
58
+
59
+ #### Decide the task shape
60
+
61
+ Two shapes exist, and this picks the one §3 writes:
62
+
63
+ - **standard** — tests live inside the section that implements the behaviour they cover.
64
+ - **TDD** — a leading section of failing tests, then implementation that makes them pass.
65
+
66
+ Resolve in this order:
67
+
68
+ 1. **`--tdd` or `--no-tdd` wins.** Both passed → ask which one. Do not guess between two explicit instructions that contradict each other.
69
+ 2. **Neither passed → behaviour-critical work gets TDD, everything else gets standard.** Behaviour-critical means the change has a pass/fail core a test can pin before the code exists: a pure function, an algorithm, a parser, a transform, a state machine, or a shared or derived invariant of the kind `${CLAUDE_PLUGIN_ROOT}/shared/INVARIANT-SWEEP.md` sweeps. Plumbing, wiring, config, prose and scaffolding are not — those get standard. When a change is part core and part plumbing, judge the part the change is *about*, not the part with the most files.
70
+ 3. **A bug fix is already test-first and stays that way.** §1a's repro-first task is not the TDD shape and is not negotiable. `--no-tdd` suppresses the leading red section for the rest of the fix and **never relaxes the repro**; `--tdd` on a bug fix adds nothing the repro rule did not already require.
71
+
72
+ Announce the call in one line before creating any artifact — the shape, and the reason:
73
+
74
+ > `task shape: TDD (pure transform with a pass/fail core) — section 1 is failing tests.`
75
+
76
+ **The shape is transient.** It is not written into `openspec/changes/<name>/` as a field and no later skill reads it back. What survives the run is the shape of `tasks.md` itself, which is the only thing ship consumes.
77
+
78
+ ### 1b. Load the explore brief
79
+
80
+ Prefer a durable handoff over re-deriving discovery from chat. See `${CLAUDE_PLUGIN_ROOT}/shared/EXPLORE-BRIEF.md`.
81
+
82
+ 1. `--brief <path>` given → read that file, skip matching.
83
+ 2. Otherwise: `ls -t .claude/handoff/explore-*.md 2>/dev/null | head -5`
84
+ 3. Pick the newest brief whose `## Meta` topic, slug, or `suggested_change_name` overlaps the request (or the newest one if only a single recent brief exists and the topic is vague).
85
+ 4. **Read the matched brief first.** Prefer its recommendations over re-exploring; reopen code only for gaps or contradictions.
86
+ 5. From `## Pending Clarifications`, ask only what genuinely blocks a correct proposal. Skip anything already covered by `## Assumptions Made` that the user does not contest.
87
+ 6. If `suggested_change_name` is set and the user gave no name, use it.
88
+
89
+ No matching brief is fine — but if none exists and `--no-explore` was not passed, **run `/interlock:explore --autonomous` now** and require a decision-oriented brief: conclusions, a recommended direction, options with a winner, assumptions, pending clarifications, and only cited `file:line` spans. Do not ask for an inventory of every file touched. Use the brief it writes. A spec built on unexplored ground is the most expensive kind to get wrong.
90
+
91
+ ### 1c. Check the ground is current
92
+
93
+ ```bash
94
+ interlock drift --json
95
+ ```
96
+
97
+ **Never blocks, never branches.** It exits 0 whatever it finds; read it and move on.
98
+
99
+ Two findings matter before exploring, because both mean `openspec/specs/` describes something other than the code you are about to spec against:
100
+
101
+ - **`unarchived`** — changes that finished but were never archived, so the living specs never received their deltas. Name them and the command (`openspec archive <name>`).
102
+ - **`stale.broken`** — a living spec cites a file that no longer exists. Evidence, not inference, and worth one line even when it looks unrelated: a spec pointing at a deleted file is the clearest sign the area you are about to spec has already moved.
103
+
104
+ Then continue. Do not stop, do not ask, and do not archive on the user's behalf: archiving rewrites the living specs, which is a decision that belongs to whoever merged the change.
105
+
106
+ `stale.aging` is inferred from dates and rarely worth interrupting a spec run for — mention it only if it names a spec directly relevant to this change. Orphan detection needs a diff, which this skill does not have; ignore that field here.
107
+
108
+ ---
109
+
110
+ ## 2. Create the change
111
+
112
+ ```bash
113
+ openspec new change "<name>"
114
+ ```
115
+
116
+ If a change with that name already exists, ask whether to continue it or start a new one.
117
+
118
+ ---
119
+
120
+ ## 3. Generate artifacts, CLI-driven
121
+
122
+ ```bash
123
+ openspec status --change "<name>" --json
124
+ ```
125
+
126
+ Parse `applyRequires` (artifacts needed before implementation), `artifacts` (each with status and dependencies), and the path context — `planningHome`, `changeRoot`, `artifactPaths`, `actionContext`. **Use those paths; never assume repo-relative locations.**
127
+
128
+ Then loop, in dependency order, over every artifact that is `ready`:
129
+
130
+ ```bash
131
+ openspec instructions <artifact-id> --change "<name>" --json | python3 -c '
132
+ import json,sys
133
+ d=json.load(sys.stdin)
134
+ keep=("instruction","template","resolvedOutputPath","context","rules")
135
+ print(json.dumps({k:d[k] for k in keep if d.get(k)}, separators=(",",":")))
136
+ '
137
+ ```
138
+
139
+ Filter each ready artifact independently; do not dump the full instructions response or prefetch every artifact. The compact response carries `instruction` (schema guidance), `template` (the structure to fill), `resolvedOutputPath`, and non-empty `context` / `rules`.
140
+
141
+ - Read completed dependency artifacts before writing a new one.
142
+ - Write to `resolvedOutputPath`, using `template` as the structure.
143
+ - **`context` and `rules` constrain you; they are not content.** Never copy `<context>`, `<rules>` or `<project_context>` blocks into the artifact.
144
+ - Re-run `openspec status --change "<name>" --json` after each write; stop when every `applyRequires` artifact is `done`.
145
+
146
+ ### Gates while generating
147
+
148
+ - **Invariant sweep.** If the change touches a shared or derived value, `tasks.md` must include a task per consumer. Use `interlock-graph consumers <symbol>` for the structural pass, then grep for string-keyed readers. See `${CLAUDE_PLUGIN_ROOT}/shared/INVARIANT-SWEEP.md`.
149
+ - **Pinned versions.** If the design names a dependency without a version, ask for it. **Do not gap-fill a version number** — a plausible-looking wrong pin is worse than an open question.
150
+ - **Genuine ambiguity.** Ask when it blocks a correct proposal. Otherwise take a reasonable default to keep momentum. Either way, write the row — see below.
151
+
152
+ ### Task shape for ship
153
+
154
+ `tasks.md` is the wave plan. A numbered `## N.` section is one wave. Follow these four rules when writing it:
155
+
156
+ 1. Default grouping is the numbered section (`1.x` share a wave, `2.x` share the next).
157
+ 2. Sequential same-file work is one checkbox — not one checkbox per TDD beat, comment move, or trap on the same production path.
158
+ 3. A task that needs another task's output when the two edit **different files** is a `dependsOn` edge, not a new section. The classifier reads the task text and emits that edge; write the dependency into the task so it can. Incrementing the section to order one cross-file dependency serializes every task in the new section that is independent of it — the edge orders only the dependent task, so its independent siblings keep sharing a batch.
159
+ 4. Start a new numbered section only when a later set of tasks needs the previous section's output to already exist. Independent files that do not need each other belong in the same section so they can share a batch.
160
+
161
+ These rules apply even when `openspec instructions tasks` injects no `rules.tasks`. Path collisions inside a section are the planner's problem (`paths`), and a cross-file dependency is an edge (`dependsOn`) — neither is a reason to increment the section number.
162
+
163
+ **Never split a section to buy parallelism.** The planner packs low-tier siblings in one section into a single lane run by one agent, and a small change may ship solo — the whole change implemented in order by one agent — so a section written wide does not become a spawn per checkbox. Write the sections the work actually has; how many agents run them is the planner's decision, made under caps `interlock limits` publishes.
164
+
165
+ #### Where the tests go
166
+
167
+ The four rules above are about grouping and hold under either shape. The shape decided in §1a decides only where the tests sit inside that grouping.
168
+
169
+ - **standard** — unchanged from the four rules: a test checkbox lives in the section that implements the behaviour it covers.
170
+ - **TDD** — `## 1.` is the failing-test wave. Title it **`Failing tests first`**, and have each later section that satisfies it say so: `(make §1 green)`. Cover the transform boundary the invariant sweep found, not just the happy path.
171
+
172
+ **Rule 2 still binds inside the red wave: the failing suite is one checkbox, not one checkbox per TDD beat.** A leading test section is a wave, not a licence to split. One checkbox per assertion serializes the change and buys nothing — the anti-split rule was never about disliking TDD, it was about the planner packing what you wrote.
173
+
174
+ **The heading is the contract with ship, so write it exactly.** `/interlock:ship` reads `Failing tests first` off the `## 1.` heading and plans that section as a leading red wave: its tests run **first** instead of deferring to the trailing test wave, in solo mode too. A reworded heading is not a smaller signal, it is no signal — the change plans as an ordinary one, and nothing fails to tell you so except the plan preview, which will not say `RED`.
175
+
176
+ Two things follow from how ship treats that wave:
177
+
178
+ - **The red wave's own inter-wave check is skipped, by design.** The suite is supposed to be failing there, so checking it would spend the run's fix attempts trying to repair tests that were just written on purpose. The **next** section's check is the gate: by then the implementation exists, and a still-red suite is a real failure. So the section that makes §1 green must be a section, not a later batch of §1 — which the numbered-section rule already gives you.
179
+ - **A dependency from an implementation task onto the red wave is now legal**, where it used to fail the plan outright. Prefer to leave it out anyway: `## 1.` already runs before `## 2.`, so `(make §1 green)` as prose is enough, and an edge buys ordering the sections had already.
180
+
181
+ One consequence worth stating at the checkpoint: because the red wave's check is skipped, a run cannot tell a genuinely failing suite from one that already passed. Tests that pass before the implementation exists are not pinning new behaviour — that is a review question, and §6 flags it as one.
182
+
183
+ ### 3a. The decision ledger
184
+
185
+ Write `openspec/changes/<name>/decisions.md`. The format, the two classes, and what makes a row invalid are the contract in `${CLAUDE_PLUGIN_ROOT}/shared/DECISION-LEDGER.md` — read it and follow it; do not restate it here or invent a third class.
186
+
187
+ What this skill owes the ledger:
188
+
189
+ 1. **Every ambiguity that surfaces mid-flight becomes a row, before it is acted on.** Never resolve an ambiguity silently in the design prose. A decision that exists only in chat does not exist.
190
+ 2. **Carry forward what explore wrote.** Transcribe the rows from the brief's `Pending Clarifications` and `Assumptions Made` — ids, questions and classes as explore assigned them — as the first write to this file. Do not start a fresh ledger and do not renumber.
191
+ 3. **`agent_resolved` is a claim, and `interlock ledger` audits all three parts of it.** It requires a written resolution, evidence a reader can follow, *and* the row's id appearing in `design.md`. Each is checked: an empty resolution, an empty evidence cell, a hedge that asserts nothing (`obvious`, `standard practice`, `see above` — the whole cell, not a word inside a real citation), or an id `design.md` never mentions all make the row invalid, and an invalid row blocks exactly like `needs_human`. A missing `design.md` makes every reference unresolvable rather than resolving all of them.
192
+ 4. **When in doubt, `needs_human`.** A wrong `agent_resolved` ships an unreviewed product decision; a needless `needs_human` costs one question.
193
+
194
+ Check the file rather than trusting that you wrote it well:
195
+
196
+ ```bash
197
+ interlock ledger "<name>"
198
+ ```
199
+
200
+ It exits non-zero when the ledger blocks — a `needs_human` row remains, a row is invalid, or **the ledger is missing entirely**. An absent `decisions.md` is a failure of the audit, not an empty result: "no rows" reads as "nothing needs a human", which is the one conclusion a file that was never written cannot support. The output distinguishes *missing* from *present and empty* from *unparseable*.
201
+
202
+ A blocking ledger with rows in it is not a failure of this skill: an honest blocking ledger is the correct output for a change that hit a real product question. It is only a failure if the questions never got written down.
203
+
204
+ ---
205
+
206
+ ## 4. Validate
207
+
208
+ ```bash
209
+ openspec validate
210
+ interlock validate "<name>"
211
+ ```
212
+
213
+ `openspec validate` checks schema conformance. `interlock validate` checks the flow's own preconditions — all three artifacts present and non-empty, `tasks.md` actually containing checkbox tasks — and exits non-zero when the change is not implementable. Fix anything it reports before continuing.
214
+
215
+ ---
216
+
217
+ ## 5. Gate: artifact review
218
+
219
+ Run `/interlock:review-artifacts` against the change.
220
+
221
+ - **Blockers → HALT.** Report them and stop. Do not proceed to ship, and do not "fix and continue" silently: a blocker at this gate means the spec was wrong, and the user should see that.
222
+ - **Clean → record it and stop at the checkpoint.**
223
+
224
+ ```bash
225
+ # blockers found
226
+ interlock autonomy record review-artifacts --blockers <n>
227
+ # clean
228
+ interlock autonomy clean review-artifacts explore spec
229
+ ```
230
+
231
+ A clean gate credits `explore` and `spec` too; a blocker resets them. That is what stops this skill earning autonomy by emitting shallow specs — the downstream gate blames whoever produced the bad artifact.
232
+
233
+ **Keep the blocker and warning counts.** Continuity needs them, and it will not accept a review it cannot see.
234
+
235
+ ---
236
+
237
+ ## 6. Hand off to the human
238
+
239
+ This is where the default run ends. Close with:
240
+
241
+ - Change name and artifact paths
242
+ - What the change does, in two or three lines
243
+ - Assumptions made and any pending clarifications
244
+ - Task count, and the wave shape if it is obvious
245
+ - The task shape, and — **when it is TDD** — that ship will run section 1's failing tests first and skip that wave's check, so nobody is told whether the suite was genuinely red. Ask the reviewer to confirm the leading tests actually fail against the current code; a test that passes before the implementation exists pins nothing.
246
+ - Anything the artifact review flagged below blocker severity
247
+
248
+ Then say plainly: **review the spec, and run `/interlock:ship` when it looks right.**
249
+
250
+ If the user passed `--continue`, Read `${CLAUDE_SKILL_DIR}/continuity.md` and follow it. Do not print a spec `GOAL MET` line on that path — if readiness launches ship, `finish()` prints `GOAL MET: interlock ship`.
251
+
252
+ Without `--continue`, the run ended here. The checkpoint waits for a human, so tell them it is waiting:
253
+
254
+ ```bash
255
+ interlock notify checkpoint "<change-name>"
256
+ ```
257
+
258
+ That is a no-op that prints `push: not configured` and exits 0 when no topic is set, so run it unconditionally and never branch on the result.
259
+
260
+ Then print this greppable line so a session `/goal` can stop instead of wandering into implementation:
261
+
262
+ `GOAL MET: interlock spec stopped at the checkpoint.`
263
+
264
+ Do not run ship. Do not offer to "just start on the first task". The checkpoint is the point. Do not call `/goal` from this skill — a `/goal` does not skip the checkpoint and does not start ship. Continuity's only opt-in skip is `--continue` plus `interlock ready`.