cclaw-cli 5.0.0 → 6.1.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/brainstorm.js +1 -1
- package/dist/artifact-linter/design.d.ts +16 -0
- package/dist/artifact-linter/design.js +83 -19
- package/dist/artifact-linter/scope.js +60 -21
- package/dist/artifact-linter/shared.d.ts +158 -14
- package/dist/artifact-linter/shared.js +319 -109
- package/dist/artifact-linter.js +83 -17
- package/dist/content/skills-elicitation.js +29 -15
- package/dist/content/skills.js +10 -4
- package/dist/content/stage-schema.d.ts +52 -0
- package/dist/content/stage-schema.js +36 -0
- 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/subagents.js +3 -1
- package/dist/content/templates.d.ts +2 -2
- package/dist/content/templates.js +17 -11
- package/dist/delegation.d.ts +52 -0
- package/dist/delegation.js +128 -3
- package/dist/flow-state.d.ts +14 -0
- package/dist/harness-adapters.js +1 -1
- package/dist/internal/advance-stage/advance.d.ts +38 -0
- package/dist/internal/advance-stage/advance.js +93 -5
- package/dist/internal/advance-stage/review-loop.d.ts +9 -0
- package/dist/internal/advance-stage/review-loop.js +42 -4
- package/package.json +1 -1
package/dist/delegation.js
CHANGED
|
@@ -7,7 +7,7 @@ import { readConfig } from "./config.js";
|
|
|
7
7
|
import { exists, withDirectoryLock, writeFileSafe } from "./fs-utils.js";
|
|
8
8
|
import { HARNESS_ADAPTERS } from "./harness-adapters.js";
|
|
9
9
|
import { readFlowState } from "./runs.js";
|
|
10
|
-
import { stageSchema } from "./content/stage-schema.js";
|
|
10
|
+
import { mandatoryAgentsFor, stageSchema } from "./content/stage-schema.js";
|
|
11
11
|
const execFileAsync = promisify(execFile);
|
|
12
12
|
const TERMINAL_DELEGATION_STATUSES = new Set(["completed", "failed", "waived", "stale"]);
|
|
13
13
|
export const DELEGATION_DISPATCH_SURFACES = [
|
|
@@ -320,6 +320,23 @@ export async function readDelegationLedger(projectRoot) {
|
|
|
320
320
|
return { runId: activeRunId, entries: [] };
|
|
321
321
|
}
|
|
322
322
|
}
|
|
323
|
+
/**
|
|
324
|
+
* Wave 24 (v6.0.0) audit-only event types that live in
|
|
325
|
+
* `delegation-events.jsonl` but do NOT carry a delegation lifecycle
|
|
326
|
+
* payload (no agent/spanId). The parser must accept them so they
|
|
327
|
+
* don't show up as corrupt lines.
|
|
328
|
+
*/
|
|
329
|
+
const NON_DELEGATION_AUDIT_EVENTS = new Set([
|
|
330
|
+
"mandatory_delegations_skipped_by_track",
|
|
331
|
+
"artifact_validation_demoted_by_track",
|
|
332
|
+
"expansion_strategist_skipped_by_track"
|
|
333
|
+
]);
|
|
334
|
+
function isAuditEventLine(parsed) {
|
|
335
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
336
|
+
return false;
|
|
337
|
+
const evt = parsed.event;
|
|
338
|
+
return typeof evt === "string" && NON_DELEGATION_AUDIT_EVENTS.has(evt);
|
|
339
|
+
}
|
|
323
340
|
export async function readDelegationEvents(projectRoot) {
|
|
324
341
|
const filePath = delegationEventsPath(projectRoot);
|
|
325
342
|
if (!(await exists(filePath))) {
|
|
@@ -338,6 +355,11 @@ export async function readDelegationEvents(projectRoot) {
|
|
|
338
355
|
if (isDelegationEvent(parsed)) {
|
|
339
356
|
events.push(parsed);
|
|
340
357
|
}
|
|
358
|
+
else if (isAuditEventLine(parsed)) {
|
|
359
|
+
// Wave 24 audit-only row (e.g. mandatory_delegations_skipped_by_track).
|
|
360
|
+
// Not a delegation lifecycle event but valid audit content.
|
|
361
|
+
continue;
|
|
362
|
+
}
|
|
341
363
|
else {
|
|
342
364
|
corruptLines.push(index + 1);
|
|
343
365
|
}
|
|
@@ -451,7 +473,17 @@ export async function checkMandatoryDelegations(projectRoot, stage, options = {}
|
|
|
451
473
|
const flowState = await readFlowState(projectRoot, {
|
|
452
474
|
repairFeatureSystem: options.repairFeatureSystem
|
|
453
475
|
});
|
|
454
|
-
const mandatory =
|
|
476
|
+
const mandatory = mandatoryAgentsFor(stage, flowState.track, options.taskClass ?? null);
|
|
477
|
+
const skippedByTrack = mandatory.length === 0 &&
|
|
478
|
+
stageSchema(stage, flowState.track).mandatoryDelegations.length > 0;
|
|
479
|
+
if (skippedByTrack) {
|
|
480
|
+
await recordMandatorySkippedByTrack(projectRoot, {
|
|
481
|
+
stage,
|
|
482
|
+
track: flowState.track,
|
|
483
|
+
taskClass: options.taskClass ?? null,
|
|
484
|
+
runId: flowState.activeRunId
|
|
485
|
+
});
|
|
486
|
+
}
|
|
455
487
|
const { activeRunId } = flowState;
|
|
456
488
|
const ledger = await readDelegationLedger(projectRoot);
|
|
457
489
|
const events = await readDelegationEvents(projectRoot);
|
|
@@ -553,6 +585,99 @@ export async function checkMandatoryDelegations(projectRoot, stage, options = {}
|
|
|
553
585
|
legacyInferredCompletions,
|
|
554
586
|
corruptEventLines: events.corruptLines,
|
|
555
587
|
staleWorkers,
|
|
556
|
-
expectedMode
|
|
588
|
+
expectedMode,
|
|
589
|
+
skippedByTrack
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Wave 24 (v6.0.0) — append a non-delegation audit event to
|
|
594
|
+
* `delegation-events.jsonl` recording that the mandatory delegation
|
|
595
|
+
* gate was skipped because of the active track / task class. Plays the
|
|
596
|
+
* same audit role as a `waived` row but does NOT carry an agent —
|
|
597
|
+
* downstream tooling treats `event === "mandatory_delegations_skipped_by_track"`
|
|
598
|
+
* lines as informational.
|
|
599
|
+
*
|
|
600
|
+
* Failures are swallowed: the audit log is best-effort. Missing the
|
|
601
|
+
* event must never block stage advance because the gate skip itself is
|
|
602
|
+
* authoritative.
|
|
603
|
+
*/
|
|
604
|
+
async function recordMandatorySkippedByTrack(projectRoot, params) {
|
|
605
|
+
const eventsPath = delegationEventsPath(projectRoot);
|
|
606
|
+
const payload = {
|
|
607
|
+
event: "mandatory_delegations_skipped_by_track",
|
|
608
|
+
stage: params.stage,
|
|
609
|
+
track: params.track,
|
|
610
|
+
taskClass: params.taskClass,
|
|
611
|
+
runId: params.runId,
|
|
612
|
+
ts: new Date().toISOString()
|
|
557
613
|
};
|
|
614
|
+
try {
|
|
615
|
+
await fs.mkdir(path.dirname(eventsPath), { recursive: true });
|
|
616
|
+
await fs.appendFile(eventsPath, `${JSON.stringify(payload)}\n`, "utf8");
|
|
617
|
+
}
|
|
618
|
+
catch {
|
|
619
|
+
// best-effort audit; never block stage advance.
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
/**
|
|
623
|
+
* Wave 25 (v6.1.0) — append a non-delegation audit event recording
|
|
624
|
+
* that one or more required artifact-validation findings were
|
|
625
|
+
* demoted from blocking to advisory because the active run is on a
|
|
626
|
+
* small-fix lane (`track === "quick"` or `taskClass === "software-bugfix"`).
|
|
627
|
+
*
|
|
628
|
+
* The event mirrors the Wave 24 `mandatory_delegations_skipped_by_track`
|
|
629
|
+
* audit pattern: best-effort write to `delegation-events.jsonl`, no
|
|
630
|
+
* agent payload, recognized by `readDelegationEvents` so it does not
|
|
631
|
+
* corrupt downstream parsers. Failures are swallowed.
|
|
632
|
+
*/
|
|
633
|
+
export async function recordArtifactValidationDemotedByTrack(projectRoot, params) {
|
|
634
|
+
if (params.sections.length === 0)
|
|
635
|
+
return;
|
|
636
|
+
const eventsPath = delegationEventsPath(projectRoot);
|
|
637
|
+
const payload = {
|
|
638
|
+
event: "artifact_validation_demoted_by_track",
|
|
639
|
+
stage: params.stage,
|
|
640
|
+
track: params.track,
|
|
641
|
+
taskClass: params.taskClass,
|
|
642
|
+
runId: params.runId,
|
|
643
|
+
sections: params.sections,
|
|
644
|
+
ts: new Date().toISOString()
|
|
645
|
+
};
|
|
646
|
+
try {
|
|
647
|
+
await fs.mkdir(path.dirname(eventsPath), { recursive: true });
|
|
648
|
+
await fs.appendFile(eventsPath, `${JSON.stringify(payload)}\n`, "utf8");
|
|
649
|
+
}
|
|
650
|
+
catch {
|
|
651
|
+
// best-effort audit; never block stage advance.
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
/**
|
|
655
|
+
* Wave 25 (v6.1.0) — append a non-delegation audit event recording
|
|
656
|
+
* that the scope-stage Expansion Strategist (`product-discovery`)
|
|
657
|
+
* delegation requirement was skipped because the active run is on a
|
|
658
|
+
* small-fix lane (`track === "quick"` or `taskClass === "software-bugfix"`).
|
|
659
|
+
*
|
|
660
|
+
* Mirrors the Wave 24 `mandatory_delegations_skipped_by_track`
|
|
661
|
+
* audit pattern: best-effort write to `delegation-events.jsonl`, no
|
|
662
|
+
* agent payload, recognized by `readDelegationEvents` so it does not
|
|
663
|
+
* corrupt downstream parsers. Failures are swallowed.
|
|
664
|
+
*/
|
|
665
|
+
export async function recordExpansionStrategistSkippedByTrack(projectRoot, params) {
|
|
666
|
+
const eventsPath = delegationEventsPath(projectRoot);
|
|
667
|
+
const payload = {
|
|
668
|
+
event: "expansion_strategist_skipped_by_track",
|
|
669
|
+
stage: "scope",
|
|
670
|
+
track: params.track,
|
|
671
|
+
taskClass: params.taskClass,
|
|
672
|
+
runId: params.runId,
|
|
673
|
+
selectedScopeMode: params.selectedScopeMode,
|
|
674
|
+
ts: new Date().toISOString()
|
|
675
|
+
};
|
|
676
|
+
try {
|
|
677
|
+
await fs.mkdir(path.dirname(eventsPath), { recursive: true });
|
|
678
|
+
await fs.appendFile(eventsPath, `${JSON.stringify(payload)}\n`, "utf8");
|
|
679
|
+
}
|
|
680
|
+
catch {
|
|
681
|
+
// best-effort audit; never block stage advance.
|
|
682
|
+
}
|
|
558
683
|
}
|
package/dist/flow-state.d.ts
CHANGED
|
@@ -76,6 +76,20 @@ export interface FlowState {
|
|
|
76
76
|
stageGateCatalog: Record<FlowStage, StageGateState>;
|
|
77
77
|
/** Active flow track (determines which stages are in the critical path for this run). */
|
|
78
78
|
track: FlowTrack;
|
|
79
|
+
/**
|
|
80
|
+
* Wave 25 (v6.1.0) — optional task class for the active run.
|
|
81
|
+
*
|
|
82
|
+
* Mirrors the `MandatoryDelegationTaskClass` union used by Wave 24's
|
|
83
|
+
* `mandatoryAgentsFor` helper. When set to `"software-bugfix"`, the
|
|
84
|
+
* artifact-validation escape (`shouldDemoteArtifactValidationByTrack`)
|
|
85
|
+
* collapses lite-tier-only checks (Architecture Diagram async/failure
|
|
86
|
+
* edges, Interaction Edge Case mandatory rows, Stale Diagram Drift,
|
|
87
|
+
* Expansion Strategist) from required → advisory.
|
|
88
|
+
*
|
|
89
|
+
* Persistence is best-effort: existing flow-state.json files written
|
|
90
|
+
* before Wave 25 simply omit the field (treated as `null`).
|
|
91
|
+
*/
|
|
92
|
+
taskClass?: "software-standard" | "software-trivial" | "software-bugfix" | null;
|
|
79
93
|
/** Stages explicitly skipped for this track (empty for standard; populated for quick). */
|
|
80
94
|
skippedStages: FlowStage[];
|
|
81
95
|
/** Stages invalidated by rewind operations and awaiting explicit acknowledgement. */
|
package/dist/harness-adapters.js
CHANGED
|
@@ -349,7 +349,7 @@ Before responding to a coding request:
|
|
|
349
349
|
|
|
350
350
|
Three rules apply to every cclaw stage in this project, regardless of which skills loaded:
|
|
351
351
|
|
|
352
|
-
1. **Q&A convergence before drafting** — for brainstorm / scope / design, walk the stage forcing questions one at a time via the harness-native question tool (Claude \`AskUserQuestion\`, Cursor \`AskQuestion\`, Codex \`request_user_input\`, Gemini \`ask_user\`). The \`qa_log_unconverged\` linter rule will block \`stage-complete\` when convergence has not been reached. Convergence is satisfied when ANY of: (a)
|
|
352
|
+
1. **Q&A convergence before drafting** — for brainstorm / scope / design, walk the stage forcing questions one at a time via the harness-native question tool (Claude \`AskUserQuestion\`, Cursor \`AskQuestion\`, Codex \`request_user_input\`, Gemini \`ask_user\`). The \`qa_log_unconverged\` linter rule will block \`stage-complete\` when convergence has not been reached. Convergence is satisfied when ANY of: (a) every forcing-question topic id is tagged \`[topic:<id>]\` in at least one \`## Q&A Log\` row, (b) the last 2 substantive rows produce no decision-changing impact (Ralph-Loop), or (c) an explicit user stop-signal row is recorded. The fixed count floor (10 for standard) was removed in Wave 23. Wave 24 (v6.0.0) made \`[topic:<id>]\` tagging mandatory (no English keyword fallback) so the gate works in any natural language.
|
|
353
353
|
2. **Subagents run after Q&A approval** — mandatory subagents in brainstorm / scope / design (\`product-discovery\`, \`critic\`, \`planner\`, \`architect\`, \`test-author\`) run only AFTER the user approves the elicitation outcome. See each stage's "Run Phase: post-elicitation" rows in the materialized Automatic Subagent Dispatch table.
|
|
354
354
|
3. **No command-line echo to chat** — the user does not run cclaw helpers manually. Never paste \`node .cclaw/hooks/...\` invocations, \`--evidence-json '{...}'\` payloads, or shell hash commands (\`shasum\`, \`sha256sum\`, \`Get-FileHash\`, \`certutil\`, etc.) into chat. Run helpers via the tool layer; report only the resulting summary.
|
|
355
355
|
|
|
@@ -19,6 +19,8 @@ interface InternalValidationReport {
|
|
|
19
19
|
corruptEventLines: number[];
|
|
20
20
|
staleWorkers: string[];
|
|
21
21
|
expectedMode: string;
|
|
22
|
+
/** Wave 24: true when mandatoryAgentsFor returned [] for the run's track / taskClass. */
|
|
23
|
+
skippedByTrack: boolean;
|
|
22
24
|
};
|
|
23
25
|
gates: {
|
|
24
26
|
ok: boolean;
|
|
@@ -32,7 +34,43 @@ interface InternalValidationReport {
|
|
|
32
34
|
issues: string[];
|
|
33
35
|
};
|
|
34
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* Wave 24 entry point — auto-hydrate evidence for an auto-hydratable
|
|
39
|
+
* gate that the agent already included in --passed but for which they
|
|
40
|
+
* forgot to provide --evidence-json. Returns silently when no
|
|
41
|
+
* hydration is possible (no auto-hydratable gate, no artifact, no
|
|
42
|
+
* envelope, etc.).
|
|
43
|
+
*
|
|
44
|
+
* Wave 25 (v6.1.0) layered `tryAutoHydrateAndSelectReviewLoopGate` on
|
|
45
|
+
* top of this so the gate is also auto-included in selectedGateIds
|
|
46
|
+
* when the artifact yields a valid envelope. Together the two helpers
|
|
47
|
+
* remove the contradiction the user reported in Wave 24:
|
|
48
|
+
* - "omit this gate from --evidence-json so stage-complete can
|
|
49
|
+
* auto-hydrate it" → "missing --evidence-json entries for passed
|
|
50
|
+
* gates: design_diagram_freshness".
|
|
51
|
+
*/
|
|
35
52
|
export declare function hydrateReviewLoopEvidenceFromArtifact(projectRoot: string, stage: FlowStage, track: FlowState["track"], selectedGateIds: string[], evidenceByGate: Record<string, string>): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Wave 25 (v6.1.0) — auto-include an auto-hydratable review-loop gate
|
|
55
|
+
* in `selectedGateIds` when:
|
|
56
|
+
* - The stage has an auto-hydratable gate registered via
|
|
57
|
+
* `AUTO_REVIEW_LOOP_GATE_BY_STAGE` (currently `design`).
|
|
58
|
+
* - The artifact yields a valid review-loop envelope via
|
|
59
|
+
* `extractReviewLoopEnvelopeFromArtifact`.
|
|
60
|
+
* - The gate is required for the active track.
|
|
61
|
+
* - The agent has NOT passed the gate yet (so we don't double-add).
|
|
62
|
+
*
|
|
63
|
+
* Returns the (possibly extended) array of selected gate IDs and
|
|
64
|
+
* mutates `evidenceByGate` to include the hydrated envelope.
|
|
65
|
+
*
|
|
66
|
+
* Together with `hydrateReviewLoopEvidenceFromArtifact` this makes the
|
|
67
|
+
* flow consistent: if the artifact contains the envelope, the agent
|
|
68
|
+
* neither has to include the gate in --passed nor pass --evidence-json
|
|
69
|
+
* for it. If the artifact does NOT contain the envelope, the agent
|
|
70
|
+
* gets a clear error pointing at the artifact section to add (via
|
|
71
|
+
* `reviewLoopArtifactFixHint`).
|
|
72
|
+
*/
|
|
73
|
+
export declare function tryAutoHydrateAndSelectReviewLoopGate(projectRoot: string, stage: FlowStage, track: FlowState["track"], requiredGateIds: string[], selectedGateIds: string[], evidenceByGate: Record<string, string>): Promise<string[]>;
|
|
36
74
|
export declare function buildValidationReport(projectRoot: string, flowState: FlowState, options?: {
|
|
37
75
|
allowBlockedReviewRoute?: boolean;
|
|
38
76
|
extraStageFlags?: string[];
|
|
@@ -10,7 +10,7 @@ import { readFlowState, writeFlowState } from "../../runs.js";
|
|
|
10
10
|
import { stageSchema } from "../../content/stage-schema.js";
|
|
11
11
|
import { extractReviewLoopEnvelopeFromArtifact } from "../../content/review-loop.js";
|
|
12
12
|
import { unique } from "./helpers.js";
|
|
13
|
-
import { AUTO_REVIEW_LOOP_GATE_BY_STAGE, reviewLoopArtifactFixHint, validateGateEvidenceShape } from "./review-loop.js";
|
|
13
|
+
import { AUTO_REVIEW_LOOP_GATE_BY_STAGE, reviewLoopArtifactFixHint, reviewLoopEnvelopeExample, validateGateEvidenceShape } from "./review-loop.js";
|
|
14
14
|
import { ensureProactiveDelegationTrace } from "./verify.js";
|
|
15
15
|
function resolveSuccessorTransition(stage, track, transitionTargets, satisfiedGuards, selectedTransitionGuards) {
|
|
16
16
|
const natural = transitionTargets[0] ?? null;
|
|
@@ -57,6 +57,21 @@ function nextInteractionHints(flowState, args, successor) {
|
|
|
57
57
|
}
|
|
58
58
|
return hints;
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Wave 24 entry point — auto-hydrate evidence for an auto-hydratable
|
|
62
|
+
* gate that the agent already included in --passed but for which they
|
|
63
|
+
* forgot to provide --evidence-json. Returns silently when no
|
|
64
|
+
* hydration is possible (no auto-hydratable gate, no artifact, no
|
|
65
|
+
* envelope, etc.).
|
|
66
|
+
*
|
|
67
|
+
* Wave 25 (v6.1.0) layered `tryAutoHydrateAndSelectReviewLoopGate` on
|
|
68
|
+
* top of this so the gate is also auto-included in selectedGateIds
|
|
69
|
+
* when the artifact yields a valid envelope. Together the two helpers
|
|
70
|
+
* remove the contradiction the user reported in Wave 24:
|
|
71
|
+
* - "omit this gate from --evidence-json so stage-complete can
|
|
72
|
+
* auto-hydrate it" → "missing --evidence-json entries for passed
|
|
73
|
+
* gates: design_diagram_freshness".
|
|
74
|
+
*/
|
|
60
75
|
export async function hydrateReviewLoopEvidenceFromArtifact(projectRoot, stage, track, selectedGateIds, evidenceByGate) {
|
|
61
76
|
const gateId = AUTO_REVIEW_LOOP_GATE_BY_STAGE[stage];
|
|
62
77
|
if (!gateId)
|
|
@@ -87,6 +102,63 @@ export async function hydrateReviewLoopEvidenceFromArtifact(projectRoot, stage,
|
|
|
87
102
|
return;
|
|
88
103
|
evidenceByGate[gateId] = JSON.stringify(envelope);
|
|
89
104
|
}
|
|
105
|
+
/**
|
|
106
|
+
* Wave 25 (v6.1.0) — auto-include an auto-hydratable review-loop gate
|
|
107
|
+
* in `selectedGateIds` when:
|
|
108
|
+
* - The stage has an auto-hydratable gate registered via
|
|
109
|
+
* `AUTO_REVIEW_LOOP_GATE_BY_STAGE` (currently `design`).
|
|
110
|
+
* - The artifact yields a valid review-loop envelope via
|
|
111
|
+
* `extractReviewLoopEnvelopeFromArtifact`.
|
|
112
|
+
* - The gate is required for the active track.
|
|
113
|
+
* - The agent has NOT passed the gate yet (so we don't double-add).
|
|
114
|
+
*
|
|
115
|
+
* Returns the (possibly extended) array of selected gate IDs and
|
|
116
|
+
* mutates `evidenceByGate` to include the hydrated envelope.
|
|
117
|
+
*
|
|
118
|
+
* Together with `hydrateReviewLoopEvidenceFromArtifact` this makes the
|
|
119
|
+
* flow consistent: if the artifact contains the envelope, the agent
|
|
120
|
+
* neither has to include the gate in --passed nor pass --evidence-json
|
|
121
|
+
* for it. If the artifact does NOT contain the envelope, the agent
|
|
122
|
+
* gets a clear error pointing at the artifact section to add (via
|
|
123
|
+
* `reviewLoopArtifactFixHint`).
|
|
124
|
+
*/
|
|
125
|
+
export async function tryAutoHydrateAndSelectReviewLoopGate(projectRoot, stage, track, requiredGateIds, selectedGateIds, evidenceByGate) {
|
|
126
|
+
const gateId = AUTO_REVIEW_LOOP_GATE_BY_STAGE[stage];
|
|
127
|
+
if (!gateId)
|
|
128
|
+
return selectedGateIds;
|
|
129
|
+
const reviewStage = stage === "scope" || stage === "design" ? stage : null;
|
|
130
|
+
if (!reviewStage)
|
|
131
|
+
return selectedGateIds;
|
|
132
|
+
if (!requiredGateIds.includes(gateId))
|
|
133
|
+
return selectedGateIds;
|
|
134
|
+
if (selectedGateIds.includes(gateId)) {
|
|
135
|
+
// Already selected — fall through to the existing hydration helper
|
|
136
|
+
// for the manual --evidence-json path.
|
|
137
|
+
await hydrateReviewLoopEvidenceFromArtifact(projectRoot, stage, track, selectedGateIds, evidenceByGate);
|
|
138
|
+
return selectedGateIds;
|
|
139
|
+
}
|
|
140
|
+
const existing = evidenceByGate[gateId];
|
|
141
|
+
if (typeof existing === "string" && existing.trim().length > 0) {
|
|
142
|
+
return [...selectedGateIds, gateId];
|
|
143
|
+
}
|
|
144
|
+
const resolved = await resolveArtifactPath(stage, {
|
|
145
|
+
projectRoot,
|
|
146
|
+
track,
|
|
147
|
+
intent: "read"
|
|
148
|
+
});
|
|
149
|
+
let raw = "";
|
|
150
|
+
try {
|
|
151
|
+
raw = await fs.readFile(resolved.absPath, "utf8");
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return selectedGateIds;
|
|
155
|
+
}
|
|
156
|
+
const envelope = extractReviewLoopEnvelopeFromArtifact(raw, reviewStage, resolved.relPath);
|
|
157
|
+
if (!envelope)
|
|
158
|
+
return selectedGateIds;
|
|
159
|
+
evidenceByGate[gateId] = JSON.stringify(envelope);
|
|
160
|
+
return [...selectedGateIds, gateId];
|
|
161
|
+
}
|
|
90
162
|
export async function buildValidationReport(projectRoot, flowState, options = {}) {
|
|
91
163
|
const delegation = await checkMandatoryDelegations(projectRoot, flowState.currentStage);
|
|
92
164
|
const gates = await verifyCurrentStageGateEvidence(projectRoot, flowState, {
|
|
@@ -111,7 +183,8 @@ export async function buildValidationReport(projectRoot, flowState, options = {}
|
|
|
111
183
|
legacyInferredCompletions: delegation.legacyInferredCompletions,
|
|
112
184
|
corruptEventLines: delegation.corruptEventLines,
|
|
113
185
|
staleWorkers: delegation.staleWorkers,
|
|
114
|
-
expectedMode: delegation.expectedMode
|
|
186
|
+
expectedMode: delegation.expectedMode,
|
|
187
|
+
skippedByTrack: delegation.skippedByTrack
|
|
115
188
|
},
|
|
116
189
|
gates: {
|
|
117
190
|
ok: gates.ok,
|
|
@@ -228,9 +301,17 @@ export async function runAdvanceStage(projectRoot, args, io) {
|
|
|
228
301
|
.flatMap((target) => getTransitionGuards(args.stage, target, flowState.track))
|
|
229
302
|
.filter((guardId) => !allowedGateIds.has(guardId)));
|
|
230
303
|
const selectableGateIds = new Set([...allowedGateIds, ...transitionGuardIds]);
|
|
231
|
-
|
|
304
|
+
let selectedGateIds = args.passedGateIds.length > 0
|
|
232
305
|
? args.passedGateIds.filter((gateId) => selectableGateIds.has(gateId))
|
|
233
306
|
: requiredGateIds;
|
|
307
|
+
// Wave 25 (v6.1.0): if the active stage has an auto-hydratable
|
|
308
|
+
// review-loop gate (currently `design.design_architecture_locked`)
|
|
309
|
+
// and the artifact already contains a valid review-loop envelope,
|
|
310
|
+
// include the gate in selectedGateIds and hydrate evidence in one
|
|
311
|
+
// step. This removes the Wave 24 contradiction between "omit from
|
|
312
|
+
// --evidence-json so we can auto-hydrate" and "missing
|
|
313
|
+
// --evidence-json entries for passed gates".
|
|
314
|
+
selectedGateIds = await tryAutoHydrateAndSelectReviewLoopGate(projectRoot, args.stage, flowState.track, requiredGateIds, selectedGateIds, args.evidenceByGate);
|
|
234
315
|
const selectedGateIdSet = new Set(selectedGateIds);
|
|
235
316
|
const selectedTransitionGuards = selectedGateIds.filter((gateId) => transitionGuardIds.has(gateId));
|
|
236
317
|
const blockedReviewRoute = args.stage === "review" && selectedGateIdSet.has("review_verdict_blocked");
|
|
@@ -239,7 +320,11 @@ export async function runAdvanceStage(projectRoot, args, io) {
|
|
|
239
320
|
: requiredGateIds;
|
|
240
321
|
const missingRequired = requiredForSelectedRoute.filter((gateId) => !selectedGateIdSet.has(gateId));
|
|
241
322
|
if (missingRequired.length > 0) {
|
|
242
|
-
|
|
323
|
+
const autoHydrateGate = AUTO_REVIEW_LOOP_GATE_BY_STAGE[args.stage];
|
|
324
|
+
const autoHydrateHint = autoHydrateGate && missingRequired.includes(autoHydrateGate) && (args.stage === "scope" || args.stage === "design")
|
|
325
|
+
? ` Auto-hydratable gate "${autoHydrateGate}" was NOT auto-included because the design artifact is missing the review-loop envelope. Add a \`## ${args.stage === "scope" ? "Scope Outside Voice Loop" : "Design Outside Voice Loop"}\` table (example envelope: ${reviewLoopEnvelopeExample(args.stage)}), or pass --evidence-json='{"${autoHydrateGate}": "<envelope-json>"}' alongside --passed=...,${autoHydrateGate}.`
|
|
326
|
+
: "";
|
|
327
|
+
io.stderr.write(`cclaw internal advance-stage: required gates not selected as passed: ${missingRequired.join(", ")}.${autoHydrateHint}\n`);
|
|
243
328
|
return 1;
|
|
244
329
|
}
|
|
245
330
|
const mandatory = new Set(schema.mandatoryDelegations);
|
|
@@ -268,7 +353,10 @@ export async function runAdvanceStage(projectRoot, args, io) {
|
|
|
268
353
|
});
|
|
269
354
|
}
|
|
270
355
|
}
|
|
271
|
-
|
|
356
|
+
// Wave 25 (v6.1.0): hydration + auto-select happens earlier via
|
|
357
|
+
// `tryAutoHydrateAndSelectReviewLoopGate`. The previous explicit
|
|
358
|
+
// call here was redundant (helper already covered both the
|
|
359
|
+
// already-selected and not-yet-selected paths).
|
|
272
360
|
const catalog = flowState.stageGateCatalog[args.stage];
|
|
273
361
|
const nextPassed = unique([...catalog.passed, ...selectedGateIds]).filter((gateId) => allowedGateIds.has(gateId));
|
|
274
362
|
const nextPassedSet = new Set(nextPassed);
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import type { FlowStage } from "../../types.js";
|
|
2
2
|
export declare const AUTO_REVIEW_LOOP_GATE_BY_STAGE: Partial<Record<FlowStage, string>>;
|
|
3
|
+
/**
|
|
4
|
+
* Wave 25 (v6.1.0) — exact JSON shape that gate-evidence validators
|
|
5
|
+
* accept for a review-loop envelope. The error messages emitted by
|
|
6
|
+
* `validateReviewLoopGateEvidence` always include this example so the
|
|
7
|
+
* agent never has to guess where `stage` lives (top-level of the
|
|
8
|
+
* envelope, NOT inside `payload`). Keep `stage`/`targetScore`/etc. in
|
|
9
|
+
* the order shown so a copy-paste from the error survives.
|
|
10
|
+
*/
|
|
11
|
+
export declare function reviewLoopEnvelopeExample(stage: "scope" | "design"): string;
|
|
3
12
|
export declare function pickReviewLoopEnvelope(value: unknown): Record<string, unknown> | null;
|
|
4
13
|
export declare function validateReviewLoopGateEvidence(stage: "scope" | "design", evidence: string): string | null;
|
|
5
14
|
export declare function validateUserApprovalEvidence(evidence: string): string | null;
|
|
@@ -11,6 +11,29 @@ const REVIEW_LOOP_STOP_REASONS = new Set([
|
|
|
11
11
|
"max_iterations_reached",
|
|
12
12
|
"user_opt_out"
|
|
13
13
|
]);
|
|
14
|
+
/**
|
|
15
|
+
* Wave 25 (v6.1.0) — exact JSON shape that gate-evidence validators
|
|
16
|
+
* accept for a review-loop envelope. The error messages emitted by
|
|
17
|
+
* `validateReviewLoopGateEvidence` always include this example so the
|
|
18
|
+
* agent never has to guess where `stage` lives (top-level of the
|
|
19
|
+
* envelope, NOT inside `payload`). Keep `stage`/`targetScore`/etc. in
|
|
20
|
+
* the order shown so a copy-paste from the error survives.
|
|
21
|
+
*/
|
|
22
|
+
export function reviewLoopEnvelopeExample(stage) {
|
|
23
|
+
return JSON.stringify({
|
|
24
|
+
type: "review-loop",
|
|
25
|
+
stage,
|
|
26
|
+
targetScore: 0.8,
|
|
27
|
+
maxIterations: 3,
|
|
28
|
+
stopReason: "quality_threshold_met",
|
|
29
|
+
iterations: [{ iteration: 1, qualityScore: 0.8, findingsCount: 0 }]
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function reviewLoopEnvelopeShapeHint(stage) {
|
|
33
|
+
return (`Expected envelope: ${reviewLoopEnvelopeExample(stage)}` +
|
|
34
|
+
" (top-level keys: type, stage, targetScore, maxIterations, stopReason, iterations[]). " +
|
|
35
|
+
"Stage MUST be at the top level — not inside payload.");
|
|
36
|
+
}
|
|
14
37
|
export function pickReviewLoopEnvelope(value) {
|
|
15
38
|
const direct = asRecord(value);
|
|
16
39
|
if (!direct)
|
|
@@ -31,14 +54,17 @@ export function validateReviewLoopGateEvidence(stage, evidence) {
|
|
|
31
54
|
parsed = JSON.parse(evidence);
|
|
32
55
|
}
|
|
33
56
|
catch {
|
|
34
|
-
return "must be JSON containing a review-loop envelope (`type: \"review-loop\"`) in top-level, `payload`, or `reviewLoop`."
|
|
57
|
+
return ("must be JSON containing a review-loop envelope (`type: \"review-loop\"`) in top-level, `payload`, or `reviewLoop`. " +
|
|
58
|
+
reviewLoopEnvelopeShapeHint(stage));
|
|
35
59
|
}
|
|
36
60
|
const envelope = pickReviewLoopEnvelope(parsed);
|
|
37
61
|
if (!envelope) {
|
|
38
|
-
return "must include a review-loop envelope (`type: \"review-loop\"`) in top-level, `payload`, or `reviewLoop`."
|
|
62
|
+
return ("must include a review-loop envelope (`type: \"review-loop\"`) in top-level, `payload`, or `reviewLoop`. " +
|
|
63
|
+
reviewLoopEnvelopeShapeHint(stage));
|
|
39
64
|
}
|
|
40
65
|
if (envelope.stage !== stage) {
|
|
41
|
-
return `review-loop envelope stage must be "${stage}"
|
|
66
|
+
return (`review-loop envelope stage must be "${stage}" at the top level of the envelope, not inside payload. ` +
|
|
67
|
+
reviewLoopEnvelopeShapeHint(stage));
|
|
42
68
|
}
|
|
43
69
|
const targetScore = envelope.targetScore;
|
|
44
70
|
if (typeof targetScore !== "number" || Number.isNaN(targetScore) || targetScore < 0 || targetScore > 1) {
|
|
@@ -157,5 +183,17 @@ export async function validateGateEvidenceShape(projectRoot, stage, gateId, evid
|
|
|
157
183
|
export function reviewLoopArtifactFixHint(stage, gateId) {
|
|
158
184
|
if (AUTO_REVIEW_LOOP_GATE_BY_STAGE[stage] !== gateId)
|
|
159
185
|
return "";
|
|
160
|
-
|
|
186
|
+
// Wave 25 (v6.1.0): the consistent flow is "include the gate in
|
|
187
|
+
// --passed AND let stage-complete auto-hydrate evidence from the
|
|
188
|
+
// artifact". Wave 24's hint told agents to omit the gate from
|
|
189
|
+
// --evidence-json, but they then hit
|
|
190
|
+
// `missing --evidence-json entries for passed gates: <gateId>`
|
|
191
|
+
// because hydration only runs when --evidence-json is also present
|
|
192
|
+
// OR when an artifact section yields a parseable envelope. The new
|
|
193
|
+
// hint tells the agent to:
|
|
194
|
+
// 1. Add the artifact section (so hydration succeeds), AND
|
|
195
|
+
// 2. Include the gate in --passed.
|
|
196
|
+
// No --evidence-json entry is required in that case.
|
|
197
|
+
const stageReviewSection = stage === "scope" ? "Scope Outside Voice Loop" : "Design Outside Voice Loop";
|
|
198
|
+
return (` Fix in two steps: (1) Add a \`## ${stageReviewSection}\` table to the artifact with rows like \`| 1 | 0.80 | 0 |\` plus \`- Stop reason: quality_threshold_met\`, \`- Target score: 0.80\`, and \`- Max iterations: 3\`. (2) Re-run \`stage-complete ${stage} --passed=...,${gateId},...\` — stage-complete will auto-hydrate the envelope from the artifact, so you do NOT need to pass --evidence-json for ${gateId}.`);
|
|
161
199
|
}
|