cclaw-cli 0.55.2 → 2.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 (92) hide show
  1. package/README.md +3 -3
  2. package/dist/artifact-linter/brainstorm.js +59 -1
  3. package/dist/artifact-linter/design.js +46 -1
  4. package/dist/artifact-linter/plan.js +22 -1
  5. package/dist/artifact-linter/review.js +35 -1
  6. package/dist/artifact-linter/scope.js +33 -9
  7. package/dist/artifact-linter/shared.d.ts +12 -10
  8. package/dist/artifact-linter/shared.js +102 -41
  9. package/dist/artifact-linter/ship.js +36 -0
  10. package/dist/artifact-linter/spec.js +23 -1
  11. package/dist/artifact-linter/tdd.js +74 -0
  12. package/dist/artifact-linter.d.ts +1 -1
  13. package/dist/artifact-linter.js +11 -1
  14. package/dist/constants.d.ts +1 -1
  15. package/dist/constants.js +1 -0
  16. package/dist/content/closeout-guidance.d.ts +1 -1
  17. package/dist/content/closeout-guidance.js +10 -11
  18. package/dist/content/core-agents.d.ts +35 -36
  19. package/dist/content/core-agents.js +189 -99
  20. package/dist/content/diff-command.js +1 -1
  21. package/dist/content/examples.d.ts +0 -3
  22. package/dist/content/examples.js +197 -752
  23. package/dist/content/hook-events.js +1 -2
  24. package/dist/content/hook-manifest.d.ts +3 -4
  25. package/dist/content/hook-manifest.js +22 -25
  26. package/dist/content/hooks.js +54 -14
  27. package/dist/content/idea.d.ts +60 -0
  28. package/dist/content/idea.js +404 -0
  29. package/dist/content/learnings.d.ts +2 -4
  30. package/dist/content/learnings.js +10 -26
  31. package/dist/content/meta-skill.js +4 -3
  32. package/dist/content/node-hooks.js +368 -164
  33. package/dist/content/observe.js +3 -3
  34. package/dist/content/opencode-plugin.js +12 -32
  35. package/dist/content/reference-patterns.js +2 -2
  36. package/dist/content/runtime-shared-snippets.d.ts +8 -0
  37. package/dist/content/runtime-shared-snippets.js +80 -0
  38. package/dist/content/session-hooks.js +1 -1
  39. package/dist/content/skills-elicitation.d.ts +1 -0
  40. package/dist/content/skills-elicitation.js +123 -0
  41. package/dist/content/skills.d.ts +1 -0
  42. package/dist/content/skills.js +54 -2
  43. package/dist/content/stage-schema.js +107 -63
  44. package/dist/content/stages/brainstorm.js +7 -3
  45. package/dist/content/stages/design.js +4 -0
  46. package/dist/content/stages/review.js +8 -8
  47. package/dist/content/stages/schema-types.d.ts +2 -2
  48. package/dist/content/stages/scope.js +7 -3
  49. package/dist/content/stages/ship.js +1 -1
  50. package/dist/content/start-command.js +4 -4
  51. package/dist/content/status-command.js +3 -3
  52. package/dist/content/subagent-context-skills.js +156 -1
  53. package/dist/content/subagents.d.ts +0 -5
  54. package/dist/content/subagents.js +12 -82
  55. package/dist/content/templates.js +108 -6
  56. package/dist/content/utility-skills.js +26 -97
  57. package/dist/flow-state.d.ts +12 -6
  58. package/dist/flow-state.js +5 -6
  59. package/dist/gate-evidence.d.ts +0 -31
  60. package/dist/gate-evidence.js +3 -181
  61. package/dist/harness-adapters.js +1 -1
  62. package/dist/hook-schemas/claude-hooks.v1.json +2 -3
  63. package/dist/hook-schemas/codex-hooks.v1.json +1 -1
  64. package/dist/hook-schemas/cursor-hooks.v1.json +1 -1
  65. package/dist/install.js +50 -7
  66. package/dist/internal/advance-stage/advance.js +22 -2
  67. package/dist/internal/advance-stage/parsers.d.ts +1 -0
  68. package/dist/internal/advance-stage/parsers.js +6 -0
  69. package/dist/internal/advance-stage/review-loop.js +1 -10
  70. package/dist/knowledge-store.d.ts +2 -20
  71. package/dist/knowledge-store.js +43 -57
  72. package/dist/policy.js +3 -3
  73. package/dist/retro-gate.js +8 -90
  74. package/dist/run-archive.js +1 -4
  75. package/dist/run-persistence.d.ts +1 -1
  76. package/dist/run-persistence.js +43 -111
  77. package/dist/runtime/run-hook.entry.d.ts +3 -0
  78. package/dist/runtime/run-hook.entry.js +5 -0
  79. package/dist/runtime/run-hook.mjs +9647 -0
  80. package/dist/track-heuristics.d.ts +7 -1
  81. package/dist/track-heuristics.js +12 -0
  82. package/package.json +4 -2
  83. package/dist/content/hook-inline-snippets.d.ts +0 -96
  84. package/dist/content/hook-inline-snippets.js +0 -515
  85. package/dist/content/idea-command.d.ts +0 -8
  86. package/dist/content/idea-command.js +0 -322
  87. package/dist/content/idea-frames.d.ts +0 -31
  88. package/dist/content/idea-frames.js +0 -140
  89. package/dist/content/idea-ranking.d.ts +0 -25
  90. package/dist/content/idea-ranking.js +0 -65
  91. package/dist/trace-matrix.d.ts +0 -27
  92. package/dist/trace-matrix.js +0 -226
@@ -32,14 +32,13 @@ export interface RetroState {
32
32
  /**
33
33
  * Ship closeout substate machine.
34
34
  *
35
- * After ship completes, cclaw auto-chains retro compound → archive.
35
+ * After ship completes, cclaw auto-chains post-ship review → archive.
36
36
  * Each step is interruptible: `/cc` reads `shipSubstate` and resumes
37
37
  * from the correct step even across sessions.
38
38
  *
39
39
  * - `idle` — ship not complete, or closeout not yet started.
40
- * - `retro_review` — 09-retro.md draft exists; awaiting user edit/accept/skip.
41
- * - `compound_review` retro accepted; compound pass awaiting execution
42
- * (or user skip).
40
+ * - `post_ship_review` — unified closeout leg: retro acceptance/edit/skip
41
+ * plus compound pass execution (or explicit skip).
43
42
  * - `ready_to_archive` — retro + compound done; archive is the next
44
43
  * automatic step.
45
44
  * - `archived` — archive completed in this session (transient — archive
@@ -53,7 +52,7 @@ export interface RetroState {
53
52
  * These are not duplicates: `done` lives in stage transitions; `archived` /
54
53
  * `idle` live in closeout lifecycle state.
55
54
  */
56
- export declare const SHIP_SUBSTATES: readonly ["idle", "retro_review", "compound_review", "ready_to_archive", "archived"];
55
+ export declare const SHIP_SUBSTATES: readonly ["idle", "post_ship_review", "ready_to_archive", "archived"];
57
56
  export type ShipSubstate = (typeof SHIP_SUBSTATES)[number];
58
57
  export interface CloseoutState {
59
58
  shipSubstate: ShipSubstate;
@@ -83,11 +82,18 @@ export interface FlowState {
83
82
  staleStages: Partial<Record<FlowStage, StaleStageMarker>>;
84
83
  /** Chronological rewind operations for the active run. */
85
84
  rewinds: RewindRecord[];
85
+ /** Optional per-stage interaction hints carried from prior stage transitions. */
86
+ interactionHints?: Partial<Record<FlowStage, StageInteractionHint>>;
86
87
  /** Mandatory retrospective gate status before archive. */
87
88
  retro: RetroState;
88
- /** Ship → retrocompound → archive substate for resumable closeout. */
89
+ /** Ship → post_ship_review → archive substate for resumable closeout. */
89
90
  closeout: CloseoutState;
90
91
  }
92
+ export interface StageInteractionHint {
93
+ skipQuestions?: boolean;
94
+ sourceStage?: FlowStage;
95
+ recordedAt?: string;
96
+ }
91
97
  export interface InitialFlowStateOptions {
92
98
  activeRunId?: string;
93
99
  track?: FlowTrack;
@@ -5,14 +5,13 @@ export const FLOW_STATE_SCHEMA_VERSION = 1;
5
5
  /**
6
6
  * Ship closeout substate machine.
7
7
  *
8
- * After ship completes, cclaw auto-chains retro compound → archive.
8
+ * After ship completes, cclaw auto-chains post-ship review → archive.
9
9
  * Each step is interruptible: `/cc` reads `shipSubstate` and resumes
10
10
  * from the correct step even across sessions.
11
11
  *
12
12
  * - `idle` — ship not complete, or closeout not yet started.
13
- * - `retro_review` — 09-retro.md draft exists; awaiting user edit/accept/skip.
14
- * - `compound_review` retro accepted; compound pass awaiting execution
15
- * (or user skip).
13
+ * - `post_ship_review` — unified closeout leg: retro acceptance/edit/skip
14
+ * plus compound pass execution (or explicit skip).
16
15
  * - `ready_to_archive` — retro + compound done; archive is the next
17
16
  * automatic step.
18
17
  * - `archived` — archive completed in this session (transient — archive
@@ -28,8 +27,7 @@ export const FLOW_STATE_SCHEMA_VERSION = 1;
28
27
  */
29
28
  export const SHIP_SUBSTATES = [
30
29
  "idle",
31
- "retro_review",
32
- "compound_review",
30
+ "post_ship_review",
33
31
  "ready_to_archive",
34
32
  "archived"
35
33
  ];
@@ -92,6 +90,7 @@ export function createInitialFlowState(activeRunIdOrOptions = {}, maybeTrack) {
92
90
  skippedStages,
93
91
  staleStages: {},
94
92
  rewinds: [],
93
+ interactionHints: {},
95
94
  retro: {
96
95
  required: false,
97
96
  completedAt: undefined,
@@ -29,37 +29,6 @@ export interface CompletedStagesClosureResult {
29
29
  blocked: string[];
30
30
  }>;
31
31
  }
32
- export declare const RECONCILIATION_NOTICES_REL_PATH = ".cclaw/state/reconciliation-notices.json";
33
- export type ReconciliationNoticeKind = "gate_demotion" | "closeout_substate_demotion";
34
- export interface CloseoutSubstateDemotionPayload {
35
- previous: string;
36
- next: string;
37
- reason: string;
38
- }
39
- export interface ReconciliationNotice {
40
- id: string;
41
- runId: string;
42
- stage: FlowStage;
43
- gateId: string;
44
- reason: string;
45
- demotedAt: string;
46
- kind?: ReconciliationNoticeKind;
47
- payload?: CloseoutSubstateDemotionPayload;
48
- }
49
- export interface ReconciliationNoticesPayload {
50
- schemaVersion: number;
51
- notices: ReconciliationNotice[];
52
- parseOk: boolean;
53
- schemaOk: boolean;
54
- }
55
- export interface ReconciliationNoticeBuckets {
56
- activeBlocked: ReconciliationNotice[];
57
- currentStageBlocked: ReconciliationNotice[];
58
- unsynced: ReconciliationNotice[];
59
- staleRun: ReconciliationNotice[];
60
- }
61
- export declare function readReconciliationNotices(projectRoot: string): Promise<ReconciliationNoticesPayload>;
62
- export declare function classifyReconciliationNotices(flowState: FlowState, notices: ReconciliationNotice[]): ReconciliationNoticeBuckets;
63
32
  export declare function verifyCurrentStageGateEvidence(projectRoot: string, flowState: FlowState): Promise<GateEvidenceCheckResult>;
64
33
  export declare function verifyCompletedStagesGateClosure(flowState: FlowState): CompletedStagesClosureResult;
65
34
  export interface GateReconciliationResult {
@@ -6,14 +6,12 @@ import { readConfig } from "./config.js";
6
6
  import { RUNTIME_ROOT } from "./constants.js";
7
7
  import { stageSchema } from "./content/stage-schema.js";
8
8
  import { readDelegationLedger } from "./delegation.js";
9
- import { ensureDir, exists, writeFileSafe } from "./fs-utils.js";
9
+ import { exists } from "./fs-utils.js";
10
10
  import { computeEarlyLoopStatus, isEarlyLoopStage, normalizeEarlyLoopMaxIterations } from "./early-loop.js";
11
11
  import { detectPublicApiChanges } from "./internal/detect-public-api-changes.js";
12
12
  import { readFlowState, writeFlowState } from "./runs.js";
13
13
  import { parseTddCycleLog, validateTddCycleOrder } from "./tdd-cycle.js";
14
14
  import { validateTddVerificationEvidence } from "./tdd-verification-evidence.js";
15
- import { buildTraceMatrix } from "./trace-matrix.js";
16
- import { FLOW_STAGES } from "./types.js";
17
15
  async function currentStageArtifactExists(projectRoot, stage, track) {
18
16
  const resolved = await resolveArtifactPath(stage, {
19
17
  projectRoot,
@@ -107,8 +105,7 @@ async function discoverRealTestCommands(projectRoot) {
107
105
  return unique(commands);
108
106
  }
109
107
  async function verifyDiscoveredCommandEvidence(projectRoot, stage, gateId, flowState) {
110
- if (!(stage === "tdd" && gateId === "tdd_verified_before_complete") &&
111
- !(stage === "review" && gateId === "review_trace_matrix_clean")) {
108
+ if (!(stage === "tdd" && gateId === "tdd_verified_before_complete")) {
112
109
  return null;
113
110
  }
114
111
  const commands = await discoverRealTestCommands(projectRoot);
@@ -207,8 +204,6 @@ async function readEarlyLoopGateSnapshot(projectRoot, flowState) {
207
204
  };
208
205
  }
209
206
  }
210
- const RECONCILIATION_NOTICES_FILE = "reconciliation-notices.json";
211
- const RECONCILIATION_NOTICES_SCHEMA_VERSION = 1;
212
207
  const DESIGN_RESEARCH_REQUIRED_SECTIONS = [
213
208
  "Stack Analysis",
214
209
  "Features & Patterns",
@@ -216,123 +211,6 @@ const DESIGN_RESEARCH_REQUIRED_SECTIONS = [
216
211
  "Pitfalls & Risks",
217
212
  "Synthesis"
218
213
  ];
219
- export const RECONCILIATION_NOTICES_REL_PATH = `${RUNTIME_ROOT}/state/${RECONCILIATION_NOTICES_FILE}`;
220
- function isFlowStageValue(value) {
221
- return typeof value === "string" && FLOW_STAGES.includes(value);
222
- }
223
- function reconciliationNoticesPath(projectRoot) {
224
- return path.join(projectRoot, RUNTIME_ROOT, "state", RECONCILIATION_NOTICES_FILE);
225
- }
226
- function defaultReconciliationNoticesPayload() {
227
- return {
228
- schemaVersion: RECONCILIATION_NOTICES_SCHEMA_VERSION,
229
- notices: [],
230
- parseOk: true,
231
- schemaOk: true
232
- };
233
- }
234
- function sanitizeReconciliationNotice(raw) {
235
- if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
236
- return null;
237
- }
238
- const typed = raw;
239
- if (typeof typed.id !== "string" ||
240
- typeof typed.runId !== "string" ||
241
- !isFlowStageValue(typed.stage) ||
242
- typeof typed.gateId !== "string" ||
243
- typeof typed.reason !== "string" ||
244
- typeof typed.demotedAt !== "string") {
245
- return null;
246
- }
247
- const kind = typed.kind === "closeout_substate_demotion"
248
- ? "closeout_substate_demotion"
249
- : "gate_demotion";
250
- let payload;
251
- if (kind === "closeout_substate_demotion" && typed.payload && typeof typed.payload === "object" && !Array.isArray(typed.payload)) {
252
- const payloadTyped = typed.payload;
253
- if (typeof payloadTyped.previous === "string" &&
254
- typeof payloadTyped.next === "string" &&
255
- typeof payloadTyped.reason === "string") {
256
- payload = {
257
- previous: payloadTyped.previous,
258
- next: payloadTyped.next,
259
- reason: payloadTyped.reason
260
- };
261
- }
262
- }
263
- return {
264
- id: typed.id,
265
- runId: typed.runId,
266
- stage: typed.stage,
267
- gateId: typed.gateId,
268
- reason: typed.reason,
269
- demotedAt: typed.demotedAt,
270
- kind,
271
- payload
272
- };
273
- }
274
- export async function readReconciliationNotices(projectRoot) {
275
- const filePath = reconciliationNoticesPath(projectRoot);
276
- if (!(await exists(filePath))) {
277
- return defaultReconciliationNoticesPayload();
278
- }
279
- try {
280
- const raw = JSON.parse(await fs.readFile(filePath, "utf8"));
281
- const schemaOk = raw.schemaVersion === RECONCILIATION_NOTICES_SCHEMA_VERSION;
282
- const notices = Array.isArray(raw.notices)
283
- ? raw.notices
284
- .map((value) => sanitizeReconciliationNotice(value))
285
- .filter((value) => value !== null)
286
- : [];
287
- return {
288
- schemaVersion: RECONCILIATION_NOTICES_SCHEMA_VERSION,
289
- notices,
290
- parseOk: true,
291
- schemaOk
292
- };
293
- }
294
- catch {
295
- return {
296
- ...defaultReconciliationNoticesPayload(),
297
- parseOk: false,
298
- schemaOk: false
299
- };
300
- }
301
- }
302
- async function writeReconciliationNotices(projectRoot, payload) {
303
- const filePath = reconciliationNoticesPath(projectRoot);
304
- await ensureDir(path.dirname(filePath));
305
- await writeFileSafe(filePath, `${JSON.stringify({
306
- schemaVersion: RECONCILIATION_NOTICES_SCHEMA_VERSION,
307
- notices: payload.notices
308
- }, null, 2)}\n`, { mode: 0o600 });
309
- }
310
- export function classifyReconciliationNotices(flowState, notices) {
311
- const activeBlocked = [];
312
- const currentStageBlocked = [];
313
- const unsynced = [];
314
- const staleRun = [];
315
- for (const notice of notices) {
316
- if (notice.runId !== flowState.activeRunId) {
317
- staleRun.push(notice);
318
- continue;
319
- }
320
- if (notice.kind === "closeout_substate_demotion") {
321
- continue;
322
- }
323
- const stageCatalog = flowState.stageGateCatalog[notice.stage];
324
- const blocked = stageCatalog.blocked.includes(notice.gateId);
325
- if (!blocked) {
326
- unsynced.push(notice);
327
- continue;
328
- }
329
- activeBlocked.push(notice);
330
- if (notice.stage === flowState.currentStage) {
331
- currentStageBlocked.push(notice);
332
- }
333
- }
334
- return { activeBlocked, currentStageBlocked, unsynced, staleRun };
335
- }
336
214
  export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
337
215
  const stage = flowState.currentStage;
338
216
  const schema = stageSchema(stage, flowState.track);
@@ -448,23 +326,6 @@ export async function verifyCurrentStageGateEvidence(projectRoot, flowState) {
448
326
  if (!securityAttestation.ok) {
449
327
  issues.push(`review security attestation failed: ${securityAttestation.errors.join("; ")}`);
450
328
  }
451
- const traceGateRequired = schema.requiredGates.some((gate) => gate.id === "review_trace_matrix_clean" && gate.tier === "required");
452
- if (traceGateRequired) {
453
- const trace = await buildTraceMatrix(projectRoot);
454
- const traceIssues = [];
455
- if (trace.orphanedCriteria.length > 0) {
456
- traceIssues.push(`orphaned criteria: ${trace.orphanedCriteria.join(", ")}`);
457
- }
458
- if (trace.orphanedTasks.length > 0) {
459
- traceIssues.push(`orphaned tasks: ${trace.orphanedTasks.join(", ")}`);
460
- }
461
- if (trace.orphanedTests.length > 0) {
462
- traceIssues.push(`orphaned tests: ${trace.orphanedTests.join(", ")}`);
463
- }
464
- if (traceIssues.length > 0) {
465
- issues.push(`review trace-matrix gate blocked (review_trace_matrix_clean): ${traceIssues.join("; ")}.`);
466
- }
467
- }
468
329
  }
469
330
  if (stage === "design") {
470
331
  const researchGateRequired = schema.requiredGates.some((gate) => gate.id === "design_research_complete" && gate.tier === "required");
@@ -739,47 +600,8 @@ export function reconcileCurrentStageGateCatalog(flowState) {
739
600
  export async function reconcileAndWriteCurrentStageGateCatalog(projectRoot) {
740
601
  const state = await readFlowState(projectRoot);
741
602
  const { nextState, reconciliation } = reconcileCurrentStageGateCatalog(state);
742
- const effectiveState = reconciliation.changed ? nextState : state;
743
603
  if (reconciliation.changed) {
744
- await writeFlowState(projectRoot, effectiveState);
745
- }
746
- const noticesPayload = await readReconciliationNotices(projectRoot);
747
- let noticesChanged = false;
748
- const noticeBuckets = classifyReconciliationNotices(effectiveState, noticesPayload.notices);
749
- if (noticeBuckets.unsynced.length > 0 || noticeBuckets.staleRun.length > 0) {
750
- const dropIds = new Set([...noticeBuckets.unsynced, ...noticeBuckets.staleRun].map((notice) => notice.id));
751
- noticesPayload.notices = noticesPayload.notices.filter((notice) => !dropIds.has(notice.id));
752
- noticesChanged = true;
753
- }
754
- if (reconciliation.demotedGateIds.length > 0) {
755
- const existing = new Set(noticesPayload.notices.map((notice) => `${notice.runId}:${notice.stage}:${notice.gateId}:${notice.kind ?? "gate_demotion"}`));
756
- for (const gateId of reconciliation.demotedGateIds) {
757
- const dedupeKey = `${effectiveState.activeRunId}:${reconciliation.stage}:${gateId}:gate_demotion`;
758
- if (existing.has(dedupeKey)) {
759
- continue;
760
- }
761
- const ts = new Date().toISOString();
762
- noticesPayload.notices.push({
763
- id: `${dedupeKey}:${ts}`,
764
- runId: effectiveState.activeRunId,
765
- stage: reconciliation.stage,
766
- gateId,
767
- reason: "demoted from passed to blocked during gate reconciliation (missing evidence)",
768
- demotedAt: ts,
769
- kind: "gate_demotion"
770
- });
771
- existing.add(dedupeKey);
772
- noticesChanged = true;
773
- }
774
- }
775
- if (noticesChanged) {
776
- noticesPayload.notices.sort((a, b) => {
777
- if (a.demotedAt === b.demotedAt) {
778
- return a.id.localeCompare(b.id);
779
- }
780
- return a.demotedAt.localeCompare(b.demotedAt);
781
- });
782
- await writeReconciliationNotices(projectRoot, noticesPayload);
604
+ await writeFlowState(projectRoot, nextState);
783
605
  }
784
606
  return {
785
607
  ...reconciliation,
@@ -378,7 +378,7 @@ When in doubt, prefer **non-trivial** — the quick track is opt-in and only saf
378
378
  Knowledge capture and curation run automatically as part of stage completion
379
379
  protocols via the internal \`learnings\` skill — no user-facing command.
380
380
  Reusable entries land in \`.cclaw/knowledge.jsonl\` as strict JSONL with
381
- \`type\`, \`trigger\`, \`action\`, and \`origin_run\` metadata.
381
+ \`type\`, \`trigger\`, \`action\`, \`confidence\`, \`stage\`, and \`origin_stage\` metadata.
382
382
 
383
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.
384
384
 
@@ -2,12 +2,11 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "cclaw://hooks/claude/v1",
4
4
  "harness": "claude",
5
- "schemaVersion": 1,
5
+ "schemaVersion": 2,
6
6
  "requiredEvents": [
7
7
  "SessionStart",
8
8
  "PreToolUse",
9
9
  "PostToolUse",
10
- "Stop",
11
- "PreCompact"
10
+ "Stop"
12
11
  ]
13
12
  }
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "cclaw://hooks/codex/v1",
4
4
  "harness": "codex",
5
- "schemaVersion": 1,
5
+ "schemaVersion": 2,
6
6
  "requiredEvents": [
7
7
  "SessionStart",
8
8
  "UserPromptSubmit",
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "cclaw://hooks/cursor/v1",
4
4
  "harness": "cursor",
5
- "schemaVersion": 1,
5
+ "schemaVersion": 2,
6
6
  "requiredEvents": [
7
7
  "sessionStart",
8
8
  "sessionResume",
package/dist/install.js CHANGED
@@ -6,7 +6,7 @@ import { CCLAW_VERSION, FLOW_VERSION, REQUIRED_DIRS, RUNTIME_ROOT } from "./cons
6
6
  import { writeConfig, createDefaultConfig, readConfig, configPath, detectLanguageRulePacks, detectAdvancedKeys } from "./config.js";
7
7
  import { learnSkillMarkdown } from "./content/learnings.js";
8
8
  import { stageCommandShimMarkdown } from "./content/stage-command.js";
9
- import { ideaCommandContract, ideaCommandSkillMarkdown } from "./content/idea-command.js";
9
+ import { ideaCommandContract, ideaCommandSkillMarkdown } from "./content/idea.js";
10
10
  import { startCommandContract, startCommandSkillMarkdown } from "./content/start-command.js";
11
11
  import { viewCommandContract, viewCommandSkillMarkdown } from "./content/view-command.js";
12
12
  import { cancelCommandContract, cancelCommandSkillMarkdown } from "./content/cancel-command.js";
@@ -19,7 +19,8 @@ import { META_SKILL_NAME, usingCclawSkillMarkdown } from "./content/meta-skill.j
19
19
  import { ARTIFACT_TEMPLATES, CURSOR_WORKFLOW_RULE_MDC, RULEBOOK_MARKDOWN, buildRulesJson } from "./content/templates.js";
20
20
  import { STATE_CONTRACTS } from "./content/state-contracts.js";
21
21
  import { REVIEW_PROMPTS } from "./content/review-prompts.js";
22
- import { stageSkillFolder, stageSkillMarkdown } from "./content/skills.js";
22
+ import { stageSkillFolder, stageSkillMarkdown, executingWavesSkillMarkdown } from "./content/skills.js";
23
+ import { adaptiveElicitationSkillMarkdown } from "./content/skills-elicitation.js";
23
24
  import { LANGUAGE_RULE_PACK_DIR, LANGUAGE_RULE_PACK_FILES, LANGUAGE_RULE_PACK_GENERATORS, LEGACY_LANGUAGE_RULE_PACK_FOLDERS } from "./content/utility-skills.js";
24
25
  import { RESEARCH_PLAYBOOKS } from "./content/research-playbooks.js";
25
26
  import { SUBAGENT_CONTEXT_SKILLS } from "./content/subagent-context-skills.js";
@@ -470,6 +471,9 @@ async function writeArtifactTemplates(projectRoot) {
470
471
  await writeFileSafe(runtimePath(projectRoot, "templates", "state-contracts", fileName), content);
471
472
  }));
472
473
  }
474
+ async function writeWavePlansScaffold(projectRoot) {
475
+ await writeFileSafe(runtimePath(projectRoot, "wave-plans", ".gitkeep"), "");
476
+ }
473
477
  async function writeSkills(projectRoot, config) {
474
478
  const skillTrack = config?.defaultTrack ?? "standard";
475
479
  for (const stage of FLOW_STAGES) {
@@ -486,6 +490,8 @@ async function writeSkills(projectRoot, config) {
486
490
  await writeFileSafe(runtimePath(projectRoot, "skills", "parallel-dispatch", "SKILL.md"), parallelAgentsSkill());
487
491
  await writeFileSafe(runtimePath(projectRoot, "skills", "session", "SKILL.md"), sessionHooksSkillMarkdown());
488
492
  await writeFileSafe(runtimePath(projectRoot, "skills", "iron-laws", "SKILL.md"), ironLawsSkillMarkdown());
493
+ await writeFileSafe(runtimePath(projectRoot, "skills", "executing-waves", "SKILL.md"), executingWavesSkillMarkdown());
494
+ await writeFileSafe(runtimePath(projectRoot, "skills", "adaptive-elicitation", "SKILL.md"), adaptiveElicitationSkillMarkdown());
489
495
  await writeFileSafe(runtimePath(projectRoot, "skills", META_SKILL_NAME, "SKILL.md"), usingCclawSkillMarkdown());
490
496
  // In-thread research procedures (no YAML frontmatter, not delegated personas).
491
497
  for (const [fileName, markdown] of Object.entries(RESEARCH_PLAYBOOKS)) {
@@ -897,6 +903,33 @@ async function writeMergedHookJson(projectRoot, hookFilePath, generatedJson) {
897
903
  }
898
904
  await writeFileSafe(hookFilePath, `${JSON.stringify(mergedDoc, null, 2)}\n`);
899
905
  }
906
+ async function readBundledRunHookRuntimeScript(options) {
907
+ const bundleUrl = new URL("./runtime/run-hook.mjs", import.meta.url);
908
+ try {
909
+ await fs.stat(bundleUrl);
910
+ }
911
+ catch {
912
+ return null;
913
+ }
914
+ try {
915
+ const moduleUrl = `${bundleUrl.href}?ts=${Date.now()}`;
916
+ const loaded = await import(moduleUrl);
917
+ const factory = typeof loaded.buildRunHookRuntimeScript === "function"
918
+ ? loaded.buildRunHookRuntimeScript
919
+ : typeof loaded.default === "function"
920
+ ? loaded.default
921
+ : null;
922
+ if (!factory)
923
+ return null;
924
+ const script = factory(options);
925
+ if (typeof script !== "string")
926
+ return null;
927
+ return script.trim().length > 0 ? script : null;
928
+ }
929
+ catch {
930
+ return null;
931
+ }
932
+ }
900
933
  async function writeHooks(projectRoot, config) {
901
934
  const harnesses = config.harnesses;
902
935
  const hooksDir = runtimePath(projectRoot, "hooks");
@@ -911,14 +944,16 @@ async function writeHooks(projectRoot, config) {
911
944
  await writeFileSafe(path.join(hooksDir, "stage-complete.mjs"), stageCompleteScript());
912
945
  await writeFileSafe(path.join(hooksDir, "start-flow.mjs"), startFlowScript());
913
946
  await writeFileSafe(path.join(hooksDir, "cancel-run.mjs"), cancelRunScript());
914
- await writeFileSafe(path.join(hooksDir, "run-hook.mjs"), nodeHookRuntimeScript({
947
+ const hookRuntimeOptions = {
915
948
  strictness: effectiveStrictness,
916
949
  tddTestPathPatterns: config.tdd?.testPathPatterns ?? config.tddTestGlobs,
917
950
  tddProductionPathPatterns: config.tdd?.productionPathPatterns,
918
951
  compoundRecurrenceThreshold: config.compound?.recurrenceThreshold,
919
952
  earlyLoopEnabled: config.earlyLoop?.enabled,
920
953
  earlyLoopMaxIterations: config.earlyLoop?.maxIterations
921
- }));
954
+ };
955
+ const bundledHookRuntime = await readBundledRunHookRuntimeScript(hookRuntimeOptions);
956
+ await writeFileSafe(path.join(hooksDir, "run-hook.mjs"), bundledHookRuntime ?? nodeHookRuntimeScript(hookRuntimeOptions));
922
957
  await writeFileSafe(path.join(hooksDir, "run-hook.cmd"), runHookCmdScript());
923
958
  await writeFileSafe(path.join(hooksDir, "delegation-record.mjs"), delegationRecordScript());
924
959
  const opencodePluginSource = opencodePluginJs();
@@ -1047,8 +1082,13 @@ async function syncDisabledHarnessArtifacts(projectRoot, harnesses) {
1047
1082
  }
1048
1083
  }
1049
1084
  async function writeState(projectRoot, config, forceReset = false) {
1085
+ // Fresh init no longer materializes flow-state.json. The first managed
1086
+ // `/cc <idea>` start-flow call creates the state file.
1087
+ if (!forceReset) {
1088
+ return;
1089
+ }
1050
1090
  const statePath = runtimePath(projectRoot, "state", "flow-state.json");
1051
- if (!forceReset && (await exists(statePath))) {
1091
+ if (await exists(statePath)) {
1052
1092
  return;
1053
1093
  }
1054
1094
  const state = createInitialFlowState({ track: config.defaultTrack ?? "standard" });
@@ -1196,6 +1236,7 @@ async function materializeRuntime(projectRoot, config, forceStateReset, operatio
1196
1236
  writeEntryCommands(projectRoot),
1197
1237
  writeSkills(projectRoot, config),
1198
1238
  writeArtifactTemplates(projectRoot),
1239
+ writeWavePlansScaffold(projectRoot),
1199
1240
  writeRulebook(projectRoot)
1200
1241
  ]);
1201
1242
  await writeState(projectRoot, config, forceStateReset);
@@ -1260,7 +1301,9 @@ export async function initCclaw(options) {
1260
1301
  // and only appear in the on-disk file when the user sets them explicitly
1261
1302
  // or a non-default value was detected (e.g. languageRulePacks).
1262
1303
  await writeConfig(options.projectRoot, config, { mode: "minimal" });
1263
- await materializeRuntime(options.projectRoot, config, true, "init");
1304
+ // Init should scaffold runtime surfaces but leave flow-state creation to the
1305
+ // first managed start-flow invocation.
1306
+ await materializeRuntime(options.projectRoot, config, false, "init");
1264
1307
  }
1265
1308
  export async function syncCclaw(projectRoot, options = {}) {
1266
1309
  if (options.harnesses !== undefined && options.harnesses.length === 0) {
@@ -1384,7 +1427,7 @@ function isManagedRuntimeHookCommand(command) {
1384
1427
  // (e.g. `node .cclaw\hooks\run-hook.mjs ...`) still round-trip through
1385
1428
  // sync without being duplicated alongside freshly generated entries.
1386
1429
  const normalized = command.trim().replace(/\s+/gu, " ").replace(/\\/gu, "/");
1387
- if (/(^|\s)(?:node\s+)?(?:"|')?(?:\.\/)?\.cclaw\/hooks\/run-hook\.(?:mjs|cmd)(?:"|')?\s+(?:session-start|stop-handoff|stop-checkpoint|pre-compact|prompt-guard|workflow-guard|context-monitor|verify-current-state)(?:\s|$)/u.test(normalized)) {
1430
+ if (/(^|\s)(?:node\s+)?(?:"|')?(?:\.\/)?\.cclaw\/hooks\/run-hook\.(?:mjs|cmd)(?:"|')?\s+(?:session-start|stop-handoff|stop-checkpoint|pre-compact|prompt-guard|workflow-guard|pre-tool-pipeline|prompt-pipeline|context-monitor|verify-current-state)(?:\s|$)/u.test(normalized)) {
1388
1431
  return true;
1389
1432
  }
1390
1433
  // Codex UserPromptSubmit non-blocking state nudge.
@@ -40,6 +40,23 @@ function resolveSuccessorTransition(stage, track, transitionTargets, satisfiedGu
40
40
  }
41
41
  return natural;
42
42
  }
43
+ function nextInteractionHints(flowState, args, successor) {
44
+ const hints = { ...(flowState.interactionHints ?? {}) };
45
+ delete hints[args.stage];
46
+ if (successor) {
47
+ if (args.skipQuestions) {
48
+ hints[successor] = {
49
+ skipQuestions: true,
50
+ sourceStage: args.stage,
51
+ recordedAt: new Date().toISOString()
52
+ };
53
+ }
54
+ else {
55
+ delete hints[successor];
56
+ }
57
+ }
58
+ return hints;
59
+ }
43
60
  export async function hydrateReviewLoopEvidenceFromArtifact(projectRoot, stage, track, selectedGateIds, evidenceByGate) {
44
61
  const gateId = AUTO_REVIEW_LOOP_GATE_BY_STAGE[stage];
45
62
  if (!gateId)
@@ -168,7 +185,6 @@ export async function harvestStageLearnings(projectRoot, stage, track) {
168
185
  const appendResult = await appendKnowledge(projectRoot, parsed.entries, {
169
186
  stage,
170
187
  originStage: stage,
171
- originRun: null,
172
188
  project: path.basename(projectRoot)
173
189
  });
174
190
  if (appendResult.invalid > 0) {
@@ -244,6 +260,7 @@ export async function runAdvanceStage(projectRoot, args, io) {
244
260
  mode: "mandatory",
245
261
  status: "waived",
246
262
  waiverReason,
263
+ runId: flowState.activeRunId,
247
264
  fulfillmentMode: "role-switch",
248
265
  ts: new Date().toISOString()
249
266
  });
@@ -453,10 +470,12 @@ export async function runAdvanceStage(projectRoot, args, io) {
453
470
  : flowState.completedStages.includes(args.stage)
454
471
  ? [...flowState.completedStages]
455
472
  : [...flowState.completedStages, args.stage];
473
+ const interactionHints = nextInteractionHints(flowState, args, successor);
456
474
  const finalState = {
457
475
  ...candidateState,
458
476
  completedStages,
459
- currentStage: successor ?? args.stage
477
+ currentStage: successor ?? args.stage,
478
+ interactionHints
460
479
  };
461
480
  await writeFlowState(projectRoot, finalState);
462
481
  if (!args.quiet) {
@@ -467,6 +486,7 @@ export async function runAdvanceStage(projectRoot, args, io) {
467
486
  nextStage: successor,
468
487
  currentStage: finalState.currentStage,
469
488
  completedStages: finalState.completedStages,
489
+ skipQuestionsHint: args.skipQuestions,
470
490
  learnings: {
471
491
  parsed: learningsHarvest.parsedEntries,
472
492
  appended: learningsHarvest.appendedEntries,
@@ -8,6 +8,7 @@ export interface AdvanceStageArgs {
8
8
  waiverReason?: string;
9
9
  acceptProactiveWaiver: boolean;
10
10
  acceptProactiveWaiverReason?: string;
11
+ skipQuestions: boolean;
11
12
  quiet: boolean;
12
13
  json: boolean;
13
14
  }
@@ -12,6 +12,7 @@ export function parseAdvanceStageArgs(tokens) {
12
12
  let waiverReason;
13
13
  let acceptProactiveWaiver = false;
14
14
  let acceptProactiveWaiverReason;
15
+ let skipQuestions = false;
15
16
  let quiet = false;
16
17
  let json = false;
17
18
  for (let i = 0; i < flagTokens.length; i += 1) {
@@ -80,6 +81,10 @@ export function parseAdvanceStageArgs(tokens) {
80
81
  acceptProactiveWaiver = true;
81
82
  continue;
82
83
  }
84
+ if (token === "--skip-questions") {
85
+ skipQuestions = true;
86
+ continue;
87
+ }
83
88
  if (token === "--accept-proactive-waiver-reason") {
84
89
  if (!nextToken || nextToken.startsWith("--")) {
85
90
  throw new Error("--accept-proactive-waiver-reason requires a text value.");
@@ -102,6 +107,7 @@ export function parseAdvanceStageArgs(tokens) {
102
107
  waiverReason,
103
108
  acceptProactiveWaiver,
104
109
  acceptProactiveWaiverReason,
110
+ skipQuestions,
105
111
  quiet,
106
112
  json
107
113
  };
@@ -1,5 +1,5 @@
1
1
  import { SHIP_FINALIZATION_MODES } from "../../constants.js";
2
- import { PASS_STATUS_PATTERN, TEST_COMMAND_HINT_PATTERN, validateTddVerificationEvidence } from "../../tdd-verification-evidence.js";
2
+ import { validateTddVerificationEvidence } from "../../tdd-verification-evidence.js";
3
3
  import { asRecord } from "./helpers.js";
4
4
  export const AUTO_REVIEW_LOOP_GATE_BY_STAGE = {
5
5
  design: "design_architecture_locked"
@@ -134,15 +134,6 @@ export function validateUserApprovalEvidence(evidence) {
134
134
  // guaranteed to carry the structural breadcrumbs downstream tooling
135
135
  // expects. Previously only `tdd:tdd_verified_before_complete` was checked.
136
136
  const GATE_EVIDENCE_VALIDATORS = {
137
- "review:review_trace_matrix_clean": (evidence) => {
138
- if (!TEST_COMMAND_HINT_PATTERN.test(evidence)) {
139
- return "must include the fresh verification command that was run before ship handoff (for example `npm test`, `pytest`, `go test`, or equivalent).";
140
- }
141
- if (!PASS_STATUS_PATTERN.test(evidence)) {
142
- return "must include explicit success status (for example `PASS` or `GREEN`).";
143
- }
144
- return null;
145
- },
146
137
  "ship:ship_finalization_executed": (evidence) => {
147
138
  if (!SHIP_FINALIZATION_MODE_PATTERN.test(evidence)) {
148
139
  return `must name the finalization mode that ran (for example ${SHIP_FINALIZATION_MODE_HINT}).`;