cclaw-cli 6.2.0 → 6.4.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/dist/artifact-linter/shared.d.ts +3 -0
- package/dist/artifact-linter/shared.js +10 -0
- package/dist/artifact-linter.d.ts +1 -1
- package/dist/artifact-linter.js +51 -3
- package/dist/content/closeout-guidance.js +4 -4
- package/dist/content/hooks.js +153 -1
- package/dist/content/skills-elicitation.js +3 -0
- package/dist/content/skills.js +2 -0
- package/dist/content/stage-schema.js +14 -3
- package/dist/content/stages/schema-types.d.ts +5 -0
- package/dist/content/start-command.js +5 -1
- package/dist/content/subagents.js +2 -0
- package/dist/content/templates.d.ts +1 -1
- package/dist/content/templates.js +5 -0
- package/dist/early-loop.d.ts +4 -0
- package/dist/early-loop.js +14 -1
- package/dist/flow-state.d.ts +19 -2
- package/dist/flow-state.js +2 -2
- package/dist/internal/advance-stage/advance.js +29 -6
- package/dist/internal/advance-stage/parsers.js +3 -2
- package/dist/internal/advance-stage/proactive-delegation-trace.d.ts +6 -4
- package/dist/internal/advance-stage/proactive-delegation-trace.js +13 -8
- package/dist/internal/advance-stage/start-flow.d.ts +3 -1
- package/dist/internal/advance-stage/start-flow.js +66 -1
- package/dist/internal/early-loop-status.js +7 -2
- package/dist/run-persistence.js +48 -1
- package/package.json +1 -1
|
@@ -385,6 +385,9 @@ export interface LearningsParseResult {
|
|
|
385
385
|
errors: string[];
|
|
386
386
|
details: string;
|
|
387
387
|
}
|
|
388
|
+
/** Multiline block used by linter + learnings harvest stderr (identical text). */
|
|
389
|
+
export declare function formatLearningsErrorsBullets(errors: string[]): string;
|
|
390
|
+
export declare function learningsParseFailureHumanSummary(artifactRelPath: string, errors: string[]): string;
|
|
388
391
|
export declare const LEARNING_TYPE_SET: Set<LearningEntryType>;
|
|
389
392
|
export declare const LEARNING_CONFIDENCE_SET: Set<LearningConfidence>;
|
|
390
393
|
export declare const LEARNING_SEVERITY_SET: Set<LearningSeverity>;
|
|
@@ -1501,6 +1501,16 @@ export function hasVerificationLadderTableRow(sectionBody) {
|
|
|
1501
1501
|
}
|
|
1502
1502
|
return false;
|
|
1503
1503
|
}
|
|
1504
|
+
/** Multiline block used by linter + learnings harvest stderr (identical text). */
|
|
1505
|
+
export function formatLearningsErrorsBullets(errors) {
|
|
1506
|
+
if (errors.length === 0) {
|
|
1507
|
+
return "Errors:\n - Learnings section could not be parsed.";
|
|
1508
|
+
}
|
|
1509
|
+
return `Errors:\n${errors.map((error) => ` - ${error}`).join("\n")}`;
|
|
1510
|
+
}
|
|
1511
|
+
export function learningsParseFailureHumanSummary(artifactRelPath, errors) {
|
|
1512
|
+
return `learnings harvest failed for \`${artifactRelPath}\`.\n${formatLearningsErrorsBullets(errors)}`;
|
|
1513
|
+
}
|
|
1504
1514
|
export const LEARNING_TYPE_SET = new Set(["rule", "pattern", "lesson", "compound"]);
|
|
1505
1515
|
export const LEARNING_CONFIDENCE_SET = new Set(["high", "medium", "low"]);
|
|
1506
1516
|
export const LEARNING_SEVERITY_SET = new Set(["critical", "important", "suggestion"]);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { FlowStage, FlowTrack } from "./types.js";
|
|
2
2
|
import { type LintResult } from "./artifact-linter/shared.js";
|
|
3
3
|
export { validateReviewArmy, checkReviewVerdictConsistency, checkReviewSecurityNoChangeAttestation, checkReviewTddNoCrossArtifactDuplication, type ReviewVerdictConsistencyResult, type ReviewSecurityNoChangeAttestationResult, type ReviewTddDuplicationConflict, type ReviewTddDuplicationResult } from "./artifact-linter/review-army.js";
|
|
4
|
-
export { type LintFinding, type LintResult, type LearningEntryType, type LearningConfidence, type LearningSeverity, type LearningSource, type LearningSeedEntry, type LearningsParseResult, extractMarkdownSectionBody, parseLearningsSection } from "./artifact-linter/shared.js";
|
|
4
|
+
export { type LintFinding, type LintResult, type LearningEntryType, type LearningConfidence, type LearningSeverity, type LearningSource, type LearningSeedEntry, type LearningsParseResult, formatLearningsErrorsBullets, learningsParseFailureHumanSummary, extractMarkdownSectionBody, parseLearningsSection } from "./artifact-linter/shared.js";
|
|
5
5
|
export interface LintArtifactOptions {
|
|
6
6
|
/**
|
|
7
7
|
* Stage-level flags supplied by the caller (typically `advance-stage`)
|
package/dist/artifact-linter.js
CHANGED
|
@@ -3,7 +3,7 @@ import { resolveArtifactPath as resolveStageArtifactPath } from "./artifact-path
|
|
|
3
3
|
import { exists } from "./fs-utils.js";
|
|
4
4
|
import { stageSchema } from "./content/stage-schema.js";
|
|
5
5
|
import { readFlowState } from "./run-persistence.js";
|
|
6
|
-
import { duplicateH2Headings, extractH2Sections, extractRequirementIdsFromMarkdown, isShortCircuitActivated, normalizeHeadingTitle, parseFrontmatter, parseLearningsSection, sectionBodyByAnyName, sectionBodyByHeadingPrefix, sectionBodyByName, validateSectionBody } from "./artifact-linter/shared.js";
|
|
6
|
+
import { duplicateH2Headings, extractH2Sections, extractRequirementIdsFromMarkdown, isShortCircuitActivated, normalizeHeadingTitle, parseFrontmatter, parseLearningsSection, sectionBodyByAnyName, sectionBodyByHeadingPrefix, sectionBodyByName, validateSectionBody, formatLearningsErrorsBullets } from "./artifact-linter/shared.js";
|
|
7
7
|
import { shouldDemoteArtifactValidationByTrack } from "./content/stage-schema.js";
|
|
8
8
|
import { recordArtifactValidationDemotedByTrack } from "./delegation.js";
|
|
9
9
|
import { lintBrainstormStage } from "./artifact-linter/brainstorm.js";
|
|
@@ -15,7 +15,7 @@ import { lintTddStage } from "./artifact-linter/tdd.js";
|
|
|
15
15
|
import { lintReviewStage } from "./artifact-linter/review.js";
|
|
16
16
|
import { lintShipStage } from "./artifact-linter/ship.js";
|
|
17
17
|
export { validateReviewArmy, checkReviewVerdictConsistency, checkReviewSecurityNoChangeAttestation, checkReviewTddNoCrossArtifactDuplication } from "./artifact-linter/review-army.js";
|
|
18
|
-
export { extractMarkdownSectionBody, parseLearningsSection } from "./artifact-linter/shared.js";
|
|
18
|
+
export { formatLearningsErrorsBullets, learningsParseFailureHumanSummary, extractMarkdownSectionBody, parseLearningsSection } from "./artifact-linter/shared.js";
|
|
19
19
|
const FRONTMATTER_REQUIRED_KEYS = [
|
|
20
20
|
"stage",
|
|
21
21
|
"schema_version",
|
|
@@ -117,6 +117,8 @@ export async function lintArtifact(projectRoot, stage, track = "standard", optio
|
|
|
117
117
|
let discoveryMode = "guided";
|
|
118
118
|
let taskClass = null;
|
|
119
119
|
let activeRunId = null;
|
|
120
|
+
let completedStagesForAudit = [];
|
|
121
|
+
let completedStageMetaForAudit;
|
|
120
122
|
try {
|
|
121
123
|
const flowState = await readFlowState(projectRoot);
|
|
122
124
|
const hint = flowState.interactionHints?.[stage];
|
|
@@ -125,12 +127,16 @@ export async function lintArtifact(projectRoot, stage, track = "standard", optio
|
|
|
125
127
|
discoveryMode = flowState.discoveryMode ?? "guided";
|
|
126
128
|
taskClass = flowState.taskClass ?? null;
|
|
127
129
|
activeRunId = flowState.activeRunId ?? null;
|
|
130
|
+
completedStagesForAudit = flowState.completedStages;
|
|
131
|
+
completedStageMetaForAudit = flowState.completedStageMeta;
|
|
128
132
|
}
|
|
129
133
|
catch {
|
|
130
134
|
activeStageFlags = [];
|
|
131
135
|
discoveryMode = "guided";
|
|
132
136
|
taskClass = null;
|
|
133
137
|
activeRunId = null;
|
|
138
|
+
completedStagesForAudit = [];
|
|
139
|
+
completedStageMetaForAudit = undefined;
|
|
134
140
|
}
|
|
135
141
|
for (const extra of options.extraStageFlags ?? []) {
|
|
136
142
|
if (typeof extra === "string" && extra.length > 0 && !activeStageFlags.includes(extra)) {
|
|
@@ -186,14 +192,56 @@ export async function lintArtifact(projectRoot, stage, track = "standard", optio
|
|
|
186
192
|
const meaningfulStageNoneWarning = learnings.ok && learnings.none && ["design", "tdd", "review"].includes(stage)
|
|
187
193
|
? " Warning: design/tdd/review usually produce reusable decisions, test patterns, or review lessons; keep `None this stage` only for truly mechanical work."
|
|
188
194
|
: "";
|
|
195
|
+
const learningsErrorBlock = !learnings.ok && learnings.errors.length > 0
|
|
196
|
+
? `\n${formatLearningsErrorsBullets(learnings.errors)}`
|
|
197
|
+
: "";
|
|
189
198
|
findings.push({
|
|
190
199
|
section: "Learnings",
|
|
191
200
|
required: requireLearnings,
|
|
192
201
|
rule: "`## Learnings` must contain either a single `- None this stage.` bullet or JSON bullets compatible with knowledge.jsonl fields (type/trigger/action/confidence required).",
|
|
193
202
|
found: learnings.ok,
|
|
194
|
-
details: `${learnings.details}${meaningfulStageNoneWarning}`
|
|
203
|
+
details: `${learnings.details}${learningsErrorBlock}${meaningfulStageNoneWarning}`
|
|
195
204
|
});
|
|
196
205
|
}
|
|
206
|
+
for (const doneStage of completedStagesForAudit) {
|
|
207
|
+
const completionIso = completedStageMetaForAudit?.[doneStage]?.completedAt;
|
|
208
|
+
if (!completionIso)
|
|
209
|
+
continue;
|
|
210
|
+
const completedMs = Date.parse(completionIso);
|
|
211
|
+
if (!Number.isFinite(completedMs))
|
|
212
|
+
continue;
|
|
213
|
+
try {
|
|
214
|
+
const resolvedDone = await resolveStageArtifactPath(doneStage, {
|
|
215
|
+
projectRoot,
|
|
216
|
+
track,
|
|
217
|
+
intent: "read"
|
|
218
|
+
});
|
|
219
|
+
if (!(await exists(resolvedDone.absPath)))
|
|
220
|
+
continue;
|
|
221
|
+
const artifactStat = await fs.stat(resolvedDone.absPath);
|
|
222
|
+
if (artifactStat.mtimeMs <= completedMs)
|
|
223
|
+
continue;
|
|
224
|
+
const priorRaw = await fs.readFile(resolvedDone.absPath, "utf8");
|
|
225
|
+
const priorSections = extractH2Sections(priorRaw);
|
|
226
|
+
const amendBody = sectionBodyByName(priorSections, "Amendments");
|
|
227
|
+
const trimmedAmend = amendBody === null
|
|
228
|
+
? ""
|
|
229
|
+
: amendBody.replace(/<!--[\s\S]*?-->/gu, "").replace(/\s+/gu, " ").trim();
|
|
230
|
+
if (trimmedAmend.length > 0)
|
|
231
|
+
continue;
|
|
232
|
+
findings.push({
|
|
233
|
+
section: "stage_artifact_post_closure_mutation",
|
|
234
|
+
required: false,
|
|
235
|
+
rule: "stage_artifact_post_closure_mutation — substantive post-closure edit without `## Amendments` (advisory)",
|
|
236
|
+
found: false,
|
|
237
|
+
details: `Completed stage "${doneStage}" snapshot closed at ${completionIso}, but ${resolvedDone.relPath} has a newer mtime without nonempty \`## Amendments\`. ` +
|
|
238
|
+
"Append dated bullets describing each drift fix, or restore the archived copy."
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
197
245
|
const stageContext = {
|
|
198
246
|
projectRoot,
|
|
199
247
|
stage,
|
|
@@ -19,9 +19,9 @@ export function closeoutSubstateProtocolBullets() {
|
|
|
19
19
|
return `When \`currentStage === "ship"\`, route by **${closeoutSubstateInline()}**:
|
|
20
20
|
- \`"idle"\` or missing -> outcome: initialize closeout by setting
|
|
21
21
|
${closeoutSubstateInline()} = \`"post_ship_review"\`, then continue \`/cc\`
|
|
22
|
-
into the in-stage retro protocol (draft + one structured accept/edit/
|
|
22
|
+
into the in-stage retro protocol (draft + one structured accept/edit/no changes ask).
|
|
23
23
|
- \`"post_ship_review"\` -> outcome: execute the unified post-ship closeout leg
|
|
24
|
-
(retro acceptance/edit/
|
|
24
|
+
(retro acceptance/edit/no changes + in-stage compound scan, not \`ce:compound\`)
|
|
25
25
|
and advance toward archive readiness:
|
|
26
26
|
read \`.cclaw/state/compound-readiness.json\` plus the relevant tail of
|
|
27
27
|
\`.cclaw/knowledge.jsonl\`, assess overlap before adding duplicate knowledge,
|
|
@@ -30,8 +30,8 @@ export function closeoutSubstateProtocolBullets() {
|
|
|
30
30
|
guidance in place instead of introducing extra lineage metadata. Optionally ask
|
|
31
31
|
whether to scan Cursor/Claude/Codex
|
|
32
32
|
session transcripts for matching historical learnings; only do it after opt-in.
|
|
33
|
-
Ask **one** structured question (apply /
|
|
34
|
-
single accept-all /
|
|
33
|
+
Ask **one** structured question (apply / no changes) per candidate cluster or a
|
|
34
|
+
single accept-all / no-changes choice, then advance substate.
|
|
35
35
|
- \`"ready_to_archive"\` -> outcome: continue \`/cc\` so the runtime archive step
|
|
36
36
|
executes, snapshots, and resets active state.
|
|
37
37
|
- \`"archived"\` (transient) -> outcome: report "run archived" and stop (flow complete).`;
|
package/dist/content/hooks.js
CHANGED
|
@@ -191,7 +191,7 @@ export function cancelRunScript() {
|
|
|
191
191
|
return internalHelperScript("cancel-run", "cancel-run", "Usage: node " + RUNTIME_ROOT + "/hooks/cancel-run.mjs --reason=<text> [--disposition=<cancelled|abandoned>] [--name=<slug>]");
|
|
192
192
|
}
|
|
193
193
|
export function stageCompleteScript() {
|
|
194
|
-
return internalHelperScript("stage-complete", "advance-stage", "Usage: node " + RUNTIME_ROOT + "/hooks/stage-complete.mjs <stage> [--passed=...] [--evidence-json=...] [--waive-delegation=...] [--waiver-reason=...] [--skip-questions] [--json]", {
|
|
194
|
+
return internalHelperScript("stage-complete", "advance-stage", "Usage: node " + RUNTIME_ROOT + "/hooks/stage-complete.mjs <stage> [--passed=...] [--evidence-json=...] [--waive-delegation=...] [--waiver-reason=...] [--accept-proactive-waiver] [--accept-proactive-waiver-reason=\"<why safe>\"] [--skip-questions] [--json]", {
|
|
195
195
|
positionalArgName: "stage",
|
|
196
196
|
positionalArgRequired: true,
|
|
197
197
|
defaultQuietEnvVar: "CCLAW_STAGE_COMPLETE_QUIET"
|
|
@@ -296,6 +296,7 @@ function usage() {
|
|
|
296
296
|
"Usage:",
|
|
297
297
|
" node .cclaw/hooks/delegation-record.mjs --stage=<stage> --agent=<agent> --mode=<mandatory|proactive> --status=<scheduled|launched|acknowledged|completed|failed|waived|stale> --span-id=<id> [--dispatch-id=<id>] [--worker-run-id=<id>] [--dispatch-surface=<surface>] [--agent-definition-path=<path>] [--ack-ts=<iso>] [--launched-ts=<iso>] [--completed-ts=<iso>] [--evidence-ref=<ref>] [--waiver-reason=<text>] [--json]",
|
|
298
298
|
" node .cclaw/hooks/delegation-record.mjs --rerecord --span-id=<id> --dispatch-id=<id> --dispatch-surface=<surface> --agent-definition-path=<path> [--ack-ts=<iso>] [--completed-ts=<iso>] [--evidence-ref=<ref>] [--json]",
|
|
299
|
+
" node .cclaw/hooks/delegation-record.mjs --repair --span-id=<id> --repair-reason=\"<why>\" [--json]",
|
|
299
300
|
"",
|
|
300
301
|
"Allowed --dispatch-surface values:",
|
|
301
302
|
" " + VALID_DISPATCH_SURFACES.join(", "),
|
|
@@ -496,10 +497,161 @@ async function runRerecord(args, json) {
|
|
|
496
497
|
process.stdout.write(JSON.stringify({ ok: true, event, rerecord: true }, null, 2) + "\\n");
|
|
497
498
|
}
|
|
498
499
|
|
|
500
|
+
const LIFECYCLE_PHASES = ["scheduled", "launched", "acknowledged", "completed"];
|
|
501
|
+
|
|
502
|
+
function mergeSpanTemplate(spanEvents) {
|
|
503
|
+
const base = {};
|
|
504
|
+
const keys = [
|
|
505
|
+
"stage",
|
|
506
|
+
"agent",
|
|
507
|
+
"mode",
|
|
508
|
+
"runId",
|
|
509
|
+
"dispatchId",
|
|
510
|
+
"dispatchSurface",
|
|
511
|
+
"agentDefinitionPath",
|
|
512
|
+
"workerRunId",
|
|
513
|
+
"fulfillmentMode",
|
|
514
|
+
"schemaVersion",
|
|
515
|
+
"parentSpanId",
|
|
516
|
+
"evidenceRefs",
|
|
517
|
+
"waiverReason"
|
|
518
|
+
];
|
|
519
|
+
for (const e of spanEvents) {
|
|
520
|
+
if (!e || typeof e !== "object") continue;
|
|
521
|
+
for (const k of keys) {
|
|
522
|
+
if (base[k] === undefined && e[k] !== undefined) {
|
|
523
|
+
base[k] = e[k];
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
return base;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function repairFulfillmentMode(base) {
|
|
531
|
+
if (base.fulfillmentMode) return base.fulfillmentMode;
|
|
532
|
+
if (base.dispatchSurface === "role-switch") return "role-switch";
|
|
533
|
+
if (base.dispatchSurface === "cursor-task" || base.dispatchSurface === "generic-task") {
|
|
534
|
+
return "generic-dispatch";
|
|
535
|
+
}
|
|
536
|
+
return "isolated";
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
async function runRepair(args, json) {
|
|
540
|
+
const problems = [];
|
|
541
|
+
if (!args["span-id"]) problems.push("repair mode requires --span-id");
|
|
542
|
+
if (!args["repair-reason"] || String(args["repair-reason"]).trim().length === 0) {
|
|
543
|
+
problems.push("repair mode requires --repair-reason=<text>");
|
|
544
|
+
}
|
|
545
|
+
if (problems.length > 0) {
|
|
546
|
+
emitProblems(problems, json, 2);
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
const spanId = args["span-id"];
|
|
550
|
+
const repairedReason = String(args["repair-reason"]).trim();
|
|
551
|
+
const root = await detectRoot();
|
|
552
|
+
const events = await readDelegationEvents(root);
|
|
553
|
+
const spanEvents = events.filter(
|
|
554
|
+
(e) => e && e.spanId === spanId && typeof e.event === "string" && LIFECYCLE_PHASES.includes(e.event)
|
|
555
|
+
);
|
|
556
|
+
if (spanEvents.length === 0) {
|
|
557
|
+
emitProblems(
|
|
558
|
+
["repair refused: no lifecycle delegation-events.jsonl rows found for --span-id=" + spanId],
|
|
559
|
+
json,
|
|
560
|
+
2
|
|
561
|
+
);
|
|
562
|
+
return;
|
|
563
|
+
}
|
|
564
|
+
const present = new Set(spanEvents.map((e) => e.event));
|
|
565
|
+
const base = mergeSpanTemplate(spanEvents);
|
|
566
|
+
if (!base.stage || !base.agent || !base.mode) {
|
|
567
|
+
emitProblems(["repair refused: span events missing stage/agent/mode to clone"], json, 2);
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
const runId =
|
|
571
|
+
typeof base.runId === "string" && base.runId.length > 0 ? base.runId : await readRunId(root);
|
|
572
|
+
const fulfillmentMode = repairFulfillmentMode(base);
|
|
573
|
+
const schemaVersion =
|
|
574
|
+
typeof base.schemaVersion === "number" && base.schemaVersion > 0
|
|
575
|
+
? base.schemaVersion
|
|
576
|
+
: LEDGER_SCHEMA_VERSION;
|
|
577
|
+
const evidenceRefs = Array.isArray(base.evidenceRefs)
|
|
578
|
+
? base.evidenceRefs.filter((r) => typeof r === "string" && r.trim().length > 0)
|
|
579
|
+
: [];
|
|
580
|
+
const now = new Date().toISOString();
|
|
581
|
+
const appended = [];
|
|
582
|
+
|
|
583
|
+
for (const status of LIFECYCLE_PHASES) {
|
|
584
|
+
if (present.has(status)) continue;
|
|
585
|
+
if (status === "completed" && base.dispatchSurface !== "role-switch") {
|
|
586
|
+
if (!base.dispatchId || !base.dispatchSurface || !base.agentDefinitionPath) {
|
|
587
|
+
emitProblems(
|
|
588
|
+
[
|
|
589
|
+
"repair refused: cannot synthesize completed row without dispatchId, dispatchSurface, and agentDefinitionPath on span " +
|
|
590
|
+
spanId
|
|
591
|
+
],
|
|
592
|
+
json,
|
|
593
|
+
2
|
|
594
|
+
);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (status === "completed" && base.dispatchSurface === "role-switch" && evidenceRefs.length === 0) {
|
|
599
|
+
emitProblems(
|
|
600
|
+
["repair refused: role-switch completed synthesis requires evidenceRefs on span " + spanId],
|
|
601
|
+
json,
|
|
602
|
+
2
|
|
603
|
+
);
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
const launchedTs =
|
|
607
|
+
status === "launched" || status === "acknowledged" || status === "completed" ? now : undefined;
|
|
608
|
+
const ackTs = status === "acknowledged" || status === "completed" ? now : undefined;
|
|
609
|
+
const completedTs = status === "completed" ? now : undefined;
|
|
610
|
+
const endTs = status === "completed" ? now : undefined;
|
|
611
|
+
const row = {
|
|
612
|
+
stage: base.stage,
|
|
613
|
+
agent: base.agent,
|
|
614
|
+
mode: base.mode,
|
|
615
|
+
status,
|
|
616
|
+
spanId,
|
|
617
|
+
dispatchId: base.dispatchId,
|
|
618
|
+
workerRunId: base.workerRunId,
|
|
619
|
+
dispatchSurface: base.dispatchSurface,
|
|
620
|
+
agentDefinitionPath: base.agentDefinitionPath,
|
|
621
|
+
fulfillmentMode,
|
|
622
|
+
evidenceRefs,
|
|
623
|
+
runId,
|
|
624
|
+
startTs: now,
|
|
625
|
+
ts: now,
|
|
626
|
+
launchedTs,
|
|
627
|
+
ackTs,
|
|
628
|
+
completedTs,
|
|
629
|
+
endTs,
|
|
630
|
+
schemaVersion
|
|
631
|
+
};
|
|
632
|
+
const clean = Object.fromEntries(Object.entries(row).filter(([, value]) => value !== undefined));
|
|
633
|
+
const event = { ...clean, event: status, eventTs: now, repairedAt: now, repairedReason };
|
|
634
|
+
await persistEntry(root, runId, clean, event);
|
|
635
|
+
present.add(status);
|
|
636
|
+
appended.push(status);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (json) {
|
|
640
|
+
process.stdout.write(
|
|
641
|
+
JSON.stringify({ ok: true, repair: true, spanId, appended, repairedAt: now, repairedReason }, null, 2) + "\\n"
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
499
646
|
async function main() {
|
|
500
647
|
const args = parseArgs(process.argv.slice(2));
|
|
501
648
|
const json = args.json !== undefined;
|
|
502
649
|
|
|
650
|
+
if (args.repair) {
|
|
651
|
+
await runRepair(args, json);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
|
|
503
655
|
if (args.rerecord) {
|
|
504
656
|
await runRerecord(args, json);
|
|
505
657
|
return;
|
|
@@ -46,6 +46,7 @@ These behaviors are the exact reason this skill exists. The linter will block yo
|
|
|
46
46
|
- Ask exactly one question per turn and wait for the answer before asking the next one.
|
|
47
47
|
- Use harness-native question tools first; prose fallback is allowed only when the tool is unavailable.
|
|
48
48
|
- Keep a running Q&A trace in the active artifact under \`## Q&A Log\` in \`${RUNTIME_ROOT}/artifacts/\` as append-only rows.
|
|
49
|
+
- **Early-loop ledger discipline**: Never append \`.cclaw/state/early-loop-log.jsonl\` rows whose \`iteration\` exceeds the active \`maxIterations\`. If the cap fired, escalate or accept convergence outcomes—do not bump the iteration counter afterward. \`deriveEarlyLoopStatus\` clamps persistence, but the log source should stay honest too.
|
|
49
50
|
- **Convergence floor**: do NOT advance the stage (do NOT call \`stage-complete.mjs\`) until Q&A converges. The machine contract matches \`evaluateQaLogFloor\` in \`src/artifact-linter/shared.ts\` (rule \`qa_log_unconverged\`). Pass when ANY holds: (a) every forcing-question topic id is tagged \`[topic:<id>]\` on at least one \`## Q&A Log\` row; (b) the Ralph-Loop detector fires (last 2 substantive rows are non-decision-changing: \`skip\`/\`continue\`/\`no-change\`/\`done\`/etc.) **and** the log has at least \`max(2, questionBudgetHint(discoveryMode, stage).min)\` substantive rows — **unless** \`discoveryMode\` is \`guided\` or \`deep\` with pending forcing-topic ids (then Ralph-Loop alone cannot pass until topics are tagged, a stop-signal is recorded, or \`--skip-questions\` downgrades the finding to advisory); (c) an explicit user stop-signal row; or (d) \`--skip-questions\` was persisted (unconverged is advisory only). Wave 24 (v6.0.0) made \`[topic:<id>]\` mandatory (no English keyword fallback).
|
|
50
51
|
- **NEVER run shell hash commands** (\`shasum\`, \`sha256sum\`, \`md5sum\`, \`Get-FileHash\`, \`certutil\`, etc.) to compute artifact hashes. If a linter ever asks you for a hash, that is a linter bug — report failure and stop, do not auto-fix in bash.
|
|
51
52
|
- **NEVER paste cclaw command lines into chat** (e.g. \`node .cclaw/hooks/stage-complete.mjs ... --evidence-json '{...}'\`). Run them via the tool layer; report only the resulting summary. The user does not run cclaw manually and seeing the command line is noise.
|
|
@@ -85,6 +86,8 @@ Treat these as stop-and-draft signals:
|
|
|
85
86
|
- EN: "enough", "skip", "just draft it", "stop asking", "move on", "no more questions"
|
|
86
87
|
- UA: "досить", "вистачить", "давай драфт", "рухаємось далі"
|
|
87
88
|
|
|
89
|
+
Label disambiguation: The word **skip** is a valid stop phrase during brainstorm/scope/design Q&A. In ship closeout retros, compound clustering, or any structured retro ask, expose **no changes** / **accept as-is** for the passive option instead of wording it as "skip" so agents do not mix elicitation stop-signals with closeout choreography.
|
|
90
|
+
|
|
88
91
|
When detected:
|
|
89
92
|
- Append a Q&A Log row exactly like: \`Turn N | (stop-signal) | <user quote> | stop-and-draft\` — this row satisfies the linter floor escape hatch.
|
|
90
93
|
- Do not ask another question in this stage loop.
|
package/dist/content/skills.js
CHANGED
|
@@ -387,8 +387,10 @@ function completionParametersBlock(schema, track) {
|
|
|
387
387
|
- \`completion helper JSON diagnostics\`: append \`--json\` to receive a machine-readable validation failure summary.
|
|
388
388
|
- \`delegation lifecycle proof\`: use the delegation helper recipe in this section with explicit lifecycle rows: \`--status=scheduled\` -> \`--status=launched\` -> \`--status=acknowledged\` -> \`--status=completed\` (completed isolated/generic requires prior ACK for the same span or \`--ack-ts=<iso>\`).
|
|
389
389
|
- Fill \`## Learnings\` before closeout: either \`- None this stage.\` or JSON bullets with required keys \`type\`, \`trigger\`, \`action\`, \`confidence\` (knowledge-schema compatible).
|
|
390
|
+
- If you edit any completed-stage artifact after it shipped (\`completedStageMeta\` timestamps exist), append a short \`## Amendments\` section with dated bullets (timestamp + reason) instead of overwriting the archived narrative silently — advisory linter rule \`stage_artifact_post_closure_mutation\` enforces visibility when this trail is missing.
|
|
390
391
|
- Record mandatory delegation lifecycle in \`${RUNTIME_ROOT}/state/delegation-log.json\` and append proof events to \`${RUNTIME_ROOT}/state/delegation-events.jsonl\`; the ledger is current state, the event log is audit proof.${mandatoryAgents.length > 0 ? ` If a mandatory delegation cannot run in this harness, use \`--waive-delegation=${mandatoryAgents.join(",")} --waiver-reason="<why safe>"\` on the completion helper.` : ""} If proactive delegations were intentionally skipped, rerun only with \`--accept-proactive-waiver\` (optionally \`--accept-proactive-waiver-reason="<why safe>"\`) after explicit user approval.
|
|
391
392
|
- Never edit raw \`flow-state.json\` to complete a stage, even in advisory mode; that bypasses validation, gate evidence, and Learnings harvest. If a helper fails, report a one-line human-readable failure plus fenced JSON diagnostics; never echo the invoking command line or apply a manual state workaround.
|
|
393
|
+
- Stage completion claim requires \`stage-complete\` exit 0 in the current turn. Quote the success line; do not paraphrase, do not infer success from skipped retries.
|
|
392
394
|
- Completion protocol: verify required gates, update the artifact, then use the completion helper with \`--evidence-json\` and \`--passed\` for every satisfied gate.
|
|
393
395
|
`;
|
|
394
396
|
}
|
|
@@ -10,6 +10,14 @@ import { trackRenderContext } from "./track-render-context.js";
|
|
|
10
10
|
// Protocol). They are no longer re-exported from here to avoid duplication
|
|
11
11
|
// and drift. Stage skills cite the meta-skill by path instead.
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Optional artifact appendix (documentation-only — not a tiered gate)
|
|
15
|
+
//
|
|
16
|
+
// `## Amendments` — after a stage is closed, substantive edits SHOULD append dated
|
|
17
|
+
// bullets (ISO timestamp + reason) here instead of silently rewriting history. The
|
|
18
|
+
// linter surfaces advisory `stage_artifact_post_closure_mutation` findings when mtimes
|
|
19
|
+
// move without this trail (`completedStageMeta` must exist).
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
13
21
|
export const SKILL_ENVELOPE_KINDS = [
|
|
14
22
|
"stage-output",
|
|
15
23
|
"gate-result",
|
|
@@ -498,7 +506,8 @@ const STAGE_AUTO_SUBAGENT_DISPATCH = {
|
|
|
498
506
|
runPhase: "post-elicitation",
|
|
499
507
|
when: "When repository, market, docs, or prior-art context changes the approach set. Runs only after the adaptive elicitation Q&A loop converges.",
|
|
500
508
|
purpose: "Provide search-before-read summaries and context-readiness evidence before large reads or decisions.",
|
|
501
|
-
requiresUserGate: false
|
|
509
|
+
requiresUserGate: false,
|
|
510
|
+
essentialAcrossModes: true
|
|
502
511
|
}
|
|
503
512
|
],
|
|
504
513
|
scope: [
|
|
@@ -535,7 +544,8 @@ const STAGE_AUTO_SUBAGENT_DISPATCH = {
|
|
|
535
544
|
runPhase: "post-elicitation",
|
|
536
545
|
when: "When churn, prior attempts, reference patterns, or external constraints may change scope boundaries. Runs only after the adaptive elicitation Q&A loop converges.",
|
|
537
546
|
purpose: "Summarize search/context findings before the scope contract locks accepted/rejected/deferred ideas.",
|
|
538
|
-
requiresUserGate: false
|
|
547
|
+
requiresUserGate: false,
|
|
548
|
+
essentialAcrossModes: true
|
|
539
549
|
},
|
|
540
550
|
{
|
|
541
551
|
agent: "product-discovery",
|
|
@@ -598,7 +608,8 @@ const STAGE_AUTO_SUBAGENT_DISPATCH = {
|
|
|
598
608
|
runPhase: "post-elicitation",
|
|
599
609
|
when: "When framework/library docs, repo graph context, or reference contracts may change the design. Runs only after the adaptive elicitation Q&A loop converges.",
|
|
600
610
|
purpose: "Run search-before-read context synthesis before architecture locks.",
|
|
601
|
-
requiresUserGate: false
|
|
611
|
+
requiresUserGate: false,
|
|
612
|
+
essentialAcrossModes: true
|
|
602
613
|
},
|
|
603
614
|
{
|
|
604
615
|
agent: "security-reviewer",
|
|
@@ -53,6 +53,11 @@ export interface StageAutoSubagentDispatch {
|
|
|
53
53
|
returnSchema?: StageSubagentReturnSchema;
|
|
54
54
|
/** Optional skill folder the dispatched agent should load as additional context. */
|
|
55
55
|
skill?: string;
|
|
56
|
+
/**
|
|
57
|
+
* When true on a proactive dispatch row for brainstorm/scope/design, the trace
|
|
58
|
+
* gate keeps this rule even when `discoveryMode` is `lean` or `guided`.
|
|
59
|
+
*/
|
|
60
|
+
essentialAcrossModes?: boolean;
|
|
56
61
|
}
|
|
57
62
|
export type StageComplexityTier = "lightweight" | "standard" | "deep";
|
|
58
63
|
export interface StagePhilosophy {
|
|
@@ -87,6 +87,8 @@ ${conversationLanguagePolicyMarkdown()}
|
|
|
87
87
|
> \`Choose discovery mode: Lean / Guided / Deep\`.
|
|
88
88
|
> \`Lean\` = compact shaping, \`Guided\` = recommended default with enough questions and key specialists before drafting, \`Deep\` = stronger probing and broader specialist/research passes.
|
|
89
89
|
> Mention the internal track recommendation only as context, not as the primary decision. Offer track override only when reset, contradiction, or reclassification evidence makes the internal recommendation suspect.
|
|
90
|
+
Normalize the user's answer before calling the start helper: \`trim\`, lower-case, map UI labels to \`lean\` / \`guided\` / \`deep\` only; if the answer is not one of those three, re-ask the same question once with the compact definitions. In \`flow-state.json\`, persist only the canonical lowercase token (never \`Deep\`/\`Guided\` casing).
|
|
91
|
+
If the user prompt is one short line (at most 12 words) and the workspace matches an empty-repo signal set — either \`flow-state.repoSignals\` from the last successful \`start-flow\` shows \`fileCount < 5\` with \`hasReadme\` and \`hasPackageManifest\` both false, OR (before any \`start-flow\` yet) a shallow scan finds no root \`README.md\`, no root \`package.json\`/\`pyproject.toml\`/\`Cargo.toml\`, and fewer than five relevant files excluding \`node_modules\`/\`.git\` — recommend \`guided\` and ask for explicit confirmation before defaulting to \`deep\`.
|
|
90
92
|
If the harness's native ask tool is available (\`AskUserQuestion\` / \`AskQuestion\` / \`question\` / \`request_user_input\`), send exactly ONE question; on schema error, fall back to a plain-text lettered list.
|
|
91
93
|
10. Start the tracked flow only through the managed helper:
|
|
92
94
|
\`node .cclaw/hooks/start-flow.mjs --track=<quick|medium|standard> --discovery-mode=<lean|guided|deep> --class=<class> --prompt=<prompt> --stack=<stack> --reason=<matched heuristic>\`
|
|
@@ -188,8 +190,10 @@ ${conversationLanguagePolicyMarkdown()}
|
|
|
188
190
|
- On conflict, prefer \`standard\` over \`medium\`, and \`medium\` over \`quick\`.
|
|
189
191
|
- Always state the recommendation as a one-line reason citing matched triggers and a high/medium/low track selection confidence. Clarify that the heuristic is advisory until the managed helper writes state; after that, \`/cc\` follows the selected track. Include override guidance: switch to standard when architecture, schema, migration, security, or unclear scope appears; switch to medium when product framing is needed but architecture is known.
|
|
190
192
|
8. Ask for the single explicit start choice: \`Lean / Guided / Deep\`. Use \`Guided\` as the recommended default unless the user clearly wants compact shaping or unusually deep probing. Keep track internal unless contradiction/reset/reclassification requires surfacing an override.
|
|
193
|
+
Normalize the answer (\`trim\`, lower-case) to exactly \`lean\` / \`guided\` / \`deep\` before invoking the start helper; re-ask once if the reply is not one of those. Pass only canonical lowercase tokens to \`--discovery-mode\`.
|
|
194
|
+
If the prompt is one short line (at most 12 words) and the workspace matches an empty-repo signal set — either persisted \`repoSignals\` has \`fileCount < 5\` with \`hasReadme\` and \`hasPackageManifest\` false, OR a shallow scan before the first \`start-flow\` shows the same — recommend \`guided\` and confirm before defaulting to \`deep\`.
|
|
191
195
|
9. Run the managed start helper: \`node .cclaw/hooks/start-flow.mjs --track=<quick|medium|standard> --discovery-mode=<lean|guided|deep> --class=<class> --prompt=<prompt> --stack=<stack> --reason=<matched heuristic>\`. The helper writes \`${flowPath}\`, including \`discoveryMode\`, computes \`skippedStages\`, resets the gate catalog, and writes \`${RUNTIME_ROOT}/artifacts/00-idea.md\`. If it fails, STOP, report one human-readable failure line from the JSON \`error\` field, and include the helper JSON payload in a fenced \`json\` block; do not echo the invoking command line, and do not manually edit flow state.
|
|
192
|
-
|
|
196
|
+
10. Load and execute the **first stage skill of the chosen track** (\`brainstorm\` for medium/standard, \`spec\` for quick) plus its matching command file.
|
|
193
197
|
|
|
194
198
|
### Reclassification on discovery
|
|
195
199
|
|
|
@@ -578,6 +578,8 @@ Use this payload when a stage needs context-readiness or search-before-read evid
|
|
|
578
578
|
${MARKDOWN_CODE_FENCE}
|
|
579
579
|
You are a researcher subagent.
|
|
580
580
|
|
|
581
|
+
SCOPE — External plus internal research: search official docs/libraries/prior art and current best practices (use web or package-index tooling whenever it is live; cite URLs or authoritative references). Internally scan the repo for commits, manifests, conventions, migrations, configuration, and latent decisions—even when the workspace is sparse, external benchmarking stays mandatory.
|
|
582
|
+
|
|
581
583
|
QUESTION: {one falsifiable research question}
|
|
582
584
|
SCOPE: {repo paths, docs, references, providers to inspect}
|
|
583
585
|
CONTEXT READINESS: {graph/search/provider status if known}
|
|
@@ -7,5 +7,5 @@ export declare const RULEBOOK_MARKDOWN = "# Cclaw Rulebook\n\n## MUST_ALWAYS\n-
|
|
|
7
7
|
* (premature draft, premature subagent dispatch, command-line echo to chat).
|
|
8
8
|
*/
|
|
9
9
|
export declare const CURSOR_GUIDELINES_RULE_MDC = "---\ndescription: cclaw zero-install behavior baseline (always-on)\nglobs:\n - \"**/*\"\nalwaysApply: true\n---\n\n<!-- cclaw-managed-cursor-guidelines-rule -->\n\n# Cclaw Baseline Guidelines\n\nThese three rules apply to every Cursor agent session in this project,\nregardless of whether stage skills loaded.\n\n## 1. Q&A floor before drafting (brainstorm/scope/design)\n\nBefore drafting any `.cclaw/artifacts/01-brainstorm-*.md`,\n`02-scope-*.md`, or `03-design-*.md`, verify that the artifact's\n`## Q&A Log` table demonstrates Ralph-Loop convergence: every\nforcing-question topic id is tagged `[topic:<id>]` on at least one row\n(see the stage's forcing-questions checklist for the id list), the last\n2 turns produce no new decision-changing impact, OR an explicit user\nstop-signal row is recorded. Walk the stage forcing questions one at a\ntime via the `AskQuestion` tool. If you find yourself proposing a\ndraft after 1-2 questions while forcing topic ids remain untagged, STOP\nand continue the loop.\n\nThe `qa_log_unconverged` linter rule will block `stage-complete` when\nconvergence has not been reached. Wave 24 (v6.0.0) made `[topic:<id>]`\ntagging mandatory; the English keyword fallback was removed because it\nmis-reported convergence on RU/UA Q&A logs.\n\n## 2. Mandatory subagents run after Q&A approval\n\nFor brainstorm / scope / design, mandatory subagents (\n`product-discovery`, `critic`, `planner`, `architect`,\n`test-author`) run **only AFTER the user approves the elicitation\noutcome**, never before the Q&A loop converges. Dispatching them early\npreempts the user dialogue and violates the elicitation contract \u2014 the\nlinter will block stage-complete.\n\nSee each stage's \"Run Phase: post-elicitation\" rows in the materialized\nAutomatic Subagent Dispatch table.\n\n## 3. Never echo cclaw command lines to chat\n\nThe user does not run cclaw helpers (`node .cclaw/hooks/...`) manually.\nNEVER paste full command lines, `--evidence-json '{...}'` payloads,\n`--waive-delegation=...`, or shell hash commands (`shasum`,\n`sha256sum`, `Get-FileHash`, `certutil`, etc.) into chat. Run the\nhelper via the tool layer and report only the resulting summary. On\nfailure, report a compact human-readable summary plus the helper JSON in\na single fenced `json` block.\n";
|
|
10
|
-
export declare const CURSOR_WORKFLOW_RULE_MDC = "---\ndescription: cclaw workflow guardrails for Cursor agent sessions\nglobs:\n - \"**/*\"\nalwaysApply: true\n---\n\n<!-- cclaw-managed-cursor-workflow-rule -->\n\n# Cclaw Workflow Guardrails\n\n## Activation Rule\n\nBefore responding to coding work:\n1. Read `.cclaw/state/flow-state.json`.\n2. Start with `/cc` or continue with `/cc`.\n3. If no software-stage flow applies, respond normally.\n\n## Stage Order\n\n`brainstorm -> scope -> design -> spec -> plan -> tdd -> review -> ship`\n\nTrack-specific skips are allowed only when `flow-state.track` + `skippedStages` explicitly say so.\n\n## Task Classification\n\n| Class | Route |\n|---|---|\n| non-trivial software work | `/cc <idea>` |\n| trivial software fix | `/cc <idea>` (quick track) |\n| bugfix with repro | `/cc <idea>` and enforce RED-first in tdd |\n| pure question / non-software | direct answer (no stage flow) |\n\n## Command Surface\n\n- `/cc` = entry and resume.\n- `/cc` = only progression path.\n- Knowledge capture and recall use the `learnings` skill when requested.\n\n## Verification Discipline\n\n- No completion claim without fresh command evidence in this turn.\n- Do not mark gates passed from memory.\n- Keep evidence in `.cclaw/artifacts/`; archive through closeout via `/cc` or cancel early via `node .cclaw/hooks/cancel-run.mjs`.\n\n## Delegation And Approvals\n\n- Machine-only checks in design/plan/tdd/review/ship should auto-dispatch when tooling supports it.\n- **For brainstorm / scope / design stages**: ask user input continuously via adaptive elicitation (one question per turn through the harness-native question tool \u2014 `AskQuestion` in Cursor). Walk the stage forcing-questions list one-by-one. **Tag each Q&A Log row's `Decision impact` cell with `[topic:<id>]`** (the id is given in the stage's forcing-questions checklist) so the linter can verify coverage in any natural language. Do NOT batch and do NOT defer to a single approval gate at the end. The `qa_log_unconverged` linter rule will block `stage-complete` when convergence is not reached (forcing topic ids untagged AND last 2 turns still produce decision-changing rows AND no stop-signal).\n- **For other stages** (spec/plan/tdd/build/review/ship): ask user input only at explicit approval gates (scope mode, plan approval, challenge resolution, ship finalization), not for routine progress updates.\n- If you find yourself proposing a draft after 1-2 questions in brainstorm/scope/design, STOP \u2014 go back to the forcing-questions list and continue.\n- Mandatory subagents in brainstorm/scope/design run only AFTER the user approves the elicitation outcome (see each stage's \"Run Phase: post-elicitation\" rows). Dispatching them before the Q&A loop converges violates the contract.\n- Never echo cclaw command lines (`node .cclaw/hooks/...`, `--evidence-json '{...}'`) to chat \u2014 the user does not run cclaw manually. Run helpers via the tool layer; report only the resulting summary.\n- If harness capabilities are partial, record waiver reasons in delegation logs.\n\n## Routing Source Of Truth\n\n- Primary router: `.cclaw/skills/using-cclaw/SKILL.md`.\n- Stage behavior: current stage skill plus `.cclaw/state/flow-state.json`.\n- Preamble budget: keep role/status announcements brief and avoid repeating\n them unless the stage or role changes.\n";
|
|
10
|
+
export declare const CURSOR_WORKFLOW_RULE_MDC = "---\ndescription: cclaw workflow guardrails for Cursor agent sessions\nglobs:\n - \"**/*\"\nalwaysApply: true\n---\n\n<!-- cclaw-managed-cursor-workflow-rule -->\n\n# Cclaw Workflow Guardrails\n\n## Activation Rule\n\nBefore responding to coding work:\n1. Read `.cclaw/state/flow-state.json`.\n2. Start with `/cc` or continue with `/cc`.\n3. If no software-stage flow applies, respond normally.\n\n## Stage Order\n\n`brainstorm -> scope -> design -> spec -> plan -> tdd -> review -> ship`\n\nTrack-specific skips are allowed only when `flow-state.track` + `skippedStages` explicitly say so.\n\n## Task Classification\n\n| Class | Route |\n|---|---|\n| non-trivial software work | `/cc <idea>` |\n| trivial software fix | `/cc <idea>` (quick track) |\n| bugfix with repro | `/cc <idea>` and enforce RED-first in tdd |\n| pure question / non-software | direct answer (no stage flow) |\n\n## Command Surface\n\n- `/cc` = entry and resume.\n- `/cc` = only progression path.\n- Knowledge capture and recall use the `learnings` skill when requested.\n\n## Verification Discipline\n\n- No completion claim without fresh command evidence in this turn.\n- Stage completion claim requires `stage-complete` exit 0 in the current turn. Quote the success line; do not paraphrase, do not infer success from skipped retries.\n\n## Protocol label hygiene\n\n`skip` wording means different things depending on phase: brainstorm/scope/design Q&A stop-signals may still literal **skip**/enough/move-on wording; structured ship closeout retros and compound clustering prompts should expose **no changes** (or accept-as-is language) rather than labeling the passive path as skip. Keep the verbs aligned with the harness question copy you present to the human.\n- Do not mark gates passed from memory.\n- Keep evidence in `.cclaw/artifacts/`; archive through closeout via `/cc` or cancel early via `node .cclaw/hooks/cancel-run.mjs`.\n\n## Delegation And Approvals\n\n- Machine-only checks in design/plan/tdd/review/ship should auto-dispatch when tooling supports it.\n- **For brainstorm / scope / design stages**: ask user input continuously via adaptive elicitation (one question per turn through the harness-native question tool \u2014 `AskQuestion` in Cursor). Walk the stage forcing-questions list one-by-one. **Tag each Q&A Log row's `Decision impact` cell with `[topic:<id>]`** (the id is given in the stage's forcing-questions checklist) so the linter can verify coverage in any natural language. Do NOT batch and do NOT defer to a single approval gate at the end. The `qa_log_unconverged` linter rule will block `stage-complete` when convergence is not reached (forcing topic ids untagged AND last 2 turns still produce decision-changing rows AND no stop-signal).\n- **For other stages** (spec/plan/tdd/build/review/ship): ask user input only at explicit approval gates (scope mode, plan approval, challenge resolution, ship finalization), not for routine progress updates.\n- If you find yourself proposing a draft after 1-2 questions in brainstorm/scope/design, STOP \u2014 go back to the forcing-questions list and continue.\n- Mandatory subagents in brainstorm/scope/design run only AFTER the user approves the elicitation outcome (see each stage's \"Run Phase: post-elicitation\" rows). Dispatching them before the Q&A loop converges violates the contract.\n- Never echo cclaw command lines (`node .cclaw/hooks/...`, `--evidence-json '{...}'`) to chat \u2014 the user does not run cclaw manually. Run helpers via the tool layer; report only the resulting summary.\n- If harness capabilities are partial, record waiver reasons in delegation logs.\n\n## Routing Source Of Truth\n\n- Primary router: `.cclaw/skills/using-cclaw/SKILL.md`.\n- Stage behavior: current stage skill plus `.cclaw/state/flow-state.json`.\n- Preamble budget: keep role/status announcements brief and avoid repeating\n them unless the stage or role changes.\n";
|
|
11
11
|
export declare function buildRulesJson(): Record<string, unknown>;
|
|
@@ -1565,6 +1565,11 @@ Track-specific skips are allowed only when \`flow-state.track\` + \`skippedStage
|
|
|
1565
1565
|
## Verification Discipline
|
|
1566
1566
|
|
|
1567
1567
|
- No completion claim without fresh command evidence in this turn.
|
|
1568
|
+
- Stage completion claim requires \`stage-complete\` exit 0 in the current turn. Quote the success line; do not paraphrase, do not infer success from skipped retries.
|
|
1569
|
+
|
|
1570
|
+
## Protocol label hygiene
|
|
1571
|
+
|
|
1572
|
+
\`skip\` wording means different things depending on phase: brainstorm/scope/design Q&A stop-signals may still literal **skip**/enough/move-on wording; structured ship closeout retros and compound clustering prompts should expose **no changes** (or accept-as-is language) rather than labeling the passive path as skip. Keep the verbs aligned with the harness question copy you present to the human.
|
|
1568
1573
|
- Do not mark gates passed from memory.
|
|
1569
1574
|
- Keep evidence in \`.cclaw/artifacts/\`; archive through closeout via \`/cc\` or cancel early via \`node .cclaw/hooks/cancel-run.mjs\`.
|
|
1570
1575
|
|
package/dist/early-loop.d.ts
CHANGED
|
@@ -23,6 +23,10 @@ export interface EarlyLoopStatus {
|
|
|
23
23
|
escalationReason?: string;
|
|
24
24
|
lastUpdatedAt: string;
|
|
25
25
|
}
|
|
26
|
+
export declare function clampEarlyLoopStatusForWrite(status: EarlyLoopStatus): {
|
|
27
|
+
status: EarlyLoopStatus;
|
|
28
|
+
clampedFrom: number | null;
|
|
29
|
+
};
|
|
26
30
|
export interface EarlyLoopLogConcern {
|
|
27
31
|
id: string;
|
|
28
32
|
severity: EarlyLoopConcernSeverity;
|
package/dist/early-loop.js
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import { DEFAULT_EARLY_LOOP_MAX_ITERATIONS } from "./config.js";
|
|
3
3
|
export const EARLY_LOOP_STAGES = ["brainstorm", "scope", "design"];
|
|
4
|
+
export function clampEarlyLoopStatusForWrite(status) {
|
|
5
|
+
if (status.iteration <= status.maxIterations) {
|
|
6
|
+
return { status, clampedFrom: null };
|
|
7
|
+
}
|
|
8
|
+
return {
|
|
9
|
+
status: {
|
|
10
|
+
...status,
|
|
11
|
+
iteration: status.maxIterations
|
|
12
|
+
},
|
|
13
|
+
clampedFrom: status.iteration
|
|
14
|
+
};
|
|
15
|
+
}
|
|
4
16
|
const CONCERN_ID_PREFIX = "C-";
|
|
5
17
|
function severityWeight(severity) {
|
|
6
18
|
if (severity === "critical")
|
|
@@ -234,11 +246,12 @@ export function deriveEarlyLoopStatus(entries, options) {
|
|
|
234
246
|
convergenceTripped = true;
|
|
235
247
|
escalationReason = `max iterations ${maxIterations} reached with ${openConcerns.length} open concern(s)`;
|
|
236
248
|
}
|
|
249
|
+
const iteration = Math.min(currentIteration, maxIterations);
|
|
237
250
|
return {
|
|
238
251
|
schemaVersion: 1,
|
|
239
252
|
stage: options.stage,
|
|
240
253
|
runId: options.runId,
|
|
241
|
-
iteration
|
|
254
|
+
iteration,
|
|
242
255
|
maxIterations,
|
|
243
256
|
openConcerns,
|
|
244
257
|
resolvedConcerns,
|
package/dist/flow-state.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { DiscoveryMode, FlowStage, FlowTrack, TransitionRule } from "./types.js";
|
|
2
2
|
export declare const TRANSITION_RULES: TransitionRule[];
|
|
3
3
|
export declare const FLOW_STATE_SCHEMA_VERSION = 1;
|
|
4
|
+
/** Snapshot from `collectRepoSignals` at last successful `start-flow` (optional on older states). */
|
|
5
|
+
export interface RepoSignals {
|
|
6
|
+
fileCount: number;
|
|
7
|
+
hasReadme: boolean;
|
|
8
|
+
hasPackageManifest: boolean;
|
|
9
|
+
capturedAt: string;
|
|
10
|
+
}
|
|
4
11
|
export interface StageGateState {
|
|
5
12
|
required: string[];
|
|
6
13
|
recommended: string[];
|
|
@@ -37,8 +44,8 @@ export interface RetroState {
|
|
|
37
44
|
* from the correct step even across sessions.
|
|
38
45
|
*
|
|
39
46
|
* - `idle` — ship not complete, or closeout not yet started.
|
|
40
|
-
* - `post_ship_review` — unified closeout leg: retro acceptance/edit/
|
|
41
|
-
* plus compound pass execution (or explicit
|
|
47
|
+
* - `post_ship_review` — unified closeout leg: retro acceptance/edit/no changes
|
|
48
|
+
* plus compound pass execution (or explicit no-additional-changes path).
|
|
42
49
|
* - `ready_to_archive` — retro + compound done; archive is the next
|
|
43
50
|
* automatic step.
|
|
44
51
|
* - `archived` — archive completed in this session (transient — archive
|
|
@@ -104,6 +111,16 @@ export interface FlowState {
|
|
|
104
111
|
retro: RetroState;
|
|
105
112
|
/** Ship → post_ship_review → archive substate for resumable closeout. */
|
|
106
113
|
closeout: CloseoutState;
|
|
114
|
+
/** Repo shape signals captured at last successful start-flow (omit on legacy files). */
|
|
115
|
+
repoSignals?: RepoSignals;
|
|
116
|
+
/**
|
|
117
|
+
* Best-effort stage completion timestamps (ISO strings) captured as stages
|
|
118
|
+
* enter `completedStages`. Missing keys behave like legacy flows with no audit
|
|
119
|
+
* clock for post-closure mutation hints.
|
|
120
|
+
*/
|
|
121
|
+
completedStageMeta?: Partial<Record<FlowStage, {
|
|
122
|
+
completedAt: string;
|
|
123
|
+
}>>;
|
|
107
124
|
}
|
|
108
125
|
export interface StageInteractionHint {
|
|
109
126
|
skipQuestions?: boolean;
|
package/dist/flow-state.js
CHANGED
|
@@ -10,8 +10,8 @@ export const FLOW_STATE_SCHEMA_VERSION = 1;
|
|
|
10
10
|
* from the correct step even across sessions.
|
|
11
11
|
*
|
|
12
12
|
* - `idle` — ship not complete, or closeout not yet started.
|
|
13
|
-
* - `post_ship_review` — unified closeout leg: retro acceptance/edit/
|
|
14
|
-
* plus compound pass execution (or explicit
|
|
13
|
+
* - `post_ship_review` — unified closeout leg: retro acceptance/edit/no changes
|
|
14
|
+
* plus compound pass execution (or explicit no-additional-changes path).
|
|
15
15
|
* - `ready_to_archive` — retro + compound done; archive is the next
|
|
16
16
|
* automatic step.
|
|
17
17
|
* - `archived` — archive completed in this session (transient — archive
|
|
@@ -3,7 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { resolveArtifactPath } from "../../artifact-paths.js";
|
|
4
4
|
import { appendDelegation, checkMandatoryDelegations, readDelegationEvents, readDelegationLedger } from "../../delegation.js";
|
|
5
5
|
import { verifyCompletedStagesGateClosure, verifyCurrentStageGateEvidence } from "../../gate-evidence.js";
|
|
6
|
-
import { extractMarkdownSectionBody, parseLearningsSection } from "../../artifact-linter.js";
|
|
6
|
+
import { extractMarkdownSectionBody, learningsParseFailureHumanSummary, parseLearningsSection } from "../../artifact-linter.js";
|
|
7
7
|
import { getAvailableTransitions, getTransitionGuards } from "../../flow-state.js";
|
|
8
8
|
import { appendKnowledge } from "../../knowledge-store.js";
|
|
9
9
|
import { readFlowState, writeFlowState } from "../../runs.js";
|
|
@@ -262,7 +262,7 @@ export async function harvestStageLearnings(projectRoot, stage, track) {
|
|
|
262
262
|
parsedEntries: 0,
|
|
263
263
|
appendedEntries: 0,
|
|
264
264
|
skippedDuplicates: 0,
|
|
265
|
-
details: parsed.
|
|
265
|
+
details: learningsParseFailureHumanSummary(resolvedArtifact.relPath, parsed.errors)
|
|
266
266
|
};
|
|
267
267
|
}
|
|
268
268
|
const appendResult = await appendKnowledge(projectRoot, parsed.entries, {
|
|
@@ -434,6 +434,19 @@ export async function runAdvanceStage(projectRoot, args, io) {
|
|
|
434
434
|
extraStageFlags: args.skipQuestions ? ["--skip-questions"] : undefined
|
|
435
435
|
});
|
|
436
436
|
if (!validation.ok) {
|
|
437
|
+
const delegationFailureCount = validation.delegation.missing.length +
|
|
438
|
+
validation.delegation.missingEvidence.length +
|
|
439
|
+
validation.delegation.missingDispatchProof.length +
|
|
440
|
+
validation.delegation.legacyInferredCompletions.length +
|
|
441
|
+
validation.delegation.corruptEventLines.length +
|
|
442
|
+
validation.delegation.staleWorkers.length;
|
|
443
|
+
const gatesFailureCount = validation.gates.issues.length;
|
|
444
|
+
const closureFailureCount = validation.completedStages.issues.length;
|
|
445
|
+
const failureCounts = {
|
|
446
|
+
delegation: delegationFailureCount,
|
|
447
|
+
gates: gatesFailureCount,
|
|
448
|
+
closure: closureFailureCount
|
|
449
|
+
};
|
|
437
450
|
const ledgerForDiag = await readDelegationLedger(projectRoot).catch(() => ({ entries: [] }));
|
|
438
451
|
const eventsForDiag = await readDelegationEvents(projectRoot).catch(() => ({ events: [], corruptLines: [] }));
|
|
439
452
|
const ledgerEntriesText = await fs.readFile(path.join(projectRoot, ".cclaw/state/delegation-events.jsonl"), "utf8").catch(() => "");
|
|
@@ -482,6 +495,7 @@ export async function runAdvanceStage(projectRoot, args, io) {
|
|
|
482
495
|
command: "advance-stage",
|
|
483
496
|
stage: args.stage,
|
|
484
497
|
kind: "validation-failed",
|
|
498
|
+
failureCounts,
|
|
485
499
|
delegation: validation.delegation,
|
|
486
500
|
gates: validation.gates,
|
|
487
501
|
completedStages: validation.completedStages,
|
|
@@ -493,7 +507,7 @@ export async function runAdvanceStage(projectRoot, args, io) {
|
|
|
493
507
|
nextActions
|
|
494
508
|
})}\n`);
|
|
495
509
|
}
|
|
496
|
-
io.stderr.write(`cclaw internal advance-stage: validation failed for stage "${args.stage}".\n`);
|
|
510
|
+
io.stderr.write(`cclaw internal advance-stage: validation failed for stage "${args.stage}" (delegation=${failureCounts.delegation}, gates=${failureCounts.gates}, closure=${failureCounts.closure}).\n`);
|
|
497
511
|
if (validation.delegation.missing.length > 0) {
|
|
498
512
|
io.stderr.write(`- missing delegations: ${validation.delegation.missing.join(", ")}\n`);
|
|
499
513
|
io.stderr.write(` next action: run the named agent(s) for this stage, or rerun with --waive-delegation=${validation.delegation.missing.join(",")} --waiver-reason="<why safe>" only when the user accepts the safety trade-off.\n`);
|
|
@@ -531,7 +545,8 @@ export async function runAdvanceStage(projectRoot, args, io) {
|
|
|
531
545
|
const proactiveTrace = await ensureProactiveDelegationTrace(projectRoot, args.stage, {
|
|
532
546
|
acceptWaiver: args.acceptProactiveWaiver,
|
|
533
547
|
waiverReason: args.acceptProactiveWaiverReason,
|
|
534
|
-
discoveryMode: flowState.discoveryMode
|
|
548
|
+
discoveryMode: flowState.discoveryMode,
|
|
549
|
+
repoSignals: flowState.repoSignals
|
|
535
550
|
});
|
|
536
551
|
if (proactiveTrace.missingRules.length > 0) {
|
|
537
552
|
const missingSummary = proactiveTrace.missingRules
|
|
@@ -560,20 +575,28 @@ export async function runAdvanceStage(projectRoot, args, io) {
|
|
|
560
575
|
}
|
|
561
576
|
const learningsHarvest = await harvestStageLearnings(projectRoot, args.stage, flowState.track);
|
|
562
577
|
if (!learningsHarvest.ok) {
|
|
563
|
-
io.stderr.write(`cclaw internal advance-stage:
|
|
578
|
+
io.stderr.write(`cclaw internal advance-stage: ${learningsHarvest.details}\n`);
|
|
564
579
|
return 1;
|
|
565
580
|
}
|
|
566
581
|
const satisfiedGuards = new Set([...nextPassed, ...selectedTransitionGuards]);
|
|
567
582
|
const successor = resolveSuccessorTransition(args.stage, flowState.track, transitionTargets, satisfiedGuards, new Set(selectedTransitionGuards));
|
|
568
583
|
const completedStages = blockedReviewRoute
|
|
569
|
-
? flowState.completedStages.filter((
|
|
584
|
+
? flowState.completedStages.filter((finished) => finished !== args.stage)
|
|
570
585
|
: flowState.completedStages.includes(args.stage)
|
|
571
586
|
? [...flowState.completedStages]
|
|
572
587
|
: [...flowState.completedStages, args.stage];
|
|
588
|
+
let completedStageMeta = flowState.completedStageMeta ?? {};
|
|
589
|
+
if (!blockedReviewRoute && !flowState.completedStages.includes(args.stage)) {
|
|
590
|
+
completedStageMeta = {
|
|
591
|
+
...completedStageMeta,
|
|
592
|
+
[args.stage]: { completedAt: new Date().toISOString() }
|
|
593
|
+
};
|
|
594
|
+
}
|
|
573
595
|
const interactionHints = nextInteractionHints(flowState, args, successor);
|
|
574
596
|
const finalState = {
|
|
575
597
|
...candidateState,
|
|
576
598
|
completedStages,
|
|
599
|
+
completedStageMeta,
|
|
577
600
|
currentStage: successor ?? args.stage,
|
|
578
601
|
interactionHints
|
|
579
602
|
};
|
|
@@ -248,10 +248,11 @@ export function parseStartFlowArgs(tokens) {
|
|
|
248
248
|
}
|
|
249
249
|
if (token === "--discovery-mode" || token.startsWith("--discovery-mode=")) {
|
|
250
250
|
const raw = readValue("--discovery-mode").trim();
|
|
251
|
-
|
|
251
|
+
const normalized = raw.toLowerCase();
|
|
252
|
+
if (!isDiscoveryMode(normalized)) {
|
|
252
253
|
throw new Error(`--discovery-mode must be one of: lean, guided, deep.`);
|
|
253
254
|
}
|
|
254
|
-
discoveryMode =
|
|
255
|
+
discoveryMode = normalized;
|
|
255
256
|
continue;
|
|
256
257
|
}
|
|
257
258
|
if (token === "--class" || token.startsWith("--class=")) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type StageAutoSubagentDispatch } from "../../content/stage-schema.js";
|
|
2
2
|
import type { DiscoveryMode, FlowStage } from "../../types.js";
|
|
3
|
+
import type { RepoSignals } from "../../flow-state.js";
|
|
3
4
|
export interface ProactiveDelegationTraceResult {
|
|
4
5
|
missingRules: StageAutoSubagentDispatch[];
|
|
5
6
|
}
|
|
@@ -7,13 +8,14 @@ export interface ProactiveDelegationTraceResult {
|
|
|
7
8
|
* Ensure every proactive dispatch rule for the stage has a ledger row for the
|
|
8
9
|
* active run, or an explicit user-flag waiver.
|
|
9
10
|
*
|
|
10
|
-
* Lean/guided discovery on
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* Lean/guided discovery on brainstorm/scope/design keeps only proactive rules
|
|
12
|
+
* marked `essentialAcrossModes` (researcher today) so external research stays
|
|
13
|
+
* auditable without requiring every discretionary proactive lens. Deep
|
|
14
|
+
* discovery evaluates the full proactive matrix.
|
|
14
15
|
*/
|
|
15
16
|
export declare function ensureProactiveDelegationTrace(projectRoot: string, stage: FlowStage, options: {
|
|
16
17
|
acceptWaiver: boolean;
|
|
17
18
|
waiverReason?: string;
|
|
18
19
|
discoveryMode: DiscoveryMode;
|
|
20
|
+
repoSignals?: RepoSignals;
|
|
19
21
|
}): Promise<ProactiveDelegationTraceResult>;
|
|
@@ -3,20 +3,25 @@ import { stageAutoSubagentDispatch } from "../../content/stage-schema.js";
|
|
|
3
3
|
function isEarlyElicitationStage(stage) {
|
|
4
4
|
return stage === "brainstorm" || stage === "scope" || stage === "design";
|
|
5
5
|
}
|
|
6
|
+
function proactiveRulesForDiscoveryMode(stage, discoveryMode) {
|
|
7
|
+
const proactiveRules = stageAutoSubagentDispatch(stage).filter((rule) => rule.mode === "proactive");
|
|
8
|
+
if (isEarlyElicitationStage(stage) && (discoveryMode === "lean" || discoveryMode === "guided")) {
|
|
9
|
+
return proactiveRules.filter((rule) => rule.essentialAcrossModes === true);
|
|
10
|
+
}
|
|
11
|
+
return proactiveRules;
|
|
12
|
+
}
|
|
6
13
|
/**
|
|
7
14
|
* Ensure every proactive dispatch rule for the stage has a ledger row for the
|
|
8
15
|
* active run, or an explicit user-flag waiver.
|
|
9
16
|
*
|
|
10
|
-
* Lean/guided discovery on
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
17
|
+
* Lean/guided discovery on brainstorm/scope/design keeps only proactive rules
|
|
18
|
+
* marked `essentialAcrossModes` (researcher today) so external research stays
|
|
19
|
+
* auditable without requiring every discretionary proactive lens. Deep
|
|
20
|
+
* discovery evaluates the full proactive matrix.
|
|
14
21
|
*/
|
|
15
22
|
export async function ensureProactiveDelegationTrace(projectRoot, stage, options) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
const proactiveRules = stageAutoSubagentDispatch(stage).filter((rule) => rule.mode === "proactive");
|
|
23
|
+
void options.repoSignals;
|
|
24
|
+
const proactiveRules = proactiveRulesForDiscoveryMode(stage, options.discoveryMode);
|
|
20
25
|
if (proactiveRules.length === 0)
|
|
21
26
|
return { missingRules: [] };
|
|
22
27
|
const ledger = await readDelegationLedger(projectRoot);
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { type FlowState } from "../../flow-state.js";
|
|
1
|
+
import { type FlowState, type RepoSignals } from "../../flow-state.js";
|
|
2
2
|
import type { StartFlowArgs } from "./parsers.js";
|
|
3
3
|
import type { Writable } from "node:stream";
|
|
4
4
|
interface InternalIo {
|
|
5
5
|
stdout: Writable;
|
|
6
6
|
stderr: Writable;
|
|
7
7
|
}
|
|
8
|
+
/** One-pass repo snapshot (max ~200 files, skips `node_modules`/`.git`). */
|
|
9
|
+
export declare function collectRepoSignals(projectRoot: string): Promise<RepoSignals>;
|
|
8
10
|
export declare function discoverStartFlowContext(projectRoot: string): Promise<string[]>;
|
|
9
11
|
export declare function appendIdeaArtifact(projectRoot: string, args: StartFlowArgs, previous?: FlowState): Promise<void>;
|
|
10
12
|
export declare function runStartFlow(projectRoot: string, args: StartFlowArgs, io: InternalIo): Promise<number>;
|
|
@@ -13,6 +13,68 @@ function resolveTaskClass(className, fallback) {
|
|
|
13
13
|
}
|
|
14
14
|
return fallback;
|
|
15
15
|
}
|
|
16
|
+
const REPO_SIGNAL_SKIP_DIRS = new Set(["node_modules", ".git"]);
|
|
17
|
+
/** One-pass repo snapshot (max ~200 files, skips `node_modules`/`.git`). */
|
|
18
|
+
export async function collectRepoSignals(projectRoot) {
|
|
19
|
+
const capturedAt = new Date().toISOString();
|
|
20
|
+
const cap = 200;
|
|
21
|
+
let fileCount = 0;
|
|
22
|
+
async function visit(absDir, depth) {
|
|
23
|
+
if (fileCount >= cap)
|
|
24
|
+
return;
|
|
25
|
+
let entries;
|
|
26
|
+
try {
|
|
27
|
+
entries = await fs.readdir(absDir, { withFileTypes: true });
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
for (const ent of entries) {
|
|
33
|
+
if (fileCount >= cap)
|
|
34
|
+
return;
|
|
35
|
+
const name = ent.name;
|
|
36
|
+
if (REPO_SIGNAL_SKIP_DIRS.has(name))
|
|
37
|
+
continue;
|
|
38
|
+
const abs = path.join(absDir, name);
|
|
39
|
+
if (ent.isFile()) {
|
|
40
|
+
fileCount += 1;
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (ent.isDirectory() && depth < 1) {
|
|
44
|
+
await visit(abs, depth + 1);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
let hasReadme = false;
|
|
49
|
+
let hasPackageManifest = false;
|
|
50
|
+
for (const fname of ["README.md", "readme.md", "Readme.md"]) {
|
|
51
|
+
try {
|
|
52
|
+
const st = await fs.stat(path.join(projectRoot, fname));
|
|
53
|
+
if (st.isFile())
|
|
54
|
+
hasReadme = true;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// ignore
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
for (const manifest of ["package.json", "pyproject.toml", "Cargo.toml"]) {
|
|
61
|
+
try {
|
|
62
|
+
const st = await fs.stat(path.join(projectRoot, manifest));
|
|
63
|
+
if (st.isFile())
|
|
64
|
+
hasPackageManifest = true;
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// ignore
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
await visit(projectRoot, 0);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
fileCount = Math.min(fileCount, cap);
|
|
75
|
+
}
|
|
76
|
+
return { fileCount, hasReadme, hasPackageManifest, capturedAt };
|
|
77
|
+
}
|
|
16
78
|
export async function discoverStartFlowContext(projectRoot) {
|
|
17
79
|
const lines = [];
|
|
18
80
|
const seedFiles = (await listFilesUnder(projectRoot, path.join(RUNTIME_ROOT, "seeds"), 10))
|
|
@@ -145,6 +207,8 @@ export async function runStartFlow(projectRoot, args, io) {
|
|
|
145
207
|
}
|
|
146
208
|
};
|
|
147
209
|
}
|
|
210
|
+
const repoSignals = await collectRepoSignals(projectRoot);
|
|
211
|
+
nextState = { ...nextState, repoSignals };
|
|
148
212
|
await writeFlowState(projectRoot, nextState, { allowReset: true });
|
|
149
213
|
await appendIdeaArtifact(projectRoot, args, current);
|
|
150
214
|
if (!args.quiet) {
|
|
@@ -157,7 +221,8 @@ export async function runStartFlow(projectRoot, args, io) {
|
|
|
157
221
|
taskClass: nextState.taskClass ?? null,
|
|
158
222
|
currentStage: nextState.currentStage,
|
|
159
223
|
skippedStages: nextState.skippedStages,
|
|
160
|
-
activeRunId: nextState.activeRunId
|
|
224
|
+
activeRunId: nextState.activeRunId,
|
|
225
|
+
repoSignals
|
|
161
226
|
}, null, 2)}\n`);
|
|
162
227
|
}
|
|
163
228
|
return 0;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { RUNTIME_ROOT } from "../constants.js";
|
|
3
|
-
import { computeEarlyLoopStatus, formatEarlyLoopStatusLine, isEarlyLoopStage } from "../early-loop.js";
|
|
3
|
+
import { clampEarlyLoopStatusForWrite, computeEarlyLoopStatus, formatEarlyLoopStatusLine, isEarlyLoopStage } from "../early-loop.js";
|
|
4
4
|
import { writeFileSafe } from "../fs-utils.js";
|
|
5
5
|
import { readFlowState } from "../runs.js";
|
|
6
6
|
function parseArgs(tokens) {
|
|
@@ -71,7 +71,12 @@ export async function runEarlyLoopStatusCommand(projectRoot, argv, io) {
|
|
|
71
71
|
return 1;
|
|
72
72
|
}
|
|
73
73
|
const runId = (args.runId ?? flow?.activeRunId ?? "active").trim() || "active";
|
|
74
|
-
|
|
74
|
+
let status = await computeEarlyLoopStatus(stage, runId, path.join(stateDir(projectRoot), "early-loop-log.jsonl"));
|
|
75
|
+
const persisted = clampEarlyLoopStatusForWrite(status);
|
|
76
|
+
if (persisted.clampedFrom !== null) {
|
|
77
|
+
io.stderr.write(`cclaw internal early-loop-status: early-loop iteration ${persisted.clampedFrom} exceeds maxIterations ${status.maxIterations}; clamping before write.\n`);
|
|
78
|
+
status = persisted.status;
|
|
79
|
+
}
|
|
75
80
|
if (args.write) {
|
|
76
81
|
const target = path.join(stateDir(projectRoot), "early-loop.json");
|
|
77
82
|
await writeFileSafe(target, `${JSON.stringify(status, null, 2)}\n`);
|
package/dist/run-persistence.js
CHANGED
|
@@ -163,7 +163,32 @@ function coerceTrack(value) {
|
|
|
163
163
|
return isFlowTrack(value) ? value : "standard";
|
|
164
164
|
}
|
|
165
165
|
function coerceDiscoveryMode(value) {
|
|
166
|
-
|
|
166
|
+
if (typeof value === "string") {
|
|
167
|
+
const normalized = value.trim().toLowerCase();
|
|
168
|
+
if (isDiscoveryMode(normalized))
|
|
169
|
+
return normalized;
|
|
170
|
+
}
|
|
171
|
+
return "guided";
|
|
172
|
+
}
|
|
173
|
+
function coerceRepoSignals(value) {
|
|
174
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
const typed = value;
|
|
178
|
+
const fileCountRaw = typed.fileCount;
|
|
179
|
+
const fileCount = typeof fileCountRaw === "number" && Number.isFinite(fileCountRaw) && fileCountRaw >= 0
|
|
180
|
+
? Math.min(Math.floor(fileCountRaw), 1_000_000)
|
|
181
|
+
: undefined;
|
|
182
|
+
const capturedAt = typeof typed.capturedAt === "string" ? typed.capturedAt.trim() : "";
|
|
183
|
+
if (fileCount === undefined || !capturedAt) {
|
|
184
|
+
return undefined;
|
|
185
|
+
}
|
|
186
|
+
return {
|
|
187
|
+
fileCount,
|
|
188
|
+
hasReadme: typed.hasReadme === true,
|
|
189
|
+
hasPackageManifest: typed.hasPackageManifest === true,
|
|
190
|
+
capturedAt
|
|
191
|
+
};
|
|
167
192
|
}
|
|
168
193
|
/**
|
|
169
194
|
* Wave 24 follow-up (v6.1.1) — preserve `flow-state.json#taskClass`
|
|
@@ -230,6 +255,24 @@ function sanitizeStaleStages(value) {
|
|
|
230
255
|
}
|
|
231
256
|
return out;
|
|
232
257
|
}
|
|
258
|
+
function sanitizeCompletedStageMeta(value) {
|
|
259
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
const out = {};
|
|
263
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
264
|
+
if (!isFlowStage(key))
|
|
265
|
+
continue;
|
|
266
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
267
|
+
continue;
|
|
268
|
+
const record = raw;
|
|
269
|
+
const ca = typeof record.completedAt === "string" ? record.completedAt.trim() : "";
|
|
270
|
+
if (ca.length > 0) {
|
|
271
|
+
out[key] = { completedAt: ca };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return Object.keys(out).length > 0 ? out : undefined;
|
|
275
|
+
}
|
|
233
276
|
function sanitizeRewinds(value) {
|
|
234
277
|
if (!Array.isArray(value)) {
|
|
235
278
|
return [];
|
|
@@ -385,6 +428,8 @@ function coerceFlowState(parsed) {
|
|
|
385
428
|
? activeRunIdRaw.trim()
|
|
386
429
|
: next.activeRunId;
|
|
387
430
|
const taskClass = coerceTaskClass(parsed.taskClass);
|
|
431
|
+
const repoSignals = coerceRepoSignals(parsed.repoSignals);
|
|
432
|
+
const completedStageMeta = sanitizeCompletedStageMeta(parsed.completedStageMeta);
|
|
388
433
|
const state = {
|
|
389
434
|
schemaVersion: FLOW_STATE_SCHEMA_VERSION,
|
|
390
435
|
activeRunId,
|
|
@@ -395,6 +440,8 @@ function coerceFlowState(parsed) {
|
|
|
395
440
|
track,
|
|
396
441
|
discoveryMode,
|
|
397
442
|
...(taskClass !== undefined ? { taskClass } : {}),
|
|
443
|
+
...(repoSignals ? { repoSignals } : {}),
|
|
444
|
+
...(completedStageMeta ? { completedStageMeta } : {}),
|
|
398
445
|
skippedStages: sanitizeSkippedStages(parsed.skippedStages, track),
|
|
399
446
|
staleStages: sanitizeStaleStages(parsed.staleStages),
|
|
400
447
|
rewinds: sanitizeRewinds(parsed.rewinds),
|