cclaw-cli 6.2.0 → 6.3.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/content/hooks.js +153 -1
- package/dist/content/skills.js +1 -0
- package/dist/content/stage-schema.js +4 -2
- package/dist/content/stages/schema-types.d.ts +6 -0
- package/dist/content/start-command.js +5 -1
- package/dist/content/templates.d.ts +1 -1
- package/dist/content/templates.js +1 -0
- package/dist/flow-state.d.ts +9 -0
- package/dist/internal/advance-stage/advance.js +17 -2
- package/dist/internal/advance-stage/parsers.js +3 -2
- package/dist/internal/advance-stage/proactive-delegation-trace.d.ts +2 -0
- package/dist/internal/advance-stage/proactive-delegation-trace.js +17 -1
- package/dist/internal/advance-stage/start-flow.d.ts +3 -1
- package/dist/internal/advance-stage/start-flow.js +64 -0
- package/dist/run-persistence.js +28 -1
- package/package.json +1 -1
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;
|
package/dist/content/skills.js
CHANGED
|
@@ -389,6 +389,7 @@ function completionParametersBlock(schema, track) {
|
|
|
389
389
|
- Fill \`## Learnings\` before closeout: either \`- None this stage.\` or JSON bullets with required keys \`type\`, \`trigger\`, \`action\`, \`confidence\` (knowledge-schema compatible).
|
|
390
390
|
- 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
391
|
- 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.
|
|
392
|
+
- 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
393
|
- Completion protocol: verify required gates, update the artifact, then use the completion helper with \`--evidence-json\` and \`--passed\` for every satisfied gate.
|
|
393
394
|
`;
|
|
394
395
|
}
|
|
@@ -498,7 +498,8 @@ const STAGE_AUTO_SUBAGENT_DISPATCH = {
|
|
|
498
498
|
runPhase: "post-elicitation",
|
|
499
499
|
when: "When repository, market, docs, or prior-art context changes the approach set. Runs only after the adaptive elicitation Q&A loop converges.",
|
|
500
500
|
purpose: "Provide search-before-read summaries and context-readiness evidence before large reads or decisions.",
|
|
501
|
-
requiresUserGate: false
|
|
501
|
+
requiresUserGate: false,
|
|
502
|
+
dependsOnInternalRepoSignals: true
|
|
502
503
|
}
|
|
503
504
|
],
|
|
504
505
|
scope: [
|
|
@@ -535,7 +536,8 @@ const STAGE_AUTO_SUBAGENT_DISPATCH = {
|
|
|
535
536
|
runPhase: "post-elicitation",
|
|
536
537
|
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
538
|
purpose: "Summarize search/context findings before the scope contract locks accepted/rejected/deferred ideas.",
|
|
538
|
-
requiresUserGate: false
|
|
539
|
+
requiresUserGate: false,
|
|
540
|
+
dependsOnInternalRepoSignals: true
|
|
539
541
|
},
|
|
540
542
|
{
|
|
541
543
|
agent: "product-discovery",
|
|
@@ -53,6 +53,12 @@ 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, proactive trace requirements for this row may be skipped on an
|
|
58
|
+
* empty/sparse repo (see `ensureProactiveDelegationTrace`). Used for
|
|
59
|
+
* `researcher` on early elicitation stages in deep discovery mode.
|
|
60
|
+
*/
|
|
61
|
+
dependsOnInternalRepoSignals?: boolean;
|
|
56
62
|
}
|
|
57
63
|
export type StageComplexityTier = "lightweight" | "standard" | "deep";
|
|
58
64
|
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
|
|
|
@@ -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- 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,7 @@ 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.
|
|
1568
1569
|
- Do not mark gates passed from memory.
|
|
1569
1570
|
- Keep evidence in \`.cclaw/artifacts/\`; archive through closeout via \`/cc\` or cancel early via \`node .cclaw/hooks/cancel-run.mjs\`.
|
|
1570
1571
|
|
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[];
|
|
@@ -104,6 +111,8 @@ 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;
|
|
107
116
|
}
|
|
108
117
|
export interface StageInteractionHint {
|
|
109
118
|
skipQuestions?: boolean;
|
|
@@ -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
|
|
@@ -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
|
}
|
|
@@ -16,4 +17,5 @@ export declare function ensureProactiveDelegationTrace(projectRoot: string, stag
|
|
|
16
17
|
acceptWaiver: boolean;
|
|
17
18
|
waiverReason?: string;
|
|
18
19
|
discoveryMode: DiscoveryMode;
|
|
20
|
+
repoSignals?: RepoSignals;
|
|
19
21
|
}): Promise<ProactiveDelegationTraceResult>;
|
|
@@ -3,6 +3,20 @@ 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 isSparseRepoForResearcherSkip(repoSignals) {
|
|
7
|
+
if (!repoSignals)
|
|
8
|
+
return false;
|
|
9
|
+
return repoSignals.fileCount < 5 && !repoSignals.hasReadme && !repoSignals.hasPackageManifest;
|
|
10
|
+
}
|
|
11
|
+
function skipRepoDependentProactiveRule(rule, stage, discoveryMode, repoSignals) {
|
|
12
|
+
if (discoveryMode !== "deep")
|
|
13
|
+
return false;
|
|
14
|
+
if (stage !== "brainstorm" && stage !== "scope")
|
|
15
|
+
return false;
|
|
16
|
+
if (!rule.dependsOnInternalRepoSignals)
|
|
17
|
+
return false;
|
|
18
|
+
return isSparseRepoForResearcherSkip(repoSignals);
|
|
19
|
+
}
|
|
6
20
|
/**
|
|
7
21
|
* Ensure every proactive dispatch rule for the stage has a ledger row for the
|
|
8
22
|
* active run, or an explicit user-flag waiver.
|
|
@@ -16,7 +30,9 @@ export async function ensureProactiveDelegationTrace(projectRoot, stage, options
|
|
|
16
30
|
if (isEarlyElicitationStage(stage) && (options.discoveryMode === "lean" || options.discoveryMode === "guided")) {
|
|
17
31
|
return { missingRules: [] };
|
|
18
32
|
}
|
|
19
|
-
const proactiveRules = stageAutoSubagentDispatch(stage)
|
|
33
|
+
const proactiveRules = stageAutoSubagentDispatch(stage)
|
|
34
|
+
.filter((rule) => rule.mode === "proactive")
|
|
35
|
+
.filter((rule) => !skipRepoDependentProactiveRule(rule, stage, options.discoveryMode, options.repoSignals));
|
|
20
36
|
if (proactiveRules.length === 0)
|
|
21
37
|
return { missingRules: [] };
|
|
22
38
|
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) {
|
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`
|
|
@@ -385,6 +410,7 @@ function coerceFlowState(parsed) {
|
|
|
385
410
|
? activeRunIdRaw.trim()
|
|
386
411
|
: next.activeRunId;
|
|
387
412
|
const taskClass = coerceTaskClass(parsed.taskClass);
|
|
413
|
+
const repoSignals = coerceRepoSignals(parsed.repoSignals);
|
|
388
414
|
const state = {
|
|
389
415
|
schemaVersion: FLOW_STATE_SCHEMA_VERSION,
|
|
390
416
|
activeRunId,
|
|
@@ -395,6 +421,7 @@ function coerceFlowState(parsed) {
|
|
|
395
421
|
track,
|
|
396
422
|
discoveryMode,
|
|
397
423
|
...(taskClass !== undefined ? { taskClass } : {}),
|
|
424
|
+
...(repoSignals ? { repoSignals } : {}),
|
|
398
425
|
skippedStages: sanitizeSkippedStages(parsed.skippedStages, track),
|
|
399
426
|
staleStages: sanitizeStaleStages(parsed.staleStages),
|
|
400
427
|
rewinds: sanitizeRewinds(parsed.rewinds),
|