cclaw-cli 0.51.30 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/README.md +24 -18
  2. package/dist/artifact-linter/brainstorm.d.ts +2 -0
  3. package/dist/artifact-linter/brainstorm.js +289 -0
  4. package/dist/artifact-linter/design.d.ts +2 -0
  5. package/dist/artifact-linter/design.js +354 -0
  6. package/dist/artifact-linter/plan.d.ts +2 -0
  7. package/dist/artifact-linter/plan.js +183 -0
  8. package/dist/artifact-linter/review-army.d.ts +24 -0
  9. package/dist/artifact-linter/review-army.js +365 -0
  10. package/dist/artifact-linter/review.d.ts +2 -0
  11. package/dist/artifact-linter/review.js +99 -0
  12. package/dist/artifact-linter/scope.d.ts +2 -0
  13. package/dist/artifact-linter/scope.js +125 -0
  14. package/dist/artifact-linter/shared.d.ts +247 -0
  15. package/dist/artifact-linter/shared.js +1517 -0
  16. package/dist/artifact-linter/ship.d.ts +2 -0
  17. package/dist/artifact-linter/ship.js +82 -0
  18. package/dist/artifact-linter/spec.d.ts +2 -0
  19. package/dist/artifact-linter/spec.js +130 -0
  20. package/dist/artifact-linter/tdd.d.ts +2 -0
  21. package/dist/artifact-linter/tdd.js +198 -0
  22. package/dist/artifact-linter.d.ts +4 -76
  23. package/dist/artifact-linter.js +56 -2949
  24. package/dist/cli.d.ts +1 -6
  25. package/dist/cli.js +4 -159
  26. package/dist/codex-feature-flag.d.ts +1 -1
  27. package/dist/codex-feature-flag.js +1 -1
  28. package/dist/config.d.ts +3 -2
  29. package/dist/config.js +67 -3
  30. package/dist/constants.d.ts +1 -7
  31. package/dist/constants.js +10 -15
  32. package/dist/content/cancel-command.js +2 -2
  33. package/dist/content/closeout-guidance.d.ts +1 -1
  34. package/dist/content/closeout-guidance.js +15 -13
  35. package/dist/content/core-agents.d.ts +46 -29
  36. package/dist/content/core-agents.js +216 -82
  37. package/dist/content/decision-protocol.d.ts +1 -1
  38. package/dist/content/decision-protocol.js +1 -1
  39. package/dist/content/diff-command.js +1 -1
  40. package/dist/content/examples.d.ts +0 -3
  41. package/dist/content/examples.js +197 -752
  42. package/dist/content/harness-doc.js +20 -2
  43. package/dist/content/hook-manifest.d.ts +2 -2
  44. package/dist/content/hook-manifest.js +2 -2
  45. package/dist/content/hooks.d.ts +1 -0
  46. package/dist/content/hooks.js +32 -137
  47. package/dist/content/idea.d.ts +60 -0
  48. package/dist/content/idea.js +404 -0
  49. package/dist/content/iron-laws.d.ts +0 -1
  50. package/dist/content/iron-laws.js +31 -16
  51. package/dist/content/learnings.d.ts +2 -4
  52. package/dist/content/learnings.js +11 -27
  53. package/dist/content/meta-skill.js +7 -7
  54. package/dist/content/node-hooks.d.ts +10 -0
  55. package/dist/content/node-hooks.js +163 -95
  56. package/dist/content/opencode-plugin.js +15 -29
  57. package/dist/content/reference-patterns.js +2 -2
  58. package/dist/content/runtime-shared-snippets.d.ts +8 -0
  59. package/dist/content/runtime-shared-snippets.js +80 -0
  60. package/dist/content/session-hooks.js +1 -1
  61. package/dist/content/skills.d.ts +1 -0
  62. package/dist/content/skills.js +69 -7
  63. package/dist/content/stage-schema.js +147 -61
  64. package/dist/content/stages/_lint-metadata/index.js +26 -2
  65. package/dist/content/stages/brainstorm.js +13 -7
  66. package/dist/content/stages/design.js +16 -11
  67. package/dist/content/stages/plan.js +7 -4
  68. package/dist/content/stages/review.js +12 -12
  69. package/dist/content/stages/schema-types.d.ts +2 -2
  70. package/dist/content/stages/scope.js +15 -12
  71. package/dist/content/stages/ship.js +3 -3
  72. package/dist/content/stages/spec.js +9 -3
  73. package/dist/content/stages/tdd.js +14 -4
  74. package/dist/content/start-command.js +11 -10
  75. package/dist/content/status-command.js +5 -5
  76. package/dist/content/subagent-context-skills.js +156 -1
  77. package/dist/content/subagents.d.ts +0 -5
  78. package/dist/content/subagents.js +65 -81
  79. package/dist/content/templates.d.ts +1 -1
  80. package/dist/content/templates.js +187 -154
  81. package/dist/content/tree-command.js +2 -2
  82. package/dist/content/utility-skills.d.ts +2 -2
  83. package/dist/content/utility-skills.js +28 -99
  84. package/dist/content/view-command.js +4 -2
  85. package/dist/delegation.d.ts +2 -0
  86. package/dist/delegation.js +2 -1
  87. package/dist/early-loop.d.ts +66 -0
  88. package/dist/early-loop.js +275 -0
  89. package/dist/flow-state.d.ts +5 -6
  90. package/dist/flow-state.js +4 -6
  91. package/dist/gate-evidence.d.ts +0 -23
  92. package/dist/gate-evidence.js +111 -153
  93. package/dist/harness-adapters.d.ts +2 -2
  94. package/dist/harness-adapters.js +48 -19
  95. package/dist/install.js +190 -32
  96. package/dist/internal/advance-stage/advance.d.ts +50 -0
  97. package/dist/internal/advance-stage/advance.js +479 -0
  98. package/dist/internal/advance-stage/cancel-run.d.ts +8 -0
  99. package/dist/internal/advance-stage/cancel-run.js +19 -0
  100. package/dist/internal/advance-stage/flow-state-coercion.d.ts +3 -0
  101. package/dist/internal/advance-stage/flow-state-coercion.js +81 -0
  102. package/dist/internal/advance-stage/helpers.d.ts +14 -0
  103. package/dist/internal/advance-stage/helpers.js +145 -0
  104. package/dist/internal/advance-stage/hook.d.ts +8 -0
  105. package/dist/internal/advance-stage/hook.js +40 -0
  106. package/dist/internal/advance-stage/parsers.d.ts +54 -0
  107. package/dist/internal/advance-stage/parsers.js +307 -0
  108. package/dist/internal/advance-stage/review-loop.d.ts +7 -0
  109. package/dist/internal/advance-stage/review-loop.js +161 -0
  110. package/dist/internal/advance-stage/rewind.d.ts +14 -0
  111. package/dist/internal/advance-stage/rewind.js +108 -0
  112. package/dist/internal/advance-stage/start-flow.d.ts +11 -0
  113. package/dist/internal/advance-stage/start-flow.js +136 -0
  114. package/dist/internal/advance-stage/verify.d.ts +29 -0
  115. package/dist/internal/advance-stage/verify.js +225 -0
  116. package/dist/internal/advance-stage.js +21 -1470
  117. package/dist/internal/compound-readiness.d.ts +1 -1
  118. package/dist/internal/compound-readiness.js +2 -2
  119. package/dist/internal/early-loop-status.d.ts +7 -0
  120. package/dist/internal/early-loop-status.js +90 -0
  121. package/dist/internal/runtime-integrity.d.ts +7 -0
  122. package/dist/internal/runtime-integrity.js +288 -0
  123. package/dist/internal/tdd-red-evidence.js +1 -1
  124. package/dist/knowledge-store.d.ts +5 -28
  125. package/dist/knowledge-store.js +57 -84
  126. package/dist/managed-resources.js +24 -2
  127. package/dist/policy.js +7 -9
  128. package/dist/retro-gate.js +8 -90
  129. package/dist/run-archive.d.ts +1 -1
  130. package/dist/run-archive.js +13 -16
  131. package/dist/run-persistence.js +20 -15
  132. package/dist/runtime/run-hook.entry.d.ts +3 -0
  133. package/dist/runtime/run-hook.entry.js +5 -0
  134. package/dist/runtime/run-hook.mjs +9477 -0
  135. package/dist/tdd-cycle.d.ts +3 -3
  136. package/dist/tdd-cycle.js +1 -1
  137. package/dist/types.d.ts +18 -10
  138. package/package.json +4 -2
  139. package/dist/content/hook-inline-snippets.d.ts +0 -83
  140. package/dist/content/hook-inline-snippets.js +0 -302
  141. package/dist/content/ideate-command.d.ts +0 -8
  142. package/dist/content/ideate-command.js +0 -315
  143. package/dist/content/ideate-frames.d.ts +0 -31
  144. package/dist/content/ideate-frames.js +0 -140
  145. package/dist/content/ideate-ranking.d.ts +0 -25
  146. package/dist/content/ideate-ranking.js +0 -65
  147. package/dist/content/next-command.d.ts +0 -20
  148. package/dist/content/next-command.js +0 -298
  149. package/dist/content/seed-shelf.d.ts +0 -36
  150. package/dist/content/seed-shelf.js +0 -301
  151. package/dist/content/stage-common-guidance.d.ts +0 -1
  152. package/dist/content/stage-common-guidance.js +0 -106
  153. package/dist/doctor-registry.d.ts +0 -10
  154. package/dist/doctor-registry.js +0 -186
  155. package/dist/doctor.d.ts +0 -17
  156. package/dist/doctor.js +0 -2201
  157. package/dist/internal/hook-manifest.d.ts +0 -16
  158. package/dist/internal/hook-manifest.js +0 -77
  159. package/dist/trace-matrix.d.ts +0 -27
  160. package/dist/trace-matrix.js +0 -226
@@ -2,16 +2,16 @@ import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { checkReviewSecurityNoChangeAttestation, checkReviewVerdictConsistency, extractMarkdownSectionBody, lintArtifact, validateReviewArmy } from "./artifact-linter.js";
4
4
  import { resolveArtifactPath } from "./artifact-paths.js";
5
+ import { readConfig } from "./config.js";
5
6
  import { RUNTIME_ROOT } from "./constants.js";
6
7
  import { stageSchema } from "./content/stage-schema.js";
7
8
  import { readDelegationLedger } from "./delegation.js";
8
- import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
9
+ import { exists } from "./fs-utils.js";
10
+ import { computeEarlyLoopStatus, isEarlyLoopStage, normalizeEarlyLoopMaxIterations } from "./early-loop.js";
9
11
  import { detectPublicApiChanges } from "./internal/detect-public-api-changes.js";
10
12
  import { readFlowState, writeFlowState } from "./runs.js";
11
13
  import { parseTddCycleLog, validateTddCycleOrder } from "./tdd-cycle.js";
12
14
  import { validateTddVerificationEvidence } from "./tdd-verification-evidence.js";
13
- import { buildTraceMatrix } from "./trace-matrix.js";
14
- import { FLOW_STAGES } from "./types.js";
15
15
  async function currentStageArtifactExists(projectRoot, stage, track) {
16
16
  const resolved = await resolveArtifactPath(stage, {
17
17
  projectRoot,
@@ -105,8 +105,7 @@ async function discoverRealTestCommands(projectRoot) {
105
105
  return unique(commands);
106
106
  }
107
107
  async function verifyDiscoveredCommandEvidence(projectRoot, stage, gateId, flowState) {
108
- if (!(stage === "tdd" && gateId === "tdd_verified_before_complete") &&
109
- !(stage === "review" && gateId === "review_trace_matrix_clean")) {
108
+ if (!(stage === "tdd" && gateId === "tdd_verified_before_complete")) {
110
109
  return null;
111
110
  }
112
111
  const commands = await discoverRealTestCommands(projectRoot);
@@ -119,111 +118,99 @@ async function verifyDiscoveredCommandEvidence(projectRoot, stage, gateId, flowS
119
118
  return null;
120
119
  return `${stage} verification gate blocked (${gateId}): guard evidence must cite one discovered real test command: ${commands.join(", ")}.`;
121
120
  }
122
- const RECONCILIATION_NOTICES_FILE = "reconciliation-notices.json";
123
- const RECONCILIATION_NOTICES_SCHEMA_VERSION = 1;
124
- const DESIGN_RESEARCH_REQUIRED_SECTIONS = [
125
- "Stack Analysis",
126
- "Features & Patterns",
127
- "Architecture Options",
128
- "Pitfalls & Risks",
129
- "Synthesis"
130
- ];
131
- export const RECONCILIATION_NOTICES_REL_PATH = `${RUNTIME_ROOT}/state/${RECONCILIATION_NOTICES_FILE}`;
132
- function isFlowStageValue(value) {
133
- return typeof value === "string" && FLOW_STAGES.includes(value);
134
- }
135
- function reconciliationNoticesPath(projectRoot) {
136
- return path.join(projectRoot, RUNTIME_ROOT, "state", RECONCILIATION_NOTICES_FILE);
137
- }
138
- function defaultReconciliationNoticesPayload() {
139
- return {
140
- schemaVersion: RECONCILIATION_NOTICES_SCHEMA_VERSION,
141
- notices: [],
142
- parseOk: true,
143
- schemaOk: true
144
- };
145
- }
146
- function sanitizeReconciliationNotice(raw) {
147
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
121
+ function toEarlyLoopGateSnapshot(value) {
122
+ if (!value || typeof value !== "object" || Array.isArray(value))
148
123
  return null;
149
- }
150
- const typed = raw;
151
- if (typeof typed.id !== "string" ||
152
- typeof typed.runId !== "string" ||
153
- !isFlowStageValue(typed.stage) ||
154
- typeof typed.gateId !== "string" ||
155
- typeof typed.reason !== "string" ||
156
- typeof typed.demotedAt !== "string") {
124
+ const typed = value;
125
+ const stage = typeof typed.stage === "string" ? typed.stage : "";
126
+ const runId = typeof typed.runId === "string" ? typed.runId : "active";
127
+ const iteration = typeof typed.iteration === "number" && Number.isInteger(typed.iteration) && typed.iteration >= 0
128
+ ? typed.iteration
129
+ : 0;
130
+ const maxIterations = normalizeEarlyLoopMaxIterations(typeof typed.maxIterations === "number" ? typed.maxIterations : undefined);
131
+ const openConcernIds = Array.isArray(typed.openConcerns)
132
+ ? typed.openConcerns
133
+ .flatMap((concern) => {
134
+ if (!concern || typeof concern !== "object" || Array.isArray(concern))
135
+ return [];
136
+ const id = concern.id;
137
+ return typeof id === "string" && id.trim().length > 0 ? [id.trim()] : [];
138
+ })
139
+ .sort((a, b) => a.localeCompare(b, "en"))
140
+ : [];
141
+ if (stage.length === 0)
157
142
  return null;
158
- }
159
143
  return {
160
- id: typed.id,
161
- runId: typed.runId,
162
- stage: typed.stage,
163
- gateId: typed.gateId,
164
- reason: typed.reason,
165
- demotedAt: typed.demotedAt
144
+ stage,
145
+ runId,
146
+ iteration,
147
+ maxIterations,
148
+ openConcernIds,
149
+ openConcernCount: openConcernIds.length,
150
+ convergenceTripped: typed.convergenceTripped === true,
151
+ escalationReason: typeof typed.escalationReason === "string" && typed.escalationReason.trim().length > 0
152
+ ? typed.escalationReason.trim()
153
+ : undefined
166
154
  };
167
155
  }
168
- export async function readReconciliationNotices(projectRoot) {
169
- const filePath = reconciliationNoticesPath(projectRoot);
170
- if (!(await exists(filePath))) {
171
- return defaultReconciliationNoticesPayload();
156
+ async function readEarlyLoopGateSnapshot(projectRoot, flowState) {
157
+ if (!isEarlyLoopStage(flowState.currentStage)) {
158
+ return { snapshot: null };
159
+ }
160
+ const stateDir = path.join(projectRoot, RUNTIME_ROOT, "state");
161
+ const statusPath = path.join(stateDir, "early-loop.json");
162
+ let onDisk = null;
163
+ if (await exists(statusPath)) {
164
+ try {
165
+ onDisk = toEarlyLoopGateSnapshot(JSON.parse(await fs.readFile(statusPath, "utf8")));
166
+ }
167
+ catch (error) {
168
+ const reason = error instanceof Error ? error.message : String(error);
169
+ return {
170
+ snapshot: null,
171
+ issue: `early loop gate blocked (early_loop_open_concerns): unable to parse ${statusPath} (${reason}). ` +
172
+ "Rebuild status with `cclaw internal early-loop-status --write`."
173
+ };
174
+ }
175
+ }
176
+ if (onDisk &&
177
+ onDisk.stage === flowState.currentStage &&
178
+ onDisk.runId === flowState.activeRunId) {
179
+ return { snapshot: onDisk };
172
180
  }
173
181
  try {
174
- const raw = JSON.parse(await fs.readFile(filePath, "utf8"));
175
- const schemaOk = raw.schemaVersion === RECONCILIATION_NOTICES_SCHEMA_VERSION;
176
- const notices = Array.isArray(raw.notices)
177
- ? raw.notices
178
- .map((value) => sanitizeReconciliationNotice(value))
179
- .filter((value) => value !== null)
180
- : [];
182
+ const config = await readConfig(projectRoot);
183
+ const computed = await computeEarlyLoopStatus(flowState.currentStage, flowState.activeRunId, path.join(stateDir, "early-loop-log.jsonl"), {
184
+ maxIterations: config.earlyLoop?.maxIterations
185
+ });
181
186
  return {
182
- schemaVersion: RECONCILIATION_NOTICES_SCHEMA_VERSION,
183
- notices,
184
- parseOk: true,
185
- schemaOk
187
+ snapshot: {
188
+ stage: computed.stage,
189
+ runId: computed.runId,
190
+ iteration: computed.iteration,
191
+ maxIterations: computed.maxIterations,
192
+ openConcernIds: computed.openConcerns.map((concern) => concern.id),
193
+ openConcernCount: computed.openConcerns.length,
194
+ convergenceTripped: computed.convergenceTripped,
195
+ escalationReason: computed.escalationReason
196
+ }
186
197
  };
187
198
  }
188
- catch {
199
+ catch (error) {
200
+ const reason = error instanceof Error ? error.message : String(error);
189
201
  return {
190
- ...defaultReconciliationNoticesPayload(),
191
- parseOk: false,
192
- schemaOk: false
202
+ snapshot: null,
203
+ issue: `early loop gate blocked (early_loop_open_concerns): unable to compute status from early-loop-log.jsonl (${reason}).`
193
204
  };
194
205
  }
195
206
  }
196
- async function writeReconciliationNotices(projectRoot, payload) {
197
- const filePath = reconciliationNoticesPath(projectRoot);
198
- await ensureDir(path.dirname(filePath));
199
- await writeFileSafe(filePath, `${JSON.stringify({
200
- schemaVersion: RECONCILIATION_NOTICES_SCHEMA_VERSION,
201
- notices: payload.notices
202
- }, null, 2)}\n`, { mode: 0o600 });
203
- }
204
- export function classifyReconciliationNotices(flowState, notices) {
205
- const activeBlocked = [];
206
- const currentStageBlocked = [];
207
- const unsynced = [];
208
- const staleRun = [];
209
- for (const notice of notices) {
210
- if (notice.runId !== flowState.activeRunId) {
211
- staleRun.push(notice);
212
- continue;
213
- }
214
- const stageCatalog = flowState.stageGateCatalog[notice.stage];
215
- const blocked = stageCatalog.blocked.includes(notice.gateId);
216
- if (!blocked) {
217
- unsynced.push(notice);
218
- continue;
219
- }
220
- activeBlocked.push(notice);
221
- if (notice.stage === flowState.currentStage) {
222
- currentStageBlocked.push(notice);
223
- }
224
- }
225
- return { activeBlocked, currentStageBlocked, unsynced, staleRun };
226
- }
207
+ const DESIGN_RESEARCH_REQUIRED_SECTIONS = [
208
+ "Stack Analysis",
209
+ "Features & Patterns",
210
+ "Architecture Options",
211
+ "Pitfalls & Risks",
212
+ "Synthesis"
213
+ ];
227
214
  export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
228
215
  const stage = flowState.currentStage;
229
216
  const schema = stageSchema(stage, flowState.track);
@@ -239,6 +226,7 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
239
226
  const recommendedSet = new Set(recommended);
240
227
  const allowedSet = new Set([...required, ...recommended]);
241
228
  const issues = [];
229
+ const softNotices = [];
242
230
  const catalogRequired = unique(catalog.required);
243
231
  const catalogRecommended = unique(catalog.recommended ?? []);
244
232
  const catalogConditional = unique(catalog.conditional ?? []);
@@ -338,23 +326,6 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
338
326
  if (!securityAttestation.ok) {
339
327
  issues.push(`review security attestation failed: ${securityAttestation.errors.join("; ")}`);
340
328
  }
341
- const traceGateRequired = schema.requiredGates.some((gate) => gate.id === "review_trace_matrix_clean" && gate.tier === "required");
342
- if (traceGateRequired) {
343
- const trace = await buildTraceMatrix(projectRoot);
344
- const traceIssues = [];
345
- if (trace.orphanedCriteria.length > 0) {
346
- traceIssues.push(`orphaned criteria: ${trace.orphanedCriteria.join(", ")}`);
347
- }
348
- if (trace.orphanedTasks.length > 0) {
349
- traceIssues.push(`orphaned tasks: ${trace.orphanedTasks.join(", ")}`);
350
- }
351
- if (trace.orphanedTests.length > 0) {
352
- traceIssues.push(`orphaned tests: ${trace.orphanedTests.join(", ")}`);
353
- }
354
- if (traceIssues.length > 0) {
355
- issues.push(`review trace-matrix gate blocked (review_trace_matrix_clean): ${traceIssues.join("; ")}.`);
356
- }
357
- }
358
329
  }
359
330
  if (stage === "design") {
360
331
  const researchGateRequired = schema.requiredGates.some((gate) => gate.id === "design_research_complete" && gate.tier === "required");
@@ -439,8 +410,33 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
439
410
  }
440
411
  }
441
412
  }
413
+ if (isEarlyLoopStage(stage)) {
414
+ const { snapshot, issue } = await readEarlyLoopGateSnapshot(projectRoot, flowState);
415
+ if (issue) {
416
+ issues.push(issue);
417
+ }
418
+ else if (snapshot && snapshot.openConcernCount > 0) {
419
+ const concernTail = snapshot.openConcernIds.length > 3
420
+ ? `, +${snapshot.openConcernIds.length - 3} more`
421
+ : "";
422
+ const concernSample = snapshot.openConcernIds.slice(0, 3).join(", ");
423
+ if (snapshot.convergenceTripped) {
424
+ const reason = snapshot.escalationReason ?? "convergence guard tripped";
425
+ softNotices.push(`early loop escalation notice (early_loop_open_concerns): ${reason}; ` +
426
+ `open concerns remain (${concernSample}${concernTail}). Request explicit human override before advancing.`);
427
+ }
428
+ else {
429
+ issues.push(`early loop gate blocked (early_loop_open_concerns): ` +
430
+ `${snapshot.openConcernCount} open concern(s) remain after iteration ` +
431
+ `${snapshot.iteration}/${snapshot.maxIterations} (${concernSample}${concernTail}).`);
432
+ }
433
+ }
434
+ }
442
435
  const missingRequired = required.filter((gateId) => !passedSet.has(gateId));
443
- const missingRecommended = recommended.filter((gateId) => !passedSet.has(gateId));
436
+ const missingRecommended = [
437
+ ...recommended.filter((gateId) => !passedSet.has(gateId)),
438
+ ...softNotices
439
+ ];
444
440
  const missingTriggeredConditional = [];
445
441
  const blockingBlocked = catalog.blocked.filter((gateId) => requiredSet.has(gateId));
446
442
  const complete = missingRequired.length === 0 && blockingBlocked.length === 0;
@@ -604,46 +600,8 @@ export function reconcileCurrentStageGateCatalog(flowState) {
604
600
  export async function reconcileAndWriteCurrentStageGateCatalog(projectRoot) {
605
601
  const state = await readFlowState(projectRoot);
606
602
  const { nextState, reconciliation } = reconcileCurrentStageGateCatalog(state);
607
- const effectiveState = reconciliation.changed ? nextState : state;
608
603
  if (reconciliation.changed) {
609
- await writeFlowState(projectRoot, effectiveState);
610
- }
611
- const noticesPayload = await readReconciliationNotices(projectRoot);
612
- let noticesChanged = false;
613
- const noticeBuckets = classifyReconciliationNotices(effectiveState, noticesPayload.notices);
614
- if (noticeBuckets.unsynced.length > 0 || noticeBuckets.staleRun.length > 0) {
615
- const dropIds = new Set([...noticeBuckets.unsynced, ...noticeBuckets.staleRun].map((notice) => notice.id));
616
- noticesPayload.notices = noticesPayload.notices.filter((notice) => !dropIds.has(notice.id));
617
- noticesChanged = true;
618
- }
619
- if (reconciliation.demotedGateIds.length > 0) {
620
- const existing = new Set(noticesPayload.notices.map((notice) => `${notice.runId}:${notice.stage}:${notice.gateId}`));
621
- for (const gateId of reconciliation.demotedGateIds) {
622
- const dedupeKey = `${effectiveState.activeRunId}:${reconciliation.stage}:${gateId}`;
623
- if (existing.has(dedupeKey)) {
624
- continue;
625
- }
626
- const ts = new Date().toISOString();
627
- noticesPayload.notices.push({
628
- id: `${dedupeKey}:${ts}`,
629
- runId: effectiveState.activeRunId,
630
- stage: reconciliation.stage,
631
- gateId,
632
- reason: "demoted from passed to blocked during gate reconciliation (missing evidence)",
633
- demotedAt: ts
634
- });
635
- existing.add(dedupeKey);
636
- noticesChanged = true;
637
- }
638
- }
639
- if (noticesChanged) {
640
- noticesPayload.notices.sort((a, b) => {
641
- if (a.demotedAt === b.demotedAt) {
642
- return a.id.localeCompare(b.id);
643
- }
644
- return a.demotedAt.localeCompare(b.demotedAt);
645
- });
646
- await writeReconciliationNotices(projectRoot, noticesPayload);
604
+ await writeFlowState(projectRoot, nextState);
647
605
  }
648
606
  return {
649
607
  ...reconciliation,
@@ -32,7 +32,7 @@ export type SubagentFallback =
32
32
  * directories under a skills root (Codex CLI ≥0.89, Jan 2026). cclaw
33
33
  * writes `<commandDir>/<skillName>/SKILL.md` and the agent invokes it
34
34
  * either via `/use <skillName>` or via automatic description matching
35
- * when the user's text mentions `/cc`, `/cc-ideate`, or `/cc-cancel`.
35
+ * when the user's text mentions `/cc`, `/cc-idea`, or `/cc-cancel`.
36
36
  */
37
37
  export type ShimKind = "command" | "skill";
38
38
  export interface HarnessAdapter {
@@ -47,7 +47,7 @@ export interface HarnessAdapter {
47
47
  * Root directory where cclaw writes `/cc*` entry points.
48
48
  *
49
49
  * - For `shimKind: "command"` this is the directory containing flat
50
- * markdown files (`<commandDir>/cc.md`, `<commandDir>/cc-ideate.md`, …).
50
+ * markdown files (`<commandDir>/cc.md`, `<commandDir>/cc-idea.md`, …).
51
51
  * - For `shimKind: "skill"` this is the skills root that contains
52
52
  * per-skill subdirectories (`<commandDir>/<skillName>/SKILL.md`).
53
53
  */
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
  import { RUNTIME_ROOT } from "./constants.js";
4
4
  import { conversationLanguagePolicyMarkdown } from "./content/language-policy.js";
5
5
  import { CCLAW_AGENTS, agentMarkdown } from "./content/core-agents.js";
6
- import { ironLawsAgentsMdBlock } from "./content/iron-laws.js";
6
+ import { IRON_LAWS } from "./content/iron-laws.js";
7
7
  import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
8
8
  export const CCLAW_MARKER_START = "<!-- cclaw-start -->";
9
9
  export const CCLAW_MARKER_END = "<!-- cclaw-end -->";
@@ -15,11 +15,11 @@ const RUNTIME_AGENTS_BLOCK_PATTERN = new RegExp(RUNTIME_AGENTS_BLOCK_SOURCE, "u"
15
15
  const RUNTIME_AGENTS_BLOCK_GLOBAL_PATTERN = new RegExp(RUNTIME_AGENTS_BLOCK_SOURCE, "gu");
16
16
  const UTILITY_SHIMS = [
17
17
  {
18
- fileName: "cc-ideate.md",
19
- skillName: "cc-ideate",
20
- command: "ideate",
21
- skillFolder: "flow-ideate",
22
- commandFile: "ideate.md"
18
+ fileName: "cc-idea.md",
19
+ skillName: "cc-idea",
20
+ command: "idea",
21
+ skillFolder: "flow-idea",
22
+ commandFile: "idea.md"
23
23
  },
24
24
  {
25
25
  fileName: "cc-cancel.md",
@@ -163,9 +163,9 @@ export function harnessDispatchSurface(harnessId) {
163
163
  case "cursor":
164
164
  return "Use Cursor Subagent/Task with a generic subagent_type (explore for read-only mapping, generalPurpose for broader work, shell/browser-use when specifically needed) and paste the cclaw role prompt; record fulfillmentMode: \"generic-dispatch\" with evidenceRefs.";
165
165
  case "opencode":
166
- return "Use OpenCode subagents: invoke the generated .opencode/agents/<agent>.md agent via Task or @<agent>; if agents or plugin registration are missing, run `cclaw sync` and check opencode.json(.c) plugin registration with `cclaw doctor --explain`; record scheduled/launched/acknowledged/completed events with spanId+dispatchId before claiming fulfillmentMode: \"isolated\".";
166
+ return "Use OpenCode subagents: invoke the generated .opencode/agents/<agent>.md agent via Task or @<agent>; if agents or plugin registration are missing, run `cclaw sync` and check opencode.json(.c) plugin registration with `npx cclaw-cli sync`; record scheduled/launched/acknowledged/completed events with spanId+dispatchId before claiming fulfillmentMode: \"isolated\".";
167
167
  case "codex":
168
- return "Use Codex native subagents: ask Codex to spawn the generated .codex/agents/<agent>.toml agent(s) by name; if hooks are inert, set `[features] codex_hooks = true` in ~/.codex/config.toml or rerun init/sync repair, then `cclaw doctor --explain`; record scheduled/launched/acknowledged/completed events with spanId+dispatchId before claiming fulfillmentMode: \"isolated\".";
168
+ return "Use Codex native subagents: ask Codex to spawn the generated .codex/agents/<agent>.toml agent(s) by name; if hooks are inert, set `[features] codex_hooks = true` in ~/.codex/config.toml or rerun init/sync repair, then `npx cclaw-cli sync`; record scheduled/launched/acknowledged/completed events with spanId+dispatchId before claiming fulfillmentMode: \"isolated\".";
169
169
  }
170
170
  }
171
171
  /**
@@ -292,6 +292,35 @@ export function harnessesByTier() {
292
292
  return tierOrder[harnessTier(a)] - tierOrder[harnessTier(b)];
293
293
  });
294
294
  }
295
+ function ironLawsAgentsMdBlock() {
296
+ const enforcedLawIds = new Set([
297
+ "stop-clean-or-handoff",
298
+ "review-coverage-complete-before-ship"
299
+ ]);
300
+ const enforcedRows = IRON_LAWS
301
+ .filter((law) => enforcedLawIds.has(law.id))
302
+ .map((law) => `| \`${law.id}\` | ${law.rule} | ${law.enforcement} |`)
303
+ .join("\n");
304
+ const advisoryRows = IRON_LAWS
305
+ .filter((law) => !enforcedLawIds.has(law.id))
306
+ .map((law) => {
307
+ const appliesTo = law.appliesTo === "all" ? "all stages" : law.appliesTo.join(", ");
308
+ return `- \`${law.id}\` (applies to: ${appliesTo})`;
309
+ })
310
+ .join("\n");
311
+ return `### Iron Laws
312
+
313
+ These rules are always-on. The hook-enforced runtime laws are:
314
+
315
+ | ID | Rule | Enforced by |
316
+ |---|---|---|
317
+ ${enforcedRows}
318
+
319
+ Advisory laws are stage-owned through each stage's HARD-GATE block:
320
+
321
+ ${advisoryRows}
322
+ `;
323
+ }
295
324
  function agentsMdBlock() {
296
325
  return `${CCLAW_MARKER_START}
297
326
  ## Cclaw — Workflow Adapter
@@ -343,13 +372,13 @@ When in doubt, prefer **non-trivial** — the quick track is opt-in and only saf
343
372
  | Command | Purpose |
344
373
  |---|---|
345
374
  | \`/cc\` | **Entry point.** No args = resume or progress current flow. With prompt = classify task and start the right flow. |
346
- | \`/cc-ideate\` | **Ideate mode.** Generates a ranked repo-improvement backlog before implementation. |
375
+ | \`/cc-idea\` | **Idea mode.** Generates a ranked repo-improvement backlog before implementation. |
347
376
  | \`/cc-cancel\` | **Non-completion closeout.** Archives a cancelled/abandoned run with a required reason. |
348
377
 
349
378
  Knowledge capture and curation run automatically as part of stage completion
350
379
  protocols via the internal \`learnings\` skill — no user-facing command.
351
380
  Reusable entries land in \`.cclaw/knowledge.jsonl\` as strict JSONL with
352
- \`type\`, \`trigger\`, \`action\`, and \`origin_run\` metadata.
381
+ \`type\`, \`trigger\`, \`action\`, \`confidence\`, \`stage\`, and \`origin_stage\` metadata.
353
382
 
354
383
  **Stage order:** brainstorm > scope > design > spec > plan > tdd > review > ship, then closeout: retro > compound > archive. Use \`/cc\` to keep moving through normal work and post-ship closeout; use \`/cc-cancel\` for cancelled/abandoned runs. Gates must pass before handoff.
355
384
 
@@ -372,11 +401,11 @@ If the same approach fails three times in a row (same command, same finding, sam
372
401
  ### Codex users
373
402
 
374
403
  OpenAI Codex CLI has **no native \`/cc\` slash command** (custom prompts
375
- were deprecated in v0.89, Jan 2026). The \`/cc\`, \`/cc-ideate\`, and
404
+ were deprecated in v0.89, Jan 2026). The \`/cc\`, \`/cc-idea\`, and
376
405
  \`/cc-cancel\` tokens above describe intent — in Codex they map onto skills cclaw installs at
377
406
  \`.agents/skills/cc*/SKILL.md\`. Activate one of two ways:
378
407
 
379
- - Type \`/use cc\` (or \`cc-ideate\` / \`cc-cancel\`) at Codex's prompt.
408
+ - Type \`/use cc\` (or \`cc-idea\` / \`cc-cancel\`) at Codex's prompt.
380
409
  - Type \`/cc …\` as plain text — Codex matches the skill \`description\`
381
410
  frontmatter (which spells out the token verbatim) and loads the right
382
411
  skill body automatically.
@@ -385,9 +414,9 @@ Codex CLI v0.114+ (Mar 2026) **does** expose lifecycle hooks via
385
414
  \`.codex/hooks.json\`, gated by the \`[features] codex_hooks = true\` flag
386
415
  in \`~/.codex/config.toml\`. cclaw generates \`.codex/hooks.json\` on
387
416
  sync; if the feature flag is off, hooks are inert and cclaw's
388
- session-start rehydration simply does not fire. Run \`cclaw doctor\` to
417
+ session-start rehydration simply does not fire. Run \`npx cclaw-cli sync\` to
389
418
  see if the flag is missing. \`.codex/commands/*\` is still unused by
390
- Codex CLI and is removed on every sync. Run \`cclaw doctor --explain\` for
419
+ Codex CLI and is removed on every sync. Run \`npx cclaw-cli sync\` for
391
420
  hook coverage details (Bash-only \`PreToolUse\`/\`PostToolUse\`; other events are full).
392
421
  ${CCLAW_MARKER_END}`;
393
422
  }
@@ -449,7 +478,7 @@ function utilityShimBehavior(command) {
449
478
  switch (command) {
450
479
  case "cc":
451
480
  return "This is the entry command, not a flow stage. It may initialize or resume flow state after confirmation.";
452
- case "ideate":
481
+ case "idea":
453
482
  return "This is an ideation command, not a flow stage. It may write ideation artifacts/seeds but does not advance flow state.";
454
483
  case "cancel":
455
484
  return "This is a non-completion closeout utility, not a flow stage. It requires a reason and archives cancelled or abandoned work without presenting it as completed.";
@@ -484,8 +513,8 @@ function codexSkillDescription(command) {
484
513
  switch (command) {
485
514
  case "cc":
486
515
  return `Entry point for the cclaw track-aware workflow ending in ship plus auto-closeout (retro → compound → archive). Use whenever the user types \`/cc\`, \`/cclaw\`, or asks to "start the flow", "begin cclaw", "kick off the workflow", "classify this task", or wants to start/resume a non-trivial software change. No args = resume the active stage from \`.cclaw/state/flow-state.json\`. With a prompt = classify and pick a track (quick/medium/standard).`;
487
- case "ideate":
488
- return `Read-only repo-improvement ideate mode for cclaw. Use when the user types \`/cc-ideate\` or asks to "ideate", "scan the repo for TODOs/tech debt", "generate a backlog", or wants a ranked list of candidate ideas before committing to a single flow. Does not mutate \`.cclaw/state/flow-state.json\`.`;
516
+ case "idea":
517
+ return `Read-only repo-improvement idea mode for cclaw. Use when the user types \`/cc-idea\` or asks to "scan the repo for TODOs/tech debt", "generate a backlog", "brainstorm improvement ideas", or wants a ranked list of candidate ideas before committing to a single flow. Does not mutate \`.cclaw/state/flow-state.json\`.`;
489
518
  case "cancel":
490
519
  return `Cancel or abandon the active cclaw run. Use when the user types \`/cc-cancel\` or asks to cancel, abandon, stop, discard, or reset an unfinished run. Requires a reason and archives with cancelled/abandoned disposition.`;
491
520
  default:
@@ -518,7 +547,7 @@ under \`.agents/skills/${skillSlug}/\` so the user can either:
518
547
 
519
548
  Lifecycle hooks **are** available in Codex CLI v0.114+ (behind the
520
549
  \`[features] codex_hooks = true\` flag in \`~/.codex/config.toml\`) and
521
- cclaw installs a matching \`.codex/hooks.json\`; run \`cclaw doctor --explain\`
550
+ cclaw installs a matching \`.codex/hooks.json\`; run \`npx cclaw-cli sync\`
522
551
  for the current hook surface and limitations.
523
552
 
524
553
  ## Protocol
@@ -542,7 +571,7 @@ for the current hook surface and limitations.
542
571
  unavailable or disabled, and then include non-empty \`evidenceRefs\`.
543
572
  - Codex's \`PreToolUse\` / \`PostToolUse\` hooks currently only intercept
544
573
  the \`Bash\` tool. \`Write\`, \`Edit\`, \`WebSearch\`, and MCP tool calls
545
- are **not** gated by hooks — use \`cclaw doctor --explain\` for what cclaw
574
+ are **not** gated by hooks — use \`npx cclaw-cli sync\` for what cclaw
546
575
  substitutes with in-turn agent steps for those call classes.
547
576
  - Codex's \`SessionStart\` matcher only supports \`startup|resume\`. Claude
548
577
  and Cursor also fire on \`clear\` and \`compact\`, so mid-session