cclaw-cli 6.8.0 → 6.9.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.
- package/dist/artifact-linter/design.js +1 -1
- package/dist/artifact-linter/shared.js +2 -1
- package/dist/artifact-linter/tdd.d.ts +11 -0
- package/dist/artifact-linter/tdd.js +174 -7
- package/dist/content/harness-doc.js +1 -1
- package/dist/content/hooks.js +5 -1
- package/dist/content/iron-laws.js +6 -2
- package/dist/content/node-hooks.js +15 -1308
- package/dist/content/skills-elicitation.js +2 -2
- package/dist/content/stages/brainstorm.js +2 -2
- package/dist/content/stages/design.js +2 -2
- package/dist/content/stages/scope.js +2 -2
- package/dist/content/stages/tdd.js +1 -0
- package/dist/content/subagents.js +11 -1
- package/dist/delegation.d.ts +10 -3
- package/dist/delegation.js +13 -4
- package/dist/early-loop.js +15 -1
- package/dist/gate-evidence.js +15 -23
- package/dist/harness-adapters.js +4 -2
- package/dist/install.js +37 -221
- package/dist/internal/detect-supply-chain-changes.d.ts +6 -0
- package/dist/internal/detect-supply-chain-changes.js +138 -0
- package/dist/internal/flow-state-repair.d.ts +7 -0
- package/dist/internal/flow-state-repair.js +57 -18
- package/dist/run-persistence.d.ts +2 -0
- package/dist/run-persistence.js +62 -3
- package/dist/runtime/run-hook.mjs +44 -8729
- package/package.json +1 -1
|
@@ -47,7 +47,7 @@ These behaviors are the exact reason this skill exists. The linter will block yo
|
|
|
47
47
|
- Use harness-native question tools first; prose fallback is allowed only when the tool is unavailable.
|
|
48
48
|
- Keep a running Q&A trace in the active artifact under \`## Q&A Log\` in \`${RUNTIME_ROOT}/artifacts/\` as append-only rows.
|
|
49
49
|
- **Early-loop ledger discipline**: Never append \`.cclaw/state/early-loop-log.jsonl\` rows whose \`iteration\` exceeds the active \`maxIterations\`. If the cap fired, escalate or accept convergence outcomes—do not bump the iteration counter afterward. \`deriveEarlyLoopStatus\` clamps persistence, but the log source should stay honest too.
|
|
50
|
-
- **Convergence floor**: do NOT advance the stage (do NOT call \`stage-complete.mjs\`) until Q&A converges. The machine contract matches \`evaluateQaLogFloor\` in \`src/artifact-linter/shared.ts\` (rule \`qa_log_unconverged\`). Pass when ANY holds: (a) every forcing-question topic id is tagged \`[topic:<id>]\` on at least one \`## Q&A Log\` row; (b) the Ralph
|
|
50
|
+
- **Convergence floor (a.k.a. "Q&A Ralph Loop" / "Elicitation Convergence")**: do NOT advance the stage (do NOT call \`stage-complete.mjs\`) until Q&A converges. The machine contract matches \`evaluateQaLogFloor\` in \`src/artifact-linter/shared.ts\` (rule \`qa_log_unconverged\`). Pass when ANY holds: (a) every forcing-question topic id is tagged \`[topic:<id>]\` on at least one \`## Q&A Log\` row; (b) the Q&A Ralph Loop detector fires (last 2 substantive rows are non-decision-changing: \`skip\`/\`continue\`/\`no-change\`/\`done\`/etc.) **and** the log has at least \`max(2, questionBudgetHint(discoveryMode, stage).min)\` substantive rows — **unless** \`discoveryMode\` is \`guided\` or \`deep\` with pending forcing-topic ids (then the Q&A Ralph Loop alone cannot pass until topics are tagged, a stop-signal is recorded, or \`--skip-questions\` downgrades the finding to advisory); (c) an explicit user stop-signal row; or (d) \`--skip-questions\` was persisted (unconverged is advisory only). Wave 24 (v6.0.0) made \`[topic:<id>]\` mandatory (no English keyword fallback). The "Q&A Ralph Loop" is the elicitation-stage convergence mechanism; the producer/critic Concern Ledger that drives early-stage iteration is the **Early-Loop**, persisted in \`.cclaw/state/early-loop-log.jsonl\` and \`early-loop.json\` — they are different machines, do not conflate them.
|
|
51
51
|
- **NEVER run shell hash commands** (\`shasum\`, \`sha256sum\`, \`md5sum\`, \`Get-FileHash\`, \`certutil\`, etc.) to compute artifact hashes. If a linter ever asks you for a hash, that is a linter bug — report failure and stop, do not auto-fix in bash.
|
|
52
52
|
- **NEVER paste cclaw command lines into chat** (e.g. \`node .cclaw/hooks/stage-complete.mjs ... --evidence-json '{...}'\`). Run them via the tool layer; report only the resulting summary. The user does not run cclaw manually and seeing the command line is noise.
|
|
53
53
|
|
|
@@ -107,7 +107,7 @@ Do not ask extra questions "for theater" on simple low-risk work.
|
|
|
107
107
|
|
|
108
108
|
## Question Budget Hint (\`questionBudgetHint\` — min rows feed the convergence floor)
|
|
109
109
|
|
|
110
|
-
Source of truth: \`questionBudgetHint(discoveryMode, stage)\`. The \`Min\` column is **not advisory** for the Ralph
|
|
110
|
+
Source of truth: \`questionBudgetHint(discoveryMode, stage)\`. The \`Min\` column is **not advisory** for the Q&A Ralph Loop exit: \`evaluateQaLogFloor\` requires at least \`max(2, Min)\` substantive rows before the no-new-decisions path can converge (other exits — full topic coverage, stop-signal, \`--skip-questions\` advisory — ignore that minimum). \`Recommended\` and \`Hard cap warning\` remain pacing hints for the harness.
|
|
111
111
|
|
|
112
112
|
${budgetTable}
|
|
113
113
|
|
|
@@ -36,7 +36,7 @@ export const BRAINSTORM = {
|
|
|
36
36
|
},
|
|
37
37
|
executionModel: {
|
|
38
38
|
checklist: [
|
|
39
|
-
"**ADAPTIVE ELICITATION COMES FIRST (no exceptions, no subagent dispatch before).** Load `.cclaw/skills/adaptive-elicitation/SKILL.md`. Walk the brainstorm forcing questions one-at-a-time via the harness-native question tool, append one row to `## Q&A Log` (`Turn | Question | User answer (1-line) | Decision impact`) after each user answer **and stamp the row's `Decision impact` cell with the matching `[topic:<id>]` tag** (e.g. `[topic:pain]`). Continue until every forcing-question topic id is tagged on a row OR Ralph
|
|
39
|
+
"**ADAPTIVE ELICITATION COMES FIRST (no exceptions, no subagent dispatch before).** Load `.cclaw/skills/adaptive-elicitation/SKILL.md`. Walk the brainstorm forcing questions one-at-a-time via the harness-native question tool, append one row to `## Q&A Log` (`Turn | Question | User answer (1-line) | Decision impact`) after each user answer **and stamp the row's `Decision impact` cell with the matching `[topic:<id>]` tag** (e.g. `[topic:pain]`). Continue until every forcing-question topic id is tagged on a row OR the Q&A Ralph Loop convergence detector says no new decision-changing rows in last 2 iterations OR user records an explicit stop-signal row. Only then proceed to delegations, drafts, or analysis. The linter `qa_log_unconverged` rule will block `stage-complete` if convergence is not reached.",
|
|
40
40
|
"**Explore project context** — after the elicitation loop converges, inspect existing files/docs/recent activity to refine the Discovered context section; capture matching files/patterns/seeds in `Context > Discovered context` so downstream stages don't redo discovery.",
|
|
41
41
|
"**Brainstorm forcing questions (must be covered or explicitly waived)** — `pain: what pain are we solving`; `direct-path: what is the direct path`; `operator: who is the first operator/user affected`; `no-go: what no-go boundaries are non-negotiable`. Tag the matching `## Q&A Log` row's `Decision impact` cell with `[topic:<id>]` (e.g. `[topic:pain]`) so the linter can verify coverage in any natural language. Tags are MANDATORY for forcing-question rows; un-tagged rows do NOT count toward coverage. Round 6 (v6.7.0) removed the counterfactual `do-nothing` topic; the Problem Decision Record already captures `Do-nothing consequence`.",
|
|
42
42
|
"**Discovery posture (flow-state `discoveryMode`)** — follow `lean` / `guided` / `deep` from the active run. Use lean for smallest safe discovery pass; guided as the default balanced pass; escalate to deep when ambiguity, architecture, external dependency, security/data risk, or explicit think-bigger requests warrant fuller option pressure and mandatory specialist coverage.",
|
|
@@ -52,7 +52,7 @@ export const BRAINSTORM = {
|
|
|
52
52
|
"**Compare 2-3 distinct approaches with stable Role/Upside columns** — Role values are `baseline` | `challenger` | `wild-card`; Upside is `low` | `modest` | `high` | `higher`; include real trade-offs, reuse notes, and reference-pattern source/disposition when a known pattern influenced the option; include exactly one challenger with explicit `high` or `higher` upside.",
|
|
53
53
|
"**Collect reaction before recommending** — ask which option feels closest and what concern remains, then recommend based on that reaction.",
|
|
54
54
|
"**Write the `Not Doing` list** — name 3-5 things this brainstorm explicitly is not committing to (vs. deferred). This protects scope from silent enlargement and the next stage from rework.",
|
|
55
|
-
"**Run
|
|
55
|
+
"**Run Early-Loop / Concern Ledger discipline** — after each producer iteration, append a `Critic Pass` JSONL row to `.cclaw/state/early-loop-log.jsonl`, refresh `.cclaw/state/early-loop.json`, and iterate until open concerns clear or convergence guard escalates. (This is the producer-critic concern ledger, not the Q&A Ralph Loop used for elicitation convergence.)",
|
|
56
56
|
"**Embedded Grill (post-pick, one-at-a-time)** — after `Selected Direction` is named, if grilling triggers fire (irreversibility, security/auth boundary, domain-model ambiguity per `adaptive-elicitation:Conditional Grilling`), continue the elicitation loop with sharper questions **one at a time**, appended to `## Q&A Log` and reflected as rows in `## Embedded Grill`. Do NOT batch the 3-5 grill checks — each one follows the Core Protocol (ask, wait, log, self-eval, ask next).",
|
|
57
57
|
"**Self-review before user approval** — re-read the artifact and patch contradictions, weak trade-offs, placeholders, ambiguity, and weak handoff language. Record the result in `Self-Review Notes` using the calibrated review format: `- Status: Approved` (or `Issues Found`), `- Patches applied:` with inline note or sub-bullets, `- Remaining concerns:` with inline note or sub-bullets. Use `Patches applied: None` and `Remaining concerns: None` when there is nothing to record.",
|
|
58
58
|
"**Request explicit approval to close the stage** — state exactly what direction is being approved after the adaptive elicitation loop converges; do not advance without approval and artifact review.",
|
|
@@ -41,7 +41,7 @@ export const DESIGN = {
|
|
|
41
41
|
},
|
|
42
42
|
executionModel: {
|
|
43
43
|
checklist: [
|
|
44
|
-
"**ADAPTIVE ELICITATION COMES FIRST (no exceptions, no subagent dispatch before).** Load `.cclaw/skills/adaptive-elicitation/SKILL.md`. Walk the design forcing questions one-at-a-time via the harness-native question tool, append one row to `## Q&A Log` (`Turn | Question | User answer (1-line) | Decision impact`) after each user answer **and stamp the row's `Decision impact` cell with the matching `[topic:<id>]` tag** (e.g. `[topic:data-flow]`). Continue until every forcing-question topic id is tagged on a row OR Ralph
|
|
44
|
+
"**ADAPTIVE ELICITATION COMES FIRST (no exceptions, no subagent dispatch before).** Load `.cclaw/skills/adaptive-elicitation/SKILL.md`. Walk the design forcing questions one-at-a-time via the harness-native question tool, append one row to `## Q&A Log` (`Turn | Question | User answer (1-line) | Decision impact`) after each user answer **and stamp the row's `Decision impact` cell with the matching `[topic:<id>]` tag** (e.g. `[topic:data-flow]`). Continue until every forcing-question topic id is tagged on a row OR the Q&A Ralph Loop convergence detector says no new decision-changing rows in last 2 iterations OR user records an explicit stop-signal row. Only then proceed to research, investigator pass, architecture lock, or any delegations. The linter `qa_log_unconverged` rule will block `stage-complete` if convergence is not reached.",
|
|
45
45
|
"**Design forcing questions (must be covered or explicitly waived)** — `data-flow: what is the end-to-end data flow`; `seams: where are seams/ownership boundaries`; `invariants: which invariants must hold`; `not-refactor: what will explicitly NOT be refactored now`. Tag the matching `## Q&A Log` row's `Decision impact` cell with `[topic:<id>]` (e.g. `[topic:data-flow]`) so the linter can verify coverage in any natural language. Tags are MANDATORY for forcing-question rows; un-tagged rows do NOT count toward coverage.",
|
|
46
46
|
"**Out-of-scope carry-forward (do NOT re-author)** — scope OWNS the out-of-scope list. Cite scope's `## In Scope / Out of Scope > Out of Scope` via `## Upstream Handoff > Decisions carried forward`; do NOT add a separate `## NOT in scope` section in the design artifact. Add a row to `## Spec Handoff` only if a design-stage decision NEWLY excludes something not already in scope's out-of-scope.",
|
|
47
47
|
"Compact design lock — design does not decide what to build; it decides how the approved scope works. For simple slices, produce a tight lock: upstream handoff, existing fit, architecture boundary, one labeled diagram, data/state flow, critical path, failure/rescue, trust boundaries, test/perf expectations, rollout/rollback, rejected alternative, and spec handoff.",
|
|
@@ -55,7 +55,7 @@ export const DESIGN = {
|
|
|
55
55
|
"Review core risk areas — existing system fit, data/state flow, critical path, security/trust boundaries, tests, performance budget, observability/debuggability, rollout/rollback, rejected alternatives, and spec handoff.",
|
|
56
56
|
"**ADR + pre-mortem contract** — capture ADR-style decision rows (context, decision, alternatives, consequences), run a pre-mortem on likely failures, and map each critical flow to a validating test and diagram anchor before lock.",
|
|
57
57
|
"Critic pass — run/reconcile adversarial second opinion on architecture, coupling, failure modes, and cheaper alternatives; record outcomes per the Design Outside Voice Loop policy.",
|
|
58
|
-
"**Run
|
|
58
|
+
"**Run Early-Loop / Concern Ledger discipline** — after each producer iteration, append a `Critic Pass` JSONL row to `.cclaw/state/early-loop-log.jsonl`, refresh `.cclaw/state/early-loop.json`, and iterate until open concerns clear or convergence guard escalates. (This is the producer-critic concern ledger, not the Q&A Ralph Loop used for elicitation convergence.)",
|
|
59
59
|
"Run stale-diagram audit as a design freshness gate (default-on; explicit config opt-out allowed).",
|
|
60
60
|
"Capture leftovers — seed high-upside deferred ideas, list unresolved decisions with defaults, document distribution for new artifact types, and cross-reference deferred items to scope or unresolved decisions."
|
|
61
61
|
],
|
|
@@ -46,7 +46,7 @@ export const SCOPE = {
|
|
|
46
46
|
},
|
|
47
47
|
executionModel: {
|
|
48
48
|
checklist: [
|
|
49
|
-
"**ADAPTIVE ELICITATION COMES FIRST (no exceptions, no subagent dispatch before).** Load `.cclaw/skills/adaptive-elicitation/SKILL.md`. Walk the scope forcing questions one-at-a-time via the harness-native question tool, append one row to `## Q&A Log` (`Turn | Question | User answer (1-line) | Decision impact`) after each user answer **and stamp the row's `Decision impact` cell with the matching `[topic:<id>]` tag** (e.g. `[topic:in-out]`). Continue until every forcing-question topic id is tagged on a row OR Ralph
|
|
49
|
+
"**ADAPTIVE ELICITATION COMES FIRST (no exceptions, no subagent dispatch before).** Load `.cclaw/skills/adaptive-elicitation/SKILL.md`. Walk the scope forcing questions one-at-a-time via the harness-native question tool, append one row to `## Q&A Log` (`Turn | Question | User answer (1-line) | Decision impact`) after each user answer **and stamp the row's `Decision impact` cell with the matching `[topic:<id>]` tag** (e.g. `[topic:in-out]`). Continue until every forcing-question topic id is tagged on a row OR the Q&A Ralph Loop convergence detector says no new decision-changing rows in last 2 iterations OR user records an explicit stop-signal row. Only then propose the scope contract draft, recommend a mode, or dispatch any delegations. The linter `qa_log_unconverged` rule will block `stage-complete` if convergence is not reached.",
|
|
50
50
|
"**Scope forcing questions (must be covered or explicitly waived)** — `in-out: what is definitely in/out`; `locked-upstream: which upstream decisions are locked`. Tag the matching `## Q&A Log` row's `Decision impact` cell with `[topic:<id>]` (e.g. `[topic:in-out]`) so the linter can verify coverage in any natural language. Tags are MANDATORY for forcing-question rows; un-tagged rows do NOT count toward coverage. Round 6 (v6.7.0) removed the counterfactual `rollback` and `failure-modes` topics from scope forcing questions; Design still owns the Failure Mode Table and rollback evidence.",
|
|
51
51
|
"**Scope contract first** — read brainstorm handoff, name upstream decisions used, explicit drift, confidence, unresolved questions, and next-stage risk hints; draft the in-scope/out-of-scope/deferred/discretion contract before any design choice.",
|
|
52
52
|
"**Premise carry-forward (do NOT re-author)** — brainstorm OWNS the premise check (right problem / direct path). Cite brainstorm's `## Premise Check` section in `## Upstream Handoff > Decisions carried forward`. Add a row to `## Premise Drift` only when the scope-stage Q&A surfaced NEW evidence that materially changes the brainstorm answer (e.g. new constraint, new user signal). Otherwise mark `Premise Drift: None` — do not duplicate the brainstorm premise table.",
|
|
@@ -58,7 +58,7 @@ export const SCOPE = {
|
|
|
58
58
|
"**Architecture handoff (do NOT pick architecture tier here)** — design OWNS architecture choice (minimum-viable / product-grade / ideal). Scope only picks the SCOPE MODE (HOLD/SELECTIVE/EXPAND/REDUCE) and boundary; record in `## Scope Contract > Design handoff` what design must decide (e.g. `architecture-tier`, `framework`, `data-model`). Do NOT enumerate Implementation Alternatives in scope.",
|
|
59
59
|
"**Constraints (carry-forward from brainstorm/external sources)** — record explicit external/regulatory/system/integration constraints in `## Scope Contract > Constraints`. Spec OWNS testable assumptions (`## Assumptions Before Finalization`); do NOT duplicate constraint material as assumption material.",
|
|
60
60
|
"**Run outside voice before final approval** — for simple/low-risk scope, record one concise adversarial self-check row; for complex/high-risk/configured scope, iterate until threshold. Record the loop summary in `## Scope Outside Voice Loop`, but do not treat it as user approval.",
|
|
61
|
-
"**Run
|
|
61
|
+
"**Run Early-Loop / Concern Ledger discipline** — after each producer iteration, append a `Critic Pass` JSONL row to `.cclaw/state/early-loop-log.jsonl`, refresh `.cclaw/state/early-loop.json`, and iterate until open concerns clear or convergence guard escalates. (This is the producer-critic concern ledger, not the Q&A Ralph Loop used for elicitation convergence.)",
|
|
62
62
|
"**Ask only one decision-changing question** — if the user rejects the contract but is unsure, offer 3-4 concrete scope moves instead of open-ended interrogation.",
|
|
63
63
|
"**Write the scope contract after approval** — include selected mode, in scope, out of scope, requirements, locked decisions, discretion areas, deferred ideas, accepted/rejected reference ideas, success definition, design handoff, completion dashboard, and explicit approval evidence."
|
|
64
64
|
],
|
|
@@ -58,6 +58,7 @@ export const TDD = {
|
|
|
58
58
|
],
|
|
59
59
|
interactionProtocol: [
|
|
60
60
|
"Pick one vertical slice at a time: source item, RED test, GREEN implementation, REFACTOR, and verification evidence move together.",
|
|
61
|
+
"Slice implementers are sequential by default. Parallel implementers are allowed only when (a) lanes touch non-overlapping files, (b) the controller passes `--allow-parallel` on each ledger row, and (c) an `integration-overseer` is dispatched after the parallel lanes and writes cohesion-evidence into the artifact before the gate is marked passed.",
|
|
61
62
|
"Controller owns orchestration; one mandatory `test-author` delegation carries phase-specific RED -> GREEN -> REFACTOR evidence instead of spawning separate workers by default.",
|
|
62
63
|
"Before writing RED tests, discover relevant existing tests and commands so the new test extends the suite instead of fighting it.",
|
|
63
64
|
"Before implementation, perform a system-wide impact check across callbacks, state, interfaces, schemas, and external contracts touched by the slice.",
|
|
@@ -176,7 +176,17 @@ Before parallel dispatch, answer yes to all gates: tasks are independent, write
|
|
|
176
176
|
- Copy each task verbatim into a working queue (checklist is fine).
|
|
177
177
|
- Normalize each task so it includes: goal, acceptance criteria, constraints, and explicit “out of scope.”
|
|
178
178
|
|
|
179
|
-
2. **For each task
|
|
179
|
+
2. **For each task — sequential by default; parallel only with cohesion controls:**
|
|
180
|
+
- Implementation subagents are sequential by default. Parallel implementers
|
|
181
|
+
are allowed only when ALL three conditions hold:
|
|
182
|
+
- (a) the lanes touch non-overlapping files (verify via the plan's task
|
|
183
|
+
file-set list before dispatch),
|
|
184
|
+
- (b) the controller passes \`--allow-parallel\` on each ledger row, and
|
|
185
|
+
- (c) an \`integration-overseer\` is dispatched after the parallel lanes
|
|
186
|
+
complete and writes cohesion-evidence (cross-file integration tests,
|
|
187
|
+
contract checks, or merge-conflict scan) into the artifact before any
|
|
188
|
+
gate is marked passed.
|
|
189
|
+
If any of the three conditions are unmet, serialize.
|
|
180
190
|
1. **Dispatch implementer subagent** with the **full task text pasted in** (not a file reference).
|
|
181
191
|
2. **Check return status:** \`DONE\` / \`DONE_WITH_CONCERNS\` / \`NEEDS_CONTEXT\` / \`BLOCKED\`
|
|
182
192
|
3. If \`DONE\`: dispatch **reviewer** subagent to verify actual code matches spec and quality expectations.
|
package/dist/delegation.d.ts
CHANGED
|
@@ -232,11 +232,18 @@ export declare class DispatchDuplicateError extends Error {
|
|
|
232
232
|
});
|
|
233
233
|
}
|
|
234
234
|
/**
|
|
235
|
-
* v6.
|
|
235
|
+
* v6.9.0 — find the latest active span for a given `(stage, agent)`
|
|
236
236
|
* pair in the supplied ledger entries. Returns the row whose latest
|
|
237
237
|
* status (after the latest-by-spanId fold) is still in the active set
|
|
238
|
-
* (`scheduled | launched | acknowledged`).
|
|
239
|
-
*
|
|
238
|
+
* (`scheduled | launched | acknowledged`).
|
|
239
|
+
*
|
|
240
|
+
* Run-scope is **strict**: only entries whose `runId` matches the
|
|
241
|
+
* supplied `runId` are folded. Entries with empty/missing `runId`
|
|
242
|
+
* (legacy ledgers from v6.8 and earlier) are treated as NOT belonging
|
|
243
|
+
* to the current run, so they cannot keep an old span "active" across
|
|
244
|
+
* a fresh dispatch and trip a spurious `dispatch_duplicate`. This
|
|
245
|
+
* fixes R7: a slice-implementer that ran in run-1 must not block a
|
|
246
|
+
* slice-implementer scheduled in run-2.
|
|
240
247
|
*
|
|
241
248
|
* keep in sync with the inline copy in
|
|
242
249
|
* `src/content/hooks.ts::delegationRecordScript`.
|
package/dist/delegation.js
CHANGED
|
@@ -548,18 +548,27 @@ export class DispatchDuplicateError extends Error {
|
|
|
548
548
|
}
|
|
549
549
|
}
|
|
550
550
|
/**
|
|
551
|
-
* v6.
|
|
551
|
+
* v6.9.0 — find the latest active span for a given `(stage, agent)`
|
|
552
552
|
* pair in the supplied ledger entries. Returns the row whose latest
|
|
553
553
|
* status (after the latest-by-spanId fold) is still in the active set
|
|
554
|
-
* (`scheduled | launched | acknowledged`).
|
|
555
|
-
*
|
|
554
|
+
* (`scheduled | launched | acknowledged`).
|
|
555
|
+
*
|
|
556
|
+
* Run-scope is **strict**: only entries whose `runId` matches the
|
|
557
|
+
* supplied `runId` are folded. Entries with empty/missing `runId`
|
|
558
|
+
* (legacy ledgers from v6.8 and earlier) are treated as NOT belonging
|
|
559
|
+
* to the current run, so they cannot keep an old span "active" across
|
|
560
|
+
* a fresh dispatch and trip a spurious `dispatch_duplicate`. This
|
|
561
|
+
* fixes R7: a slice-implementer that ran in run-1 must not block a
|
|
562
|
+
* slice-implementer scheduled in run-2.
|
|
556
563
|
*
|
|
557
564
|
* keep in sync with the inline copy in
|
|
558
565
|
* `src/content/hooks.ts::delegationRecordScript`.
|
|
559
566
|
*/
|
|
560
567
|
export function findActiveSpanForPair(stage, agent, runId, ledger) {
|
|
561
568
|
const sameRun = ledger.entries.filter((entry) => {
|
|
562
|
-
if (entry.runId
|
|
569
|
+
if (typeof entry.runId !== "string" || entry.runId.length === 0)
|
|
570
|
+
return false;
|
|
571
|
+
if (entry.runId !== runId)
|
|
563
572
|
return false;
|
|
564
573
|
return entry.stage === stage && entry.agent === agent;
|
|
565
574
|
});
|
package/dist/early-loop.js
CHANGED
|
@@ -137,9 +137,23 @@ export function parseEarlyLoopLog(text, options = {}) {
|
|
|
137
137
|
continue;
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
|
+
// v6.9.0 schema repair: legacy logs may carry rows with no runId
|
|
141
|
+
// (the prior parser silently coerced them to "active", which then
|
|
142
|
+
// collided across runs). Surface a structured warning on read but
|
|
143
|
+
// skip the row so derived status doesn't fold cross-run state.
|
|
144
|
+
// Writers must always provide a runId (enforced upstream in the
|
|
145
|
+
// CLI/hook surface).
|
|
146
|
+
if (runId.length === 0) {
|
|
147
|
+
issues?.push({
|
|
148
|
+
lineNumber,
|
|
149
|
+
reason: "missing-runId: legacy entry skipped to avoid cross-run pollution",
|
|
150
|
+
rawLine: raw
|
|
151
|
+
});
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
140
154
|
entries.push({
|
|
141
155
|
ts: normalizeText(parsed.ts, ""),
|
|
142
|
-
runId
|
|
156
|
+
runId,
|
|
143
157
|
stage: stage.length > 0 ? stage : "brainstorm",
|
|
144
158
|
iteration,
|
|
145
159
|
concerns,
|
package/dist/gate-evidence.js
CHANGED
|
@@ -9,8 +9,8 @@ import { readDelegationLedger } from "./delegation.js";
|
|
|
9
9
|
import { exists } from "./fs-utils.js";
|
|
10
10
|
import { computeEarlyLoopStatus, isEarlyLoopStage, normalizeEarlyLoopMaxIterations } from "./early-loop.js";
|
|
11
11
|
import { detectPublicApiChanges } from "./internal/detect-public-api-changes.js";
|
|
12
|
+
import { detectSupplyChainChanges } from "./internal/detect-supply-chain-changes.js";
|
|
12
13
|
import { readFlowState, writeFlowState } from "./runs.js";
|
|
13
|
-
import { parseTddCycleLog, validateTddCycleOrder } from "./tdd-cycle.js";
|
|
14
14
|
import { validateTddVerificationEvidence } from "./tdd-verification-evidence.js";
|
|
15
15
|
async function currentStageArtifactExists(projectRoot, stage, track) {
|
|
16
16
|
const resolved = await resolveArtifactPath(stage, {
|
|
@@ -376,35 +376,20 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState, opt
|
|
|
376
376
|
}
|
|
377
377
|
if (stage === "tdd") {
|
|
378
378
|
const docsDriftDetection = await detectPublicApiChanges(projectRoot);
|
|
379
|
-
|
|
379
|
+
const supplyChainDetection = await detectSupplyChainChanges(projectRoot);
|
|
380
|
+
if (docsDriftDetection.triggered || supplyChainDetection.triggered) {
|
|
380
381
|
const ledger = await readDelegationLedger(projectRoot);
|
|
381
382
|
const hasDocUpdaterCompletion = ledger.entries.some((entry) => entry.runId === flowState.activeRunId &&
|
|
382
383
|
entry.stage === "tdd" &&
|
|
383
384
|
entry.agent === "doc-updater" &&
|
|
384
385
|
entry.status === "completed");
|
|
385
386
|
if (!hasDocUpdaterCompletion) {
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
try {
|
|
392
|
-
const tddLogRaw = await fs.readFile(tddLogPath, "utf8");
|
|
393
|
-
const parsedCycles = parseTddCycleLog(tddLogRaw);
|
|
394
|
-
const tddOrderValidation = validateTddCycleOrder(parsedCycles, {
|
|
395
|
-
runId: flowState.activeRunId
|
|
396
|
-
});
|
|
397
|
-
if (!tddOrderValidation.ok) {
|
|
398
|
-
const details = [...tddOrderValidation.issues];
|
|
399
|
-
if (tddOrderValidation.openRedSlices.length > 0) {
|
|
400
|
-
details.push(`open red slices: ${tddOrderValidation.openRedSlices.join(", ")}`);
|
|
401
|
-
}
|
|
402
|
-
issues.push(`tdd cycle order gate blocked: ${details.join("; ")}`);
|
|
387
|
+
if (docsDriftDetection.triggered) {
|
|
388
|
+
issues.push(`tdd docs drift gate blocked (tdd_docs_drift_check): public surface changes detected (${docsDriftDetection.changedFiles.join(", ")}) but no completed doc-updater delegation exists for the active run.`);
|
|
389
|
+
}
|
|
390
|
+
if (supplyChainDetection.triggered) {
|
|
391
|
+
issues.push(`tdd docs drift gate blocked (tdd_docs_drift_check): supply-chain changes detected (${supplyChainDetection.changedFiles.join(", ")}) but no completed doc-updater delegation exists for the active run.`);
|
|
403
392
|
}
|
|
404
|
-
}
|
|
405
|
-
catch (err) {
|
|
406
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
407
|
-
issues.push(`tdd cycle order gate blocked: unable to read tdd-cycle-log.jsonl (${reason}).`);
|
|
408
393
|
}
|
|
409
394
|
}
|
|
410
395
|
}
|
|
@@ -477,6 +462,13 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState, opt
|
|
|
477
462
|
forcingPending: floor.forcingPending,
|
|
478
463
|
noNewDecisions: floor.noNewDecisions
|
|
479
464
|
};
|
|
465
|
+
// v6.9.0 — when the QA log floor is blocking, mirror that decision into
|
|
466
|
+
// `gates.issues` so the harness has a single structured source of truth
|
|
467
|
+
// for "this stage is blocked". The `qa_log_unconverged` linter rule
|
|
468
|
+
// remains the verbose detail/fallback channel.
|
|
469
|
+
if (qaLogFloor.blocking) {
|
|
470
|
+
issues.push(`qa log floor blocked (qa_log_unconverged): ${qaLogFloor.count}/${qaLogFloor.min} entries on stage "${stage}" (track=${flowState.track}, discoveryMode=${flowState.discoveryMode ?? "default"}). Continue elicitation or pass --skip-questions to record the stop.`);
|
|
471
|
+
}
|
|
480
472
|
}
|
|
481
473
|
return {
|
|
482
474
|
ok: issues.length === 0,
|
package/dist/harness-adapters.js
CHANGED
|
@@ -293,9 +293,11 @@ export function harnessesByTier() {
|
|
|
293
293
|
});
|
|
294
294
|
}
|
|
295
295
|
function ironLawsAgentsMdBlock() {
|
|
296
|
+
// v6.9.0: keep this set in sync with `ironLawsSkillMarkdown()` —
|
|
297
|
+
// post-Phase A, only `stop-clean-or-handoff` is still hook-enforced
|
|
298
|
+
// (Stop hook). All other iron laws live in stage HARD-GATE blocks.
|
|
296
299
|
const enforcedLawIds = new Set([
|
|
297
|
-
"stop-clean-or-handoff"
|
|
298
|
-
"review-coverage-complete-before-ship"
|
|
300
|
+
"stop-clean-or-handoff"
|
|
299
301
|
]);
|
|
300
302
|
const enforcedRows = IRON_LAWS
|
|
301
303
|
.filter((law) => enforcedLawIds.has(law.id))
|
package/dist/install.js
CHANGED
|
@@ -38,8 +38,6 @@ import { FLOW_STAGES } from "./types.js";
|
|
|
38
38
|
const OPENCODE_PLUGIN_REL_PATH = ".opencode/plugins/cclaw-plugin.mjs";
|
|
39
39
|
const CURSOR_RULE_REL_PATH = ".cursor/rules/cclaw-workflow.mdc";
|
|
40
40
|
const CURSOR_GUIDELINES_REL_PATH = ".cursor/rules/cclaw-guidelines.mdc";
|
|
41
|
-
const GIT_HOOK_MANAGED_MARKER = "cclaw-managed-git-hook";
|
|
42
|
-
const GIT_HOOK_RUNTIME_REL_DIR = `${RUNTIME_ROOT}/hooks/git`;
|
|
43
41
|
const INIT_SENTINEL_FILE = ".init-in-progress";
|
|
44
42
|
const execFileAsync = promisify(execFile);
|
|
45
43
|
function runtimePath(projectRoot, ...segments) {
|
|
@@ -145,13 +143,17 @@ const DEPRECATED_COMMAND_FILES = [
|
|
|
145
143
|
const DEPRECATED_SKILL_FILES = [
|
|
146
144
|
["flow-finish", "SKILL.md"],
|
|
147
145
|
["flow-ops", "SKILL.md"],
|
|
148
|
-
["tdd-cycle-log", "SKILL.md"],
|
|
149
146
|
["flow-retro", "SKILL.md"],
|
|
150
147
|
["flow-compound", "SKILL.md"],
|
|
151
148
|
["flow-archive", "SKILL.md"],
|
|
152
149
|
["flow-rewind", "SKILL.md"],
|
|
153
150
|
["using-git-worktrees", "SKILL.md"]
|
|
154
151
|
];
|
|
152
|
+
// Skill folders whose entire directory should be removed on sync so the
|
|
153
|
+
// abandoned tree doesn't linger in user projects.
|
|
154
|
+
const DEPRECATED_SKILL_FOLDERS_FULL = [
|
|
155
|
+
"tdd-cycle-log"
|
|
156
|
+
];
|
|
155
157
|
const DEPRECATED_STATE_FILES = [
|
|
156
158
|
"checkpoint.json",
|
|
157
159
|
"flow-state.snapshot.json",
|
|
@@ -161,7 +163,10 @@ const DEPRECATED_STATE_FILES = [
|
|
|
161
163
|
"harness-gaps.json",
|
|
162
164
|
"context-mode.json",
|
|
163
165
|
"session-digest.md",
|
|
164
|
-
"context-warnings.jsonl"
|
|
166
|
+
"context-warnings.jsonl",
|
|
167
|
+
// Runtime Honesty 6.9.0 removed the per-run TDD cycle JSONL: gate evidence
|
|
168
|
+
// now reads cycle phase progression directly from the artifact table.
|
|
169
|
+
"tdd-cycle-log.jsonl"
|
|
165
170
|
];
|
|
166
171
|
const DEPRECATED_HOOK_FILES = [
|
|
167
172
|
"observe.sh",
|
|
@@ -193,225 +198,33 @@ async function resolveGitHooksDir(projectRoot) {
|
|
|
193
198
|
return null;
|
|
194
199
|
}
|
|
195
200
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const HOOK_NAME = ${JSON.stringify(hookName)};
|
|
205
|
-
const RUNTIME_ROOT = ${JSON.stringify(RUNTIME_ROOT)};
|
|
206
|
-
|
|
207
|
-
function runGit(args, cwd) {
|
|
208
|
-
const result = spawnSync("git", args, {
|
|
209
|
-
cwd,
|
|
210
|
-
encoding: "utf8",
|
|
211
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
212
|
-
});
|
|
213
|
-
return {
|
|
214
|
-
status: typeof result.status === "number" ? result.status : 1,
|
|
215
|
-
stdout: typeof result.stdout === "string" ? result.stdout : ""
|
|
216
|
-
};
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function resolveRepoRoot() {
|
|
220
|
-
const result = runGit(["rev-parse", "--show-toplevel"], process.cwd());
|
|
221
|
-
if (result.status === 0) {
|
|
222
|
-
const root = result.stdout.trim();
|
|
223
|
-
if (root.length > 0) return root;
|
|
224
|
-
}
|
|
225
|
-
return process.cwd();
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function isZeroSha(value) {
|
|
229
|
-
return /^0{40,64}$/u.test(value);
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function readStdin() {
|
|
233
|
-
try {
|
|
234
|
-
return fs.readFileSync(0, "utf8");
|
|
235
|
-
} catch {
|
|
236
|
-
return "";
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function uniqueLines(chunks) {
|
|
241
|
-
return [...new Set(chunks
|
|
242
|
-
.join("\n")
|
|
243
|
-
.split(/\r?\n/gu)
|
|
244
|
-
.map((line) => line.trim())
|
|
245
|
-
.filter((line) => line.length > 0))].join("\n");
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
function diffNames(root, range) {
|
|
249
|
-
const result = runGit(["diff", "--name-only", range], root);
|
|
250
|
-
return result.status === 0 ? result.stdout : "";
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
function changedFilesFromUnpushedCommits(root, localSha = "HEAD") {
|
|
254
|
-
const revList = runGit(["rev-list", "--reverse", localSha, "--not", "--remotes"], root);
|
|
255
|
-
if (revList.status !== 0 || revList.stdout.trim().length === 0) {
|
|
256
|
-
return "";
|
|
257
|
-
}
|
|
258
|
-
const chunks = [];
|
|
259
|
-
for (const commit of revList.stdout.split(/\r?\n/gu).map((line) => line.trim()).filter(Boolean)) {
|
|
260
|
-
const diffTree = runGit(["diff-tree", "--no-commit-id", "--name-only", "-r", "--root", commit], root);
|
|
261
|
-
if (diffTree.status === 0) chunks.push(diffTree.stdout);
|
|
262
|
-
}
|
|
263
|
-
return uniqueLines(chunks);
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
function changedFilesFromPrePushStdin(root, stdin) {
|
|
267
|
-
const chunks = [];
|
|
268
|
-
for (const rawLine of stdin.split(/\r?\n/gu)) {
|
|
269
|
-
const parts = rawLine.trim().split(/\s+/u);
|
|
270
|
-
if (parts.length < 4) continue;
|
|
271
|
-
const [localRef, localSha, remoteRef, remoteSha] = parts;
|
|
272
|
-
void localRef;
|
|
273
|
-
void remoteRef;
|
|
274
|
-
if (!localSha || isZeroSha(localSha)) continue;
|
|
275
|
-
if (remoteSha && !isZeroSha(remoteSha)) {
|
|
276
|
-
chunks.push(diffNames(root, remoteSha + ".." + localSha));
|
|
277
|
-
continue;
|
|
278
|
-
}
|
|
279
|
-
const upstream = runGit(["rev-parse", "--verify", "--quiet", "@{upstream}"], root);
|
|
280
|
-
if (upstream.status === 0 && upstream.stdout.trim().length > 0) {
|
|
281
|
-
chunks.push(diffNames(root, upstream.stdout.trim() + ".." + localSha));
|
|
282
|
-
continue;
|
|
283
|
-
}
|
|
284
|
-
chunks.push(changedFilesFromUnpushedCommits(root, localSha));
|
|
285
|
-
}
|
|
286
|
-
return uniqueLines(chunks);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
function resolveChangedFiles(root) {
|
|
290
|
-
if (HOOK_NAME === "pre-commit") {
|
|
291
|
-
const result = runGit(["diff", "--cached", "--name-only"], root);
|
|
292
|
-
return result.status === 0 ? result.stdout : "";
|
|
293
|
-
}
|
|
294
|
-
const stdinChanged = changedFilesFromPrePushStdin(root, readStdin());
|
|
295
|
-
if (stdinChanged.length > 0) {
|
|
296
|
-
return stdinChanged;
|
|
297
|
-
}
|
|
298
|
-
const upstreamResult = runGit(["diff", "--name-only", "@{upstream}..HEAD"], root);
|
|
299
|
-
if (upstreamResult.status === 0) {
|
|
300
|
-
return upstreamResult.stdout;
|
|
301
|
-
}
|
|
302
|
-
const unpushed = changedFilesFromUnpushedCommits(root);
|
|
303
|
-
if (unpushed.length > 0) {
|
|
304
|
-
return unpushed;
|
|
305
|
-
}
|
|
306
|
-
const fallback = runGit(["diff", "--name-only", "HEAD~1...HEAD"], root);
|
|
307
|
-
return fallback.status === 0 ? fallback.stdout : "";
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
const root = resolveRepoRoot();
|
|
311
|
-
const runtimeHook = path.join(root, RUNTIME_ROOT, "hooks", "run-hook.mjs");
|
|
312
|
-
if (!fs.existsSync(runtimeHook)) {
|
|
313
|
-
// cclaw git relay is installed but the runtime entrypoint is missing —
|
|
314
|
-
// warn visibly (without blocking the commit) so the drift is noticed.
|
|
315
|
-
process.stderr.write(
|
|
316
|
-
"[cclaw] " + HOOK_NAME + ": " + runtimeHook + " not found; run \`cclaw sync\` to reinstall\\n"
|
|
317
|
-
);
|
|
318
|
-
process.exit(0);
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
const changedFiles = resolveChangedFiles(root)
|
|
322
|
-
.split(/\\r?\\n/gu)
|
|
323
|
-
.map((line) => line.trim())
|
|
324
|
-
.filter((line) => line.length > 0);
|
|
325
|
-
if (changedFiles.length === 0) {
|
|
326
|
-
process.exit(0);
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
const payload = JSON.stringify({
|
|
330
|
-
tool_name: "Write",
|
|
331
|
-
tool_input: {
|
|
332
|
-
path: changedFiles.join("\\n"),
|
|
333
|
-
paths: changedFiles
|
|
334
|
-
}
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
const result = spawnSync(process.execPath, [runtimeHook, "prompt-guard"], {
|
|
338
|
-
cwd: root,
|
|
339
|
-
env: process.env,
|
|
340
|
-
input: payload,
|
|
341
|
-
encoding: "utf8",
|
|
342
|
-
stdio: ["pipe", "ignore", "inherit"]
|
|
343
|
-
});
|
|
344
|
-
process.exit(typeof result.status === "number" ? result.status : 1);
|
|
345
|
-
`;
|
|
346
|
-
}
|
|
347
|
-
function managedGitRelayHook(hookName) {
|
|
348
|
-
return `#!/usr/bin/env node
|
|
349
|
-
// ${GIT_HOOK_MANAGED_MARKER}: relay ${hookName}
|
|
350
|
-
import fs from "node:fs";
|
|
351
|
-
import path from "node:path";
|
|
352
|
-
import process from "node:process";
|
|
353
|
-
import { spawn, spawnSync } from "node:child_process";
|
|
354
|
-
|
|
355
|
-
const RUNTIME_REL_DIR = ${JSON.stringify(GIT_HOOK_RUNTIME_REL_DIR)};
|
|
356
|
-
const HOOK_NAME = ${JSON.stringify(hookName)};
|
|
357
|
-
|
|
358
|
-
function resolveRepoRoot() {
|
|
359
|
-
const result = spawnSync("git", ["rev-parse", "--show-toplevel"], {
|
|
360
|
-
cwd: process.cwd(),
|
|
361
|
-
encoding: "utf8",
|
|
362
|
-
stdio: ["ignore", "pipe", "ignore"]
|
|
363
|
-
});
|
|
364
|
-
if (typeof result.status === "number" && result.status === 0) {
|
|
365
|
-
const root = (result.stdout || "").trim();
|
|
366
|
-
if (root.length > 0) return root;
|
|
367
|
-
}
|
|
368
|
-
return process.cwd();
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
const root = resolveRepoRoot();
|
|
372
|
-
const runtimeHook = path.join(root, RUNTIME_REL_DIR, HOOK_NAME + ".mjs");
|
|
373
|
-
if (!fs.existsSync(runtimeHook)) {
|
|
374
|
-
process.exit(0);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
const child = spawn(process.execPath, [runtimeHook, ...process.argv.slice(2)], {
|
|
378
|
-
cwd: root,
|
|
379
|
-
env: process.env,
|
|
380
|
-
stdio: "inherit"
|
|
381
|
-
});
|
|
382
|
-
child.on("error", () => process.exit(1));
|
|
383
|
-
child.on("close", (code, signal) => {
|
|
384
|
-
process.exit(signal ? 1 : typeof code === "number" ? code : 1);
|
|
385
|
-
});
|
|
386
|
-
`;
|
|
387
|
-
}
|
|
388
|
-
async function removeManagedGitHookRelays(projectRoot) {
|
|
201
|
+
// Legacy cleanup: prior versions installed Node-based git pre-commit/pre-push relays
|
|
202
|
+
// under .git/hooks/* and a runtime tree at .cclaw/hooks/git/. Runtime Honesty 6.9.0
|
|
203
|
+
// removed managed git hooks entirely; the cleanup below stays so existing installs
|
|
204
|
+
// shed the leftover files on next sync/uninstall.
|
|
205
|
+
const LEGACY_GIT_HOOK_MANAGED_MARKER = "cclaw-managed-git-hook";
|
|
206
|
+
const LEGACY_GIT_HOOK_RUNTIME_REL_DIR = `${RUNTIME_ROOT}/hooks/git`;
|
|
207
|
+
async function cleanupLegacyManagedGitHookRelays(projectRoot) {
|
|
389
208
|
const hooksDir = await resolveGitHooksDir(projectRoot);
|
|
390
|
-
if (
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
content
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
continue;
|
|
209
|
+
if (hooksDir) {
|
|
210
|
+
for (const hookName of ["pre-commit", "pre-push"]) {
|
|
211
|
+
const hookPath = path.join(hooksDir, hookName);
|
|
212
|
+
if (!(await exists(hookPath)))
|
|
213
|
+
continue;
|
|
214
|
+
let content = "";
|
|
215
|
+
try {
|
|
216
|
+
content = await fs.readFile(hookPath, "utf8");
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
content = "";
|
|
220
|
+
}
|
|
221
|
+
if (!content.includes(LEGACY_GIT_HOOK_MANAGED_MARKER))
|
|
222
|
+
continue;
|
|
223
|
+
await fs.rm(hookPath, { force: true });
|
|
406
224
|
}
|
|
407
|
-
await fs.rm(hookPath, { force: true });
|
|
408
225
|
}
|
|
409
|
-
}
|
|
410
|
-
async function syncManagedGitHooks(projectRoot, config) {
|
|
411
|
-
void config;
|
|
412
|
-
await removeManagedGitHookRelays(projectRoot);
|
|
413
226
|
try {
|
|
414
|
-
await fs.rm(path.join(projectRoot,
|
|
227
|
+
await fs.rm(path.join(projectRoot, LEGACY_GIT_HOOK_RUNTIME_REL_DIR), { recursive: true, force: true });
|
|
415
228
|
}
|
|
416
229
|
catch {
|
|
417
230
|
// best-effort cleanup
|
|
@@ -1021,6 +834,9 @@ async function cleanLegacyArtifacts(projectRoot) {
|
|
|
1021
834
|
for (const legacyFolder of DEPRECATED_STAGE_SKILL_FOLDERS) {
|
|
1022
835
|
await removeBestEffort(runtimePath(projectRoot, "skills", legacyFolder), true);
|
|
1023
836
|
}
|
|
837
|
+
for (const legacyFolder of DEPRECATED_SKILL_FOLDERS_FULL) {
|
|
838
|
+
await removeBestEffort(runtimePath(projectRoot, "skills", legacyFolder), true);
|
|
839
|
+
}
|
|
1024
840
|
for (const legacyAgentFile of DEPRECATED_AGENT_FILES) {
|
|
1025
841
|
await removeBestEffort(runtimePath(projectRoot, "agents", legacyAgentFile));
|
|
1026
842
|
}
|
|
@@ -1173,7 +989,7 @@ async function materializeRuntime(projectRoot, config, forceStateReset, operatio
|
|
|
1173
989
|
await ensureKnowledgeStore(projectRoot);
|
|
1174
990
|
await writeHooks(projectRoot, config);
|
|
1175
991
|
await syncDisabledHarnessArtifacts(projectRoot, harnesses);
|
|
1176
|
-
await
|
|
992
|
+
await cleanupLegacyManagedGitHookRelays(projectRoot);
|
|
1177
993
|
await syncHarnessShims(projectRoot, harnesses);
|
|
1178
994
|
await assertExpectedHarnessShims(projectRoot, harnesses);
|
|
1179
995
|
await writeCursorWorkflowRule(projectRoot, harnesses);
|
|
@@ -1401,7 +1217,7 @@ export async function uninstallCclaw(projectRoot) {
|
|
|
1401
1217
|
}
|
|
1402
1218
|
await removeCclawFromAgentsMd(projectRoot);
|
|
1403
1219
|
await removeGitignorePatterns(projectRoot);
|
|
1404
|
-
await
|
|
1220
|
+
await cleanupLegacyManagedGitHookRelays(projectRoot);
|
|
1405
1221
|
const hookFiles = [
|
|
1406
1222
|
".claude/hooks/hooks.json",
|
|
1407
1223
|
".cursor/hooks.json",
|