cclaw-cli 0.51.24 → 0.51.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +135 -414
- package/dist/artifact-linter.js +10 -6
- package/dist/config.d.ts +1 -1
- package/dist/config.js +28 -3
- package/dist/content/core-agents.d.ts +110 -0
- package/dist/content/core-agents.js +255 -3
- package/dist/content/examples.js +8 -5
- package/dist/content/harness-doc.d.ts +1 -0
- package/dist/content/harness-doc.js +3 -0
- package/dist/content/hooks.d.ts +1 -0
- package/dist/content/hooks.js +189 -0
- package/dist/content/next-command.js +10 -6
- package/dist/content/reference-patterns.d.ts +18 -0
- package/dist/content/reference-patterns.js +391 -0
- package/dist/content/skills.js +42 -36
- package/dist/content/stage-common-guidance.js +19 -3
- package/dist/content/stage-schema.d.ts +12 -0
- package/dist/content/stage-schema.js +184 -28
- package/dist/content/stages/_lint-metadata/index.js +3 -2
- package/dist/content/stages/brainstorm.js +7 -3
- package/dist/content/stages/design.js +12 -3
- package/dist/content/stages/review.js +7 -5
- package/dist/content/stages/schema-types.d.ts +9 -2
- package/dist/content/stages/scope.js +8 -2
- package/dist/content/stages/ship.js +3 -2
- package/dist/content/stages/tdd.js +18 -13
- package/dist/content/start-command.js +3 -2
- package/dist/content/status-command.js +17 -6
- package/dist/content/subagents.js +286 -40
- package/dist/content/templates.js +64 -3
- package/dist/content/tree-command.js +7 -1
- package/dist/delegation.d.ts +34 -1
- package/dist/delegation.js +168 -8
- package/dist/doctor-registry.js +9 -0
- package/dist/doctor.js +121 -6
- package/dist/gate-evidence.js +25 -2
- package/dist/harness-adapters.d.ts +6 -0
- package/dist/harness-adapters.js +28 -4
- package/dist/install.js +5 -10
- package/dist/internal/advance-stage.js +179 -26
- package/dist/run-persistence.js +21 -3
- package/dist/tdd-verification-evidence.d.ts +17 -0
- package/dist/tdd-verification-evidence.js +43 -0
- package/dist/types.d.ts +10 -0
- package/package.json +1 -1
package/dist/content/examples.js
CHANGED
|
@@ -490,7 +490,7 @@ const GOOD_BAD_EXAMPLES = {
|
|
|
490
490
|
},
|
|
491
491
|
{
|
|
492
492
|
label: "Scope change trace",
|
|
493
|
-
good: "Scope delta at 2026-04-15: user asked to add per-user mute preferences. Decision: moved from Out-of-scope → In-scope; acknowledged cost (≈1 day, +1 schema migration); risk: touches settings surface. Recorded in
|
|
493
|
+
good: "Scope delta at 2026-04-15: user asked to add per-user mute preferences. Decision: moved from Out-of-scope → In-scope; acknowledged cost (≈1 day, +1 schema migration); risk: touches settings surface. Recorded in \`.cclaw/artifacts/03-design-<slug>.md#scope-trace\`. Requires re-running scope review before design lock.",
|
|
494
494
|
bad: "Added mute preferences to scope.",
|
|
495
495
|
lesson: "Scope changes silently are how projects drift. Every in↔out move needs a timestamp, a cost estimate, and a link to the next review it invalidates."
|
|
496
496
|
}
|
|
@@ -676,12 +676,13 @@ function exampleSummaryBullets(stage) {
|
|
|
676
676
|
const STAGE_EXAMPLE_SECTION_HEADINGS = {
|
|
677
677
|
brainstorm: [
|
|
678
678
|
"Problem Decision Record (product or technical-maintenance framing)",
|
|
679
|
-
"
|
|
679
|
+
"Reference Pattern Candidates and approaches with trade-offs",
|
|
680
680
|
"Recommended direction + open questions",
|
|
681
681
|
"Clarification log and decision record"
|
|
682
682
|
],
|
|
683
683
|
scope: [
|
|
684
684
|
"In-scope / out-of-scope / deferred lists with concrete capabilities",
|
|
685
|
+
"Reference Pattern Registry with accepted/rejected/deferred dispositions",
|
|
685
686
|
"Requirements table with stable R# IDs",
|
|
686
687
|
"Boundary stress-tests and non-negotiables",
|
|
687
688
|
"Decision record for premise challenges"
|
|
@@ -689,6 +690,7 @@ const STAGE_EXAMPLE_SECTION_HEADINGS = {
|
|
|
689
690
|
design: [
|
|
690
691
|
"Blast-radius file list",
|
|
691
692
|
"Mandatory architecture diagram (Mermaid)",
|
|
693
|
+
"Reference-Grade Contracts for mirrored patterns",
|
|
692
694
|
"Failure-mode table with detection + mitigation",
|
|
693
695
|
"Test strategy + performance budget",
|
|
694
696
|
"Completion dashboard + unresolved decisions"
|
|
@@ -706,7 +708,7 @@ const STAGE_EXAMPLE_SECTION_HEADINGS = {
|
|
|
706
708
|
"No-Placeholder scan row + WAIT_FOR_CONFIRM marker"
|
|
707
709
|
],
|
|
708
710
|
tdd: [
|
|
709
|
-
"RED evidence per slice (failing test output)",
|
|
711
|
+
"RED evidence per vertical slice (failing test output)",
|
|
710
712
|
"Acceptance mapping per slice",
|
|
711
713
|
"GREEN evidence (full-suite pass)",
|
|
712
714
|
"REFACTOR notes with behavior-preservation confirmation",
|
|
@@ -716,10 +718,11 @@ const STAGE_EXAMPLE_SECTION_HEADINGS = {
|
|
|
716
718
|
"Spec-compliance findings (Layer 1)",
|
|
717
719
|
"Code-quality findings (Layer 2)",
|
|
718
720
|
"Severity, evidence, and status per finding",
|
|
719
|
-
"
|
|
721
|
+
"Victory Detector-backed go / no-go verdict"
|
|
720
722
|
],
|
|
721
723
|
ship: [
|
|
722
724
|
"Release checklist (version, changelog, tag, artifacts)",
|
|
725
|
+
"Victory Detector: valid review, fresh preflight, rollback, finalization enum",
|
|
723
726
|
"Rollback plan with trigger, steps, verification",
|
|
724
727
|
"Runbook (how to verify the release post-deploy)",
|
|
725
728
|
"Sign-off block"
|
|
@@ -732,7 +735,7 @@ const DOMAIN_LABELS = {
|
|
|
732
735
|
"data-pipeline": "Data pipeline / ETL"
|
|
733
736
|
};
|
|
734
737
|
export const RESEARCH_FLEET_USAGE_EXAMPLE = [
|
|
735
|
-
"Before drafting
|
|
738
|
+
"Before drafting `.cclaw/artifacts/03-design-<slug>.md`, run `research/research-fleet.md` once and",
|
|
736
739
|
"capture all four lenses in `.cclaw/artifacts/02a-research.md`.",
|
|
737
740
|
"Dispatch semantics by harness: Claude/OpenCode/Codex = native subagents;",
|
|
738
741
|
"Cursor = generic-dispatch Task mapping; role-switch only as degraded fallback.",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function harnessIntegrationDocMarkdown(): string;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export function harnessIntegrationDocMarkdown() {
|
|
2
|
+
return "# Harness Integration Matrix\n\nGenerated from `src/harness-adapters.ts` capabilities and hook event mappings. For the end-to-end subagent dispatch model, proof sequence, controller/worker responsibilities, and future roadmap, see [`docs/subagent-flow.md`](./subagent-flow.md).\n\n## Capability tiers\n\n| Harness | ID | Tier | declaredSupport | runtimeLaunch | Fallback | proofRequired | proofSource | Hook surface | Structured ask |\n|---|---|---|---|---|---|---|---|---|---|\n| Claude Code | `claude` | `tier1` (full native automation) | full | native Task launch | native | spanId+dispatchId or workerRunId+ACK | `.cclaw/state/delegation-events.jsonl` + ledger | full | AskUserQuestion |\n| Cursor | `cursor` | `tier2` (supported with fallback paths) | generic | generic Task/Subagent role prompt | generic-dispatch | spanId+dispatchId/evidenceRefs | events + artifact evidenceRefs | full | AskQuestion |\n| OpenCode | `opencode` | `tier2` hooks, native dispatch declared | full | prompt-level launch via Task / `@agent` against `.opencode/agents` | native | spanId+dispatchId+ackTs+completedTs | `.opencode/agents/<agent>.md` + events | plugin | question |\n| OpenAI Codex | `codex` | `tier2` hooks, native dispatch declared | full | prompt-level request to spawn `.codex/agents` custom agents | native | spanId+dispatchId+ackTs+completedTs | `.codex/agents/<agent>.toml` + events | limited | request_user_input |\n\nFallback legend:\n\n- `native` \u2014 first-class named subagent dispatch (Claude).\n- `generic-dispatch` \u2014 generic Task dispatcher mapped to cclaw roles (Cursor).\n- `role-switch` \u2014 degraded fallback for a runtime where declared native/generic dispatch is unavailable; explicit role headers, artifact outputs, and non-empty delegation-log evidenceRefs are required.\n- `waiver` \u2014 no parity path; reserved for harnesses that cannot role-switch (none shipped).\n\n## Stage-Aware Native Dispatch Workflow\n\nOpenCode and Codex receive generated native isolated subagents. Use them before considering role-switch fallback:\n\n1. Use the active stage skill's generated dispatch table as the source of truth.\n2. OpenCode: invoke `.opencode/agents/<agent>.md` via Task or `@<agent>`; Codex: ask Codex to spawn `.codex/agents/<agent>.toml` by name, in parallel when lanes are independent.\n3. Load `.cclaw/agents/<agent>.md`, execute only that role's stage task, and write outputs into the active stage artifact.\n4. Append `.cclaw/state/delegation-events.jsonl` for scheduled/launched/acknowledged/completed/failed/waived/stale, then mirror current state in `.cclaw/state/delegation-log.json`. The ledger is current state; the event log is proof/audit.\n5. Treat completed role-switch rows without `evidenceRefs` as unresolved; treat native isolated completion without matching `spanId` + `dispatchId`/`workerRunId` + `ackTs` + `completedTs` as fake isolated completion. Native isolated rows are not a role-switch substitute and should reflect a real dispatched worker.\n\nThis is staged agent work backed by the harness-native subagent surfaces. Role-switch remains only a degraded fallback when that surface is unavailable in the active runtime.\n\n## Parallel research dispatch semantics\n\nDesign-stage research fleet uses the same parity model:\n\n- **Claude / Cursor**: dispatch all four research lenses in one turn\n (stack, features, architecture, pitfalls) and synthesize into\n `.cclaw/artifacts/02a-research.md`.\n- **OpenCode / Codex**: dispatch generated native subagents for the same\n four lenses and run independent lanes in parallel where the active runtime\n permits. Use role-switch with evidence only as a degraded fallback.\n\n## Semantic hook event coverage\n\n| Event | Claude | Cursor | OpenCode | Codex |\n|---|---|---|---|---|\n| `session_rehydrate` | SessionStart matcher startup|resume|clear|compact | sessionStart/sessionResume/sessionClear/sessionCompact | plugin event handlers + transform rehydration | SessionStart matcher startup|resume |\n| `pre_tool_prompt_guard` | PreToolUse -> prompt-guard | preToolUse -> prompt-guard | plugin tool.execute.before -> prompt-guard | PreToolUse matcher Bash -> prompt-guard (plus UserPromptSubmit for non-Bash prompts) |\n| `pre_tool_workflow_guard` | PreToolUse -> workflow-guard | preToolUse -> workflow-guard | plugin tool.execute.before -> workflow-guard | PreToolUse matcher Bash -> workflow-guard (Bash-only) |\n| `post_tool_context_monitor` | PostToolUse -> context-monitor | postToolUse -> context-monitor | plugin tool.execute.after -> context-monitor | PostToolUse matcher Bash -> context-monitor (Bash-only) |\n| `stop_handoff` | Stop -> stop-handoff | stop -> stop-handoff | plugin session.idle -> stop-handoff | Stop -> stop-handoff |\n| `precompact_compat` | PreCompact -> pre-compact | sessionCompact -> pre-compact | plugin session.compacted -> pre-compact | missing |\n| `strict_state_verify` | missing | missing | missing | UserPromptSubmit -> verify-current-state (blocks only in strict mode) |\n\n## Hook lifecycle aliases\n\nThe generated Node dispatcher accepts a small compatibility alias set for lifecycle names: `stop` and `stop-checkpoint` route to `stop-handoff`, `precompact` routes to `pre-compact`, and `session-rehydrate` routes to `session-start`. The `pre-compact` handler is intentionally a no-op compatibility marker; rehydration remains the `session-start` responsibility after compact events. Harness JSON should still emit the canonical handler names from `src/content/hook-manifest.ts`.\n\n## Hook event casing\n\nHook keys are intentionally harness-native and must not be normalized:\n\n| Harness | ID | Event key casing |\n|---|---|---|\n| Claude Code | `claude` | PascalCase (`SessionStart`, `PreToolUse`) |\n| Cursor | `cursor` | camelCase (`sessionStart`, `preToolUse`) |\n| OpenCode | `opencode` | camelCase (`sessionStart`, `preToolUse`) |\n| OpenAI Codex | `codex` | PascalCase (`SessionStart`, `PreToolUse`) |\n\nUse the exact event names from each harness schema. Treating all hooks as one\nshared casing silently breaks generated wiring.\n\n## Interpretation\n\n- `tier1`: full native delegation + structured asks + full hook surface.\n- `tier2`: usable flow with capability gaps; mandatory delegation can require waivers.\n- Codex-specific ceiling: `PreToolUse` can only intercept `Bash`. Direct\n `Write`/`Edit` to `.cclaw/state/flow-state.json` cannot be hard-blocked\n at hook level, so the canonical path is\n `node .cclaw/hooks/stage-complete.mjs <stage>` plus the non-blocking\n `UserPromptSubmit` state nudge.\n- In `strict` mode, Codex additionally runs the generated Node/runtime `verify-current-state` path on `UserPromptSubmit` as a fail-closed check. Advisory mode remains non-blocking, including when the generated local Node entrypoint is missing; doctor reports that install drift separately. This strict-only coverage is represented explicitly by the `strict_state_verify` semantic row above.\n\n## Shared command contract\n\nAll harnesses receive the same utility commands:\n\n- `/cc` - flow entry and resume\n- `/cc-next` - stage progression and post-ship closeout\n- `/cc-ideate` - ideate mode for ranked repo-improvement backlog\n- `/cc-view` - read-only router for status/tree/diff\n\nRead-only subcommands:\n- `/cc-view status` - visual flow snapshot\n- `/cc-view tree` - deep flow tree (stages, artifacts, stale markers)\n- `/cc-view diff` - before/after flow-state diff map\n\nOperational work is handled by `/cc`, `/cc-next`, `/cc-ideate`, `/cc-view`, and `node .cclaw/hooks/stage-complete.mjs <stage>` inside the installed harness runtime. `npx cclaw-cli` is the installer/support surface for init, sync, upgrade, doctor, and explicit/manual archive; the normal stage flow must not depend on a runtime `cclaw` binary in PATH.\n\nCritical-path stage order remains canonical:\n`brainstorm -> scope -> design -> spec -> plan -> tdd -> review -> ship`\n\nEvery track then closes out through:\n`retro -> compound -> archive`\n\n## Stage -> skill folder mapping\n\n| Stage | Skill folder |\n|---|---|\n| `brainstorm` | `brainstorming` |\n| `scope` | `scope-shaping` |\n| `design` | `engineering-design-lock` |\n| `spec` | `specification-authoring` |\n| `plan` | `planning-and-task-breakdown` |\n| `tdd` | `test-driven-development` |\n| `review` | `two-layer-review` |\n| `ship` | `shipping-and-handoff` |\n\nThis map is generated from `src/constants.ts::STAGE_TO_SKILL_FOLDER` so\nskill-path naming stays explicit and stable even when stage ids differ from\nfolder names.\n\n## Install surfaces\n\nAlways generated:\n\n- `.cclaw/commands/*.md`\n- `.cclaw/skills/*/SKILL.md`\n- `.cclaw/state/*.json|*.jsonl`\n- `AGENTS.md` managed block\n\nHarness-specific additions:\n\n- `claude`: `.claude/commands/cc*.md`, `.claude/hooks/hooks.json`\n- `cursor`: `.cursor/commands/cc*.md`, `.cursor/hooks.json`, `.cursor/rules/cclaw-workflow.mdc`\n- `opencode`: `.opencode/commands/cc*.md`, `.opencode/plugins/cclaw-plugin.mjs`, opencode plugin registration with `permission.question: \"allow\"`; set `OPENCODE_ENABLE_QUESTION_TOOL=1` for ACP clients so structured asks can route through question tooling. Doctor validates the config permission and warns when the environment hint is absent.\n- `codex`: `.agents/skills/cc/SKILL.md`, `.agents/skills/cc-next/SKILL.md`, `.agents/skills/cc-ideate/SKILL.md`, `.agents/skills/cc-view/SKILL.md`, `.codex/hooks.json` (Codex CLI reads `.agents/skills/` for custom skills and consumes `.codex/hooks.json` on v0.114+ when `[features] codex_hooks = true` is set in `~/.codex/config.toml`. `.codex/commands/` and the legacy `.agents/skills/cclaw-cc*/` layout from v0.39.x are auto-cleaned on sync.)\n\n## Runtime observability\n\n- `npx cclaw-cli doctor` validates shim, hook, and lifecycle surfaces against this capability model.\n- `/cc-view status` and `/cc-view tree` surface the same harness tier/fallback facts from the generated runtime metadata.\n\n## Delegation Proof Model\n\nRuntime state is split deliberately:\n\n- `.cclaw/state/delegation-log.json` is the compact current ledger used by stage gates and `/cc-view` summaries.\n- `.cclaw/state/delegation-events.jsonl` is append-only audit proof for `scheduled`, `launched`, `acknowledged`, `completed`, `failed`, `waived`, and `stale` lifecycle transitions.\n- `.cclaw/state/subagents.json` is a lightweight active-worker tracker for status/tree/doctor surfaces.\n- `.cclaw/hooks/delegation-record.mjs` is the generated helper for lifecycle rows/events. It validates required fields and emits JSON diagnostics with `--json`.\n\nIsolated completion requires `spanId`, `dispatchId` or `workerRunId`, `dispatchSurface`, `agentDefinitionPath`, `ackTs`, `launchedTs`, and `completedTs`. Cursor/generic dispatch and role-switch also require evidence refs when artifact evidence is the proof source. Legacy inferred completions remain readable, but doctor reports them as warnings because they predate event-log proof.\n\n## Reference Audit Appendix\n\nStatus meanings: `deep` = read for transferable implementation contract; `targeted` = inspected the relevant files only; `skimmed` = searched/read enough to classify; `not relevant` = intentionally excluded from implementation influence.\n\n| Reference path under `/Users/zuevrs/Downloads/references` | Status | Findings preserved |\n|---|---|---|\n| `evanklem-evanflow/skills/evanflow-coder-overseer/SKILL.md` | deep | Contract-first coder/overseer loop, reviewer reads code rather than worker narrative, and integration overseer pattern map cleanly onto cclaw subagent guidance. |\n| `evanklem-evanflow/agents/evanflow-coder.md` | targeted | Worker role is narrow: implement the pasted contract, avoid broad orchestration, and return evidence for overseer verification. |\n| `evanklem-evanflow/agents/evanflow-overseer.md` | targeted | Overseer validates actual code and acceptance evidence before controller marks work complete. |\n| `oh-my-codex/src/agents/native-config.ts` | deep | Native agent config shape supports explicit metadata/model/tool posture; cclaw should validate generated `.codex/agents/*.toml` shape instead of trusting file presence. |\n| `oh-my-codex/src/team/state/events.ts` and `src/team/state/workers.ts` | targeted | Append-only events plus worker state are useful as separate audit/current-state layers; cclaw mirrors that with `delegation-events.jsonl` and `subagents.json`. |\n| `oh-my-openagent/src/tools/delegate-task/tools.ts` | deep | Delegation should have an explicit dispatch surface and mode instead of relying on a prose claim that an agent was launched. |\n| `oh-my-openagent/src/tools/delegate-task/subagent-resolver.ts` | targeted | Agent discovery should be checked by doctor so missing/corrupt generated agent definitions are visible before dispatch. |\n| `oh-my-openagent/src/tools/delegate-task/prompt-builder.ts` | targeted | Prompt builders should include exact invocation/return contracts; cclaw generated worker prompts now carry ACK/result schemas. |\n| `giancarloerra-socraticode/**` | skimmed | Useful for workflow/e2e and graph-oriented contract testing, but not a subagent dispatch implementation reference; no runtime pattern imported. |\n| unrelated large reference trees not named above | not relevant | Searched/skipped because they did not contain flow/subagent/harness dispatch patterns relevant to this plan. |\n";
|
|
3
|
+
}
|
package/dist/content/hooks.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export declare function startFlowScript(): string;
|
|
2
2
|
export declare function stageCompleteScript(): string;
|
|
3
|
+
export declare function delegationRecordScript(): string;
|
|
3
4
|
export declare function runHookCmdScript(): string;
|
|
4
5
|
export { claudeHooksJsonWithObservation as claudeHooksJson } from "./observe.js";
|
|
5
6
|
export { cursorHooksJsonWithObservation as cursorHooksJson } from "./observe.js";
|
package/dist/content/hooks.js
CHANGED
|
@@ -260,6 +260,195 @@ async function main() {
|
|
|
260
260
|
});
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
+
void main();
|
|
264
|
+
`;
|
|
265
|
+
}
|
|
266
|
+
export function delegationRecordScript() {
|
|
267
|
+
return `#!/usr/bin/env node
|
|
268
|
+
import fs from "node:fs/promises";
|
|
269
|
+
import path from "node:path";
|
|
270
|
+
import process from "node:process";
|
|
271
|
+
|
|
272
|
+
const RUNTIME_ROOT = ${JSON.stringify(RUNTIME_ROOT)};
|
|
273
|
+
const VALID_STATUSES = new Set(["scheduled", "launched", "acknowledged", "completed", "failed", "waived", "stale"]);
|
|
274
|
+
const TERMINAL = new Set(["completed", "failed", "waived", "stale"]);
|
|
275
|
+
|
|
276
|
+
function parseArgs(argv) {
|
|
277
|
+
const args = {};
|
|
278
|
+
for (const raw of argv) {
|
|
279
|
+
const valueMatch = /^--([^=]+)=(.*)$/u.exec(raw);
|
|
280
|
+
if (valueMatch) {
|
|
281
|
+
args[valueMatch[1]] = valueMatch[2];
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
const flagMatch = /^--([^=]+)$/u.exec(raw);
|
|
285
|
+
if (flagMatch) args[flagMatch[1]] = true;
|
|
286
|
+
}
|
|
287
|
+
return args;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
async function exists(filePath) {
|
|
291
|
+
try {
|
|
292
|
+
await fs.access(filePath);
|
|
293
|
+
return true;
|
|
294
|
+
} catch {
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async function detectRoot() {
|
|
300
|
+
const candidates = [
|
|
301
|
+
process.env.CCLAW_PROJECT_ROOT,
|
|
302
|
+
process.env.CLAUDE_PROJECT_DIR,
|
|
303
|
+
process.env.CURSOR_PROJECT_DIR,
|
|
304
|
+
process.env.CURSOR_PROJECT_ROOT,
|
|
305
|
+
process.env.OPENCODE_PROJECT_DIR,
|
|
306
|
+
process.env.OPENCODE_PROJECT_ROOT,
|
|
307
|
+
process.cwd()
|
|
308
|
+
].filter((value) => typeof value === "string" && value.length > 0);
|
|
309
|
+
for (const candidate of candidates) {
|
|
310
|
+
if (await exists(path.join(candidate, RUNTIME_ROOT))) return candidate;
|
|
311
|
+
}
|
|
312
|
+
return candidates[0] || process.cwd();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
async function readRunId(root) {
|
|
316
|
+
try {
|
|
317
|
+
const raw = await fs.readFile(path.join(root, RUNTIME_ROOT, "state", "flow-state.json"), "utf8");
|
|
318
|
+
const parsed = JSON.parse(raw);
|
|
319
|
+
return typeof parsed.activeRunId === "string" ? parsed.activeRunId : "unknown-run";
|
|
320
|
+
} catch {
|
|
321
|
+
return "unknown-run";
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async function readDelegationEvents(root) {
|
|
326
|
+
try {
|
|
327
|
+
const raw = await fs.readFile(path.join(root, RUNTIME_ROOT, "state", "delegation-events.jsonl"), "utf8");
|
|
328
|
+
return raw
|
|
329
|
+
.split(/\\r?\\n/u)
|
|
330
|
+
.filter((line) => line.trim().length > 0)
|
|
331
|
+
.map((line) => {
|
|
332
|
+
try {
|
|
333
|
+
return JSON.parse(line);
|
|
334
|
+
} catch {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
})
|
|
338
|
+
.filter((event) => event && typeof event === "object");
|
|
339
|
+
} catch {
|
|
340
|
+
return [];
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function hasPriorAck(events, args, runId) {
|
|
345
|
+
return events.some((event) =>
|
|
346
|
+
event.runId === runId &&
|
|
347
|
+
event.stage === args.stage &&
|
|
348
|
+
event.agent === args.agent &&
|
|
349
|
+
event.spanId === args["span-id"] &&
|
|
350
|
+
event.event === "acknowledged" &&
|
|
351
|
+
typeof event.ackTs === "string" &&
|
|
352
|
+
event.ackTs.length > 0
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function usage() {
|
|
357
|
+
process.stderr.write("Usage: 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]\\n");
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
async function main() {
|
|
361
|
+
const args = parseArgs(process.argv.slice(2));
|
|
362
|
+
const json = args.json !== undefined;
|
|
363
|
+
const problems = [];
|
|
364
|
+
if (!args.stage) problems.push("missing --stage");
|
|
365
|
+
if (!args.agent) problems.push("missing --agent");
|
|
366
|
+
if (args.mode !== "mandatory" && args.mode !== "proactive") problems.push("--mode must be mandatory or proactive");
|
|
367
|
+
if (!VALID_STATUSES.has(args.status)) problems.push("invalid --status");
|
|
368
|
+
if (!args["span-id"]) problems.push("missing --span-id");
|
|
369
|
+
if (args.status === "waived" && !args["waiver-reason"]) problems.push("waived status requires --waiver-reason");
|
|
370
|
+
if (args.status === "completed" && args["dispatch-surface"] !== "role-switch") {
|
|
371
|
+
for (const key of ["dispatch-id", "dispatch-surface", "agent-definition-path"]) {
|
|
372
|
+
if (!args[key]) problems.push("completed isolated/generic status requires --" + key);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
if (args.status === "completed" && args["dispatch-surface"] === "role-switch" && !args["evidence-ref"]) {
|
|
376
|
+
problems.push("completed role-switch status requires --evidence-ref");
|
|
377
|
+
}
|
|
378
|
+
if (problems.length > 0) {
|
|
379
|
+
if (json) process.stdout.write(JSON.stringify({ ok: false, problems }, null, 2) + "\\n");
|
|
380
|
+
else {
|
|
381
|
+
usage();
|
|
382
|
+
process.stderr.write("[cclaw] delegation-record: " + problems.join("; ") + "\\n");
|
|
383
|
+
}
|
|
384
|
+
process.exitCode = 1;
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const root = await detectRoot();
|
|
389
|
+
const now = new Date().toISOString();
|
|
390
|
+
const runId = await readRunId(root);
|
|
391
|
+
if (args.status === "completed" && args["dispatch-surface"] !== "role-switch" && !args["ack-ts"]) {
|
|
392
|
+
const priorEvents = await readDelegationEvents(root);
|
|
393
|
+
if (!hasPriorAck(priorEvents, args, runId)) {
|
|
394
|
+
const ackProblem = "completed isolated/generic status requires prior acknowledged event for same span or --ack-ts";
|
|
395
|
+
if (json) process.stdout.write(JSON.stringify({ ok: false, problems: [ackProblem] }, null, 2) + "\\n");
|
|
396
|
+
else {
|
|
397
|
+
usage();
|
|
398
|
+
process.stderr.write("[cclaw] delegation-record: " + ackProblem + "\\n");
|
|
399
|
+
}
|
|
400
|
+
process.exitCode = 1;
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
const status = args.status;
|
|
405
|
+
const row = {
|
|
406
|
+
stage: args.stage,
|
|
407
|
+
agent: args.agent,
|
|
408
|
+
mode: args.mode,
|
|
409
|
+
status,
|
|
410
|
+
spanId: args["span-id"],
|
|
411
|
+
dispatchId: args["dispatch-id"],
|
|
412
|
+
workerRunId: args["worker-run-id"],
|
|
413
|
+
dispatchSurface: args["dispatch-surface"],
|
|
414
|
+
agentDefinitionPath: args["agent-definition-path"],
|
|
415
|
+
fulfillmentMode: args["dispatch-surface"] === "role-switch" ? "role-switch" : args["dispatch-surface"] === "cursor-task" || args["dispatch-surface"] === "generic-task" ? "generic-dispatch" : "isolated",
|
|
416
|
+
waiverReason: args["waiver-reason"],
|
|
417
|
+
evidenceRefs: args["evidence-ref"] ? [args["evidence-ref"]] : [],
|
|
418
|
+
runId,
|
|
419
|
+
startTs: now,
|
|
420
|
+
ts: now,
|
|
421
|
+
launchedTs: args["launched-ts"] || (status === "launched" ? now : undefined),
|
|
422
|
+
ackTs: args["ack-ts"] || (status === "acknowledged" ? now : undefined),
|
|
423
|
+
completedTs: args["completed-ts"] || (status === "completed" ? now : undefined),
|
|
424
|
+
endTs: TERMINAL.has(status) ? now : undefined,
|
|
425
|
+
schemaVersion: 1
|
|
426
|
+
};
|
|
427
|
+
const clean = Object.fromEntries(Object.entries(row).filter(([, value]) => value !== undefined));
|
|
428
|
+
const event = { ...clean, event: status, eventTs: now };
|
|
429
|
+
const stateDir = path.join(root, RUNTIME_ROOT, "state");
|
|
430
|
+
await fs.mkdir(stateDir, { recursive: true });
|
|
431
|
+
await fs.appendFile(path.join(stateDir, "delegation-events.jsonl"), JSON.stringify(event) + "\\n", { encoding: "utf8", mode: 0o600 });
|
|
432
|
+
|
|
433
|
+
const ledgerPath = path.join(stateDir, "delegation-log.json");
|
|
434
|
+
let ledger = { runId, entries: [] };
|
|
435
|
+
try {
|
|
436
|
+
ledger = JSON.parse(await fs.readFile(ledgerPath, "utf8"));
|
|
437
|
+
if (!Array.isArray(ledger.entries)) ledger.entries = [];
|
|
438
|
+
} catch {
|
|
439
|
+
ledger = { runId, entries: [] };
|
|
440
|
+
}
|
|
441
|
+
if (!ledger.entries.some((entry) => entry.spanId === clean.spanId && entry.status === clean.status)) {
|
|
442
|
+
ledger.entries.push(clean);
|
|
443
|
+
ledger.runId = runId;
|
|
444
|
+
await fs.writeFile(ledgerPath, JSON.stringify(ledger, null, 2) + "\\n", { encoding: "utf8", mode: 0o600 });
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const active = ledger.entries.filter((entry) => ["scheduled", "launched", "acknowledged"].includes(entry.status));
|
|
448
|
+
await fs.writeFile(path.join(stateDir, "subagents.json"), JSON.stringify({ active, updatedAt: now }, null, 2) + "\\n", { encoding: "utf8", mode: 0o600 });
|
|
449
|
+
process.stdout.write(JSON.stringify({ ok: true, event }, null, 2) + "\\n");
|
|
450
|
+
}
|
|
451
|
+
|
|
263
452
|
void main();
|
|
264
453
|
`;
|
|
265
454
|
}
|
|
@@ -76,7 +76,7 @@ ${conversationLanguagePolicyMarkdown()}
|
|
|
76
76
|
|
|
77
77
|
1. Read **\`${flowPath}\`**. If missing → **BLOCKED** (state missing).
|
|
78
78
|
2. Parse JSON. Capture \`currentStage\` and \`stageGateCatalog[currentStage]\`.
|
|
79
|
-
3. If \`staleStages[currentStage]\` exists, do not advance automatically.
|
|
79
|
+
3. If \`staleStages[currentStage]\` exists, do not advance automatically. Report the stale marker reason/rewindId, re-run the stage artifact work, then clear only the current stage marker with \`cclaw internal rewind --ack <currentStage>\`.
|
|
80
80
|
4. Read **\`${reconciliationNoticesPath}\`** when present. If it contains entries for \`activeRunId + currentStage\` and the listed gate is still blocked in \`stageGateCatalog[currentStage].blocked\`, emit a structured warning before any stage-advance decision.
|
|
81
81
|
5. Let \`G\` = \`requiredGates\` for **\`currentStage\`** from the stage schema.
|
|
82
82
|
6. Let \`catalog\` = \`stageGateCatalog[currentStage]\` from flow state.
|
|
@@ -85,7 +85,7 @@ ${conversationLanguagePolicyMarkdown()}
|
|
|
85
85
|
9. If \`M\` is non-empty, inspect **\`${delegationPath}\`**. Treat as satisfied only if each mandatory agent is **completed** or **waived**.
|
|
86
86
|
10. For each satisfied mandatory delegation row, verify \`evidenceRefs\` is a non-empty array (unless status is \`waived\` with rationale). Missing evidenceRefs means delegation is unresolved.
|
|
87
87
|
11. If any mandatory delegation is missing and no waiver exists: **STOP** and ask the user whether to dispatch now or waive with rationale. Do not mark gates passed while delegation is unresolved.
|
|
88
|
-
12. If \`currentStage === "review"\` and \`catalog.blocked\` includes \`review_criticals_resolved\`, treat this as a hard remediation branch: recommend \`cclaw internal rewind tdd "review_blocked_by_critical
|
|
88
|
+
12. If \`currentStage === "review"\` and \`catalog.blocked\` includes \`review_criticals_resolved\`, treat this as a hard remediation branch: recommend the managed command \`cclaw internal rewind tdd "review_blocked_by_critical <finding-ids>"\`, and do not attempt to advance toward ship. After TDD rework, require \`cclaw internal rewind --ack tdd\` before continuing.
|
|
89
89
|
|
|
90
90
|
### Path A: Current stage is NOT complete (any gate unmet or delegation missing)
|
|
91
91
|
|
|
@@ -186,11 +186,13 @@ ${conversationLanguagePolicyMarkdown()}
|
|
|
186
186
|
Default output should be compact, like OMC/OMX operator surfaces:
|
|
187
187
|
|
|
188
188
|
\`\`\`
|
|
189
|
-
|
|
189
|
+
Current: <currentStage or closeout.shipSubstate> (<track>)
|
|
190
|
+
Stage: <currentStage>
|
|
190
191
|
Gates: <passed>/<required> passed, <blocked> blocked
|
|
191
192
|
Delegations: <done>/<mandatory> done
|
|
192
|
-
|
|
193
|
+
Blocked by: <none | gate/delegation/reconciliation/stale/TDD/review ids>
|
|
193
194
|
Next: <exact next action, usually /cc-next or one named remediation>
|
|
195
|
+
Evidence needed: <artifact/test/review/delegation evidence required to unblock>
|
|
194
196
|
\`\`\`
|
|
195
197
|
|
|
196
198
|
Only expand beyond this when blocked, when asking a structured question, or when
|
|
@@ -214,7 +216,7 @@ Do **not** mark gates satisfied from memory alone. Cite **artifact evidence** (p
|
|
|
214
216
|
|
|
215
217
|
1. Open **\`${flowPath}\`**.
|
|
216
218
|
2. Record \`currentStage\` and \`stageGateCatalog[currentStage]\`.
|
|
217
|
-
3. If \`staleStages[currentStage]\` exists, re-run the stage and clear marker via \`cclaw internal rewind --ack <currentStage>\` before advancing.
|
|
219
|
+
3. If \`staleStages[currentStage]\` exists, show the marker reason/rewindId, re-run the stage, and clear only the current marker via \`cclaw internal rewind --ack <currentStage>\` before advancing.
|
|
218
220
|
4. If the file is missing or invalid JSON → **BLOCKED** (report and stop).
|
|
219
221
|
5. Read \`${reconciliationNoticesPath}\` when present. For entries matching \`activeRunId + currentStage\` whose gate is still in \`stageGateCatalog[currentStage].blocked\`, show a warning with gate id + reason before proceeding.
|
|
220
222
|
|
|
@@ -242,7 +244,9 @@ Execute the stage protocol. The stage skill handles interaction, STOP points, ga
|
|
|
242
244
|
|
|
243
245
|
${ralphLoopContractSnippet()}
|
|
244
246
|
|
|
245
|
-
Special-case for review: if \`review_criticals_resolved\` is in \`blocked\`, route to rework instead of looping review forever
|
|
247
|
+
Special-case for review: if \`review_criticals_resolved\` is in \`blocked\`, route to rework instead of looping review forever - recommend \`cclaw internal rewind tdd "review_blocked_by_critical <finding-ids>"\`, then \`cclaw internal rewind --ack tdd\` after TDD rework.
|
|
248
|
+
|
|
249
|
+
Special-case for TDD blockers: when \`06-tdd.md\` records \`NO_SOURCE_CONTEXT\`, \`NO_TEST_SURFACE\`, \`NO_IMPLEMENTABLE_SLICE\`, \`RED_NOT_EXPRESSIBLE\`, or \`NO_VCS_MODE\`, keep status BLOCKED and print \`Current\`, \`Blocked by\`, \`Next\`, and \`Evidence needed\` instead of retrying speculative RED/GREEN work.
|
|
246
250
|
|
|
247
251
|
**Path B — stage IS complete (all gates met, all delegations done):**
|
|
248
252
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FlowStage } from "../types.js";
|
|
2
|
+
export interface ReferencePatternContract {
|
|
3
|
+
stage: FlowStage;
|
|
4
|
+
guidance: string[];
|
|
5
|
+
artifactSections: string[];
|
|
6
|
+
}
|
|
7
|
+
export interface ReferencePattern {
|
|
8
|
+
id: string;
|
|
9
|
+
title: string;
|
|
10
|
+
intent: string;
|
|
11
|
+
useWhen: string;
|
|
12
|
+
policyNeedles: string[];
|
|
13
|
+
contracts: ReferencePatternContract[];
|
|
14
|
+
}
|
|
15
|
+
export declare const REFERENCE_PATTERNS: ReferencePattern[];
|
|
16
|
+
export declare function referencePatternsForStage(stage: FlowStage): ReferencePattern[];
|
|
17
|
+
export declare function referencePatternContractsForStage(stage: FlowStage): ReferencePatternContract[];
|
|
18
|
+
export declare function referencePatternPolicyNeedles(stage: FlowStage): string[];
|