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