cclaw-cli 0.51.29 → 0.55.2
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 +22 -16
- package/dist/artifact-linter/brainstorm.d.ts +2 -0
- package/dist/artifact-linter/brainstorm.js +245 -0
- package/dist/artifact-linter/design.d.ts +2 -0
- package/dist/artifact-linter/design.js +323 -0
- package/dist/artifact-linter/plan.d.ts +2 -0
- package/dist/artifact-linter/plan.js +162 -0
- package/dist/artifact-linter/review-army.d.ts +24 -0
- package/dist/artifact-linter/review-army.js +365 -0
- package/dist/artifact-linter/review.d.ts +2 -0
- package/dist/artifact-linter/review.js +65 -0
- package/dist/artifact-linter/scope.d.ts +2 -0
- package/dist/artifact-linter/scope.js +115 -0
- package/dist/artifact-linter/shared.d.ts +246 -0
- package/dist/artifact-linter/shared.js +1488 -0
- package/dist/artifact-linter/ship.d.ts +2 -0
- package/dist/artifact-linter/ship.js +46 -0
- package/dist/artifact-linter/spec.d.ts +2 -0
- package/dist/artifact-linter/spec.js +108 -0
- package/dist/artifact-linter/tdd.d.ts +2 -0
- package/dist/artifact-linter/tdd.js +124 -0
- package/dist/artifact-linter.d.ts +4 -76
- package/dist/artifact-linter.js +56 -2949
- package/dist/cli.d.ts +2 -18
- package/dist/cli.js +8 -246
- package/dist/codex-feature-flag.d.ts +1 -1
- package/dist/codex-feature-flag.js +1 -1
- package/dist/config.d.ts +3 -2
- package/dist/config.js +67 -3
- package/dist/constants.d.ts +1 -7
- package/dist/constants.js +9 -15
- package/dist/content/cancel-command.js +2 -2
- package/dist/content/closeout-guidance.js +13 -10
- package/dist/content/core-agents.d.ts +18 -0
- package/dist/content/core-agents.js +51 -7
- package/dist/content/decision-protocol.d.ts +1 -1
- package/dist/content/decision-protocol.js +1 -1
- package/dist/content/examples.js +6 -6
- package/dist/content/harness-doc.js +20 -2
- package/dist/content/hook-inline-snippets.d.ts +17 -4
- package/dist/content/hook-inline-snippets.js +218 -5
- package/dist/content/hook-manifest.d.ts +2 -2
- package/dist/content/hook-manifest.js +2 -2
- package/dist/content/hooks.d.ts +1 -0
- package/dist/content/hooks.js +32 -137
- package/dist/content/idea-command.d.ts +8 -0
- package/dist/content/{ideate-command.js → idea-command.js} +57 -50
- package/dist/content/idea-frames.d.ts +31 -0
- package/dist/content/{ideate-frames.js → idea-frames.js} +9 -9
- package/dist/content/idea-ranking.d.ts +25 -0
- package/dist/content/{ideate-ranking.js → idea-ranking.js} +5 -5
- package/dist/content/iron-laws.d.ts +0 -1
- package/dist/content/iron-laws.js +31 -16
- package/dist/content/learnings.js +1 -1
- package/dist/content/meta-skill.js +11 -13
- package/dist/content/node-hooks.d.ts +10 -0
- package/dist/content/node-hooks.js +45 -11
- package/dist/content/opencode-plugin.js +3 -3
- package/dist/content/session-hooks.js +1 -1
- package/dist/content/skills.js +19 -7
- package/dist/content/stage-command.js +1 -1
- package/dist/content/stage-schema.js +44 -2
- package/dist/content/stages/_lint-metadata/index.js +26 -2
- package/dist/content/stages/brainstorm.js +13 -7
- package/dist/content/stages/design.js +16 -11
- package/dist/content/stages/plan.js +9 -6
- package/dist/content/stages/review.js +4 -4
- package/dist/content/stages/schema-types.d.ts +1 -1
- package/dist/content/stages/scope.js +15 -12
- package/dist/content/stages/ship.js +2 -2
- package/dist/content/stages/spec.js +9 -3
- package/dist/content/stages/tdd.js +14 -4
- package/dist/content/start-command.d.ts +2 -2
- package/dist/content/start-command.js +24 -21
- package/dist/content/status-command.js +8 -8
- package/dist/content/subagents.js +61 -7
- package/dist/content/templates.d.ts +1 -1
- package/dist/content/templates.js +104 -152
- package/dist/content/tree-command.js +2 -2
- package/dist/content/utility-skills.d.ts +2 -2
- package/dist/content/utility-skills.js +2 -2
- package/dist/content/view-command.js +4 -2
- package/dist/delegation.d.ts +2 -0
- package/dist/delegation.js +2 -1
- package/dist/early-loop.d.ts +66 -0
- package/dist/early-loop.js +275 -0
- package/dist/flow-state.d.ts +1 -1
- package/dist/flow-state.js +1 -1
- package/dist/gate-evidence.d.ts +8 -0
- package/dist/gate-evidence.js +141 -5
- package/dist/harness-adapters.d.ts +2 -2
- package/dist/harness-adapters.js +54 -122
- package/dist/harness-selection.d.ts +31 -0
- package/dist/harness-selection.js +214 -0
- package/dist/install.js +166 -38
- package/dist/internal/advance-stage/advance.d.ts +50 -0
- package/dist/internal/advance-stage/advance.js +480 -0
- package/dist/internal/advance-stage/cancel-run.d.ts +8 -0
- package/dist/internal/advance-stage/cancel-run.js +19 -0
- package/dist/internal/advance-stage/flow-state-coercion.d.ts +3 -0
- package/dist/internal/advance-stage/flow-state-coercion.js +81 -0
- package/dist/internal/advance-stage/helpers.d.ts +14 -0
- package/dist/internal/advance-stage/helpers.js +145 -0
- package/dist/internal/advance-stage/hook.d.ts +8 -0
- package/dist/internal/advance-stage/hook.js +40 -0
- package/dist/internal/advance-stage/parsers.d.ts +54 -0
- package/dist/internal/advance-stage/parsers.js +307 -0
- package/dist/internal/advance-stage/review-loop.d.ts +7 -0
- package/dist/internal/advance-stage/review-loop.js +170 -0
- package/dist/internal/advance-stage/rewind.d.ts +14 -0
- package/dist/internal/advance-stage/rewind.js +108 -0
- package/dist/internal/advance-stage/start-flow.d.ts +11 -0
- package/dist/internal/advance-stage/start-flow.js +136 -0
- package/dist/internal/advance-stage/verify.d.ts +29 -0
- package/dist/internal/advance-stage/verify.js +225 -0
- package/dist/internal/advance-stage.js +21 -1470
- package/dist/internal/compound-readiness.d.ts +1 -1
- package/dist/internal/compound-readiness.js +2 -2
- package/dist/internal/early-loop-status.d.ts +7 -0
- package/dist/internal/early-loop-status.js +90 -0
- package/dist/internal/runtime-integrity.d.ts +7 -0
- package/dist/internal/runtime-integrity.js +288 -0
- package/dist/internal/tdd-red-evidence.js +1 -1
- package/dist/knowledge-store.d.ts +3 -8
- package/dist/knowledge-store.js +16 -29
- package/dist/managed-resources.js +24 -2
- package/dist/policy.js +5 -7
- package/dist/run-archive.d.ts +1 -1
- package/dist/run-archive.js +16 -16
- package/dist/run-persistence.js +112 -12
- package/dist/tdd-cycle.d.ts +3 -3
- package/dist/tdd-cycle.js +1 -1
- package/dist/types.d.ts +18 -10
- package/package.json +1 -1
- package/dist/content/finish-command.d.ts +0 -2
- package/dist/content/finish-command.js +0 -26
- package/dist/content/ideate-command.d.ts +0 -8
- package/dist/content/ideate-frames.d.ts +0 -31
- package/dist/content/ideate-ranking.d.ts +0 -25
- package/dist/content/next-command.d.ts +0 -20
- package/dist/content/next-command.js +0 -298
- package/dist/content/seed-shelf.d.ts +0 -36
- package/dist/content/seed-shelf.js +0 -301
- package/dist/content/stage-common-guidance.d.ts +0 -1
- package/dist/content/stage-common-guidance.js +0 -106
- package/dist/doctor-registry.d.ts +0 -10
- package/dist/doctor-registry.js +0 -186
- package/dist/doctor.d.ts +0 -17
- package/dist/doctor.js +0 -2206
- package/dist/internal/hook-manifest.d.ts +0 -16
- package/dist/internal/hook-manifest.js +0 -77
package/dist/run-archive.js
CHANGED
|
@@ -7,7 +7,7 @@ import { readKnowledgeSafely } from "./knowledge-store.js";
|
|
|
7
7
|
import { evaluateRetroGate } from "./retro-gate.js";
|
|
8
8
|
import { ensureRunSystem, flowStateLockPathFor, readFlowState, writeFlowState } from "./run-persistence.js";
|
|
9
9
|
export const ARCHIVE_DISPOSITIONS = ["completed", "cancelled", "abandoned"];
|
|
10
|
-
const
|
|
10
|
+
const ARCHIVE_DIR_REL_PATH = `${RUNTIME_ROOT}/archive`;
|
|
11
11
|
const ACTIVE_ARTIFACTS_REL_PATH = `${RUNTIME_ROOT}/artifacts`;
|
|
12
12
|
const STATE_DIR_REL_PATH = `${RUNTIME_ROOT}/state`;
|
|
13
13
|
/** State filenames explicitly excluded from the archive snapshot. */
|
|
@@ -24,8 +24,8 @@ const CRITICAL_STATE_SNAPSHOT_FILES = new Set([
|
|
|
24
24
|
TDD_CYCLE_LOG_FILE,
|
|
25
25
|
RECONCILIATION_NOTICES_FILE
|
|
26
26
|
]);
|
|
27
|
-
function
|
|
28
|
-
return path.join(projectRoot,
|
|
27
|
+
function archiveRoot(projectRoot) {
|
|
28
|
+
return path.join(projectRoot, ARCHIVE_DIR_REL_PATH);
|
|
29
29
|
}
|
|
30
30
|
function activeArtifactsPath(projectRoot) {
|
|
31
31
|
return path.join(projectRoot, ACTIVE_ARTIFACTS_REL_PATH);
|
|
@@ -150,14 +150,14 @@ async function inferRunNameFromArtifacts(projectRoot) {
|
|
|
150
150
|
async function uniqueArchiveId(projectRoot, baseId) {
|
|
151
151
|
let index = 1;
|
|
152
152
|
let candidate = baseId;
|
|
153
|
-
while (await exists(path.join(
|
|
153
|
+
while (await exists(path.join(archiveRoot(projectRoot), candidate))) {
|
|
154
154
|
index += 1;
|
|
155
155
|
candidate = `${baseId}-${index}`;
|
|
156
156
|
}
|
|
157
157
|
return candidate;
|
|
158
158
|
}
|
|
159
159
|
export async function listRuns(projectRoot) {
|
|
160
|
-
const root =
|
|
160
|
+
const root = archiveRoot(projectRoot);
|
|
161
161
|
if (!(await exists(root))) {
|
|
162
162
|
return [];
|
|
163
163
|
}
|
|
@@ -194,15 +194,15 @@ export async function archiveRun(projectRoot, runName, options = {}) {
|
|
|
194
194
|
return withDirectoryLock(archiveLockPath(projectRoot), async () => {
|
|
195
195
|
return withDirectoryLock(flowStateLockPathFor(projectRoot), async () => {
|
|
196
196
|
const artifactsDir = activeArtifactsPath(projectRoot);
|
|
197
|
-
const
|
|
198
|
-
await ensureDir(
|
|
197
|
+
const archiveDir = archiveRoot(projectRoot);
|
|
198
|
+
await ensureDir(archiveDir);
|
|
199
199
|
await ensureDir(artifactsDir);
|
|
200
200
|
const archiveRunName = (runName?.trim() && runName.trim().length > 0)
|
|
201
201
|
? runName.trim()
|
|
202
202
|
: await inferRunNameFromArtifacts(projectRoot);
|
|
203
203
|
const archiveBaseId = `${toArchiveDate()}-${slugifyRunName(archiveRunName)}`;
|
|
204
204
|
const archiveId = await uniqueArchiveId(projectRoot, archiveBaseId);
|
|
205
|
-
const archivePath = path.join(
|
|
205
|
+
const archivePath = path.join(archiveDir, archiveId);
|
|
206
206
|
const archiveArtifactsPath = path.join(archivePath, "artifacts");
|
|
207
207
|
let sourceState = await readFlowState(projectRoot);
|
|
208
208
|
const retroGate = await evaluateRetroGate(projectRoot, sourceState);
|
|
@@ -232,20 +232,20 @@ export async function archiveRun(projectRoot, runName, options = {}) {
|
|
|
232
232
|
}
|
|
233
233
|
if (!nonCompletedDisposition && inShipCloseout && skipRetro) {
|
|
234
234
|
throw new Error("Archive blocked: --skip-retro is not allowed while current stage is ship. " +
|
|
235
|
-
"Complete closeout to ready_to_archive via /cc
|
|
235
|
+
"Complete closeout to ready_to_archive via /cc.");
|
|
236
236
|
}
|
|
237
237
|
if (!nonCompletedDisposition && inShipCloseout && !readyForArchive) {
|
|
238
238
|
throw new Error("Archive blocked: closeout is not ready_to_archive. " +
|
|
239
|
-
"Resume /cc
|
|
239
|
+
"Resume /cc until closeout reaches ready_to_archive.");
|
|
240
240
|
}
|
|
241
241
|
if (!nonCompletedDisposition && shipCompleted && !readyForArchive && !skipRetro) {
|
|
242
242
|
throw new Error("Archive blocked: closeout is not ready_to_archive. " +
|
|
243
|
-
"Resume /cc
|
|
243
|
+
"Resume /cc until closeout reaches ready_to_archive, " +
|
|
244
244
|
"or run `cclaw archive --skip-retro --retro-reason=<text>` for CLI-only flows.");
|
|
245
245
|
}
|
|
246
246
|
if (!nonCompletedDisposition && retroGate.required && !retroGate.completed && !skipRetro && !retroSkippedInCloseout) {
|
|
247
247
|
throw new Error("Archive blocked: retro gate is required after ship completion. " +
|
|
248
|
-
"Run /cc
|
|
248
|
+
"Run /cc (auto-runs retro) or, for CLI-only flows, re-run `cclaw archive --skip-retro --retro-reason=<text>`.");
|
|
249
249
|
}
|
|
250
250
|
if (retroGate.completed) {
|
|
251
251
|
const completedAt = sourceState.retro.completedAt ?? new Date().toISOString();
|
|
@@ -273,7 +273,7 @@ export async function archiveRun(projectRoot, runName, options = {}) {
|
|
|
273
273
|
await ensureDir(archivePath);
|
|
274
274
|
// Drop an `.archive-in-progress` sentinel immediately so that a crash
|
|
275
275
|
// between the artifact rename and the final manifest write leaves a
|
|
276
|
-
// recoverable marker (
|
|
276
|
+
// recoverable marker (sync reports these; re-running archive on an
|
|
277
277
|
// orphan attempts to complete or roll back). The sentinel is removed
|
|
278
278
|
// only after the manifest lands successfully.
|
|
279
279
|
const sentinelPath = path.join(archivePath, ".archive-in-progress");
|
|
@@ -326,7 +326,7 @@ export async function archiveRun(projectRoot, runName, options = {}) {
|
|
|
326
326
|
// Best-effort rollback: if artifacts were moved but the subsequent
|
|
327
327
|
// steps failed, put artifacts back so the user is not left without
|
|
328
328
|
// a working run. The sentinel is intentionally left behind for
|
|
329
|
-
// inspection;
|
|
329
|
+
// inspection; sync reports it.
|
|
330
330
|
if (artifactsMoved) {
|
|
331
331
|
try {
|
|
332
332
|
await fs.rm(artifactsDir, { recursive: true, force: true });
|
|
@@ -334,7 +334,7 @@ export async function archiveRun(projectRoot, runName, options = {}) {
|
|
|
334
334
|
}
|
|
335
335
|
catch {
|
|
336
336
|
// Rollback failed — sentinel + orphaned archive dir will be
|
|
337
|
-
// surfaced by
|
|
337
|
+
// surfaced by sync and can be reconciled manually.
|
|
338
338
|
}
|
|
339
339
|
}
|
|
340
340
|
if (stateReset) {
|
|
@@ -371,7 +371,7 @@ async function readKnowledgeStats(projectRoot) {
|
|
|
371
371
|
* Counts entries in the canonical JSONL knowledge store. An "active" entry is one
|
|
372
372
|
* non-empty line that parses as JSON with the required `type` field belonging to the
|
|
373
373
|
* allowed set. Malformed lines are ignored (not counted) but do not throw so that a
|
|
374
|
-
* hand-edited file cannot break
|
|
374
|
+
* hand-edited file cannot break sync/archive flows.
|
|
375
375
|
*/
|
|
376
376
|
export function countActiveKnowledgeEntries(text) {
|
|
377
377
|
const allowed = new Set(["rule", "pattern", "lesson", "compound"]);
|
package/dist/run-persistence.js
CHANGED
|
@@ -15,8 +15,13 @@ export class InvalidStageTransitionError extends Error {
|
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
17
|
const FLOW_STATE_REL_PATH = `${RUNTIME_ROOT}/state/flow-state.json`;
|
|
18
|
-
const
|
|
18
|
+
const ARCHIVE_DIR_REL_PATH = `${RUNTIME_ROOT}/archive`;
|
|
19
19
|
const ACTIVE_ARTIFACTS_REL_PATH = `${RUNTIME_ROOT}/artifacts`;
|
|
20
|
+
const RECONCILIATION_NOTICES_REL_PATH = `${RUNTIME_ROOT}/state/reconciliation-notices.json`;
|
|
21
|
+
const RECONCILIATION_NOTICES_LOCK_REL_PATH = `${RUNTIME_ROOT}/state/.reconciliation-notices.lock`;
|
|
22
|
+
const RECONCILIATION_NOTICES_SCHEMA_VERSION = 1;
|
|
23
|
+
const CLOSEOUT_SUBSTATE_DEMOTION_KIND = "closeout_substate_demotion";
|
|
24
|
+
const CLOSEOUT_SUBSTATE_GATE_ID = "closeout.shipSubstate";
|
|
20
25
|
const FLOW_STAGE_SET = new Set(FLOW_STAGES);
|
|
21
26
|
function validateFlowTransition(prev, next) {
|
|
22
27
|
if (prev.activeRunId !== next.activeRunId) {
|
|
@@ -60,7 +65,7 @@ function validateFlowTransition(prev, next) {
|
|
|
60
65
|
const isNaturalForward = naturalForward === next.currentStage;
|
|
61
66
|
const isReviewRewind = prev.currentStage === "review" && next.currentStage === "tdd";
|
|
62
67
|
if (!isNaturalForward && !isReviewRewind) {
|
|
63
|
-
throw new InvalidStageTransitionError(prev.currentStage, next.currentStage, `no transition rule allows "${prev.currentStage}" -> "${next.currentStage}" for track "${prev.track}". Use /cc
|
|
68
|
+
throw new InvalidStageTransitionError(prev.currentStage, next.currentStage, `no transition rule allows "${prev.currentStage}" -> "${next.currentStage}" for track "${prev.track}". Use /cc to advance stages or archive the run to reset.`);
|
|
64
69
|
}
|
|
65
70
|
}
|
|
66
71
|
function flowStatePath(projectRoot) {
|
|
@@ -69,12 +74,88 @@ function flowStatePath(projectRoot) {
|
|
|
69
74
|
function flowStateLockPath(projectRoot) {
|
|
70
75
|
return path.join(projectRoot, RUNTIME_ROOT, "state", ".flow-state.lock");
|
|
71
76
|
}
|
|
72
|
-
function
|
|
73
|
-
return path.join(projectRoot,
|
|
77
|
+
function reconciliationNoticesPath(projectRoot) {
|
|
78
|
+
return path.join(projectRoot, RECONCILIATION_NOTICES_REL_PATH);
|
|
79
|
+
}
|
|
80
|
+
function reconciliationNoticesLockPath(projectRoot) {
|
|
81
|
+
return path.join(projectRoot, RECONCILIATION_NOTICES_LOCK_REL_PATH);
|
|
82
|
+
}
|
|
83
|
+
function archiveRoot(projectRoot) {
|
|
84
|
+
return path.join(projectRoot, ARCHIVE_DIR_REL_PATH);
|
|
74
85
|
}
|
|
75
86
|
function activeArtifactsPath(projectRoot) {
|
|
76
87
|
return path.join(projectRoot, ACTIVE_ARTIFACTS_REL_PATH);
|
|
77
88
|
}
|
|
89
|
+
function asObjectRecord(value) {
|
|
90
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
91
|
+
return null;
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
async function appendCloseoutSubstateDemotionNotice(projectRoot, state, demotion) {
|
|
95
|
+
await withDirectoryLock(reconciliationNoticesLockPath(projectRoot), async () => {
|
|
96
|
+
const filePath = reconciliationNoticesPath(projectRoot);
|
|
97
|
+
const existingNotices = [];
|
|
98
|
+
if (await exists(filePath)) {
|
|
99
|
+
try {
|
|
100
|
+
const raw = JSON.parse(await fs.readFile(filePath, "utf8"));
|
|
101
|
+
const parsed = asObjectRecord(raw);
|
|
102
|
+
const noticesRaw = parsed?.notices;
|
|
103
|
+
if (Array.isArray(noticesRaw)) {
|
|
104
|
+
for (const notice of noticesRaw) {
|
|
105
|
+
const typed = asObjectRecord(notice);
|
|
106
|
+
if (typed)
|
|
107
|
+
existingNotices.push(typed);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// Keep going with an empty payload; sync can still report parse errors.
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const alreadyRecorded = existingNotices.some((notice) => {
|
|
116
|
+
if (notice.runId !== state.activeRunId)
|
|
117
|
+
return false;
|
|
118
|
+
if (notice.kind !== CLOSEOUT_SUBSTATE_DEMOTION_KIND)
|
|
119
|
+
return false;
|
|
120
|
+
const payload = asObjectRecord(notice.payload);
|
|
121
|
+
return payload?.previous === demotion.previous &&
|
|
122
|
+
payload?.next === demotion.next &&
|
|
123
|
+
payload?.reason === demotion.reason;
|
|
124
|
+
});
|
|
125
|
+
if (alreadyRecorded)
|
|
126
|
+
return;
|
|
127
|
+
const ts = new Date().toISOString();
|
|
128
|
+
existingNotices.push({
|
|
129
|
+
id: `${state.activeRunId}:${state.currentStage}:${CLOSEOUT_SUBSTATE_GATE_ID}:${CLOSEOUT_SUBSTATE_DEMOTION_KIND}:${ts}`,
|
|
130
|
+
runId: state.activeRunId,
|
|
131
|
+
stage: state.currentStage,
|
|
132
|
+
gateId: CLOSEOUT_SUBSTATE_GATE_ID,
|
|
133
|
+
reason: demotion.reason,
|
|
134
|
+
demotedAt: ts,
|
|
135
|
+
kind: CLOSEOUT_SUBSTATE_DEMOTION_KIND,
|
|
136
|
+
payload: {
|
|
137
|
+
previous: demotion.previous,
|
|
138
|
+
next: demotion.next,
|
|
139
|
+
reason: demotion.reason
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
existingNotices.sort((left, right) => {
|
|
143
|
+
const leftTs = typeof left.demotedAt === "string" ? left.demotedAt : "";
|
|
144
|
+
const rightTs = typeof right.demotedAt === "string" ? right.demotedAt : "";
|
|
145
|
+
if (leftTs === rightTs) {
|
|
146
|
+
const leftId = typeof left.id === "string" ? left.id : "";
|
|
147
|
+
const rightId = typeof right.id === "string" ? right.id : "";
|
|
148
|
+
return leftId.localeCompare(rightId);
|
|
149
|
+
}
|
|
150
|
+
return leftTs.localeCompare(rightTs);
|
|
151
|
+
});
|
|
152
|
+
await ensureDir(path.dirname(filePath));
|
|
153
|
+
await writeFileSafe(filePath, `${JSON.stringify({
|
|
154
|
+
schemaVersion: RECONCILIATION_NOTICES_SCHEMA_VERSION,
|
|
155
|
+
notices: existingNotices
|
|
156
|
+
}, null, 2)}\n`, { mode: 0o600 });
|
|
157
|
+
});
|
|
158
|
+
}
|
|
78
159
|
function isFlowStage(value) {
|
|
79
160
|
return typeof value === "string" && FLOW_STAGE_SET.has(value);
|
|
80
161
|
}
|
|
@@ -257,13 +338,14 @@ function sanitizeRetroState(value) {
|
|
|
257
338
|
function isShipSubstate(value) {
|
|
258
339
|
return typeof value === "string" && SHIP_SUBSTATES.includes(value);
|
|
259
340
|
}
|
|
260
|
-
function sanitizeCloseoutState(value) {
|
|
341
|
+
function sanitizeCloseoutState(value, onDemotion) {
|
|
261
342
|
const fallback = createInitialCloseoutState();
|
|
262
343
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
263
344
|
return fallback;
|
|
264
345
|
}
|
|
265
346
|
const typed = value;
|
|
266
347
|
let shipSubstate = isShipSubstate(typed.shipSubstate) ? typed.shipSubstate : fallback.shipSubstate;
|
|
348
|
+
const previousShipSubstate = shipSubstate;
|
|
267
349
|
const retroDraftedAt = typeof typed.retroDraftedAt === "string" ? typed.retroDraftedAt : undefined;
|
|
268
350
|
const retroAcceptedAt = typeof typed.retroAcceptedAt === "string" ? typed.retroAcceptedAt : undefined;
|
|
269
351
|
const retroSkipReason = typeof typed.retroSkipReason === "string"
|
|
@@ -288,11 +370,21 @@ function sanitizeCloseoutState(value) {
|
|
|
288
370
|
// the compound leg, which would let `archive` skip durable closeout proof.
|
|
289
371
|
const retroDone = retroAcceptedAt !== undefined || retroSkipped === true;
|
|
290
372
|
const compoundDone = compoundCompletedAt !== undefined || compoundPromoted > 0 || compoundSkipped === true;
|
|
373
|
+
let demotionReason;
|
|
291
374
|
if (!retroDone && (shipSubstate === "ready_to_archive" || shipSubstate === "compound_review")) {
|
|
292
375
|
shipSubstate = "retro_review";
|
|
376
|
+
demotionReason = "retro closeout leg is incomplete (missing retroAcceptedAt or explicit retro skip).";
|
|
293
377
|
}
|
|
294
378
|
else if (shipSubstate === "ready_to_archive" && !compoundDone) {
|
|
295
379
|
shipSubstate = "compound_review";
|
|
380
|
+
demotionReason = "compound closeout leg is incomplete (missing compound proof).";
|
|
381
|
+
}
|
|
382
|
+
if (demotionReason && previousShipSubstate !== shipSubstate) {
|
|
383
|
+
onDemotion?.({
|
|
384
|
+
previous: previousShipSubstate,
|
|
385
|
+
next: shipSubstate,
|
|
386
|
+
reason: demotionReason
|
|
387
|
+
});
|
|
296
388
|
}
|
|
297
389
|
return {
|
|
298
390
|
shipSubstate,
|
|
@@ -313,7 +405,8 @@ function coerceFlowState(parsed) {
|
|
|
313
405
|
const activeRunId = typeof activeRunIdRaw === "string" && activeRunIdRaw.trim().length > 0
|
|
314
406
|
? activeRunIdRaw.trim()
|
|
315
407
|
: next.activeRunId;
|
|
316
|
-
|
|
408
|
+
let closeoutDemotion;
|
|
409
|
+
const state = {
|
|
317
410
|
schemaVersion: FLOW_STATE_SCHEMA_VERSION,
|
|
318
411
|
activeRunId,
|
|
319
412
|
currentStage: isFlowStage(parsed.currentStage) ? parsed.currentStage : next.currentStage,
|
|
@@ -325,8 +418,11 @@ function coerceFlowState(parsed) {
|
|
|
325
418
|
staleStages: sanitizeStaleStages(parsed.staleStages),
|
|
326
419
|
rewinds: sanitizeRewinds(parsed.rewinds),
|
|
327
420
|
retro: sanitizeRetroState(parsed.retro),
|
|
328
|
-
closeout: sanitizeCloseoutState(parsed.closeout)
|
|
421
|
+
closeout: sanitizeCloseoutState(parsed.closeout, (demotion) => {
|
|
422
|
+
closeoutDemotion = demotion;
|
|
423
|
+
})
|
|
329
424
|
};
|
|
425
|
+
return { state, closeoutDemotion };
|
|
330
426
|
}
|
|
331
427
|
export class CorruptFlowStateError extends Error {
|
|
332
428
|
statePath;
|
|
@@ -388,7 +484,11 @@ export async function readFlowState(projectRoot, options = {}) {
|
|
|
388
484
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
389
485
|
await quarantineCorruptState(statePath, new Error("flow-state.json did not deserialize to a JSON object"));
|
|
390
486
|
}
|
|
391
|
-
|
|
487
|
+
const coerced = coerceFlowState(parsed);
|
|
488
|
+
if (coerced.closeoutDemotion) {
|
|
489
|
+
await appendCloseoutSubstateDemotionNotice(projectRoot, coerced.state, coerced.closeoutDemotion);
|
|
490
|
+
}
|
|
491
|
+
return coerced.state;
|
|
392
492
|
}
|
|
393
493
|
export async function writeFlowState(projectRoot, state, options = {}) {
|
|
394
494
|
const doWrite = async () => {
|
|
@@ -398,7 +498,7 @@ export async function writeFlowState(projectRoot, state, options = {}) {
|
|
|
398
498
|
const raw = await fs.readFile(statePath, "utf8");
|
|
399
499
|
const parsed = JSON.parse(raw);
|
|
400
500
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
401
|
-
const prev = coerceFlowState(parsed);
|
|
501
|
+
const prev = coerceFlowState(parsed).state;
|
|
402
502
|
validateFlowTransition(prev, state);
|
|
403
503
|
}
|
|
404
504
|
}
|
|
@@ -406,10 +506,10 @@ export async function writeFlowState(projectRoot, state, options = {}) {
|
|
|
406
506
|
if (err instanceof InvalidStageTransitionError) {
|
|
407
507
|
throw err;
|
|
408
508
|
}
|
|
409
|
-
throw new Error(`cannot validate flow-state transition because ${FLOW_STATE_REL_PATH} is unreadable or corrupt (${err instanceof Error ? err.message : String(err)}). Run \`cclaw
|
|
509
|
+
throw new Error(`cannot validate flow-state transition because ${FLOW_STATE_REL_PATH} is unreadable or corrupt (${err instanceof Error ? err.message : String(err)}). Run \`npx cclaw-cli sync\` and reconcile the state before retrying.`);
|
|
410
510
|
}
|
|
411
511
|
}
|
|
412
|
-
const safe = coerceFlowState({ ...state });
|
|
512
|
+
const safe = coerceFlowState({ ...state }).state;
|
|
413
513
|
await writeFileSafe(statePath, `${JSON.stringify(safe, null, 2)}\n`, { mode: 0o600 });
|
|
414
514
|
};
|
|
415
515
|
if (options.skipLock) {
|
|
@@ -428,7 +528,7 @@ export function flowStateLockPathFor(projectRoot) {
|
|
|
428
528
|
return flowStateLockPath(projectRoot);
|
|
429
529
|
}
|
|
430
530
|
export async function ensureRunSystem(projectRoot, _options = {}) {
|
|
431
|
-
await ensureDir(
|
|
531
|
+
await ensureDir(archiveRoot(projectRoot));
|
|
432
532
|
await ensureDir(activeArtifactsPath(projectRoot));
|
|
433
533
|
const statePath = flowStatePath(projectRoot);
|
|
434
534
|
const state = await readFlowState(projectRoot);
|
package/dist/tdd-cycle.d.ts
CHANGED
|
@@ -30,13 +30,13 @@ export interface TddCycleParseIssue {
|
|
|
30
30
|
export interface ParseTddCycleLogOptions {
|
|
31
31
|
/**
|
|
32
32
|
* Collect one issue per dropped/malformed line. Callers that care
|
|
33
|
-
* (
|
|
33
|
+
* (sync/runtime checks, red-evidence) can surface them; hooks keep soft-fail.
|
|
34
34
|
*/
|
|
35
35
|
issues?: TddCycleParseIssue[];
|
|
36
36
|
/**
|
|
37
37
|
* When true, reject lines that omit required fields instead of
|
|
38
38
|
* back-filling them with defaults. Used by validation paths
|
|
39
|
-
* (`validateTddCycleOrder`, `cclaw
|
|
39
|
+
* (`validateTddCycleOrder`, `npx cclaw-cli sync` fail-fast) to avoid silently
|
|
40
40
|
* bucketing unscoped rows into "runId=active, stage=tdd". Soft paths
|
|
41
41
|
* (generated hooks) keep the legacy defaults so a half-written file
|
|
42
42
|
* never takes the session down.
|
|
@@ -92,7 +92,7 @@ export interface RalphLoopStatus {
|
|
|
92
92
|
* Derive a lightweight Ralph Loop summary from parsed tdd-cycle-log entries.
|
|
93
93
|
* The goal is to give the model a single source of truth for "am I done
|
|
94
94
|
* iterating?" — it collapses per-slice progress and distinct closed AC IDs
|
|
95
|
-
* (from GREEN rows) into a single artifact the
|
|
95
|
+
* (from GREEN rows) into a single artifact the progression contract reads.
|
|
96
96
|
*/
|
|
97
97
|
export declare function computeRalphLoopStatus(entries: TddCycleEntry[], options?: {
|
|
98
98
|
runId?: string;
|
package/dist/tdd-cycle.js
CHANGED
|
@@ -201,7 +201,7 @@ export function pathMatchesTarget(candidate, target) {
|
|
|
201
201
|
* Derive a lightweight Ralph Loop summary from parsed tdd-cycle-log entries.
|
|
202
202
|
* The goal is to give the model a single source of truth for "am I done
|
|
203
203
|
* iterating?" — it collapses per-slice progress and distinct closed AC IDs
|
|
204
|
-
* (from GREEN rows) into a single artifact the
|
|
204
|
+
* (from GREEN rows) into a single artifact the progression contract reads.
|
|
205
205
|
*/
|
|
206
206
|
export function computeRalphLoopStatus(entries, options = {}) {
|
|
207
207
|
const runId = options.runId ?? "active";
|
package/dist/types.d.ts
CHANGED
|
@@ -71,8 +71,7 @@ export interface TrackHeuristicsConfig {
|
|
|
71
71
|
* when the harness supports native dispatch, otherwise fulfilled via
|
|
72
72
|
* an explicit in-session role switch with evidence).
|
|
73
73
|
*
|
|
74
|
-
* Track gating: `enforceOnTracks` lists the tracks where the
|
|
75
|
-
* check escalates to a warning. Tracks outside this list still see
|
|
74
|
+
* Track gating: `enforceOnTracks` lists the tracks where the sync/runtime check escalates to a warning. Tracks outside this list still see
|
|
76
75
|
* the skill prose but leave the decision to the user.
|
|
77
76
|
*
|
|
78
77
|
* All fields optional; sensible defaults: disabled, threshold 5, no
|
|
@@ -85,7 +84,7 @@ export interface SliceReviewConfig {
|
|
|
85
84
|
filesChangedThreshold?: number;
|
|
86
85
|
/** Glob hints; any plan-task touchPath match triggers review. */
|
|
87
86
|
touchTriggers?: string[];
|
|
88
|
-
/** Tracks on which missed reviews escalate to a
|
|
87
|
+
/** Tracks on which missed reviews escalate to a sync/runtime warning. */
|
|
89
88
|
enforceOnTracks?: FlowTrack[];
|
|
90
89
|
}
|
|
91
90
|
/**
|
|
@@ -116,6 +115,17 @@ export interface TddPathConfig {
|
|
|
116
115
|
export interface CompoundConfig {
|
|
117
116
|
recurrenceThreshold?: number;
|
|
118
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* Early-stage Ralph loop policy for brainstorm/scope/design.
|
|
120
|
+
*
|
|
121
|
+
* - enabled: when false, skip early-loop gate/diagnostics and hook writes.
|
|
122
|
+
* - maxIterations: capped producer/critic iterations before convergence
|
|
123
|
+
* escalation. Defaults to 3.
|
|
124
|
+
*/
|
|
125
|
+
export interface EarlyLoopConfig {
|
|
126
|
+
enabled?: boolean;
|
|
127
|
+
maxIterations?: number;
|
|
128
|
+
}
|
|
119
129
|
export interface IronLawsConfig {
|
|
120
130
|
/**
|
|
121
131
|
* Per-law escape hatch: list the iron-law ids that must always be strict,
|
|
@@ -128,13 +138,13 @@ export interface IronLawsConfig {
|
|
|
128
138
|
/**
|
|
129
139
|
* Optional opt-in audit toggles for additional stage lint gates.
|
|
130
140
|
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
141
|
+
* `scopePreAudit` stays opt-in (disabled by default). `staleDiagramAudit` is
|
|
142
|
+
* default-on and can be explicitly disabled when teams intentionally skip it.
|
|
133
143
|
*/
|
|
134
144
|
export interface OptInAuditsConfig {
|
|
135
145
|
/** When true, scope lint requires a filled `Pre-Scope System Audit` section. */
|
|
136
146
|
scopePreAudit?: boolean;
|
|
137
|
-
/**
|
|
147
|
+
/** Default true; when enabled, design lint runs stale diagram drift checks against blast radius files. */
|
|
138
148
|
staleDiagramAudit?: boolean;
|
|
139
149
|
}
|
|
140
150
|
export interface ReviewLoopExternalSecondOpinionConfig {
|
|
@@ -176,6 +186,8 @@ export interface CclawConfig {
|
|
|
176
186
|
tdd?: TddPathConfig;
|
|
177
187
|
/** Compound-stage recurrence policy overrides. */
|
|
178
188
|
compound?: CompoundConfig;
|
|
189
|
+
/** Early-stage producer/critic loop policy overrides. */
|
|
190
|
+
earlyLoop?: EarlyLoopConfig;
|
|
179
191
|
/** When true, cclaw installs managed git pre-commit/pre-push wrappers. */
|
|
180
192
|
gitHookGuards?: boolean;
|
|
181
193
|
/** Default flow track for new runs (quick = shortened path, standard = full pipeline). */
|
|
@@ -208,10 +220,6 @@ export interface CclawConfig {
|
|
|
208
220
|
/** Optional runtime knobs for outside-voice review loops. */
|
|
209
221
|
reviewLoop?: ReviewLoopConfig;
|
|
210
222
|
}
|
|
211
|
-
/**
|
|
212
|
-
* @deprecated Use `CclawConfig` instead.
|
|
213
|
-
*/
|
|
214
|
-
export type VibyConfig = CclawConfig;
|
|
215
223
|
export interface TransitionRule {
|
|
216
224
|
from: FlowStage;
|
|
217
225
|
to: FlowStage;
|
package/package.json
CHANGED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
export function finishCommandContract() {
|
|
2
|
-
return `# /cc-finish command contract
|
|
3
|
-
|
|
4
|
-
Use this command when the user says the active run is complete and wants to close it out.
|
|
5
|
-
|
|
6
|
-
## Protocol
|
|
7
|
-
|
|
8
|
-
1. Read \`.cclaw/state/flow-state.json\` and \`.cclaw/commands/next.md\`.
|
|
9
|
-
2. Confirm ship closeout is \`ready_to_archive\`. If not, route to \`/cc-next\` until retro and compound closeout are complete or explicitly skipped there.
|
|
10
|
-
3. Run \`cclaw archive --disposition=completed\` from the project root.
|
|
11
|
-
4. Report the archive path, reset run id, and any knowledge curation hint printed by the CLI.
|
|
12
|
-
|
|
13
|
-
Completed archives keep strict closeout gates: do not bypass retro or compound review from this command.
|
|
14
|
-
`;
|
|
15
|
-
}
|
|
16
|
-
export function finishCommandSkillMarkdown() {
|
|
17
|
-
return `---
|
|
18
|
-
name: flow-finish
|
|
19
|
-
description: Finish a completed cclaw run by archiving with completed disposition. Use when the user types /cc-finish or asks to finish, close, complete, or archive a successful run.
|
|
20
|
-
---
|
|
21
|
-
|
|
22
|
-
# Finish cclaw Run
|
|
23
|
-
|
|
24
|
-
Load and follow \`.cclaw/commands/finish.md\`. This is the successful closeout path and must preserve the normal ship closeout gates before archive.
|
|
25
|
-
`;
|
|
26
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { type IdeateFrameId } from "./ideate-frames.js";
|
|
2
|
-
export interface IdeateCommandOptions {
|
|
3
|
-
frameIds?: readonly IdeateFrameId[];
|
|
4
|
-
mode?: "repo-grounded" | "elsewhere-software" | "elsewhere-non-software" | "narrow";
|
|
5
|
-
}
|
|
6
|
-
export declare function minimumDistinctIdeateFrames(frameCount: number, mode?: IdeateCommandOptions["mode"]): number;
|
|
7
|
-
export declare function ideateCommandContract(options?: IdeateCommandOptions): string;
|
|
8
|
-
export declare function ideateCommandSkillMarkdown(options?: IdeateCommandOptions): string;
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
export type IdeateFrameId = "pain-friction" | "inversion" | "assumption-break" | "leverage" | "cross-domain-analogy" | "constraint-flip";
|
|
2
|
-
export interface IdeateFrame {
|
|
3
|
-
id: IdeateFrameId;
|
|
4
|
-
label: string;
|
|
5
|
-
prompt: string;
|
|
6
|
-
examplePatterns: string[];
|
|
7
|
-
}
|
|
8
|
-
export interface IdeateFrameDispatchInput {
|
|
9
|
-
focus: string;
|
|
10
|
-
mode: "repo-grounded" | "elsewhere-software" | "elsewhere-non-software";
|
|
11
|
-
signalSummary: string[];
|
|
12
|
-
}
|
|
13
|
-
export interface IdeateFrameDispatchPlanEntry {
|
|
14
|
-
frameId: IdeateFrameId;
|
|
15
|
-
label: string;
|
|
16
|
-
prompt: string;
|
|
17
|
-
}
|
|
18
|
-
export interface IdeateCandidateDraft {
|
|
19
|
-
title: string;
|
|
20
|
-
evidencePath: string;
|
|
21
|
-
summary: string;
|
|
22
|
-
frameId: IdeateFrameId;
|
|
23
|
-
}
|
|
24
|
-
export interface IdeateCandidateMerged extends Omit<IdeateCandidateDraft, "frameId"> {
|
|
25
|
-
frameIds: IdeateFrameId[];
|
|
26
|
-
}
|
|
27
|
-
export declare const DEFAULT_IDEATE_FRAME_IDS: readonly IdeateFrameId[];
|
|
28
|
-
export declare const IDEATE_FRAMES: readonly IdeateFrame[];
|
|
29
|
-
export declare function resolveIdeateFrames(frameIds?: readonly IdeateFrameId[]): IdeateFrame[];
|
|
30
|
-
export declare function buildIdeateFrameDispatchPlan(input: IdeateFrameDispatchInput, frameIds?: readonly IdeateFrameId[]): IdeateFrameDispatchPlanEntry[];
|
|
31
|
-
export declare function dedupeIdeateCandidates(drafts: readonly IdeateCandidateDraft[]): IdeateCandidateMerged[];
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
export type IdeateImpact = "high" | "medium" | "low";
|
|
2
|
-
export type IdeateEffort = "s" | "m" | "l";
|
|
3
|
-
export type IdeateConfidence = "high" | "medium" | "low";
|
|
4
|
-
export interface IdeateCandidateEvaluationInput {
|
|
5
|
-
id: string;
|
|
6
|
-
title: string;
|
|
7
|
-
impact: IdeateImpact;
|
|
8
|
-
effort: IdeateEffort;
|
|
9
|
-
confidence: IdeateConfidence;
|
|
10
|
-
rationaleStrength: number;
|
|
11
|
-
counterArgumentStrength: number;
|
|
12
|
-
}
|
|
13
|
-
export interface IdeateCandidateEvaluation extends IdeateCandidateEvaluationInput {
|
|
14
|
-
disposition: "survivor" | "critiqued-out";
|
|
15
|
-
rankingScore: number;
|
|
16
|
-
}
|
|
17
|
-
export interface IdeateRankingResult {
|
|
18
|
-
survivors: IdeateCandidateEvaluation[];
|
|
19
|
-
critiquedOut: IdeateCandidateEvaluation[];
|
|
20
|
-
recommendationId: string | null;
|
|
21
|
-
}
|
|
22
|
-
export declare function isCritiquedOut(rationaleStrength: number, counterArgumentStrength: number): boolean;
|
|
23
|
-
export declare function scoreIdeateCandidate(impact: IdeateImpact, effort: IdeateEffort, confidence: IdeateConfidence): number;
|
|
24
|
-
export declare function evaluateIdeateCandidate(input: IdeateCandidateEvaluationInput): IdeateCandidateEvaluation;
|
|
25
|
-
export declare function rankIdeateCandidates(inputs: readonly IdeateCandidateEvaluationInput[], maxSurvivors?: number): IdeateRankingResult;
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Single source of truth for how /cc-next should treat Ralph Loop status.
|
|
3
|
-
*
|
|
4
|
-
* IMPORTANT: Ralph Loop is a **progress indicator + soft pre-advance nudge**,
|
|
5
|
-
* not a hard gate. Hard enforcement always flows through flow-state.json
|
|
6
|
-
* gates via `stage-complete.mjs`. Both the command contract and the skill
|
|
7
|
-
* document render this same paragraph to prevent drift — see
|
|
8
|
-
* `tests/e2e/next-command-ralph-loop-contract.test.ts`.
|
|
9
|
-
*/
|
|
10
|
-
export declare const RALPH_LOOP_CONTRACT_MARKER = "ralph-loop-contract:v1";
|
|
11
|
-
export declare function ralphLoopContractSnippet(): string;
|
|
12
|
-
/**
|
|
13
|
-
* Command contract for /cc-next — the primary progression command.
|
|
14
|
-
* Reads flow-state, starts the current stage if unfinished, or advances if all gates pass.
|
|
15
|
-
*/
|
|
16
|
-
export declare function nextCommandContract(): string;
|
|
17
|
-
/**
|
|
18
|
-
* Skill body for /cc-next — the primary flow progression command.
|
|
19
|
-
*/
|
|
20
|
-
export declare function nextCommandSkillMarkdown(): string;
|