cclaw-cli 0.46.10 → 0.46.11

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.
@@ -29,7 +29,7 @@ export const HOOK_EVENTS_BY_HARNESS = {
29
29
  pre_tool_workflow_guard: "plugin tool.execute.before -> workflow-guard.sh",
30
30
  post_tool_context_monitor: "plugin tool.execute.after -> context-monitor.sh",
31
31
  stop_checkpoint: "plugin session.idle -> stop-checkpoint.sh",
32
- precompact_digest: "plugin session.cleared/session.resumed hooks"
32
+ precompact_digest: "plugin session.compacted -> pre-compact.sh"
33
33
  },
34
34
  codex: {
35
35
  // Codex CLI v0.114+ exposes lifecycle hooks via `.codex/hooks.json`,
@@ -240,23 +240,12 @@ export default function cclawPlugin(ctx) {
240
240
  // the system transform hook instead.
241
241
  refreshBootstrapCache();
242
242
  }
243
+ if (eventType === "session.compacted") {
244
+ await runHookScript("pre-compact.sh", eventData ?? {});
245
+ }
243
246
  if (eventType === "session.idle") {
244
247
  await runHookScript("stop-checkpoint.sh", { loop_count: 0 });
245
248
  }
246
- if (eventType === "tool.execute.before") {
247
- const toolPayload = normalizeToolPayload(eventData, undefined);
248
- const promptOk = await runHookScript("prompt-guard.sh", toolPayload);
249
- const workflowOk = await runHookScript("workflow-guard.sh", toolPayload);
250
- if (!promptOk || !workflowOk) {
251
- throw new Error(
252
- "cclaw OpenCode guard blocked tool.execute.before (prompt/workflow guard non-zero exit)."
253
- );
254
- }
255
- }
256
- if (eventType === "tool.execute.after") {
257
- const toolPayload = normalizeToolPayload(eventData, undefined);
258
- await runHookScript("context-monitor.sh", toolPayload);
259
- }
260
249
  },
261
250
  "tool.execute.before": async (input, output) => {
262
251
  const payload = normalizeToolPayload(input, output);
@@ -1,8 +1,8 @@
1
- import type { FlowStage } from "../types.js";
1
+ import type { FlowStage, FlowTrack } from "../types.js";
2
2
  /**
3
3
  * Long-form Batch Execution walkthrough. Rendered once into
4
4
  * \`.cclaw/references/stages/tdd-batch-walkthrough.md\` by the installer.
5
5
  */
6
6
  export declare const TDD_BATCH_WALKTHROUGH_MARKDOWN = "# TDD \u2014 Batch Execution Walkthrough\n\nDetailed RED / GREEN / REFACTOR transcript for a 3-task batch. Illustrative\nonly \u2014 do not copy the command names blindly, match them to your stack.\n\n## Batch 1 example tasks\n\n| Task ID | Description | AC | Verification |\n|---|---|---|---|\n| T-1 `[~3m]` | Add `User.emailNormalized` column | AC-1 | `npm test -- users/schema` |\n| T-2 `[~4m]` | Normalize on write in `UserRepo.save` | AC-1 | `npm test -- users/repo` |\n| T-3 `[~3m]` | Reject duplicates in `UserService.signup` | AC-2 | `npm test -- users/service` |\n\n## Execution transcript\n\n### T-1 \u2014 RED\n\n> Run: `npm test -- users/schema` \u2192 **FAIL** (missing column: `emailNormalized`). Captured the failure stack as RED evidence. No production code touched yet.\n\n### T-1 \u2014 GREEN\n\n> Added the column in the schema module. Re-ran `npm test -- users/schema` \u2192 **PASS**. Ran the full suite `npm test` \u2192 **PASS**. Captured both outputs as GREEN evidence.\n\n### T-1 \u2014 REFACTOR\n\n> Extracted the column definition into a shared `NormalizedEmail` type used by T-2/T-3. Re-ran `npm test` \u2192 **PASS**. Captured REFACTOR note: \"Extracted NormalizedEmail type to keep T-2/T-3 DRY; zero behavior change, all tests still green.\"\n\n### T-2 \u2014 RED / GREEN / REFACTOR\n\nWrite the repo test that expects normalised writes, watch it fail (RED), implement normalisation inside `UserRepo.save` only (GREEN), then refactor the normaliser out of the repo into a helper shared with T-3 (REFACTOR).\n\n### T-3 \u2014 RED / GREEN / REFACTOR\n\nWrite the service-level duplicate test that expects a rejection, watch it fail (RED), add the duplicate check in `UserService.signup` (GREEN), refactor the error message into a named constant (REFACTOR).\n\n## Batch gate check\n\nAfter T-3 REFACTOR, before declaring Batch 1 done:\n\n1. Run the full suite (`npm test`) one final time \u2192 **PASS** captured as batch-exit evidence.\n2. Verify the TDD artifact contains RED, GREEN, and REFACTOR evidence for T-1, T-2, **and** T-3. No partial batches.\n3. Only now mark Batch 1 complete. Batch 2 cannot start until this step.\n\n## When to stop mid-batch (do NOT push through)\n\n- A RED test fails for a reason you did not predict (e.g. an unrelated flaky test) \u2192 **pause**, diagnose, log an operational-self-improvement entry, and decide with the user before proceeding.\n- A GREEN step would require touching code outside the task's acceptance criterion \u2192 **pause**, the task is scoped wrong; adjust the plan or open a follow-up task.\n- The same RED failure reappears after a GREEN change \u2192 **escalate** per the 3-attempts rule; do not keep patching.\n";
7
7
  export declare function stageSkillFolder(stage: FlowStage): string;
8
- export declare function stageSkillMarkdown(stage: FlowStage): string;
8
+ export declare function stageSkillMarkdown(stage: FlowStage, track?: FlowTrack): string;
@@ -5,8 +5,8 @@ import { stageAutoSubagentDispatch, stageSchema } from "./stage-schema.js";
5
5
  const VERIFICATION_STAGES = ["tdd", "review", "ship"];
6
6
  const DECISION_PROTOCOL_PATH = `${RUNTIME_ROOT}/references/protocols/decision.md`;
7
7
  const COMPLETION_PROTOCOL_PATH = `${RUNTIME_ROOT}/references/protocols/completion.md`;
8
- function whenNotToUseBlock(stage) {
9
- const schema = stageSchema(stage);
8
+ function whenNotToUseBlock(stage, track) {
9
+ const schema = stageSchema(stage, track);
10
10
  if (schema.whenNotToUse.length === 0) {
11
11
  return "";
12
12
  }
@@ -15,8 +15,8 @@ ${schema.whenNotToUse.map((item) => `- ${item}`).join("\n")}
15
15
 
16
16
  `;
17
17
  }
18
- function contextLoadingBlock(stage) {
19
- const trace = stageSchema(stage).crossStageTrace;
18
+ function contextLoadingBlock(stage, track) {
19
+ const trace = stageSchema(stage, track).crossStageTrace;
20
20
  const readLines = trace.readsFrom.length > 0
21
21
  ? trace.readsFrom.map((value) => `- \`${value}\``).join("\n")
22
22
  : "- (first stage — no upstream artifacts)";
@@ -31,7 +31,7 @@ ${readLines}
31
31
  \`.cclaw/knowledge.jsonl\` when the digest is insufficient.
32
32
  `;
33
33
  }
34
- function autoSubagentDispatchBlock(stage) {
34
+ function autoSubagentDispatchBlock(stage, track) {
35
35
  const rules = stageAutoSubagentDispatch(stage);
36
36
  if (rules.length === 0)
37
37
  return "";
@@ -41,7 +41,7 @@ function autoSubagentDispatchBlock(stage) {
41
41
  return `| ${rule.agent} | ${rule.mode} | ${userGate} | ${rule.when} |`;
42
42
  })
43
43
  .join("\n");
44
- const mandatory = stageSchema(stage).mandatoryDelegations;
44
+ const mandatory = stageSchema(stage, track).mandatoryDelegations;
45
45
  const mandatoryList = mandatory.length > 0 ? mandatory.map((a) => `\`${a}\``).join(", ") : "none";
46
46
  const delegationLogRel = `${RUNTIME_ROOT}/state/delegation-log.json`;
47
47
  return `## Automatic Subagent Dispatch
@@ -54,8 +54,8 @@ Mandatory delegations for this stage: ${mandatoryList}.
54
54
  Record completion/waiver in \`${delegationLogRel}\` before stage completion.
55
55
  `;
56
56
  }
57
- function researchPlaybooksBlock(stage) {
58
- const playbooks = stageSchema(stage).researchPlaybooks ?? [];
57
+ function researchPlaybooksBlock(stage, track) {
58
+ const playbooks = stageSchema(stage, track).researchPlaybooks ?? [];
59
59
  if (playbooks.length === 0)
60
60
  return "";
61
61
  const rows = playbooks
@@ -70,8 +70,8 @@ and record outcomes in the stage artifact when relevant.
70
70
  ${rows}
71
71
  `;
72
72
  }
73
- function reviewSectionsBlock(stage) {
74
- const schema = stageSchema(stage);
73
+ function reviewSectionsBlock(stage, track) {
74
+ const schema = stageSchema(stage, track);
75
75
  if (schema.reviewSections.length === 0)
76
76
  return "";
77
77
  const sections = schema.reviewSections
@@ -103,8 +103,8 @@ Reference utility skill:
103
103
  \`.cclaw/skills/verification-before-completion/SKILL.md\`
104
104
  `;
105
105
  }
106
- function batchExecutionModeBlock(stage) {
107
- const schema = stageSchema(stage);
106
+ function batchExecutionModeBlock(stage, track) {
107
+ const schema = stageSchema(stage, track);
108
108
  if (!schema.batchExecutionAllowed)
109
109
  return "";
110
110
  return `## Batch Execution Mode
@@ -118,8 +118,8 @@ Detailed walkthrough:
118
118
  \`.cclaw/${STAGE_EXAMPLES_REFERENCE_DIR}/tdd-batch-walkthrough.md\`
119
119
  `;
120
120
  }
121
- function crossStageTraceBlock(stage) {
122
- const trace = stageSchema(stage).crossStageTrace;
121
+ function crossStageTraceBlock(stage, track) {
122
+ const trace = stageSchema(stage, track).crossStageTrace;
123
123
  const reads = trace.readsFrom.length > 0
124
124
  ? trace.readsFrom.map((r) => `- ${r}`).join("\n")
125
125
  : "- (first stage — no upstream artifacts)";
@@ -137,8 +137,8 @@ ${writes}
137
137
  Rule: ${trace.traceabilityRule}
138
138
  `;
139
139
  }
140
- function artifactValidationBlock(stage) {
141
- const validations = stageSchema(stage).artifactValidation;
140
+ function artifactValidationBlock(stage, track) {
141
+ const validations = stageSchema(stage, track).artifactValidation;
142
142
  if (validations.length === 0)
143
143
  return "";
144
144
  const rows = validations
@@ -206,7 +206,7 @@ function stageSpecificSeeAlso(stage) {
206
206
  };
207
207
  return refs[stage];
208
208
  }
209
- function completionParametersBlock(schema) {
209
+ function completionParametersBlock(schema, track) {
210
210
  const gateList = schema.requiredGates.map((g) => `\`${g.id}\``).join(", ");
211
211
  const mandatory = schema.mandatoryDelegations.length > 0
212
212
  ? schema.mandatoryDelegations.map((a) => `\`${a}\``).join(", ")
@@ -214,7 +214,7 @@ function completionParametersBlock(schema) {
214
214
  const nextStage = schema.next === "done" ? "done" : schema.next;
215
215
  const nextDescription = schema.next === "done"
216
216
  ? "flow complete"
217
- : stageSchema(schema.next).skillDescription;
217
+ : stageSchema(schema.next, track).skillDescription;
218
218
  return `## Completion Parameters
219
219
 
220
220
  - \`stage\`: \`${schema.stage}\`
@@ -230,8 +230,8 @@ Apply shared completion logic from:
230
230
  \`${COMPLETION_PROTOCOL_PATH}\`
231
231
  `;
232
232
  }
233
- function quickStartBlock(stage) {
234
- const schema = stageSchema(stage);
233
+ function quickStartBlock(stage, track) {
234
+ const schema = stageSchema(stage, track);
235
235
  const gatePreview = schema.requiredGates.slice(0, 3).map((g) => `\`${g.id}\``).join(", ");
236
236
  return `## Quick Start
237
237
 
@@ -297,8 +297,8 @@ After T-3 REFACTOR, before declaring Batch 1 done:
297
297
  export function stageSkillFolder(stage) {
298
298
  return stageSchema(stage).skillFolder;
299
299
  }
300
- export function stageSkillMarkdown(stage) {
301
- const schema = stageSchema(stage);
300
+ export function stageSkillMarkdown(stage, track = "standard") {
301
+ const schema = stageSchema(stage, track);
302
302
  const gateList = schema.requiredGates
303
303
  .map((g) => `- \`${g.id}\` — ${g.description}`)
304
304
  .join("\n");
@@ -324,7 +324,7 @@ If you are about to violate the Iron Law, STOP. No amount of urgency, partial pr
324
324
 
325
325
  </EXTREMELY-IMPORTANT>
326
326
 
327
- ${quickStartBlock(stage)}
327
+ ${quickStartBlock(stage, track)}
328
328
 
329
329
  ## Overview
330
330
  ${schema.purpose}
@@ -332,16 +332,16 @@ ${schema.purpose}
332
332
  ## When to Use
333
333
  ${schema.whenToUse.map((item) => `- ${item}`).join("\n")}
334
334
 
335
- ${whenNotToUseBlock(stage)}
335
+ ${whenNotToUseBlock(stage, track)}
336
336
  ## Inputs
337
337
  ${schema.inputs.length > 0 ? schema.inputs.map((item) => `- ${item}`).join("\n") : "- (first stage — no required inputs)"}
338
338
 
339
339
  ## Required Context
340
340
  ${schema.requiredContext.length > 0 ? schema.requiredContext.map((item) => `- ${item}`).join("\n") : "- None beyond this skill"}
341
341
 
342
- ${contextLoadingBlock(stage)}
343
- ${autoSubagentDispatchBlock(stage)}
344
- ${researchPlaybooksBlock(stage)}
342
+ ${contextLoadingBlock(stage, track)}
343
+ ${autoSubagentDispatchBlock(stage, track)}
344
+ ${researchPlaybooksBlock(stage, track)}
345
345
 
346
346
  ## Outputs
347
347
  ${schema.outputs.map((item) => `- ${item}`).join("\n")}
@@ -365,7 +365,7 @@ ${schema.interactionProtocol.map((item, i) => `${i + 1}. ${item}`).join("\n")}
365
365
  Shared decision/ask-user protocol:
366
366
  \`${DECISION_PROTOCOL_PATH}\`
367
367
 
368
- ${batchExecutionModeBlock(stage)}
368
+ ${batchExecutionModeBlock(stage, track)}
369
369
  ## Required Gates
370
370
  ${gateList}
371
371
 
@@ -375,10 +375,10 @@ ${evidenceList}
375
375
  ## Process
376
376
  ${schema.process.map((item, i) => `${i + 1}. ${item}`).join("\n")}
377
377
 
378
- ${reviewSectionsBlock(stage)}
378
+ ${reviewSectionsBlock(stage, track)}
379
379
  ${verificationBlock(stage)}
380
- ${crossStageTraceBlock(stage)}
381
- ${artifactValidationBlock(stage)}
380
+ ${crossStageTraceBlock(stage, track)}
381
+ ${artifactValidationBlock(stage, track)}
382
382
 
383
383
  ## Anti-Patterns & Red Flags
384
384
  ${mergedAntiPatterns(schema)}
@@ -386,7 +386,7 @@ ${mergedAntiPatterns(schema)}
386
386
  ## Verification
387
387
  ${schema.exitCriteria.map((item) => `- [ ] ${item}`).join("\n")}
388
388
 
389
- ${completionParametersBlock(schema)}
389
+ ${completionParametersBlock(schema, track)}
390
390
  ## Shared Stage Guidance
391
391
  See:
392
392
  - \`${STAGE_COMMON_GUIDANCE_REL_PATH}\`
@@ -1,13 +1,13 @@
1
- import type { FlowStage, TransitionRule } from "../types.js";
1
+ import type { FlowStage, FlowTrack, TransitionRule } from "../types.js";
2
2
  import type { StageAutoSubagentDispatch, StageSchema } from "./stages/schema-types.js";
3
3
  export type { ArtifactValidation, CrossStageTrace, ReviewSection, StageAutoSubagentDispatch, StageGate, StageSchema, StageSchemaInput } from "./stages/schema-types.js";
4
4
  /** Transition guard: agents with `mode: "mandatory"` in auto-subagent dispatch for this stage. */
5
5
  export declare function mandatoryDelegationsForStage(stage: FlowStage): string[];
6
- export declare function stageSchema(stage: FlowStage): StageSchema;
7
- export declare function orderedStageSchemas(): StageSchema[];
8
- export declare function stageGateIds(stage: FlowStage): string[];
9
- export declare function stageRecommendedGateIds(stage: FlowStage): string[];
6
+ export declare function stageSchema(stage: FlowStage, track?: FlowTrack): StageSchema;
7
+ export declare function orderedStageSchemas(track?: FlowTrack): StageSchema[];
8
+ export declare function stageGateIds(stage: FlowStage, track?: FlowTrack): string[];
9
+ export declare function stageRecommendedGateIds(stage: FlowStage, track?: FlowTrack): string[];
10
10
  export declare function nextCclawCommand(stage: FlowStage): string;
11
11
  export declare function buildTransitionRules(): TransitionRule[];
12
- export declare function stagePolicyNeedles(stage: FlowStage): string[];
12
+ export declare function stagePolicyNeedles(stage: FlowStage, track?: FlowTrack): string[];
13
13
  export declare function stageAutoSubagentDispatch(stage: FlowStage): StageAutoSubagentDispatch[];
@@ -1,16 +1,6 @@
1
1
  import { COMMAND_FILE_ORDER } from "../constants.js";
2
2
  import { BRAINSTORM, SCOPE, DESIGN, SPEC, PLAN, TDD, REVIEW, SHIP } from "./stages/index.js";
3
- // ---------------------------------------------------------------------------
4
- // NOTE: The former QUESTION_FORMAT_SPEC / ERROR_BUDGET_SPEC exports were
5
- // hoisted into `src/content/meta-skill.ts` (Shared Decision + Tool-Use
6
- // Protocol). They are no longer re-exported from here to avoid duplication
7
- // and drift. Stage skills cite the meta-skill by path instead.
8
- // ---------------------------------------------------------------------------
9
- /**
10
- * Gate tiers:
11
- * - required: blocking for stage completion.
12
- * - recommended: quality signal; unmet -> DONE_WITH_CONCERNS, not BLOCKED.
13
- */
3
+ import { tddStageForTrack } from "./stages/tdd.js";
14
4
  const REQUIRED_GATE_IDS = {
15
5
  brainstorm: [
16
6
  "brainstorm_approaches_compared",
@@ -39,11 +29,12 @@ const REQUIRED_GATE_IDS = {
39
29
  "plan_acceptance_mapped",
40
30
  "plan_wait_for_confirm"
41
31
  ],
42
- tdd: [
32
+ tdd: (track) => [
43
33
  "tdd_red_test_written",
44
34
  "tdd_green_full_suite",
45
35
  "tdd_refactor_completed",
46
- "tdd_verified_before_complete"
36
+ "tdd_verified_before_complete",
37
+ ...(track === "quick" ? [] : ["tdd_traceable_to_plan"])
47
38
  ],
48
39
  review: [
49
40
  "review_layer1_spec_compliance",
@@ -64,12 +55,16 @@ const REQUIRED_ARTIFACT_SECTIONS = {
64
55
  design: ["Architecture Boundaries", "Architecture Diagram", "Failure Mode Table", "Completion Dashboard"],
65
56
  spec: ["Acceptance Criteria", "Edge Cases", "Testability Map", "Approval"],
66
57
  plan: ["Task List", "Dependency Batches", "Acceptance Mapping", "WAIT_FOR_CONFIRM"],
67
- tdd: ["RED Evidence", "GREEN Evidence", "REFACTOR Notes", "Traceability"],
58
+ tdd: ["RED Evidence", "GREEN Evidence", "REFACTOR Notes", "Traceability", "Verification Ladder"],
68
59
  review: ["Layer 1 Verdict", "Review Army Contract", "Severity Summary", "Final Verdict"],
69
60
  ship: ["Preflight Results", "Release Notes", "Rollback Plan", "Finalization"]
70
61
  };
71
- function tieredStageGates(stage, gates) {
72
- const requiredSet = new Set(REQUIRED_GATE_IDS[stage]);
62
+ function resolveRequiredGateIds(stage, track) {
63
+ const raw = REQUIRED_GATE_IDS[stage];
64
+ return typeof raw === "function" ? raw(track) : raw;
65
+ }
66
+ function tieredStageGates(stage, gates, track) {
67
+ const requiredSet = new Set(resolveRequiredGateIds(stage, track));
73
68
  return gates.map((gate) => {
74
69
  return {
75
70
  ...gate,
@@ -233,9 +228,9 @@ export function mandatoryDelegationsForStage(stage) {
233
228
  .filter((d) => d.mode === "mandatory")
234
229
  .map((d) => d.agent);
235
230
  }
236
- export function stageSchema(stage) {
237
- const base = STAGE_SCHEMA_MAP[stage];
238
- const tieredGates = tieredStageGates(stage, base.requiredGates);
231
+ export function stageSchema(stage, track = "standard") {
232
+ const base = stage === "tdd" ? tddStageForTrack(track) : STAGE_SCHEMA_MAP[stage];
233
+ const tieredGates = tieredStageGates(stage, base.requiredGates, track);
239
234
  const tieredValidation = tieredArtifactValidation(stage, base.artifactValidation);
240
235
  return {
241
236
  ...base,
@@ -244,16 +239,16 @@ export function stageSchema(stage) {
244
239
  mandatoryDelegations: mandatoryDelegationsForStage(stage)
245
240
  };
246
241
  }
247
- export function orderedStageSchemas() {
248
- return COMMAND_FILE_ORDER.map((stage) => stageSchema(stage));
242
+ export function orderedStageSchemas(track = "standard") {
243
+ return COMMAND_FILE_ORDER.map((stage) => stageSchema(stage, track));
249
244
  }
250
- export function stageGateIds(stage) {
251
- return stageSchema(stage).requiredGates
245
+ export function stageGateIds(stage, track = "standard") {
246
+ return stageSchema(stage, track).requiredGates
252
247
  .filter((gate) => gate.tier === "required")
253
248
  .map((gate) => gate.id);
254
249
  }
255
- export function stageRecommendedGateIds(stage) {
256
- return stageSchema(stage).requiredGates
250
+ export function stageRecommendedGateIds(stage, track = "standard") {
251
+ return stageSchema(stage, track).requiredGates
257
252
  .filter((gate) => gate.tier === "recommended")
258
253
  .map((gate) => gate.id);
259
254
  }
@@ -275,8 +270,8 @@ export function buildTransitionRules() {
275
270
  }
276
271
  return rules;
277
272
  }
278
- export function stagePolicyNeedles(stage) {
279
- return stageSchema(stage).policyNeedles;
273
+ export function stagePolicyNeedles(stage, track = "standard") {
274
+ return stageSchema(stage, track).policyNeedles;
280
275
  }
281
276
  export function stageAutoSubagentDispatch(stage) {
282
277
  return STAGE_AUTO_SUBAGENT_DISPATCH[stage];
@@ -1,2 +1,4 @@
1
1
  import type { StageSchemaInput } from "./schema-types.js";
2
+ import type { FlowTrack } from "../../types.js";
2
3
  export declare const TDD: StageSchemaInput;
4
+ export declare function tddStageForTrack(track: FlowTrack): StageSchemaInput;
@@ -179,7 +179,7 @@ export const TDD = {
179
179
  { section: "GREEN Evidence", required: true, validationRule: "Full suite pass output captured." },
180
180
  { section: "REFACTOR Notes", required: true, validationRule: "What changed, why, behavior preservation confirmed." },
181
181
  { section: "Traceability", required: true, validationRule: "Plan task ID and spec criterion linked." },
182
- { section: "Verification Ladder", required: false, validationRule: "If present: per-slice verification tier (static, command, behavioral, human) with evidence for highest tier reached." },
182
+ { section: "Verification Ladder", required: true, validationRule: "Per-slice verification tier (static, command, behavioral, human) with evidence captured for the highest tier reached this turn." },
183
183
  { section: "Coverage Targets", required: false, validationRule: "If present: per-module or per-code-type coverage thresholds with current values and measurement commands." },
184
184
  { section: "Test Pyramid Shape", required: false, validationRule: "If present: per-slice count of Small/Medium/Large tests added, to let reviewers verify the suite is not drifting top-heavy." },
185
185
  { section: "Prove-It Reproduction", required: false, validationRule: "Required for bug-fix slices: original failing reproduction test (RED without fix), passing output with fix (GREEN), and a note confirming the test fails again if the fix is reverted." },
@@ -187,3 +187,65 @@ export const TDD = {
187
187
  ],
188
188
  batchExecutionAllowed: true
189
189
  };
190
+ function quickTrackText(value) {
191
+ return value
192
+ .replace(/\btask from the plan\b/giu, "acceptance criterion from the spec")
193
+ .replace(/\bplan task ID\b/giu, "acceptance criterion ID")
194
+ .replace(/\bplan task\b/giu, "acceptance criterion")
195
+ .replace(/\bplan row\b/giu, "acceptance row")
196
+ .replace(/\bplan slice\b/giu, "acceptance slice")
197
+ .replace(/\bplan artifact\b/giu, "spec artifact")
198
+ .replace(/\btraceable to plan slice\b/giu, "traceable to acceptance criterion")
199
+ .replace(/05-plan\.md/gu, "04-spec.md");
200
+ }
201
+ function tddQuickTrackVariant() {
202
+ return {
203
+ ...TDD,
204
+ skillDescription: quickTrackText(TDD.skillDescription),
205
+ hardGate: quickTrackText(TDD.hardGate),
206
+ checklist: TDD.checklist.map(quickTrackText),
207
+ interactionProtocol: TDD.interactionProtocol.map(quickTrackText),
208
+ process: TDD.process.map(quickTrackText),
209
+ requiredGates: TDD.requiredGates.map((gate) => gate.id === "tdd_traceable_to_plan"
210
+ ? { ...gate, description: "Change traceability to acceptance criterion is explicit." }
211
+ : gate),
212
+ requiredEvidence: TDD.requiredEvidence.map(quickTrackText),
213
+ inputs: TDD.inputs.map(quickTrackText),
214
+ requiredContext: ["spec artifact", "existing test patterns"],
215
+ policyNeedles: TDD.policyNeedles.map(quickTrackText),
216
+ reviewSections: TDD.reviewSections.map((section) => ({
217
+ ...section,
218
+ evaluationPoints: section.evaluationPoints.map(quickTrackText)
219
+ })),
220
+ crossStageTrace: {
221
+ ...TDD.crossStageTrace,
222
+ readsFrom: [".cclaw/artifacts/04-spec.md"],
223
+ traceabilityRule: "Every RED test traces to an acceptance criterion. Every GREEN change traces to a RED test. Evidence chain must be unbroken."
224
+ },
225
+ artifactValidation: TDD.artifactValidation.map((row) => {
226
+ if (row.section === "Acceptance Mapping") {
227
+ return {
228
+ ...row,
229
+ required: true,
230
+ validationRule: "Each RED test links to a spec acceptance criterion ID (for example AC-1)."
231
+ };
232
+ }
233
+ if (row.section === "Traceability") {
234
+ return {
235
+ ...row,
236
+ validationRule: "Acceptance criterion IDs are linked to RED/GREEN evidence."
237
+ };
238
+ }
239
+ return {
240
+ ...row,
241
+ validationRule: quickTrackText(row.validationRule)
242
+ };
243
+ })
244
+ };
245
+ }
246
+ export function tddStageForTrack(track) {
247
+ if (track === "quick") {
248
+ return tddQuickTrackVariant();
249
+ }
250
+ return TDD;
251
+ }
package/dist/doctor.js CHANGED
@@ -623,6 +623,8 @@ export async function doctorChecks(projectRoot, options = {}) {
623
623
  "session-start.sh",
624
624
  "stop-checkpoint.sh",
625
625
  "run-hook.cmd",
626
+ "stage-complete.sh",
627
+ "pre-compact.sh",
626
628
  "prompt-guard.sh",
627
629
  "workflow-guard.sh",
628
630
  "context-monitor.sh"
@@ -816,10 +818,13 @@ export async function doctorChecks(projectRoot, options = {}) {
816
818
  const codexDoc = await readHookDocument(codexHooksFile);
817
819
  const codexHooks = toObject(codexDoc?.hooks) ?? {};
818
820
  const codexSessionCmds = collectHookCommands(codexHooks.SessionStart);
821
+ const codexUserPromptCmds = collectHookCommands(codexHooks.UserPromptSubmit);
819
822
  const codexPreCmds = collectHookCommands(codexHooks.PreToolUse);
820
823
  const codexPostCmds = collectHookCommands(codexHooks.PostToolUse);
821
824
  const codexStopCmds = collectHookCommands(codexHooks.Stop);
822
825
  const codexWiringOk = codexSessionCmds.some((cmd) => cmd.includes("session-start.sh")) &&
826
+ codexUserPromptCmds.some((cmd) => cmd.includes("prompt-guard.sh")) &&
827
+ codexUserPromptCmds.some((cmd) => cmd.includes("verify-current-state --quiet")) &&
823
828
  codexPreCmds.some((cmd) => cmd.includes("prompt-guard.sh")) &&
824
829
  codexPreCmds.some((cmd) => cmd.includes("workflow-guard.sh")) &&
825
830
  codexPostCmds.some((cmd) => cmd.includes("context-monitor.sh")) &&
@@ -827,7 +832,7 @@ export async function doctorChecks(projectRoot, options = {}) {
827
832
  checks.push({
828
833
  name: "hook:wiring:codex",
829
834
  ok: codexWiringOk,
830
- details: `${codexHooksFile} must wire session-start/prompt-guard/workflow-guard/context-monitor/stop-checkpoint (PreToolUse/PostToolUse run Bash-only in Codex v0.114+)`
835
+ details: `${codexHooksFile} must wire SessionStart, UserPromptSubmit(prompt-guard + verify-current-state), PreToolUse(prompt/workflow), PostToolUse(context-monitor), and Stop(stop-checkpoint). PreToolUse/PostToolUse run Bash-only in Codex v0.114+`
831
836
  });
832
837
  // Feature flag warning: Codex ignores `.codex/hooks.json` unless the
833
838
  // user has `[features] codex_hooks = true` in `~/.codex/config.toml`.
@@ -895,6 +900,8 @@ export async function doctorChecks(projectRoot, options = {}) {
895
900
  if (configuredHarnesses.includes("opencode")) {
896
901
  const file = path.join(projectRoot, ".opencode/plugins/cclaw-plugin.mjs");
897
902
  let ok = false;
903
+ let singleHandlerPathOk = false;
904
+ let precompactHookOk = false;
898
905
  if (await exists(file)) {
899
906
  const content = await fs.readFile(file, "utf8");
900
907
  ok =
@@ -904,16 +911,36 @@ export async function doctorChecks(projectRoot, options = {}) {
904
911
  content.includes("prompt-guard.sh") &&
905
912
  content.includes("workflow-guard.sh") &&
906
913
  content.includes("context-monitor.sh") &&
914
+ content.includes("pre-compact.sh") &&
907
915
  content.includes('"session.idle"') &&
908
916
  content.includes('"session.resumed"') &&
917
+ content.includes('"session.compacted"') &&
909
918
  content.includes('"session.cleared"') &&
910
919
  content.includes('"experimental.chat.system.transform"');
920
+ singleHandlerPathOk =
921
+ !content.includes('eventType === "tool.execute.before"') &&
922
+ !content.includes('eventType === "tool.execute.after"') &&
923
+ content.includes('"tool.execute.before": async') &&
924
+ content.includes('"tool.execute.after": async');
925
+ precompactHookOk =
926
+ content.includes('eventType === "session.compacted"') &&
927
+ content.includes('runHookScript("pre-compact.sh"');
911
928
  }
912
929
  checks.push({
913
930
  name: "lifecycle:opencode:rehydration_events",
914
931
  ok,
915
932
  details: `${file} must include event lifecycle handler, tool.execute.before/after with prompt/workflow/context hooks, session.idle checkpoint, and transform rehydration`
916
933
  });
934
+ checks.push({
935
+ name: "hook:opencode:single_tool_handler_path",
936
+ ok: singleHandlerPathOk,
937
+ details: `${file} must route tool.execute.before/after through dedicated handlers exactly once (no duplicate event() branches).`
938
+ });
939
+ checks.push({
940
+ name: "hook:opencode:precompact_digest",
941
+ ok: precompactHookOk,
942
+ details: `${file} must run pre-compact.sh on session.compacted before bootstrap refresh.`
943
+ });
917
944
  const runtimeShape = await opencodePluginRuntimeShapeCheck(projectRoot);
918
945
  checks.push({
919
946
  name: "hook:opencode:runtime_shape",
@@ -59,10 +59,10 @@ export function createInitialFlowState(activeRunIdOrOptions = "active", maybeTra
59
59
  const track = options.track ?? "standard";
60
60
  const skippedStages = skippedStagesForTrack(track);
61
61
  const stageGateCatalog = {};
62
- for (const schema of orderedStageSchemas()) {
62
+ for (const schema of orderedStageSchemas(track)) {
63
63
  stageGateCatalog[schema.stage] = {
64
- required: stageGateIds(schema.stage),
65
- recommended: stageRecommendedGateIds(schema.stage),
64
+ required: stageGateIds(schema.stage, track),
65
+ recommended: stageRecommendedGateIds(schema.stage, track),
66
66
  conditional: [],
67
67
  triggered: [],
68
68
  passed: [],
@@ -5,8 +5,8 @@ import { RUNTIME_ROOT } from "./constants.js";
5
5
  import { stageSchema } from "./content/stage-schema.js";
6
6
  import { exists } from "./fs-utils.js";
7
7
  import { readFlowState, writeFlowState } from "./runs.js";
8
- async function currentStageArtifactExists(projectRoot, stage) {
9
- const artifactFile = stageSchema(stage).artifactFile;
8
+ async function currentStageArtifactExists(projectRoot, stage, track) {
9
+ const artifactFile = stageSchema(stage, track).artifactFile;
10
10
  const candidates = [
11
11
  path.join(projectRoot, RUNTIME_ROOT, "artifacts", artifactFile),
12
12
  path.join(projectRoot, artifactFile)
@@ -34,7 +34,7 @@ function sameStringArray(a, b) {
34
34
  }
35
35
  export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
36
36
  const stage = flowState.currentStage;
37
- const schema = stageSchema(stage);
37
+ const schema = stageSchema(stage, flowState.track);
38
38
  const catalog = flowState.stageGateCatalog[stage];
39
39
  const required = schema.requiredGates
40
40
  .filter((gate) => gate.tier === "required")
@@ -92,7 +92,7 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
92
92
  issues.push(`blocked gate "${gateId}" is not defined for stage "${stage}".`);
93
93
  }
94
94
  }
95
- const artifactPresent = await currentStageArtifactExists(projectRoot, stage);
95
+ const artifactPresent = await currentStageArtifactExists(projectRoot, stage, flowState.track);
96
96
  const shouldValidateArtifact = artifactPresent || catalog.passed.length > 0 || flowState.completedStages.includes(stage);
97
97
  if (shouldValidateArtifact) {
98
98
  const lint = await lintArtifact(projectRoot, stage);
@@ -149,7 +149,7 @@ export function verifyCompletedStagesGateClosure(flowState) {
149
149
  const issues = [];
150
150
  const openStages = [];
151
151
  for (const stage of flowState.completedStages) {
152
- const schema = stageSchema(stage);
152
+ const schema = stageSchema(stage, flowState.track);
153
153
  const catalog = flowState.stageGateCatalog[stage];
154
154
  const required = schema.requiredGates
155
155
  .filter((gate) => gate.tier === "required")
@@ -177,10 +177,11 @@ export function verifyCompletedStagesGateClosure(flowState) {
177
177
  }
178
178
  export function reconcileCurrentStageGateCatalog(flowState) {
179
179
  const stage = flowState.currentStage;
180
- const required = stageSchema(stage).requiredGates
180
+ const schema = stageSchema(stage, flowState.track);
181
+ const required = schema.requiredGates
181
182
  .filter((gate) => gate.tier === "required")
182
183
  .map((gate) => gate.id);
183
- const recommended = stageSchema(stage).requiredGates
184
+ const recommended = schema.requiredGates
184
185
  .filter((gate) => gate.tier === "recommended")
185
186
  .map((gate) => gate.id);
186
187
  const conditional = [];
@@ -5,6 +5,7 @@
5
5
  "schemaVersion": 1,
6
6
  "requiredEvents": [
7
7
  "SessionStart",
8
+ "UserPromptSubmit",
8
9
  "PreToolUse",
9
10
  "PostToolUse",
10
11
  "Stop"
package/dist/install.js CHANGED
@@ -213,9 +213,10 @@ async function writeEvalScaffold(projectRoot) {
213
213
  }
214
214
  }
215
215
  async function writeSkills(projectRoot, config) {
216
+ const skillTrack = config?.defaultTrack ?? "standard";
216
217
  for (const stage of COMMAND_FILE_ORDER) {
217
218
  const folder = stageSkillFolder(stage);
218
- await writeFileSafe(runtimePath(projectRoot, "skills", folder, "SKILL.md"), stageSkillMarkdown(stage));
219
+ await writeFileSafe(runtimePath(projectRoot, "skills", folder, "SKILL.md"), stageSkillMarkdown(stage, skillTrack));
219
220
  // Progressive disclosure (A.2#8): materialize the full example artifact as
220
221
  // a sibling reference file. The stage skill only links to it; agents load
221
222
  // the reference on demand.
@@ -354,7 +354,7 @@ async function runAdvanceStage(projectRoot, args, io) {
354
354
  io.stderr.write(`cclaw internal advance-stage: current stage is "${flowState.currentStage}", not "${args.stage}".\n`);
355
355
  return 1;
356
356
  }
357
- const schema = stageSchema(args.stage);
357
+ const schema = stageSchema(args.stage, flowState.track);
358
358
  const requiredGateIds = schema.requiredGates
359
359
  .filter((gate) => gate.tier === "required")
360
360
  .map((gate) => gate.id);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cclaw-cli",
3
- "version": "0.46.10",
3
+ "version": "0.46.11",
4
4
  "description": "Installer-first flow toolkit for coding agents",
5
5
  "type": "module",
6
6
  "bin": {