cclaw-cli 6.8.0 → 6.10.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/design.js +1 -1
- package/dist/artifact-linter/plan.js +37 -0
- package/dist/artifact-linter/shared.d.ts +48 -2
- package/dist/artifact-linter/shared.js +54 -5
- package/dist/artifact-linter/tdd.d.ts +31 -0
- package/dist/artifact-linter/tdd.js +357 -17
- package/dist/artifact-linter.js +87 -2
- package/dist/content/examples.js +9 -9
- package/dist/content/harness-doc.js +1 -1
- package/dist/content/hooks.js +140 -3
- package/dist/content/iron-laws.js +6 -2
- package/dist/content/node-hooks.js +15 -1308
- package/dist/content/reference-patterns.js +2 -2
- package/dist/content/skills-elicitation.js +2 -2
- package/dist/content/skills.js +1 -1
- package/dist/content/stages/brainstorm.js +2 -2
- package/dist/content/stages/design.js +2 -2
- package/dist/content/stages/scope.js +2 -2
- package/dist/content/stages/tdd.js +7 -8
- package/dist/content/subagents.js +20 -2
- package/dist/content/templates.js +5 -15
- package/dist/delegation.d.ts +102 -3
- package/dist/delegation.js +172 -14
- package/dist/early-loop.js +15 -1
- package/dist/gate-evidence.js +15 -23
- package/dist/harness-adapters.js +4 -2
- package/dist/install.js +37 -221
- package/dist/internal/advance-stage.js +19 -3
- package/dist/internal/detect-supply-chain-changes.d.ts +6 -0
- package/dist/internal/detect-supply-chain-changes.js +138 -0
- package/dist/internal/flow-state-repair.d.ts +7 -0
- package/dist/internal/flow-state-repair.js +57 -18
- package/dist/internal/plan-split-waves.d.ts +66 -0
- package/dist/internal/plan-split-waves.js +249 -0
- package/dist/run-persistence.d.ts +2 -0
- package/dist/run-persistence.js +62 -3
- package/dist/runtime/run-hook.mjs +44 -8729
- package/dist/tdd-slices.d.ts +90 -0
- package/dist/tdd-slices.js +375 -0
- package/package.json +1 -1
package/dist/artifact-linter.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import { resolveArtifactPath as resolveStageArtifactPath } from "./artifact-paths.js";
|
|
3
4
|
import { exists } from "./fs-utils.js";
|
|
4
5
|
import { stageSchema } from "./content/stage-schema.js";
|
|
5
6
|
import { readFlowState } from "./run-persistence.js";
|
|
6
|
-
import { duplicateH2Headings, extractH2Sections, extractRequirementIdsFromMarkdown, isShortCircuitActivated, normalizeHeadingTitle, parseFrontmatter, parseLearningsSection, sectionBodyByAnyName, sectionBodyByHeadingPrefix, sectionBodyByName, validateSectionBody, formatLearningsErrorsBullets } from "./artifact-linter/shared.js";
|
|
7
|
+
import { duplicateH2Headings, extractEvidencePointers, extractH2Sections, extractRequirementIdsFromMarkdown, isShortCircuitActivated, normalizeHeadingTitle, parseFrontmatter, parseLearningsSection, sectionBodyByAnyName, sectionBodyByHeadingPrefix, sectionBodyByName, validateSectionBody, formatLearningsErrorsBullets } from "./artifact-linter/shared.js";
|
|
8
|
+
import { foldTddSliceLedger, readTddSliceLedger } from "./tdd-slices.js";
|
|
7
9
|
import { shouldDemoteArtifactValidationByTrack } from "./content/stage-schema.js";
|
|
8
10
|
import { readDelegationLedger, recordArtifactValidationDemotedByTrack } from "./delegation.js";
|
|
9
11
|
import { classifyAndPersistFindings } from "./artifact-linter/findings-dedup.js";
|
|
@@ -145,6 +147,17 @@ export async function lintArtifact(projectRoot, stage, track = "standard", optio
|
|
|
145
147
|
}
|
|
146
148
|
}
|
|
147
149
|
const liteTierForValidators = shouldDemoteArtifactValidationByTrack(track, taskClass);
|
|
150
|
+
// v6.10.0 (T3) — pre-resolve RED/GREEN Evidence pointers and sidecar
|
|
151
|
+
// auto-satisfy state once for the whole TDD loop, then thread the
|
|
152
|
+
// booleans through `validateSectionBody`. We do the async resolution
|
|
153
|
+
// here (path existence + delegation spanId match) so the validators
|
|
154
|
+
// themselves stay sync.
|
|
155
|
+
const tddEvidenceContext = stage === "tdd"
|
|
156
|
+
? await resolveTddEvidencePointerContext({
|
|
157
|
+
projectRoot,
|
|
158
|
+
sections
|
|
159
|
+
})
|
|
160
|
+
: { red: {}, green: {} };
|
|
148
161
|
for (const v of schema.artifactValidation) {
|
|
149
162
|
const sectionKey = normalizeHeadingTitle(v.section).toLowerCase();
|
|
150
163
|
const scopeBoundaryAlias = stage === "scope" && sectionKey === "in scope / out of scope";
|
|
@@ -164,7 +177,8 @@ export async function lintArtifact(projectRoot, stage, track = "standard", optio
|
|
|
164
177
|
? { ok: false, details: `No ## heading matching required section "${v.section}".` }
|
|
165
178
|
: validateSectionBody(body, v.validationRule, v.section, {
|
|
166
179
|
sections,
|
|
167
|
-
liteTier: liteTierForValidators
|
|
180
|
+
liteTier: liteTierForValidators,
|
|
181
|
+
tddEvidence: stage === "tdd" ? tddEvidenceContext : undefined
|
|
168
182
|
});
|
|
169
183
|
const found = hasHeading && validation.ok;
|
|
170
184
|
findings.push({
|
|
@@ -420,3 +434,74 @@ const ARTIFACT_VALIDATION_LITE_DEMOTE_SECTIONS = new Set([
|
|
|
420
434
|
"Stale Diagram Drift Check",
|
|
421
435
|
"Product Discovery Delegation (Strategist Mode)"
|
|
422
436
|
]);
|
|
437
|
+
/**
|
|
438
|
+
* v6.10.0 (T3) — pre-resolve `Evidence:` pointers and sidecar
|
|
439
|
+
* auto-satisfy state for the TDD stage's RED/GREEN Evidence rows so
|
|
440
|
+
* `validateSectionBody` (sync) can short-circuit. A pointer of the form
|
|
441
|
+
* `<path>` is satisfied when the path exists on disk relative to the
|
|
442
|
+
* project root; `spanId:<id>` is satisfied when any delegation ledger
|
|
443
|
+
* row carries that span id. Sidecar auto-satisfy fires when
|
|
444
|
+
* `06-tdd-slices.jsonl` carries at least one slice with a non-empty
|
|
445
|
+
* `redOutputRef` / `greenOutputRef`.
|
|
446
|
+
*/
|
|
447
|
+
async function resolveTddEvidencePointerContext(input) {
|
|
448
|
+
const { projectRoot, sections } = input;
|
|
449
|
+
const redSection = sectionBodyByName(sections, "RED Evidence") ?? "";
|
|
450
|
+
const greenSection = sectionBodyByName(sections, "GREEN Evidence") ?? "";
|
|
451
|
+
const redPointers = extractEvidencePointers(redSection);
|
|
452
|
+
const greenPointers = extractEvidencePointers(greenSection);
|
|
453
|
+
let knownSpanIds = new Set();
|
|
454
|
+
if (redPointers.length > 0 || greenPointers.length > 0) {
|
|
455
|
+
try {
|
|
456
|
+
const ledger = await readDelegationLedger(projectRoot);
|
|
457
|
+
knownSpanIds = new Set(ledger.entries
|
|
458
|
+
.map((entry) => entry.spanId)
|
|
459
|
+
.filter((id) => typeof id === "string" && id.length > 0));
|
|
460
|
+
}
|
|
461
|
+
catch {
|
|
462
|
+
knownSpanIds = new Set();
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
async function pointerResolves(value) {
|
|
466
|
+
const trimmed = value.replace(/[`*_]/gu, "").trim();
|
|
467
|
+
if (trimmed.length === 0)
|
|
468
|
+
return false;
|
|
469
|
+
if (/^spanid\s*:/iu.test(trimmed)) {
|
|
470
|
+
const id = trimmed.replace(/^spanid\s*:\s*/iu, "").trim();
|
|
471
|
+
return id.length > 0 && knownSpanIds.has(id);
|
|
472
|
+
}
|
|
473
|
+
const candidate = path.isAbsolute(trimmed) ? trimmed : path.join(projectRoot, trimmed);
|
|
474
|
+
return exists(candidate);
|
|
475
|
+
}
|
|
476
|
+
async function anyResolved(values) {
|
|
477
|
+
for (const value of values) {
|
|
478
|
+
if (await pointerResolves(value))
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
let redSidecarAutoSatisfy = false;
|
|
484
|
+
let greenSidecarAutoSatisfy = false;
|
|
485
|
+
try {
|
|
486
|
+
const sidecar = await readTddSliceLedger(projectRoot);
|
|
487
|
+
if (sidecar.entries.length > 0) {
|
|
488
|
+
const folded = foldTddSliceLedger(sidecar.entries);
|
|
489
|
+
redSidecarAutoSatisfy = folded.some((entry) => typeof entry.redOutputRef === "string" && entry.redOutputRef.length > 0);
|
|
490
|
+
greenSidecarAutoSatisfy = folded.some((entry) => typeof entry.greenOutputRef === "string" && entry.greenOutputRef.length > 0);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
catch {
|
|
494
|
+
redSidecarAutoSatisfy = false;
|
|
495
|
+
greenSidecarAutoSatisfy = false;
|
|
496
|
+
}
|
|
497
|
+
return {
|
|
498
|
+
red: {
|
|
499
|
+
pointerSatisfied: await anyResolved(redPointers),
|
|
500
|
+
sidecarAutoSatisfy: redSidecarAutoSatisfy
|
|
501
|
+
},
|
|
502
|
+
green: {
|
|
503
|
+
pointerSatisfied: await anyResolved(greenPointers),
|
|
504
|
+
sidecarAutoSatisfy: greenSidecarAutoSatisfy
|
|
505
|
+
}
|
|
506
|
+
};
|
|
507
|
+
}
|
package/dist/content/examples.js
CHANGED
|
@@ -36,10 +36,10 @@ export const BEHAVIOR_ANCHORS = [
|
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
stage: "tdd",
|
|
39
|
-
section: "RED
|
|
40
|
-
bad: "
|
|
41
|
-
good: "
|
|
42
|
-
ruleHint: "
|
|
39
|
+
section: "Watched-RED Proof",
|
|
40
|
+
bad: "Hand-edit `S-1 | 2026-04-15T10:00 | observed RED` into the markdown table; nothing lands in the JSONL sidecar, so retries silently overwrite the row.",
|
|
41
|
+
good: "Run `cclaw-cli internal tdd-slice-record --slice S-1 --status red --test-file tests/feed.spec.ts --command \"npm test\" --paths src/api/feed.ts --ac AC-3`; the linter reads the sidecar.",
|
|
42
|
+
ruleHint: "RED/GREEN/REFACTOR transitions are recorded by `cclaw-cli internal tdd-slice-record`; the markdown tables are an auto-derived view from v6.10.0 onward."
|
|
43
43
|
},
|
|
44
44
|
{
|
|
45
45
|
stage: "review",
|
|
@@ -247,12 +247,12 @@ Plan is ready to execute after user confirmation.
|
|
|
247
247
|
| S-1 feed window | expected 30d window, got 7d |
|
|
248
248
|
| S-2 degraded banner | banner absent after forced disconnect |
|
|
249
249
|
|
|
250
|
-
## Acceptance
|
|
250
|
+
## Acceptance & Failure Map
|
|
251
251
|
|
|
252
|
-
| Slice | AC
|
|
253
|
-
| --- | --- |
|
|
254
|
-
| S-1 | AC-1 |
|
|
255
|
-
| S-2 | AC-3 |
|
|
252
|
+
| Slice | Source ID | AC ID | Expected behavior | RED-link |
|
|
253
|
+
| --- | --- | --- | --- | --- |
|
|
254
|
+
| S-1 | SRC-1 | AC-1 | feed window honors 30d cap | spanId:tdd-feed-window-red |
|
|
255
|
+
| S-2 | SRC-2 | AC-3 | degraded banner appears on disconnect | .cclaw/artifacts/06-tdd-slices.jsonl |
|
|
256
256
|
|
|
257
257
|
## GREEN
|
|
258
258
|
|
|
@@ -60,6 +60,6 @@ export function harnessIntegrationDocMarkdown() {
|
|
|
60
60
|
head + " 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",
|
|
61
61
|
perHarnessRecipeMarkdown(),
|
|
62
62
|
hookLayeringSectionMarkdown(),
|
|
63
|
-
"\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; sync 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` - stage progression and post-ship closeout\n- `/cc-idea` - idea 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-idea`, `/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, 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` | `brainstorm` |\n| `scope` | `scope` |\n| `design` | `design` |\n| `spec` | `spec` |\n| `plan` | `plan` |\n| `tdd` | `tdd` |\n| `review` | `review` |\n| `ship` | `ship` |\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. Sync/runtime checks validate the config permission and warn when the environment hint is absent.\n- `codex`: `.agents/skills/cc/SKILL.md`, `.agents/skills/cc-idea/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 sync` 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/sync reports.\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 sync 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 `<repo-relative references dir>` | 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 sync 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"
|
|
63
|
+
"\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\nRuntime Honesty 6.9.0 reduced the runtime to two dispatched handlers:\n`session-start` (rehydrate) and `stop-handoff` (clean handoff). Earlier\nreleases also generated `prompt-guard`, `workflow-guard`,\n`context-monitor`, `pre-compact`, and `verify-current-state` handlers, but\nthose entry points were unreachable via the dispatch table they shipped\nwith and have been removed.\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| `stop_handoff` | Stop -> stop-handoff | stop -> stop-handoff | plugin session.idle -> stop-handoff | Stop -> stop-handoff |\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`, and `session-rehydrate` routes to `session-start`. 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`, `Stop`) |\n| Cursor | `cursor` | camelCase (`sessionStart`, `stop`) |\n| OpenCode | `opencode` | camelCase (`sessionStart`, `stop`) |\n| OpenAI Codex | `codex` | PascalCase (`SessionStart`, `Stop`) |\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- Hook-level pre-tool blocking (prompt-guard / workflow-guard) was removed\n in 6.9.0. Stage transitions are owned by the canonical CLI path\n `node .cclaw/hooks/stage-complete.mjs <stage>`; harness sessions enforce\n workflow discipline via the iron-laws block surfaced at session-start\n rather than via per-tool hook interception.\n\n## Shared command contract\n\nAll harnesses receive the same utility commands:\n\n- `/cc` - flow entry and resume\n- `/cc` - stage progression and post-ship closeout\n- `/cc-idea` - idea 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-idea`, `/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, 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`post_ship_review -> archive`\n\n## Stage -> skill folder mapping\n\n| Stage | Skill folder |\n|---|---|\n| `brainstorm` | `brainstorm` |\n| `scope` | `scope` |\n| `design` | `design` |\n| `spec` | `spec` |\n| `plan` | `plan` |\n| `tdd` | `tdd` |\n| `review` | `review` |\n| `ship` | `ship` |\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. Sync/runtime checks validate the config permission and warn when the environment hint is absent.\n- `codex`: `.agents/skills/cc/SKILL.md`, `.agents/skills/cc-idea/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 sync` 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/sync reports.\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 sync 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 `<repo-relative references dir>` | 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 sync 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"
|
|
64
64
|
].join("");
|
|
65
65
|
}
|
package/dist/content/hooks.js
CHANGED
|
@@ -326,7 +326,7 @@ function hasPriorAck(events, args, runId) {
|
|
|
326
326
|
function usage() {
|
|
327
327
|
process.stderr.write([
|
|
328
328
|
"Usage:",
|
|
329
|
-
" 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>] [--supersede=<prevSpanId>] [--allow-parallel] [--json]",
|
|
329
|
+
" 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>] [--supersede=<prevSpanId>] [--allow-parallel] [--paths=<comma-separated>] [--override-cap=<int>] [--json]",
|
|
330
330
|
" 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]",
|
|
331
331
|
" node .cclaw/hooks/delegation-record.mjs --repair --span-id=<id> --repair-reason=\"<why>\" [--json]",
|
|
332
332
|
"",
|
|
@@ -339,6 +339,10 @@ function usage() {
|
|
|
339
339
|
"Dispatch dedup (v6.8.0):",
|
|
340
340
|
" --supersede=<prevSpanId> close the previous active span on this (stage, agent) as 'stale' before recording the new scheduled row",
|
|
341
341
|
" --allow-parallel record both spans as concurrent; new row is tagged allowParallel: true",
|
|
342
|
+
"",
|
|
343
|
+
"TDD parallel scheduler (v6.10.0):",
|
|
344
|
+
" --paths=<a,b,c> repo-relative paths the slice-implementer will edit; disjoint sets auto-promote to allowParallel, overlap throws DispatchOverlapError",
|
|
345
|
+
" --override-cap=<int> raise the slice-implementer fan-out cap once for this dispatch (default cap " + String(5) + ", env CCLAW_MAX_PARALLEL_SLICE_IMPLEMENTERS overrides globally)",
|
|
342
346
|
""
|
|
343
347
|
].join("\\n") + "\\n");
|
|
344
348
|
}
|
|
@@ -440,6 +444,13 @@ function buildRow(args, status, runId, now, options) {
|
|
|
440
444
|
// Inherit the span's startTs from prior rows so monotonic validation
|
|
441
445
|
// can compare against the original schedule, not the row write time.
|
|
442
446
|
const startTs = (options && options.spanStartTs) || now;
|
|
447
|
+
// v6.10.0 (P1): claimedPaths from --paths=<comma-separated>. Empty
|
|
448
|
+
// arrays are dropped so the row stays compatible with v6.9 readers.
|
|
449
|
+
const claimedPathsRaw = typeof args.paths === "string" ? args.paths : "";
|
|
450
|
+
const claimedPaths = claimedPathsRaw
|
|
451
|
+
.split(",")
|
|
452
|
+
.map((value) => value.trim())
|
|
453
|
+
.filter((value) => value.length > 0);
|
|
443
454
|
return {
|
|
444
455
|
stage: args.stage,
|
|
445
456
|
agent: args.agent,
|
|
@@ -461,7 +472,8 @@ function buildRow(args, status, runId, now, options) {
|
|
|
461
472
|
completedTs: args["completed-ts"] || (status === "completed" ? now : undefined),
|
|
462
473
|
endTs: TERMINAL.has(status) ? now : undefined,
|
|
463
474
|
schemaVersion: LEDGER_SCHEMA_VERSION,
|
|
464
|
-
allowParallel: args["allow-parallel"] === true ? true : undefined
|
|
475
|
+
allowParallel: args["allow-parallel"] === true ? true : undefined,
|
|
476
|
+
claimedPaths: claimedPaths.length > 0 ? claimedPaths : undefined
|
|
465
477
|
};
|
|
466
478
|
}
|
|
467
479
|
|
|
@@ -485,7 +497,11 @@ function findActiveSpanForPairInline(stage, agent, runId, entries) {
|
|
|
485
497
|
for (const entry of entries) {
|
|
486
498
|
if (!entry || typeof entry !== "object") continue;
|
|
487
499
|
if (typeof entry.spanId !== "string" || entry.spanId.length === 0) continue;
|
|
488
|
-
|
|
500
|
+
// Strict run-scope (v6.9.0 R7 fix): legacy entries without a runId
|
|
501
|
+
// are treated as foreign so they cannot keep an old span "active"
|
|
502
|
+
// across runs and trip dispatch_duplicate on a fresh dispatch.
|
|
503
|
+
if (typeof entry.runId !== "string" || entry.runId.length === 0) continue;
|
|
504
|
+
if (entry.runId !== runId) continue;
|
|
489
505
|
if (entry.stage !== stage || entry.agent !== agent) continue;
|
|
490
506
|
const existing = latestBySpan.get(entry.spanId);
|
|
491
507
|
if (!existing || effectiveTs(entry) >= effectiveTs(existing)) {
|
|
@@ -498,6 +514,102 @@ function findActiveSpanForPairInline(stage, agent, runId, entries) {
|
|
|
498
514
|
return null;
|
|
499
515
|
}
|
|
500
516
|
|
|
517
|
+
// keep in sync with computeActiveSubagents in src/delegation.ts
|
|
518
|
+
function computeActiveSubagentsInline(entries) {
|
|
519
|
+
const ACTIVE_STATUSES = new Set(["scheduled", "launched", "acknowledged"]);
|
|
520
|
+
const effectiveTs = (entry) =>
|
|
521
|
+
entry.completedTs || entry.ackTs || entry.launchedTs || entry.endTs || entry.startTs || entry.ts || "";
|
|
522
|
+
const latestBySpan = new Map();
|
|
523
|
+
for (const entry of entries) {
|
|
524
|
+
if (!entry || typeof entry !== "object") continue;
|
|
525
|
+
if (typeof entry.spanId !== "string" || entry.spanId.length === 0) continue;
|
|
526
|
+
const existing = latestBySpan.get(entry.spanId);
|
|
527
|
+
if (!existing || effectiveTs(entry) >= effectiveTs(existing)) {
|
|
528
|
+
latestBySpan.set(entry.spanId, entry);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
const active = [];
|
|
532
|
+
for (const entry of latestBySpan.values()) {
|
|
533
|
+
if (ACTIVE_STATUSES.has(entry.status)) active.push(entry);
|
|
534
|
+
}
|
|
535
|
+
return active;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// keep in sync with validateFileOverlap in src/delegation.ts
|
|
539
|
+
function validateFileOverlapInline(stamped, activeEntries) {
|
|
540
|
+
if (stamped.agent !== "slice-implementer" || stamped.stage !== "tdd") {
|
|
541
|
+
return { autoParallel: false, conflict: null };
|
|
542
|
+
}
|
|
543
|
+
const newPaths = Array.isArray(stamped.claimedPaths) ? stamped.claimedPaths : [];
|
|
544
|
+
if (newPaths.length === 0) {
|
|
545
|
+
return { autoParallel: false, conflict: null };
|
|
546
|
+
}
|
|
547
|
+
const sameLane = activeEntries.filter(
|
|
548
|
+
(entry) =>
|
|
549
|
+
entry.stage === stamped.stage &&
|
|
550
|
+
entry.agent === stamped.agent &&
|
|
551
|
+
entry.spanId !== stamped.spanId
|
|
552
|
+
);
|
|
553
|
+
if (sameLane.length === 0) {
|
|
554
|
+
return { autoParallel: true, conflict: null };
|
|
555
|
+
}
|
|
556
|
+
for (const existing of sameLane) {
|
|
557
|
+
const existingPaths = Array.isArray(existing.claimedPaths) ? existing.claimedPaths : [];
|
|
558
|
+
if (existingPaths.length === 0) {
|
|
559
|
+
return { autoParallel: false, conflict: null };
|
|
560
|
+
}
|
|
561
|
+
const overlap = newPaths.filter((p) => existingPaths.includes(p));
|
|
562
|
+
if (overlap.length > 0) {
|
|
563
|
+
return {
|
|
564
|
+
autoParallel: false,
|
|
565
|
+
conflict: {
|
|
566
|
+
existingSpanId: existing.spanId || "unknown",
|
|
567
|
+
newSpanId: stamped.spanId || "unknown",
|
|
568
|
+
pair: { stage: stamped.stage, agent: stamped.agent },
|
|
569
|
+
conflictingPaths: overlap
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
return { autoParallel: true, conflict: null };
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
const MAX_PARALLEL_SLICE_IMPLEMENTERS_INLINE = 5;
|
|
578
|
+
|
|
579
|
+
function readMaxParallelOverrideFromEnvInline() {
|
|
580
|
+
const raw = process.env.CCLAW_MAX_PARALLEL_SLICE_IMPLEMENTERS;
|
|
581
|
+
if (typeof raw !== "string" || raw.trim().length === 0) return null;
|
|
582
|
+
const parsed = Number(raw);
|
|
583
|
+
if (!Number.isFinite(parsed) || !Number.isInteger(parsed) || parsed < 1) return null;
|
|
584
|
+
return parsed;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// keep in sync with validateFanOutCap in src/delegation.ts
|
|
588
|
+
function validateFanOutCapInline(stamped, activeEntries, override) {
|
|
589
|
+
if (stamped.agent !== "slice-implementer" || stamped.stage !== "tdd") return null;
|
|
590
|
+
if (stamped.status !== "scheduled") return null;
|
|
591
|
+
let cap;
|
|
592
|
+
if (override !== null && override !== undefined && Number.isInteger(override) && override >= 1) {
|
|
593
|
+
cap = override;
|
|
594
|
+
} else {
|
|
595
|
+
cap = readMaxParallelOverrideFromEnvInline() || MAX_PARALLEL_SLICE_IMPLEMENTERS_INLINE;
|
|
596
|
+
}
|
|
597
|
+
const sameLaneActive = activeEntries.filter(
|
|
598
|
+
(entry) =>
|
|
599
|
+
entry.stage === stamped.stage &&
|
|
600
|
+
entry.agent === stamped.agent &&
|
|
601
|
+
entry.spanId !== stamped.spanId
|
|
602
|
+
);
|
|
603
|
+
if (sameLaneActive.length + 1 > cap) {
|
|
604
|
+
return {
|
|
605
|
+
cap,
|
|
606
|
+
active: sameLaneActive.length,
|
|
607
|
+
pair: { stage: stamped.stage, agent: stamped.agent }
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
return null;
|
|
611
|
+
}
|
|
612
|
+
|
|
501
613
|
function enforceDispatchDedupInline(stamped, priorEntries, args) {
|
|
502
614
|
if (stamped.status !== "scheduled") return null;
|
|
503
615
|
if (args["allow-parallel"] === true) return null;
|
|
@@ -988,6 +1100,31 @@ async function main() {
|
|
|
988
1100
|
emitErrorJson("delegation_timestamp_non_monotonic", violation, json);
|
|
989
1101
|
return;
|
|
990
1102
|
}
|
|
1103
|
+
|
|
1104
|
+
// v6.10.0 (P1+P2): file-overlap scheduler + fan-out cap. Run before
|
|
1105
|
+
// the legacy dispatch dedup so disjoint claimedPaths can auto-promote
|
|
1106
|
+
// to allowParallel and bypass the duplicate guard.
|
|
1107
|
+
if (status === "scheduled") {
|
|
1108
|
+
const sameRunPrior = priorLedger.filter((entry) => entry.runId === runId);
|
|
1109
|
+
const activeForRun = computeActiveSubagentsInline(sameRunPrior);
|
|
1110
|
+
const overlap = validateFileOverlapInline(clean, activeForRun);
|
|
1111
|
+
if (overlap.conflict) {
|
|
1112
|
+
emitErrorJson("dispatch_overlap", overlap.conflict, json);
|
|
1113
|
+
return;
|
|
1114
|
+
}
|
|
1115
|
+
if (overlap.autoParallel && clean.allowParallel !== true) {
|
|
1116
|
+
clean.allowParallel = true;
|
|
1117
|
+
args["allow-parallel"] = true;
|
|
1118
|
+
event.allowParallel = true;
|
|
1119
|
+
}
|
|
1120
|
+
const overrideRaw = typeof args["override-cap"] === "string" ? args["override-cap"] : null;
|
|
1121
|
+
const override = overrideRaw !== null ? Number(overrideRaw) : null;
|
|
1122
|
+
const capViolation = validateFanOutCapInline(clean, activeForRun, override);
|
|
1123
|
+
if (capViolation) {
|
|
1124
|
+
emitErrorJson("dispatch_cap", capViolation, json);
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
991
1128
|
const dedupViolation = enforceDispatchDedupInline(clean, priorLedger, args);
|
|
992
1129
|
if (dedupViolation) {
|
|
993
1130
|
if (dedupViolation.kind === "supersede") {
|
|
@@ -158,9 +158,13 @@ function hardGateReference(law) {
|
|
|
158
158
|
.join(", ");
|
|
159
159
|
}
|
|
160
160
|
export function ironLawsSkillMarkdown() {
|
|
161
|
+
// v6.9.0: Phase A purged the `PreToolUse` / `PostToolUse` / pre-tool
|
|
162
|
+
// pipeline handlers, so `review-coverage-complete-before-ship` is no
|
|
163
|
+
// longer hook-enforced — it now lives in the ship stage HARD-GATE.
|
|
164
|
+
// Only `stop-clean-or-handoff` (Stop hook) is still hook-enforced;
|
|
165
|
+
// everything else is stage-owned advisory.
|
|
161
166
|
const enforcedLawIds = new Set([
|
|
162
|
-
"stop-clean-or-handoff"
|
|
163
|
-
"review-coverage-complete-before-ship"
|
|
167
|
+
"stop-clean-or-handoff"
|
|
164
168
|
]);
|
|
165
169
|
const enforced = IRON_LAWS.filter((law) => enforcedLawIds.has(law.id));
|
|
166
170
|
const advisory = IRON_LAWS.filter((law) => !enforcedLawIds.has(law.id));
|