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.
- package/README.md +3 -3
- package/dist/artifact-linter/brainstorm.js +59 -1
- package/dist/artifact-linter/design.js +46 -1
- package/dist/artifact-linter/plan.js +22 -1
- package/dist/artifact-linter/review.js +35 -1
- package/dist/artifact-linter/scope.js +33 -9
- package/dist/artifact-linter/shared.d.ts +12 -10
- package/dist/artifact-linter/shared.js +102 -41
- package/dist/artifact-linter/ship.js +36 -0
- package/dist/artifact-linter/spec.js +23 -1
- package/dist/artifact-linter/tdd.js +74 -0
- package/dist/artifact-linter.d.ts +1 -1
- package/dist/artifact-linter.js +11 -1
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -0
- package/dist/content/closeout-guidance.d.ts +1 -1
- package/dist/content/closeout-guidance.js +10 -11
- package/dist/content/core-agents.d.ts +35 -36
- package/dist/content/core-agents.js +189 -99
- package/dist/content/diff-command.js +1 -1
- package/dist/content/examples.d.ts +0 -3
- package/dist/content/examples.js +197 -752
- package/dist/content/hook-events.js +1 -2
- package/dist/content/hook-manifest.d.ts +3 -4
- package/dist/content/hook-manifest.js +22 -25
- package/dist/content/hooks.js +54 -14
- package/dist/content/idea.d.ts +60 -0
- package/dist/content/idea.js +404 -0
- package/dist/content/learnings.d.ts +2 -4
- package/dist/content/learnings.js +10 -26
- package/dist/content/meta-skill.js +4 -3
- package/dist/content/node-hooks.js +368 -164
- package/dist/content/observe.js +3 -3
- package/dist/content/opencode-plugin.js +12 -32
- package/dist/content/reference-patterns.js +2 -2
- package/dist/content/runtime-shared-snippets.d.ts +8 -0
- package/dist/content/runtime-shared-snippets.js +80 -0
- package/dist/content/session-hooks.js +1 -1
- package/dist/content/skills-elicitation.d.ts +1 -0
- package/dist/content/skills-elicitation.js +123 -0
- package/dist/content/skills.d.ts +1 -0
- package/dist/content/skills.js +54 -2
- package/dist/content/stage-schema.js +107 -63
- package/dist/content/stages/brainstorm.js +7 -3
- package/dist/content/stages/design.js +4 -0
- package/dist/content/stages/review.js +8 -8
- package/dist/content/stages/schema-types.d.ts +2 -2
- package/dist/content/stages/scope.js +7 -3
- package/dist/content/stages/ship.js +1 -1
- package/dist/content/start-command.js +4 -4
- package/dist/content/status-command.js +3 -3
- package/dist/content/subagent-context-skills.js +156 -1
- package/dist/content/subagents.d.ts +0 -5
- package/dist/content/subagents.js +12 -82
- package/dist/content/templates.js +108 -6
- package/dist/content/utility-skills.js +26 -97
- package/dist/flow-state.d.ts +12 -6
- package/dist/flow-state.js +5 -6
- package/dist/gate-evidence.d.ts +0 -31
- package/dist/gate-evidence.js +3 -181
- package/dist/harness-adapters.js +1 -1
- package/dist/hook-schemas/claude-hooks.v1.json +2 -3
- package/dist/hook-schemas/codex-hooks.v1.json +1 -1
- package/dist/hook-schemas/cursor-hooks.v1.json +1 -1
- package/dist/install.js +50 -7
- package/dist/internal/advance-stage/advance.js +22 -2
- package/dist/internal/advance-stage/parsers.d.ts +1 -0
- package/dist/internal/advance-stage/parsers.js +6 -0
- package/dist/internal/advance-stage/review-loop.js +1 -10
- package/dist/knowledge-store.d.ts +2 -20
- package/dist/knowledge-store.js +43 -57
- package/dist/policy.js +3 -3
- package/dist/retro-gate.js +8 -90
- package/dist/run-archive.js +1 -4
- package/dist/run-persistence.d.ts +1 -1
- package/dist/run-persistence.js +43 -111
- package/dist/runtime/run-hook.entry.d.ts +3 -0
- package/dist/runtime/run-hook.entry.js +5 -0
- package/dist/runtime/run-hook.mjs +9647 -0
- package/dist/track-heuristics.d.ts +7 -1
- package/dist/track-heuristics.js +12 -0
- package/package.json +4 -2
- package/dist/content/hook-inline-snippets.d.ts +0 -96
- package/dist/content/hook-inline-snippets.js +0 -515
- package/dist/content/idea-command.d.ts +0 -8
- package/dist/content/idea-command.js +0 -322
- package/dist/content/idea-frames.d.ts +0 -31
- package/dist/content/idea-frames.js +0 -140
- package/dist/content/idea-ranking.d.ts +0 -25
- package/dist/content/idea-ranking.js +0 -65
- package/dist/trace-matrix.d.ts +0 -27
- package/dist/trace-matrix.js +0 -226
package/dist/flow-state.d.ts
CHANGED
|
@@ -32,14 +32,13 @@ export interface RetroState {
|
|
|
32
32
|
/**
|
|
33
33
|
* Ship closeout substate machine.
|
|
34
34
|
*
|
|
35
|
-
* After ship completes, cclaw auto-chains
|
|
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
|
-
* - `
|
|
41
|
-
*
|
|
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", "
|
|
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 →
|
|
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;
|
package/dist/flow-state.js
CHANGED
|
@@ -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
|
|
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
|
-
* - `
|
|
14
|
-
*
|
|
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
|
-
"
|
|
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,
|
package/dist/gate-evidence.d.ts
CHANGED
|
@@ -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 {
|
package/dist/gate-evidence.js
CHANGED
|
@@ -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 {
|
|
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,
|
|
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,
|
package/dist/harness-adapters.js
CHANGED
|
@@ -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 \`
|
|
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":
|
|
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
|
}
|
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
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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,
|
|
@@ -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 {
|
|
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}).`;
|