cclaw-cli 6.14.4 → 7.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.
- package/README.md +0 -2
- package/dist/artifact-linter/brainstorm.js +1 -1
- package/dist/artifact-linter/design.js +2 -2
- package/dist/artifact-linter/findings-dedup.js +1 -1
- package/dist/artifact-linter/plan.js +6 -6
- package/dist/artifact-linter/review-army.d.ts +1 -1
- package/dist/artifact-linter/review-army.js +1 -1
- package/dist/artifact-linter/scope.js +6 -6
- package/dist/artifact-linter/shared.d.ts +37 -73
- package/dist/artifact-linter/shared.js +30 -37
- package/dist/artifact-linter/spec.js +1 -1
- package/dist/artifact-linter/tdd.d.ts +20 -33
- package/dist/artifact-linter/tdd.js +89 -639
- package/dist/artifact-linter.js +11 -32
- package/dist/cli.js +1 -1
- package/dist/config.js +1 -1
- package/dist/constants.js +1 -1
- package/dist/content/core-agents.d.ts +8 -26
- package/dist/content/core-agents.js +48 -94
- package/dist/content/examples.d.ts +1 -1
- package/dist/content/examples.js +4 -4
- package/dist/content/hooks.js +62 -149
- package/dist/content/idea.js +2 -2
- package/dist/content/iron-laws.js +1 -1
- package/dist/content/node-hooks.js +2 -2
- package/dist/content/skills-elicitation.js +2 -2
- package/dist/content/skills.d.ts +4 -6
- package/dist/content/skills.js +14 -53
- package/dist/content/stage-schema.d.ts +3 -3
- package/dist/content/stage-schema.js +8 -46
- package/dist/content/stages/brainstorm.js +5 -5
- package/dist/content/stages/plan.js +2 -2
- package/dist/content/stages/review.js +1 -1
- package/dist/content/stages/schema-types.d.ts +1 -1
- package/dist/content/stages/scope.js +1 -1
- package/dist/content/stages/spec.js +2 -2
- package/dist/content/stages/tdd.js +43 -108
- package/dist/content/start-command.js +3 -3
- package/dist/content/subagent-context-skills.js +5 -3
- package/dist/content/subagents.js +13 -74
- package/dist/content/templates.d.ts +6 -6
- package/dist/content/templates.js +23 -24
- package/dist/content/utility-skills.d.ts +1 -1
- package/dist/content/utility-skills.js +1 -1
- package/dist/delegation.d.ts +79 -139
- package/dist/delegation.js +83 -215
- package/dist/early-loop.js +1 -1
- package/dist/flow-state.d.ts +24 -129
- package/dist/flow-state.js +5 -30
- package/dist/gate-evidence.d.ts +2 -7
- package/dist/gate-evidence.js +2 -59
- package/dist/harness-adapters.d.ts +1 -1
- package/dist/harness-adapters.js +11 -10
- package/dist/install.js +24 -459
- package/dist/internal/advance-stage/advance.d.ts +5 -5
- package/dist/internal/advance-stage/advance.js +9 -24
- package/dist/internal/advance-stage/parsers.d.ts +1 -1
- package/dist/internal/advance-stage/review-loop.d.ts +1 -1
- package/dist/internal/advance-stage/review-loop.js +3 -3
- package/dist/internal/advance-stage/start-flow.js +1 -3
- package/dist/internal/advance-stage.js +4 -23
- package/dist/internal/cohesion-contract-stub.d.ts +8 -13
- package/dist/internal/cohesion-contract-stub.js +18 -24
- package/dist/internal/flow-state-repair.d.ts +1 -1
- package/dist/internal/plan-split-waves.d.ts +18 -21
- package/dist/internal/plan-split-waves.js +16 -19
- package/dist/internal/wave-status.d.ts +3 -6
- package/dist/internal/wave-status.js +5 -27
- package/dist/policy.js +1 -1
- package/dist/run-persistence.js +10 -44
- package/dist/runtime/run-hook.mjs +3 -3
- package/dist/track-heuristics.js +1 -1
- package/dist/types.d.ts +2 -2
- package/package.json +1 -1
- package/dist/integration-fanin.d.ts +0 -44
- package/dist/integration-fanin.js +0 -180
- package/dist/internal/set-checkpoint-mode.d.ts +0 -16
- package/dist/internal/set-checkpoint-mode.js +0 -72
- package/dist/internal/set-integration-overseer-mode.d.ts +0 -14
- package/dist/internal/set-integration-overseer-mode.js +0 -69
- package/dist/internal/set-worktree-mode.d.ts +0 -10
- package/dist/internal/set-worktree-mode.js +0 -28
- package/dist/worktree-manager.d.ts +0 -50
- package/dist/worktree-manager.js +0 -136
- package/dist/worktree-types.d.ts +0 -36
- package/dist/worktree-types.js +0 -6
|
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { resolveArtifactPath } from "../../artifact-paths.js";
|
|
4
4
|
import { appendDelegation, checkMandatoryDelegations, readDelegationEvents, readDelegationLedger } from "../../delegation.js";
|
|
5
|
-
import { verifyCompletedStagesGateClosure, verifyCurrentStageGateEvidence
|
|
5
|
+
import { verifyCompletedStagesGateClosure, verifyCurrentStageGateEvidence } from "../../gate-evidence.js";
|
|
6
6
|
import { extractMarkdownSectionBody, learningsParseFailureHumanSummary, parseLearningsSection } from "../../artifact-linter.js";
|
|
7
7
|
import { getAvailableTransitions, getTransitionGuards } from "../../flow-state.js";
|
|
8
8
|
import { appendKnowledge } from "../../knowledge-store.js";
|
|
@@ -13,7 +13,6 @@ import { unique } from "./helpers.js";
|
|
|
13
13
|
import { AUTO_REVIEW_LOOP_GATE_BY_STAGE, reviewLoopArtifactFixHint, reviewLoopEnvelopeExample, validateGateEvidenceShape } from "./review-loop.js";
|
|
14
14
|
import { ensureProactiveDelegationTrace } from "./proactive-delegation-trace.js";
|
|
15
15
|
import { consumeWaiverToken } from "../waiver-grant.js";
|
|
16
|
-
import { runTddDeterministicFanInBeforeAdvance } from "../../integration-fanin.js";
|
|
17
16
|
function resolveSuccessorTransition(stage, track, transitionTargets, satisfiedGuards, selectedTransitionGuards) {
|
|
18
17
|
const natural = transitionTargets[0] ?? null;
|
|
19
18
|
const specialTargets = transitionTargets.filter((target) => target !== natural);
|
|
@@ -60,16 +59,16 @@ function nextInteractionHints(flowState, args, successor) {
|
|
|
60
59
|
return hints;
|
|
61
60
|
}
|
|
62
61
|
/**
|
|
63
|
-
*
|
|
62
|
+
* entry point — auto-hydrate evidence for an auto-hydratable
|
|
64
63
|
* gate that the agent already included in --passed but for which they
|
|
65
64
|
* forgot to provide --evidence-json. Returns silently when no
|
|
66
65
|
* hydration is possible (no auto-hydratable gate, no artifact, no
|
|
67
66
|
* envelope, etc.).
|
|
68
67
|
*
|
|
69
|
-
*
|
|
68
|
+
* layered `tryAutoHydrateAndSelectReviewLoopGate` on
|
|
70
69
|
* top of this so the gate is also auto-included in selectedGateIds
|
|
71
70
|
* when the artifact yields a valid envelope. Together the two helpers
|
|
72
|
-
* remove the contradiction
|
|
71
|
+
* remove the contradiction:
|
|
73
72
|
* - "omit this gate from --evidence-json so stage-complete can
|
|
74
73
|
* auto-hydrate it" → "missing --evidence-json entries for passed
|
|
75
74
|
* gates: design_diagram_freshness".
|
|
@@ -105,7 +104,7 @@ export async function hydrateReviewLoopEvidenceFromArtifact(projectRoot, stage,
|
|
|
105
104
|
evidenceByGate[gateId] = JSON.stringify(envelope);
|
|
106
105
|
}
|
|
107
106
|
/**
|
|
108
|
-
*
|
|
107
|
+
* auto-include an auto-hydratable review-loop gate
|
|
109
108
|
* in `selectedGateIds` when:
|
|
110
109
|
* - The stage has an auto-hydratable gate registered via
|
|
111
110
|
* `AUTO_REVIEW_LOOP_GATE_BY_STAGE` (currently `design`).
|
|
@@ -162,7 +161,7 @@ export async function tryAutoHydrateAndSelectReviewLoopGate(projectRoot, stage,
|
|
|
162
161
|
return [...selectedGateIds, gateId];
|
|
163
162
|
}
|
|
164
163
|
export async function buildValidationReport(projectRoot, flowState, options = {}) {
|
|
165
|
-
//
|
|
164
|
+
// forward `flowState.taskClass` so the
|
|
166
165
|
// bugfix-skip lights up via the `cclaw advance-stage` path. The
|
|
167
166
|
// delegation helper now has its own fallback (it reads `flowState`
|
|
168
167
|
// internally), but threading the value here keeps the call site
|
|
@@ -314,11 +313,11 @@ export async function runAdvanceStage(projectRoot, args, io) {
|
|
|
314
313
|
let selectedGateIds = args.passedGateIds.length > 0
|
|
315
314
|
? args.passedGateIds.filter((gateId) => selectableGateIds.has(gateId))
|
|
316
315
|
: requiredGateIds;
|
|
317
|
-
//
|
|
316
|
+
// if the active stage has an auto-hydratable
|
|
318
317
|
// review-loop gate (currently `design.design_architecture_locked`)
|
|
319
318
|
// and the artifact already contains a valid review-loop envelope,
|
|
320
319
|
// include the gate in selectedGateIds and hydrate evidence in one
|
|
321
|
-
// step. This removes the
|
|
320
|
+
// step. This removes the contradiction between "omit from
|
|
322
321
|
// --evidence-json so we can auto-hydrate" and "missing
|
|
323
322
|
// --evidence-json entries for passed gates".
|
|
324
323
|
selectedGateIds = await tryAutoHydrateAndSelectReviewLoopGate(projectRoot, args.stage, flowState.track, requiredGateIds, selectedGateIds, args.evidenceByGate);
|
|
@@ -363,7 +362,7 @@ export async function runAdvanceStage(projectRoot, args, io) {
|
|
|
363
362
|
});
|
|
364
363
|
}
|
|
365
364
|
}
|
|
366
|
-
//
|
|
365
|
+
// hydration + auto-select happens earlier via
|
|
367
366
|
// `tryAutoHydrateAndSelectReviewLoopGate`. The previous explicit
|
|
368
367
|
// call here was redundant (helper already covered both the
|
|
369
368
|
// already-selected and not-yet-selected paths).
|
|
@@ -603,20 +602,6 @@ export async function runAdvanceStage(projectRoot, args, io) {
|
|
|
603
602
|
}
|
|
604
603
|
const satisfiedGuards = new Set([...nextPassed, ...selectedTransitionGuards]);
|
|
605
604
|
const successor = resolveSuccessorTransition(args.stage, flowState.track, transitionTargets, satisfiedGuards, new Set(selectedTransitionGuards));
|
|
606
|
-
if (args.stage === "tdd" && successor !== null && successor !== "tdd") {
|
|
607
|
-
const fanIn = await runTddDeterministicFanInBeforeAdvance(projectRoot, flowState);
|
|
608
|
-
if (!fanIn.ok) {
|
|
609
|
-
io.stderr.write(`cclaw internal advance-stage: deterministic worktree fan-in failed:\n${fanIn.issues
|
|
610
|
-
.map((line) => ` - ${line}`)
|
|
611
|
-
.join("\n")}\n`);
|
|
612
|
-
return 1;
|
|
613
|
-
}
|
|
614
|
-
const closure = await verifyTddWorktreeFanInClosure(projectRoot, flowState);
|
|
615
|
-
if (closure.length > 0) {
|
|
616
|
-
io.stderr.write(`cclaw internal advance-stage: ${closure.join(" | ")}\n`);
|
|
617
|
-
return 1;
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
605
|
const completedStages = blockedReviewRoute
|
|
621
606
|
? flowState.completedStages.filter((finished) => finished !== args.stage)
|
|
622
607
|
: flowState.completedStages.includes(args.stage)
|
|
@@ -49,7 +49,7 @@ export interface StartFlowArgs {
|
|
|
49
49
|
reclassify: boolean;
|
|
50
50
|
quiet: boolean;
|
|
51
51
|
/**
|
|
52
|
-
*
|
|
52
|
+
* `/cc-ideate` handoff carry-forward.
|
|
53
53
|
* Workspace-relative POSIX path to `.cclaw/ideas/idea-YYYY-MM-DD-<slug>.md`
|
|
54
54
|
* (or wherever `/cc-ideate` wrote its artifact).
|
|
55
55
|
*/
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { FlowStage } from "../../types.js";
|
|
2
2
|
export declare const AUTO_REVIEW_LOOP_GATE_BY_STAGE: Partial<Record<FlowStage, string>>;
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* exact JSON shape that gate-evidence validators
|
|
5
5
|
* accept for a review-loop envelope. The error messages emitted by
|
|
6
6
|
* `validateReviewLoopGateEvidence` always include this example so the
|
|
7
7
|
* agent never has to guess where `stage` lives (top-level of the
|
|
@@ -12,7 +12,7 @@ const REVIEW_LOOP_STOP_REASONS = new Set([
|
|
|
12
12
|
"user_opt_out"
|
|
13
13
|
]);
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
15
|
+
* exact JSON shape that gate-evidence validators
|
|
16
16
|
* accept for a review-loop envelope. The error messages emitted by
|
|
17
17
|
* `validateReviewLoopGateEvidence` always include this example so the
|
|
18
18
|
* agent never has to guess where `stage` lives (top-level of the
|
|
@@ -183,9 +183,9 @@ export async function validateGateEvidenceShape(projectRoot, stage, gateId, evid
|
|
|
183
183
|
export function reviewLoopArtifactFixHint(stage, gateId) {
|
|
184
184
|
if (AUTO_REVIEW_LOOP_GATE_BY_STAGE[stage] !== gateId)
|
|
185
185
|
return "";
|
|
186
|
-
//
|
|
186
|
+
// the consistent flow is "include the gate in
|
|
187
187
|
// --passed AND let stage-complete auto-hydrate evidence from the
|
|
188
|
-
// artifact".
|
|
188
|
+
// artifact". An older hint told agents to omit the gate from
|
|
189
189
|
// --evidence-json, but they then hit
|
|
190
190
|
// `missing --evidence-json entries for passed gates: <gateId>`
|
|
191
191
|
// because hydration only runs when --evidence-json is also present
|
|
@@ -175,8 +175,7 @@ export async function runStartFlow(projectRoot, args, io) {
|
|
|
175
175
|
guardEvidence,
|
|
176
176
|
stageGateCatalog,
|
|
177
177
|
rewinds: current.rewinds,
|
|
178
|
-
staleStages: current.staleStages
|
|
179
|
-
worktreeExecutionMode: current.worktreeExecutionMode ?? "worktree-first"
|
|
178
|
+
staleStages: current.staleStages
|
|
180
179
|
};
|
|
181
180
|
const validation = await buildValidationReport(projectRoot, nextState);
|
|
182
181
|
const evidenceIssues = completedStageClosureEvidenceIssues(nextState);
|
|
@@ -194,7 +193,6 @@ export async function runStartFlow(projectRoot, args, io) {
|
|
|
194
193
|
if (nextTaskClass !== undefined) {
|
|
195
194
|
nextState = { ...nextState, taskClass: nextTaskClass };
|
|
196
195
|
}
|
|
197
|
-
nextState = { ...nextState, worktreeExecutionMode: "worktree-first" };
|
|
198
196
|
}
|
|
199
197
|
if (args.fromIdeaArtifact) {
|
|
200
198
|
const existingHints = nextState.interactionHints ?? {};
|
|
@@ -14,11 +14,8 @@ import { parseAdvanceStageArgs, parseCancelRunArgs, parseHookArgs, parseRewindAr
|
|
|
14
14
|
import { parseFlowStateRepairArgs, runFlowStateRepair } from "./flow-state-repair.js";
|
|
15
15
|
import { parseWaiverGrantArgs, runWaiverGrant } from "./waiver-grant.js";
|
|
16
16
|
import { FlowStateGuardMismatchError, verifyFlowStateGuard } from "../run-persistence.js";
|
|
17
|
-
import { DelegationTimestampError, DispatchCapError,
|
|
17
|
+
import { DelegationTimestampError, DispatchCapError, DispatchDuplicateError, DispatchOverlapError } from "../delegation.js";
|
|
18
18
|
import { parsePlanSplitWavesArgs, runPlanSplitWaves } from "./plan-split-waves.js";
|
|
19
|
-
import { runSetWorktreeMode } from "./set-worktree-mode.js";
|
|
20
|
-
import { runSetCheckpointMode } from "./set-checkpoint-mode.js";
|
|
21
|
-
import { runSetIntegrationOverseerMode } from "./set-integration-overseer-mode.js";
|
|
22
19
|
import { runWaveStatusCommand } from "./wave-status.js";
|
|
23
20
|
import { runCohesionContractCommand } from "./cohesion-contract-stub.js";
|
|
24
21
|
/**
|
|
@@ -33,15 +30,12 @@ const GUARD_ENFORCED_SUBCOMMANDS = new Set([
|
|
|
33
30
|
"cancel-run",
|
|
34
31
|
"rewind",
|
|
35
32
|
"verify-flow-state-diff",
|
|
36
|
-
"verify-current-state"
|
|
37
|
-
"set-worktree-mode",
|
|
38
|
-
"set-checkpoint-mode",
|
|
39
|
-
"set-integration-overseer-mode"
|
|
33
|
+
"verify-current-state"
|
|
40
34
|
]);
|
|
41
35
|
export async function runInternalCommand(projectRoot, argv, io) {
|
|
42
36
|
const [subcommand, ...tokens] = argv;
|
|
43
37
|
if (!subcommand) {
|
|
44
|
-
io.stderr.write("cclaw internal requires a subcommand: advance-stage | start-flow | cancel-run | rewind | verify-flow-state-diff | verify-current-state | envelope-validate | tdd-red-evidence | tdd-loop-status | early-loop-status | compound-readiness | runtime-integrity | hook | flow-state-repair | waiver-grant | plan-split-waves |
|
|
38
|
+
io.stderr.write("cclaw internal requires a subcommand: advance-stage | start-flow | cancel-run | rewind | verify-flow-state-diff | verify-current-state | envelope-validate | tdd-red-evidence | tdd-loop-status | early-loop-status | compound-readiness | runtime-integrity | hook | flow-state-repair | waiver-grant | plan-split-waves | wave-status | cohesion-contract\n");
|
|
45
39
|
return 1;
|
|
46
40
|
}
|
|
47
41
|
try {
|
|
@@ -96,22 +90,13 @@ export async function runInternalCommand(projectRoot, argv, io) {
|
|
|
96
90
|
if (subcommand === "plan-split-waves") {
|
|
97
91
|
return await runPlanSplitWaves(projectRoot, parsePlanSplitWavesArgs(tokens), io);
|
|
98
92
|
}
|
|
99
|
-
if (subcommand === "set-worktree-mode") {
|
|
100
|
-
return await runSetWorktreeMode(projectRoot, tokens, io);
|
|
101
|
-
}
|
|
102
|
-
if (subcommand === "set-checkpoint-mode") {
|
|
103
|
-
return await runSetCheckpointMode(projectRoot, tokens, io);
|
|
104
|
-
}
|
|
105
|
-
if (subcommand === "set-integration-overseer-mode") {
|
|
106
|
-
return await runSetIntegrationOverseerMode(projectRoot, tokens, io);
|
|
107
|
-
}
|
|
108
93
|
if (subcommand === "wave-status") {
|
|
109
94
|
return await runWaveStatusCommand(projectRoot, tokens, io);
|
|
110
95
|
}
|
|
111
96
|
if (subcommand === "cohesion-contract") {
|
|
112
97
|
return await runCohesionContractCommand(projectRoot, tokens, io);
|
|
113
98
|
}
|
|
114
|
-
io.stderr.write(`Unknown internal subcommand: ${subcommand}. Expected advance-stage | start-flow | cancel-run | rewind | verify-flow-state-diff | verify-current-state | envelope-validate | tdd-red-evidence | tdd-loop-status | early-loop-status | compound-readiness | runtime-integrity | hook | flow-state-repair | waiver-grant | plan-split-waves |
|
|
99
|
+
io.stderr.write(`Unknown internal subcommand: ${subcommand}. Expected advance-stage | start-flow | cancel-run | rewind | verify-flow-state-diff | verify-current-state | envelope-validate | tdd-red-evidence | tdd-loop-status | early-loop-status | compound-readiness | runtime-integrity | hook | flow-state-repair | waiver-grant | plan-split-waves | wave-status | cohesion-contract\n`);
|
|
115
100
|
return 1;
|
|
116
101
|
}
|
|
117
102
|
catch (err) {
|
|
@@ -135,10 +120,6 @@ export async function runInternalCommand(projectRoot, argv, io) {
|
|
|
135
120
|
io.stderr.write(`error: dispatch_cap — ${err.message}\n`);
|
|
136
121
|
return 2;
|
|
137
122
|
}
|
|
138
|
-
if (err instanceof DispatchClaimInvalidError) {
|
|
139
|
-
io.stderr.write(`error: dispatch_claim_invalid — ${err.message}\n`);
|
|
140
|
-
return 2;
|
|
141
|
-
}
|
|
142
123
|
io.stderr.write(`cclaw internal ${subcommand} failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
143
124
|
return 1;
|
|
144
125
|
}
|
|
@@ -10,20 +10,15 @@ export interface CohesionContractArgs {
|
|
|
10
10
|
}
|
|
11
11
|
export declare function parseCohesionContractArgs(tokens: string[]): CohesionContractArgs | null;
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* Scaffold a minimal cohesion-contract pair (`cohesion-contract.{md,json}`)
|
|
14
|
+
* so authors have a starting point when a multi-slice wave needs cross-slice
|
|
15
|
+
* cohesion documentation. The stub seeds `sharedTypes`, `touchpoints`, and
|
|
16
|
+
* `slices` from the active run delegation ledger and carries
|
|
17
|
+
* `status.verdict: "scaffold"` so reviewers know to fill in the real content
|
|
18
|
+
* before treating it as authoritative.
|
|
18
19
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* downstream tooling can see which slices the contract acknowledges,
|
|
22
|
-
* but the contract carries `status.verdict: "advisory_legacy"` so
|
|
23
|
-
* reviewers know not to treat it as authoritative.
|
|
24
|
-
*
|
|
25
|
-
* Refuses to overwrite an existing contract unless `--force` is
|
|
26
|
-
* passed; the existing file is treated as authored work.
|
|
20
|
+
* Refuses to overwrite an existing contract unless `--force` is passed; the
|
|
21
|
+
* existing file is treated as authored work.
|
|
27
22
|
*/
|
|
28
23
|
export declare function runCohesionContractCommand(projectRoot: string, tokens: string[], io: InternalIo): Promise<number>;
|
|
29
24
|
export {};
|
|
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { RUNTIME_ROOT } from "../constants.js";
|
|
4
4
|
import { writeFileSafe } from "../fs-utils.js";
|
|
5
|
-
import { readDelegationLedger } from "../delegation.js";
|
|
5
|
+
import { readDelegationLedger, isParallelTddSliceWorker } from "../delegation.js";
|
|
6
6
|
export function parseCohesionContractArgs(tokens) {
|
|
7
7
|
const args = { stub: false, force: false, reason: null };
|
|
8
8
|
for (const token of tokens) {
|
|
@@ -27,20 +27,15 @@ export function parseCohesionContractArgs(tokens) {
|
|
|
27
27
|
return args;
|
|
28
28
|
}
|
|
29
29
|
/**
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
30
|
+
* Scaffold a minimal cohesion-contract pair (`cohesion-contract.{md,json}`)
|
|
31
|
+
* so authors have a starting point when a multi-slice wave needs cross-slice
|
|
32
|
+
* cohesion documentation. The stub seeds `sharedTypes`, `touchpoints`, and
|
|
33
|
+
* `slices` from the active run delegation ledger and carries
|
|
34
|
+
* `status.verdict: "scaffold"` so reviewers know to fill in the real content
|
|
35
|
+
* before treating it as authoritative.
|
|
35
36
|
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* downstream tooling can see which slices the contract acknowledges,
|
|
39
|
-
* but the contract carries `status.verdict: "advisory_legacy"` so
|
|
40
|
-
* reviewers know not to treat it as authoritative.
|
|
41
|
-
*
|
|
42
|
-
* Refuses to overwrite an existing contract unless `--force` is
|
|
43
|
-
* passed; the existing file is treated as authored work.
|
|
37
|
+
* Refuses to overwrite an existing contract unless `--force` is passed; the
|
|
38
|
+
* existing file is treated as authored work.
|
|
44
39
|
*/
|
|
45
40
|
export async function runCohesionContractCommand(projectRoot, tokens, io) {
|
|
46
41
|
const parsed = parseCohesionContractArgs(tokens);
|
|
@@ -63,13 +58,12 @@ export async function runCohesionContractCommand(projectRoot, tokens, io) {
|
|
|
63
58
|
const sliceIds = collectSliceIds(ledger?.entries ?? []);
|
|
64
59
|
const reasonNote = parsed.reason
|
|
65
60
|
? `Reason: ${parsed.reason}`
|
|
66
|
-
: "Reason:
|
|
61
|
+
: "Reason: scaffold for a multi-slice wave that needs cohesion documentation.";
|
|
67
62
|
const md = [
|
|
68
63
|
"# Cohesion Contract",
|
|
69
64
|
"",
|
|
70
|
-
"
|
|
71
|
-
"_Status: `
|
|
72
|
-
"_TDD linter does not block stage-complete on `tdd.cohesion_contract_missing`._",
|
|
65
|
+
"_Scaffold generated by `cclaw-cli internal cohesion-contract --stub`._",
|
|
66
|
+
"_Status: `scaffold` — replace placeholders with real cross-slice data before review._",
|
|
73
67
|
"",
|
|
74
68
|
`${reasonNote}`,
|
|
75
69
|
"",
|
|
@@ -110,21 +104,21 @@ export async function runCohesionContractCommand(projectRoot, tokens, io) {
|
|
|
110
104
|
touchpoints: [],
|
|
111
105
|
slices: sliceIds.map((sid) => ({
|
|
112
106
|
sliceId: sid,
|
|
113
|
-
description: `
|
|
107
|
+
description: `Scaffold entry for ${sid}; replace with real behavior summary.`,
|
|
114
108
|
implemented: true,
|
|
115
109
|
testsPass: true,
|
|
116
110
|
cohesionVerified: false
|
|
117
111
|
})),
|
|
118
112
|
status: {
|
|
119
|
-
verdict: "
|
|
113
|
+
verdict: "scaffold",
|
|
120
114
|
generatedBy: "cclaw-cli internal cohesion-contract --stub",
|
|
121
|
-
reason: parsed.reason ?? "
|
|
115
|
+
reason: parsed.reason ?? "scaffold for a multi-slice wave"
|
|
122
116
|
}
|
|
123
117
|
};
|
|
124
118
|
await writeFileSafe(mdPath, md);
|
|
125
119
|
await writeFileSafe(jsonPath, `${JSON.stringify(jsonStub, null, 2)}\n`);
|
|
126
|
-
io.stdout.write(`cclaw: cohesion-contract
|
|
127
|
-
"Status:
|
|
120
|
+
io.stdout.write(`cclaw: cohesion-contract scaffold written (${sliceIds.length} slice(s) referenced). ` +
|
|
121
|
+
"Status: scaffold — replace placeholders with real cross-slice data.\n");
|
|
128
122
|
return 0;
|
|
129
123
|
}
|
|
130
124
|
async function fileExists(filePath) {
|
|
@@ -139,7 +133,7 @@ async function fileExists(filePath) {
|
|
|
139
133
|
function collectSliceIds(entries) {
|
|
140
134
|
const set = new Set();
|
|
141
135
|
for (const entry of entries) {
|
|
142
|
-
if (entry.agent
|
|
136
|
+
if (!isParallelTddSliceWorker(entry.agent ?? ""))
|
|
143
137
|
continue;
|
|
144
138
|
if (entry.status !== "completed")
|
|
145
139
|
continue;
|
|
@@ -8,7 +8,7 @@ export interface FlowStateRepairArgs {
|
|
|
8
8
|
json: boolean;
|
|
9
9
|
quiet: boolean;
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* when true, normalize `state/early-loop.json` to the canonical
|
|
12
12
|
* shape derived from `early-loop-log.jsonl`. Lets operators recover from
|
|
13
13
|
* legacy hand-written `early-loop.json` files that drifted from the
|
|
14
14
|
* source-of-truth log.
|
|
@@ -4,7 +4,7 @@ interface InternalIo {
|
|
|
4
4
|
stderr: Writable;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* split a large `05-plan.md` Implementation Units section
|
|
8
8
|
* into wave-NN.md sub-files so an executor can carry one wave at a time
|
|
9
9
|
* without re-reading the whole plan.
|
|
10
10
|
*
|
|
@@ -31,7 +31,7 @@ export interface PlanSplitWavesArgs {
|
|
|
31
31
|
}
|
|
32
32
|
export declare const PLAN_SPLIT_DEFAULT_WAVE_SIZE = 5;
|
|
33
33
|
export declare const PLAN_SPLIT_SMALL_PLAN_THRESHOLD = 50;
|
|
34
|
-
/**
|
|
34
|
+
/** Member line in Parallel Execution Plan or wave-NN.md */
|
|
35
35
|
export interface ParsedParallelWaveMember {
|
|
36
36
|
sliceId: string;
|
|
37
37
|
unitId: string;
|
|
@@ -57,11 +57,10 @@ export declare function extractParallelExecutionManagedBody(planMarkdown: string
|
|
|
57
57
|
*/
|
|
58
58
|
export declare function extractMembersListFromLine(trimmedLine: string): string | null;
|
|
59
59
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* recognize the table-format Parallel Execution Plan
|
|
63
|
-
*
|
|
64
|
-
* bullet line.
|
|
60
|
+
* Extract a `(sliceId, unitId)` pair from a markdown table data row
|
|
61
|
+
* whose first column is an `S-NN` token. Used by the wave parser to
|
|
62
|
+
* recognize the table-format Parallel Execution Plan alongside (or
|
|
63
|
+
* instead of) the `**Members:**` bullet line.
|
|
65
64
|
*
|
|
66
65
|
* Rules:
|
|
67
66
|
* - The line must start with `|` (after trimming).
|
|
@@ -70,25 +69,23 @@ export declare function extractMembersListFromLine(trimmedLine: string): string
|
|
|
70
69
|
* silently skipped.
|
|
71
70
|
* - Column 2, when present and non-empty, becomes the `unitId`
|
|
72
71
|
* verbatim (after stripping whitespace + backticks/quotes/brackets).
|
|
73
|
-
* This
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* stays bit-identical for non-table plans.
|
|
72
|
+
* This lets authors record task ids (`T-010`, `T-008a`, …) in the
|
|
73
|
+
* `unit` column without forcing a `U-NN` derivation.
|
|
74
|
+
* - When column 2 is absent or empty, fall back to the
|
|
75
|
+
* `S-NN → U-NN` derivation so the `**Members:**` parser path stays
|
|
76
|
+
* bit-identical for non-table plans.
|
|
79
77
|
*/
|
|
80
78
|
export declare function parseTableRowMember(trimmedLine: string): ParsedParallelWaveMember | null;
|
|
81
79
|
/**
|
|
82
80
|
* Parse `## Parallel Execution Plan` managed block for wave headings and
|
|
83
|
-
* member declarations. Recognizes BOTH the
|
|
84
|
-
*
|
|
85
|
-
* (`| sliceId | unit | dependsOn | …`)
|
|
86
|
-
* any plan written by `cclaw-cli sync` after v6.13.x.
|
|
81
|
+
* member declarations. Recognizes BOTH the `**Members:**` / `Members:`
|
|
82
|
+
* line shape AND the markdown-table shape
|
|
83
|
+
* (`| sliceId | unit | dependsOn | …`).
|
|
87
84
|
*
|
|
88
85
|
* Wave headings accepted (case-insensitive, trailing text allowed):
|
|
89
86
|
* - `### Wave 04`
|
|
90
87
|
* - `### Wave W-04`
|
|
91
|
-
* - `### Wave W-04 —
|
|
88
|
+
* - `### Wave W-04 — after fan-in W-03 (5 lanes …)`
|
|
92
89
|
*
|
|
93
90
|
* Within a single wave the parser dedupes by `sliceId`: if the same
|
|
94
91
|
* slice appears in both `**Members:**` and a table row, the first
|
|
@@ -138,20 +135,20 @@ export interface ImplementationUnitParallelFields {
|
|
|
138
135
|
}
|
|
139
136
|
export interface ParseImplementationUnitParallelOptions {
|
|
140
137
|
/**
|
|
141
|
-
*
|
|
138
|
+
* Continuation: when the plan predates explicit parallel
|
|
142
139
|
* bullets, units without a `parallelizable:` line default to serial eligibility
|
|
143
140
|
* in the scheduler (`parallelizable: false`).
|
|
144
141
|
*/
|
|
145
142
|
legacyParallelDefaultSerial?: boolean;
|
|
146
143
|
}
|
|
147
144
|
/**
|
|
148
|
-
* Parse
|
|
145
|
+
* Parse parallel-metadata bullets from an implementation unit body.
|
|
149
146
|
* Missing keys use conservative defaults (`dependsOn: []`, `parallelizable: true`
|
|
150
147
|
* unless `legacyParallelDefaultSerial` is set).
|
|
151
148
|
*/
|
|
152
149
|
export declare function parseImplementationUnitParallelFields(unit: ParsedImplementationUnit, options?: ParseImplementationUnitParallelOptions): ImplementationUnitParallelFields;
|
|
153
150
|
/**
|
|
154
|
-
* True when the plan has implementation units but any unit is missing
|
|
151
|
+
* True when the plan has implementation units but any unit is missing
|
|
155
152
|
* `dependsOn` / `claimedPaths` / `parallelizable` / `riskTier` bullets.
|
|
156
153
|
*/
|
|
157
154
|
export declare function planArtifactLacksV613ParallelMetadata(planMarkdown: string): boolean;
|
|
@@ -61,11 +61,10 @@ export function extractMembersListFromLine(trimmedLine) {
|
|
|
61
61
|
return null;
|
|
62
62
|
}
|
|
63
63
|
/**
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
* recognize the table-format Parallel Execution Plan
|
|
67
|
-
*
|
|
68
|
-
* bullet line.
|
|
64
|
+
* Extract a `(sliceId, unitId)` pair from a markdown table data row
|
|
65
|
+
* whose first column is an `S-NN` token. Used by the wave parser to
|
|
66
|
+
* recognize the table-format Parallel Execution Plan alongside (or
|
|
67
|
+
* instead of) the `**Members:**` bullet line.
|
|
69
68
|
*
|
|
70
69
|
* Rules:
|
|
71
70
|
* - The line must start with `|` (after trimming).
|
|
@@ -74,12 +73,11 @@ export function extractMembersListFromLine(trimmedLine) {
|
|
|
74
73
|
* silently skipped.
|
|
75
74
|
* - Column 2, when present and non-empty, becomes the `unitId`
|
|
76
75
|
* verbatim (after stripping whitespace + backticks/quotes/brackets).
|
|
77
|
-
* This
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
* stays bit-identical for non-table plans.
|
|
76
|
+
* This lets authors record task ids (`T-010`, `T-008a`, …) in the
|
|
77
|
+
* `unit` column without forcing a `U-NN` derivation.
|
|
78
|
+
* - When column 2 is absent or empty, fall back to the
|
|
79
|
+
* `S-NN → U-NN` derivation so the `**Members:**` parser path stays
|
|
80
|
+
* bit-identical for non-table plans.
|
|
83
81
|
*/
|
|
84
82
|
export function parseTableRowMember(trimmedLine) {
|
|
85
83
|
if (!trimmedLine.startsWith("|"))
|
|
@@ -109,15 +107,14 @@ export function parseTableRowMember(trimmedLine) {
|
|
|
109
107
|
}
|
|
110
108
|
/**
|
|
111
109
|
* Parse `## Parallel Execution Plan` managed block for wave headings and
|
|
112
|
-
* member declarations. Recognizes BOTH the
|
|
113
|
-
*
|
|
114
|
-
* (`| sliceId | unit | dependsOn | …`)
|
|
115
|
-
* any plan written by `cclaw-cli sync` after v6.13.x.
|
|
110
|
+
* member declarations. Recognizes BOTH the `**Members:**` / `Members:`
|
|
111
|
+
* line shape AND the markdown-table shape
|
|
112
|
+
* (`| sliceId | unit | dependsOn | …`).
|
|
116
113
|
*
|
|
117
114
|
* Wave headings accepted (case-insensitive, trailing text allowed):
|
|
118
115
|
* - `### Wave 04`
|
|
119
116
|
* - `### Wave W-04`
|
|
120
|
-
* - `### Wave W-04 —
|
|
117
|
+
* - `### Wave W-04 — after fan-in W-03 (5 lanes …)`
|
|
121
118
|
*
|
|
122
119
|
* Within a single wave the parser dedupes by `sliceId`: if the same
|
|
123
120
|
* slice appears in both `**Members:**` and a table row, the first
|
|
@@ -145,7 +142,7 @@ export function parseParallelExecutionPlanWaves(planMarkdown) {
|
|
|
145
142
|
};
|
|
146
143
|
/**
|
|
147
144
|
* Strict add: throw on duplicates within the same wave OR across waves.
|
|
148
|
-
* Used for the `**Members:**` path so
|
|
145
|
+
* Used for the `**Members:**` path so the duplicate-detection
|
|
149
146
|
* contract is preserved bit-identically.
|
|
150
147
|
*/
|
|
151
148
|
const addMemberStrict = (member) => {
|
|
@@ -324,7 +321,7 @@ export function formatNextParallelWaveSyncHint(merged) {
|
|
|
324
321
|
return `Parallel Execution Plan: ${candidate.waveId} has ${candidate.members.length} parallel members (${ids}).`;
|
|
325
322
|
}
|
|
326
323
|
/**
|
|
327
|
-
* Parse
|
|
324
|
+
* Parse parallel-metadata bullets from an implementation unit body.
|
|
328
325
|
* Missing keys use conservative defaults (`dependsOn: []`, `parallelizable: true`
|
|
329
326
|
* unless `legacyParallelDefaultSerial` is set).
|
|
330
327
|
*/
|
|
@@ -380,7 +377,7 @@ function unitBodyHasV613ParallelBullet(body, label) {
|
|
|
380
377
|
});
|
|
381
378
|
}
|
|
382
379
|
/**
|
|
383
|
-
* True when the plan has implementation units but any unit is missing
|
|
380
|
+
* True when the plan has implementation units but any unit is missing
|
|
384
381
|
* `dependsOn` / `claimedPaths` / `parallelizable` / `riskTier` bullets.
|
|
385
382
|
*/
|
|
386
383
|
export function planArtifactLacksV613ParallelMetadata(planMarkdown) {
|
|
@@ -21,9 +21,6 @@ export interface WaveStatusNextDispatch {
|
|
|
21
21
|
export interface WaveStatusReport {
|
|
22
22
|
activeRunId: string;
|
|
23
23
|
currentStage: string;
|
|
24
|
-
tddCutoverSliceId: string | null;
|
|
25
|
-
tddWorktreeCutoverSliceId: string | null;
|
|
26
|
-
legacyContinuation: boolean;
|
|
27
24
|
waves: WaveStatusWaveSummary[];
|
|
28
25
|
nextDispatch: WaveStatusNextDispatch;
|
|
29
26
|
warnings: string[];
|
|
@@ -37,11 +34,11 @@ export interface RunWaveStatusOptions {
|
|
|
37
34
|
artifactsDir?: string;
|
|
38
35
|
}
|
|
39
36
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
37
|
+
* Deterministic helper for the TDD controller. Reads the managed
|
|
38
|
+
* `<!-- parallel-exec-managed-start -->` block from
|
|
42
39
|
* `<artifacts-dir>/05-plan.md` plus the `wave-plans/` directory and
|
|
43
40
|
* reports waves + the next dispatchable members so the controller does
|
|
44
|
-
* NOT have to page through a
|
|
41
|
+
* NOT have to page through a long plan to find the active wave.
|
|
45
42
|
*
|
|
46
43
|
* Always exits 0 unless the plan is malformed (no managed block AND no
|
|
47
44
|
* wave-plans directory), in which case exit 2 with a structured error.
|
|
@@ -39,11 +39,11 @@ const TERMINAL_PHASES = new Set([
|
|
|
39
39
|
"resolve-conflict"
|
|
40
40
|
]);
|
|
41
41
|
/**
|
|
42
|
-
*
|
|
43
|
-
*
|
|
42
|
+
* Deterministic helper for the TDD controller. Reads the managed
|
|
43
|
+
* `<!-- parallel-exec-managed-start -->` block from
|
|
44
44
|
* `<artifacts-dir>/05-plan.md` plus the `wave-plans/` directory and
|
|
45
45
|
* reports waves + the next dispatchable members so the controller does
|
|
46
|
-
* NOT have to page through a
|
|
46
|
+
* NOT have to page through a long plan to find the active wave.
|
|
47
47
|
*
|
|
48
48
|
* Always exits 0 unless the plan is malformed (no managed block AND no
|
|
49
49
|
* wave-plans directory), in which case exit 2 with a structured error.
|
|
@@ -53,9 +53,6 @@ export async function runWaveStatus(projectRoot, options = {}) {
|
|
|
53
53
|
const flowState = await readFlowState(projectRoot).catch(() => null);
|
|
54
54
|
const activeRunId = flowState?.activeRunId ?? "unknown-run";
|
|
55
55
|
const currentStage = flowState?.currentStage ?? "tdd";
|
|
56
|
-
const tddCutoverSliceId = flowState?.tddCutoverSliceId ?? null;
|
|
57
|
-
const tddWorktreeCutoverSliceId = flowState?.tddWorktreeCutoverSliceId ?? null;
|
|
58
|
-
const legacyContinuation = flowState?.legacyContinuation === true;
|
|
59
56
|
let planRaw = "";
|
|
60
57
|
try {
|
|
61
58
|
planRaw = await fs.readFile(path.join(artifactsDir, "05-plan.md"), "utf8");
|
|
@@ -71,9 +68,6 @@ export async function runWaveStatus(projectRoot, options = {}) {
|
|
|
71
68
|
return {
|
|
72
69
|
activeRunId,
|
|
73
70
|
currentStage,
|
|
74
|
-
tddCutoverSliceId,
|
|
75
|
-
tddWorktreeCutoverSliceId,
|
|
76
|
-
legacyContinuation,
|
|
77
71
|
waves: [],
|
|
78
72
|
nextDispatch: {
|
|
79
73
|
waveId: null,
|
|
@@ -102,9 +96,6 @@ export async function runWaveStatus(projectRoot, options = {}) {
|
|
|
102
96
|
return {
|
|
103
97
|
activeRunId,
|
|
104
98
|
currentStage,
|
|
105
|
-
tddCutoverSliceId,
|
|
106
|
-
tddWorktreeCutoverSliceId,
|
|
107
|
-
legacyContinuation,
|
|
108
99
|
waves: [],
|
|
109
100
|
nextDispatch: {
|
|
110
101
|
waveId: null,
|
|
@@ -120,8 +111,8 @@ export async function runWaveStatus(projectRoot, options = {}) {
|
|
|
120
111
|
// Collect closed slice ids from the active run delegation ledger +
|
|
121
112
|
// events. A slice is "closed" once it carries a terminal phase
|
|
122
113
|
// (refactor, refactor-deferred, resolve-conflict) OR a phase=green
|
|
123
|
-
// event with refactorOutcome
|
|
124
|
-
//
|
|
114
|
+
// event with `refactorOutcome` recorded inline. Anything else we treat
|
|
115
|
+
// as still open so the helper never falsely advances.
|
|
125
116
|
const closedSlices = new Set();
|
|
126
117
|
let ledgerEntries = [];
|
|
127
118
|
try {
|
|
@@ -194,9 +185,6 @@ export async function runWaveStatus(projectRoot, options = {}) {
|
|
|
194
185
|
});
|
|
195
186
|
const firstOpenWave = waves.find((w) => w.status === "open" || w.status === "partial") ?? null;
|
|
196
187
|
const warnings = [];
|
|
197
|
-
if (tddCutoverSliceId) {
|
|
198
|
-
warnings.push("tddCutoverSliceId is a historical boundary; do not use it to find the active slice.");
|
|
199
|
-
}
|
|
200
188
|
if (merged.length === 0 && planRaw.length === 0) {
|
|
201
189
|
warnings.push("wave_plan_missing: 05-plan.md not found or empty under <artifacts-dir>.");
|
|
202
190
|
}
|
|
@@ -224,9 +212,6 @@ export async function runWaveStatus(projectRoot, options = {}) {
|
|
|
224
212
|
return {
|
|
225
213
|
activeRunId,
|
|
226
214
|
currentStage,
|
|
227
|
-
tddCutoverSliceId,
|
|
228
|
-
tddWorktreeCutoverSliceId,
|
|
229
|
-
legacyContinuation,
|
|
230
215
|
waves,
|
|
231
216
|
nextDispatch,
|
|
232
217
|
warnings
|
|
@@ -236,13 +221,6 @@ function formatHumanReport(report) {
|
|
|
236
221
|
const lines = [];
|
|
237
222
|
lines.push(`activeRunId: ${report.activeRunId}`);
|
|
238
223
|
lines.push(`currentStage: ${report.currentStage}`);
|
|
239
|
-
if (report.tddCutoverSliceId) {
|
|
240
|
-
lines.push(`tddCutoverSliceId: ${report.tddCutoverSliceId} (HISTORICAL)`);
|
|
241
|
-
}
|
|
242
|
-
if (report.tddWorktreeCutoverSliceId) {
|
|
243
|
-
lines.push(`tddWorktreeCutoverSliceId: ${report.tddWorktreeCutoverSliceId}`);
|
|
244
|
-
}
|
|
245
|
-
lines.push(`legacyContinuation: ${report.legacyContinuation}`);
|
|
246
224
|
lines.push("waves:");
|
|
247
225
|
if (report.waves.length === 0) {
|
|
248
226
|
lines.push(" (no waves discovered)");
|