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
|
@@ -29,6 +29,11 @@ export const ARTIFACT_TEMPLATES = {
|
|
|
29
29
|
### Discovered context
|
|
30
30
|
- (paths, prior artifacts, seeds, prompt fragments — referenced by downstream stages, or \`- None.\`)
|
|
31
31
|
|
|
32
|
+
## Reference Pattern Candidates
|
|
33
|
+
| Pattern / source | Reusable invariant | Disposition (accept/reject/defer) | Why |
|
|
34
|
+
|---|---|---|---|
|
|
35
|
+
| | | | |
|
|
36
|
+
|
|
32
37
|
## Problem Decision Record
|
|
33
38
|
- **Depth:** lite | standard | deep
|
|
34
39
|
- **Frame type:** product | technical-maintenance
|
|
@@ -60,6 +65,12 @@ export const ARTIFACT_TEMPLATES = {
|
|
|
60
65
|
## How Might We
|
|
61
66
|
- *How might we …?* — one line naming the user, the desired outcome, and the binding constraint.
|
|
62
67
|
|
|
68
|
+
## Clarity Gate
|
|
69
|
+
- Ambiguity score (0.00-1.00):
|
|
70
|
+
- Decision boundaries (what this stage will decide):
|
|
71
|
+
- Reaffirmed non-goals:
|
|
72
|
+
- Residual-risk handoff to scope:
|
|
73
|
+
|
|
63
74
|
## Sharpening Questions
|
|
64
75
|
> Ask one decision-changing question at a time. For concrete early exits, record \`None - early exit\` with rationale.
|
|
65
76
|
| # | Question | Answer / Assumption | Decision impact |
|
|
@@ -81,7 +92,7 @@ export const ARTIFACT_TEMPLATES = {
|
|
|
81
92
|
- Scope handoff:
|
|
82
93
|
|
|
83
94
|
## Approaches
|
|
84
|
-
| Approach | Role | Upside | Architecture | Trade-offs | Reuses | Recommendation |
|
|
95
|
+
| Approach | Role | Upside | Architecture | Trade-offs | Reuses / reference pattern | Recommendation |
|
|
85
96
|
|---|---|---|---|---|---|---|
|
|
86
97
|
| A | baseline | modest | | | | |
|
|
87
98
|
| B | challenger | high | | | | |
|
|
@@ -195,6 +206,20 @@ ${SEED_SHELF_SECTION}
|
|
|
195
206
|
- **Success definition:**
|
|
196
207
|
- **Design handoff:**
|
|
197
208
|
|
|
209
|
+
## Decision Drivers
|
|
210
|
+
| Driver | Weight (1-5) | Option A | Option B | Option C | Notes |
|
|
211
|
+
|---|---|---|---|---|---|
|
|
212
|
+
| Value impact | | | | | |
|
|
213
|
+
| Risk reduction | | | | | |
|
|
214
|
+
| Reversibility | | | | | |
|
|
215
|
+
| Delivery effort | | | | | |
|
|
216
|
+
| Timeline fit | | | | | |
|
|
217
|
+
|
|
218
|
+
## Scope Completeness Score
|
|
219
|
+
- Score (0.00-1.00):
|
|
220
|
+
- What is still uncertain:
|
|
221
|
+
- Blockers requiring escalation:
|
|
222
|
+
|
|
198
223
|
## Scope Mode
|
|
199
224
|
- [ ] SCOPE EXPANSION — explore ambitious alternatives; user explicitly opts into the larger product slice.
|
|
200
225
|
- [ ] SELECTIVE EXPANSION — hold baseline scope and cherry-pick one high-leverage addition.
|
|
@@ -214,6 +239,11 @@ ${SEED_SHELF_SECTION}
|
|
|
214
239
|
## Taste Calibration
|
|
215
240
|
- Optional quality-bar references from in-repo modules/files.
|
|
216
241
|
|
|
242
|
+
## Reference Pattern Registry
|
|
243
|
+
| Pattern / source | Invariant to preserve | Disposition (accepted/rejected/deferred) | Scope boundary impact |
|
|
244
|
+
|---|---|---|---|
|
|
245
|
+
| | | | |
|
|
246
|
+
|
|
217
247
|
## Reference Pull
|
|
218
248
|
- Optional evidence from \`/Users/zuevrs/Downloads/references\`; list accepted/rejected ideas or \`Not needed - compact scope\`.
|
|
219
249
|
|
|
@@ -359,6 +389,11 @@ ${SEED_SHELF_SECTION}
|
|
|
359
389
|
|---|---|---|---|---|---|---|
|
|
360
390
|
| | | | | | | |
|
|
361
391
|
|
|
392
|
+
## Architecture Decision Record (ADR)
|
|
393
|
+
| ADR ID | Context | Decision | Alternatives considered | Consequences | Reversal trigger |
|
|
394
|
+
|---|---|---|---|---|---|
|
|
395
|
+
| ADR-1 | | | | | |
|
|
396
|
+
|
|
362
397
|
## Search Before Building
|
|
363
398
|
| Layer | Label | What to reuse first |
|
|
364
399
|
|---|---|---|
|
|
@@ -466,11 +501,21 @@ ${MARKDOWN_CODE_FENCE}
|
|
|
466
501
|
|---|---|---|---|
|
|
467
502
|
| | | | |
|
|
468
503
|
|
|
504
|
+
## Pre-mortem
|
|
505
|
+
| Scenario | Earliest warning signal | Mitigation owner | Containment action |
|
|
506
|
+
|---|---|---|---|
|
|
507
|
+
| | | | |
|
|
508
|
+
|
|
469
509
|
## Test Strategy
|
|
470
510
|
- Unit:
|
|
471
511
|
- Integration:
|
|
472
512
|
- E2E:
|
|
473
513
|
|
|
514
|
+
## Test-Diagram Mapping
|
|
515
|
+
| Critical flow | Test coverage (ID/command) | Diagram anchor | Gap status |
|
|
516
|
+
|---|---|---|---|
|
|
517
|
+
| | | | covered/gap |
|
|
518
|
+
|
|
474
519
|
## Performance Budget
|
|
475
520
|
| Critical path | Metric | Target | Measurement method |
|
|
476
521
|
|---|---|---|---|
|
|
@@ -530,6 +575,11 @@ ${MARKDOWN_CODE_FENCE}
|
|
|
530
575
|
|---|---|---|
|
|
531
576
|
| | | |
|
|
532
577
|
|
|
578
|
+
## Reference-Grade Contracts
|
|
579
|
+
| Pattern / source | Reusable invariant | Local adaptation | Rejection boundary | Verification signal |
|
|
580
|
+
|---|---|---|---|---|
|
|
581
|
+
| | | | | |
|
|
582
|
+
|
|
533
583
|
## Interface Contracts
|
|
534
584
|
- Standard/Deep add-on when module boundaries or APIs change; omit for compact local changes.
|
|
535
585
|
| Module | Produces | Consumes |
|
|
@@ -558,6 +608,9 @@ ${SEED_SHELF_SECTION}
|
|
|
558
608
|
|
|
559
609
|
**Decisions made:** 0 | **Unresolved:** 0
|
|
560
610
|
|
|
611
|
+
## Learning Capture Hint
|
|
612
|
+
For meaningful design work, replace the Learnings sentinel with 1-3 JSON learning bullets, for example: \`- {"type":"lesson","trigger":"when design chooses a risky fallback path","action":"record the switch trigger and rollback signal in Spec Handoff","confidence":"medium","domain":"architecture","stage":"design"}\`
|
|
613
|
+
|
|
561
614
|
## Learnings
|
|
562
615
|
- None this stage.
|
|
563
616
|
`,
|
|
@@ -735,7 +788,7 @@ Execution rule: complete and verify each batch before starting the next batch.
|
|
|
735
788
|
|
|
736
789
|
## Execution Posture
|
|
737
790
|
- Posture: sequential | dependency-batched | blocked
|
|
738
|
-
- RED/GREEN/REFACTOR checkpoint plan:
|
|
791
|
+
- Vertical-slice RED/GREEN/REFACTOR checkpoint plan:
|
|
739
792
|
- Incremental commits: yes/no/deferred because
|
|
740
793
|
|
|
741
794
|
## RED Evidence
|
|
@@ -744,7 +797,7 @@ Execution rule: complete and verify each batch before starting the next batch.
|
|
|
744
797
|
| S-1 | | | |
|
|
745
798
|
|
|
746
799
|
## Acceptance Mapping
|
|
747
|
-
|
|
|
800
|
+
| Vertical slice | Source item ID | Spec criterion ID |
|
|
748
801
|
|---|---|---|
|
|
749
802
|
| S-1 | SRC-1 | AC-1 |
|
|
750
803
|
|
|
@@ -793,6 +846,9 @@ Execution rule: complete and verify each batch before starting the next batch.
|
|
|
793
846
|
|---|---|---|---|---|
|
|
794
847
|
| S-1 | | | | |
|
|
795
848
|
|
|
849
|
+
## Learning Capture Hint
|
|
850
|
+
For meaningful TDD work, replace the Learnings sentinel with 1-3 JSON learning bullets, for example: \`- {"type":"pattern","trigger":"when a regression only fails after state rewind","action":"keep the RED fixture and add a cycle-log assertion before GREEN","confidence":"medium","domain":"testing","stage":"tdd"}\`
|
|
851
|
+
|
|
796
852
|
## Learnings
|
|
797
853
|
- None this stage.
|
|
798
854
|
`,
|
|
@@ -853,6 +909,7 @@ Execution rule: complete and verify each batch before starting the next batch.
|
|
|
853
909
|
|
|
854
910
|
## Review Readiness Snapshot
|
|
855
911
|
|
|
912
|
+
- Victory Detector: pass | fail (Layer 1, Layer 2, security sweep, structured findings, trace evidence, unresolved-critical status)
|
|
856
913
|
- Completed checks: Layer 1, Layer 2 tags, security sweep, schema validation
|
|
857
914
|
- Delegation log: \`.cclaw/state/delegation-log.json\` required/completed/waived/pending
|
|
858
915
|
- Staleness signal: commit at last review pass vs current commit
|
|
@@ -893,6 +950,9 @@ Execution rule: complete and verify each batch before starting the next batch.
|
|
|
893
950
|
## Final Verdict
|
|
894
951
|
- APPROVED | APPROVED_WITH_CONCERNS | BLOCKED
|
|
895
952
|
|
|
953
|
+
## Learning Capture Hint
|
|
954
|
+
For meaningful review work, replace the Learnings sentinel with 1-3 JSON learning bullets, for example: \`- {"type":"lesson","trigger":"when security sweep finds no issues but touches trust boundaries","action":"record NO_SECURITY_IMPACT with inspected surfaces and rationale","confidence":"medium","domain":"security","stage":"review"}\`
|
|
955
|
+
|
|
896
956
|
## Learnings
|
|
897
957
|
- None this stage.
|
|
898
958
|
`,
|
|
@@ -961,6 +1021,7 @@ ${SHIP_FINALIZATION_ENUM_LINES}
|
|
|
961
1021
|
- NO_VCS handoff target + artifact path (if FINALIZE_NO_VCS):
|
|
962
1022
|
|
|
963
1023
|
## Completion Status
|
|
1024
|
+
- Victory Detector: pass | fail (review verdict valid, preflight fresh, rollback ready, one finalization enum selected, execution result present)
|
|
964
1025
|
- SHIPPED | SHIPPED_WITH_EXCEPTIONS | BLOCKED
|
|
965
1026
|
- Exceptions (if any):
|
|
966
1027
|
|
|
@@ -5,6 +5,12 @@ function flowStatePath() {
|
|
|
5
5
|
function delegationLogPath() {
|
|
6
6
|
return `${RUNTIME_ROOT}/state/delegation-log.json`;
|
|
7
7
|
}
|
|
8
|
+
function delegationEventsPath() {
|
|
9
|
+
return `${RUNTIME_ROOT}/state/delegation-events.jsonl`;
|
|
10
|
+
}
|
|
11
|
+
function subagentsPath() {
|
|
12
|
+
return `${RUNTIME_ROOT}/state/subagents.json`;
|
|
13
|
+
}
|
|
8
14
|
function artifactsPath() {
|
|
9
15
|
return `${RUNTIME_ROOT}/artifacts`;
|
|
10
16
|
}
|
|
@@ -30,7 +36,7 @@ Do not modify state in this command. It is a pure read/render operation.
|
|
|
30
36
|
- stage marker: passed/current/pending/skipped/stale,
|
|
31
37
|
- gates summary,
|
|
32
38
|
- artifact summary,
|
|
33
|
-
- delegation branch for current stage with fulfillmentMode labels,
|
|
39
|
+
- delegation branch for current stage with fulfillmentMode, dispatchSurface, proof status, and active tracker labels,
|
|
34
40
|
6. When \`closeout.shipSubstate !== "idle"\` or \`currentStage === "ship"\`, add
|
|
35
41
|
a closeout sub-tree:
|
|
36
42
|
- \`retro:\` line derived from \`closeout.retroDraftedAt\` /
|
package/dist/delegation.d.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { type SubagentFallback } from "./harness-adapters.js";
|
|
2
2
|
import type { FlowStage } from "./types.js";
|
|
3
3
|
export type DelegationMode = "mandatory" | "proactive";
|
|
4
|
-
export type DelegationStatus = "scheduled" | "completed" | "failed" | "waived";
|
|
4
|
+
export type DelegationStatus = "scheduled" | "launched" | "acknowledged" | "completed" | "failed" | "waived" | "stale";
|
|
5
|
+
export type DelegationDispatchSurface = "claude-task" | "cursor-task" | "opencode-agent" | "codex-agent" | "generic-task" | "role-switch" | "manual";
|
|
6
|
+
export type DelegationEventType = DelegationStatus;
|
|
5
7
|
/**
|
|
6
8
|
* How a delegation was actually fulfilled. Advisory — mirrors the harness
|
|
7
9
|
* `subagentFallback` that was in effect when the entry was recorded.
|
|
@@ -53,6 +55,20 @@ export type DelegationEntry = {
|
|
|
53
55
|
retryCount?: number;
|
|
54
56
|
/** Optional references to evidence anchors in artifacts. */
|
|
55
57
|
evidenceRefs?: string[];
|
|
58
|
+
/** Dispatch proof id from the parent/controller side. */
|
|
59
|
+
dispatchId?: string;
|
|
60
|
+
/** Worker-reported run id or task id returned by the harness. */
|
|
61
|
+
workerRunId?: string;
|
|
62
|
+
/** Concrete runtime surface used to launch the worker. */
|
|
63
|
+
dispatchSurface?: DelegationDispatchSurface;
|
|
64
|
+
/** Path to the generated or canonical agent definition used for dispatch. */
|
|
65
|
+
agentDefinitionPath?: string;
|
|
66
|
+
/** ISO timestamp when the worker was acknowledged by the harness/worker. */
|
|
67
|
+
ackTs?: string;
|
|
68
|
+
/** ISO timestamp when the worker was launched. */
|
|
69
|
+
launchedTs?: string;
|
|
70
|
+
/** ISO timestamp when the worker completed. */
|
|
71
|
+
completedTs?: string;
|
|
56
72
|
/** Optional skill marker used for role-specific mandatory checks. */
|
|
57
73
|
skill?: string;
|
|
58
74
|
/**
|
|
@@ -68,6 +84,11 @@ export type DelegationLedger = {
|
|
|
68
84
|
runId: string;
|
|
69
85
|
entries: DelegationEntry[];
|
|
70
86
|
};
|
|
87
|
+
export type DelegationEvent = DelegationEntry & {
|
|
88
|
+
event: DelegationEventType;
|
|
89
|
+
eventTs: string;
|
|
90
|
+
schemaVersion: 1;
|
|
91
|
+
};
|
|
71
92
|
/**
|
|
72
93
|
* Heuristic: does a changed file path strongly imply a trust-boundary
|
|
73
94
|
* surface? Used by tests and prompt guidance for risk-triggered review.
|
|
@@ -79,6 +100,10 @@ export type DelegationLedger = {
|
|
|
79
100
|
*/
|
|
80
101
|
export declare function isTrustBoundaryPath(filePath: string): boolean;
|
|
81
102
|
export declare function readDelegationLedger(projectRoot: string): Promise<DelegationLedger>;
|
|
103
|
+
export declare function readDelegationEvents(projectRoot: string): Promise<{
|
|
104
|
+
events: DelegationEvent[];
|
|
105
|
+
corruptLines: number[];
|
|
106
|
+
}>;
|
|
82
107
|
export declare function appendDelegation(projectRoot: string, entry: DelegationEntry): Promise<void>;
|
|
83
108
|
/**
|
|
84
109
|
* Aggregate the fulfillment mode cclaw expects for the active harness set.
|
|
@@ -96,6 +121,14 @@ export declare function checkMandatoryDelegations(projectRoot: string, stage: Fl
|
|
|
96
121
|
staleIgnored: string[];
|
|
97
122
|
/** Delegation rows missing required evidence under a role-switch fallback. */
|
|
98
123
|
missingEvidence: string[];
|
|
124
|
+
/** Native isolated completion rows that lack dispatch proof. */
|
|
125
|
+
missingDispatchProof: string[];
|
|
126
|
+
/** Legacy inferred isolated completions accepted only as migration warnings. */
|
|
127
|
+
legacyInferredCompletions: string[];
|
|
128
|
+
/** Current-run event log lines that could not be parsed. */
|
|
129
|
+
corruptEventLines: number[];
|
|
130
|
+
/** Current-run scheduled rows with no terminal row sharing the same spanId. */
|
|
131
|
+
staleWorkers: string[];
|
|
99
132
|
/** Expected fulfillment mode for the active harness set. */
|
|
100
133
|
expectedMode: DelegationFulfillmentMode;
|
|
101
134
|
}>;
|
package/dist/delegation.js
CHANGED
|
@@ -9,12 +9,19 @@ import { HARNESS_ADAPTERS } from "./harness-adapters.js";
|
|
|
9
9
|
import { readFlowState } from "./runs.js";
|
|
10
10
|
import { stageSchema } from "./content/stage-schema.js";
|
|
11
11
|
const execFileAsync = promisify(execFile);
|
|
12
|
+
const TERMINAL_DELEGATION_STATUSES = new Set(["completed", "failed", "waived", "stale"]);
|
|
12
13
|
function delegationLogPath(projectRoot) {
|
|
13
14
|
return path.join(projectRoot, RUNTIME_ROOT, "state", "delegation-log.json");
|
|
14
15
|
}
|
|
15
16
|
function delegationLockPath(projectRoot) {
|
|
16
17
|
return path.join(projectRoot, RUNTIME_ROOT, "state", ".delegation.lock");
|
|
17
18
|
}
|
|
19
|
+
function delegationEventsPath(projectRoot) {
|
|
20
|
+
return path.join(projectRoot, RUNTIME_ROOT, "state", "delegation-events.jsonl");
|
|
21
|
+
}
|
|
22
|
+
function subagentsStatePath(projectRoot) {
|
|
23
|
+
return path.join(projectRoot, RUNTIME_ROOT, "state", "subagents.json");
|
|
24
|
+
}
|
|
18
25
|
function createSpanId() {
|
|
19
26
|
return `dspan-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
20
27
|
}
|
|
@@ -130,11 +137,19 @@ function isDelegationEntry(value) {
|
|
|
130
137
|
const o = value;
|
|
131
138
|
const modeOk = o.mode === "mandatory" || o.mode === "proactive";
|
|
132
139
|
const statusOk = o.status === "scheduled" ||
|
|
140
|
+
o.status === "launched" ||
|
|
141
|
+
o.status === "acknowledged" ||
|
|
133
142
|
o.status === "completed" ||
|
|
134
143
|
o.status === "failed" ||
|
|
135
|
-
o.status === "waived"
|
|
144
|
+
o.status === "waived" ||
|
|
145
|
+
o.status === "stale";
|
|
136
146
|
const timestampOk = typeof o.ts === "string" ||
|
|
137
147
|
typeof o.startTs === "string";
|
|
148
|
+
const terminalStatus = o.status === "completed" || o.status === "failed" || o.status === "waived" || o.status === "stale";
|
|
149
|
+
const lifecycleOk = (o.status !== "scheduled" && o.status !== "launched" && o.status !== "acknowledged") || o.endTs === undefined;
|
|
150
|
+
const terminalLifecycleOk = !terminalStatus ||
|
|
151
|
+
o.endTs === undefined ||
|
|
152
|
+
typeof o.endTs === "string";
|
|
138
153
|
const retryOk = o.retryCount === undefined ||
|
|
139
154
|
(typeof o.retryCount === "number" &&
|
|
140
155
|
Number.isFinite(o.retryCount) &&
|
|
@@ -146,6 +161,8 @@ function isDelegationEntry(value) {
|
|
|
146
161
|
modeOk &&
|
|
147
162
|
statusOk &&
|
|
148
163
|
timestampOk &&
|
|
164
|
+
lifecycleOk &&
|
|
165
|
+
terminalLifecycleOk &&
|
|
149
166
|
(o.spanId === undefined || typeof o.spanId === "string") &&
|
|
150
167
|
(o.parentSpanId === undefined || typeof o.parentSpanId === "string") &&
|
|
151
168
|
(o.startTs === undefined || typeof o.startTs === "string") &&
|
|
@@ -160,12 +177,53 @@ function isDelegationEntry(value) {
|
|
|
160
177
|
o.fulfillmentMode === "role-switch" ||
|
|
161
178
|
o.fulfillmentMode === "harness-waiver") &&
|
|
162
179
|
(o.conditionTrigger === undefined || typeof o.conditionTrigger === "string") &&
|
|
180
|
+
(o.dispatchId === undefined || typeof o.dispatchId === "string") &&
|
|
181
|
+
(o.workerRunId === undefined || typeof o.workerRunId === "string") &&
|
|
182
|
+
(o.dispatchSurface === undefined || isDelegationDispatchSurface(o.dispatchSurface)) &&
|
|
183
|
+
(o.agentDefinitionPath === undefined || typeof o.agentDefinitionPath === "string") &&
|
|
184
|
+
(o.ackTs === undefined || typeof o.ackTs === "string") &&
|
|
185
|
+
(o.launchedTs === undefined || typeof o.launchedTs === "string") &&
|
|
186
|
+
(o.completedTs === undefined || typeof o.completedTs === "string") &&
|
|
163
187
|
(o.tokens === undefined || isDelegationTokenUsage(o.tokens)) &&
|
|
164
188
|
retryOk &&
|
|
165
189
|
(o.evidenceRefs === undefined || (Array.isArray(o.evidenceRefs) && o.evidenceRefs.every((item) => typeof item === "string"))) &&
|
|
166
190
|
(o.skill === undefined || typeof o.skill === "string") &&
|
|
167
191
|
(o.schemaVersion === undefined || o.schemaVersion === 1));
|
|
168
192
|
}
|
|
193
|
+
function isDelegationDispatchSurface(value) {
|
|
194
|
+
return (value === "claude-task" ||
|
|
195
|
+
value === "cursor-task" ||
|
|
196
|
+
value === "opencode-agent" ||
|
|
197
|
+
value === "codex-agent" ||
|
|
198
|
+
value === "generic-task" ||
|
|
199
|
+
value === "role-switch" ||
|
|
200
|
+
value === "manual");
|
|
201
|
+
}
|
|
202
|
+
function statusTimestampPatch(entry, ts) {
|
|
203
|
+
const patch = { ...entry };
|
|
204
|
+
if (patch.status === "launched")
|
|
205
|
+
patch.launchedTs = patch.launchedTs ?? ts;
|
|
206
|
+
if (patch.status === "acknowledged")
|
|
207
|
+
patch.ackTs = patch.ackTs ?? ts;
|
|
208
|
+
if (patch.status === "completed")
|
|
209
|
+
patch.completedTs = patch.completedTs ?? patch.endTs ?? ts;
|
|
210
|
+
return patch;
|
|
211
|
+
}
|
|
212
|
+
function eventFromEntry(entry) {
|
|
213
|
+
const eventTs = entry.completedTs ?? entry.ackTs ?? entry.launchedTs ?? entry.endTs ?? entry.startTs ?? entry.ts ?? new Date().toISOString();
|
|
214
|
+
return {
|
|
215
|
+
...entry,
|
|
216
|
+
event: entry.status,
|
|
217
|
+
eventTs,
|
|
218
|
+
schemaVersion: 1
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
function isDelegationEvent(value) {
|
|
222
|
+
if (!isDelegationEntry(value))
|
|
223
|
+
return false;
|
|
224
|
+
const o = value;
|
|
225
|
+
return o.event === o.status && typeof o.eventTs === "string";
|
|
226
|
+
}
|
|
169
227
|
function parseLedger(raw, runId) {
|
|
170
228
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
171
229
|
return { runId, entries: [] };
|
|
@@ -185,7 +243,11 @@ function parseLedger(raw, runId) {
|
|
|
185
243
|
...item,
|
|
186
244
|
spanId: item.spanId ?? createSpanId(),
|
|
187
245
|
startTs: ts,
|
|
246
|
+
endTs: TERMINAL_DELEGATION_STATUSES.has(item.status) ? (item.endTs ?? ts) : undefined,
|
|
188
247
|
ts,
|
|
248
|
+
launchedTs: item.launchedTs ?? (item.status === "launched" ? ts : undefined),
|
|
249
|
+
ackTs: item.ackTs ?? (item.status === "acknowledged" ? ts : undefined),
|
|
250
|
+
completedTs: item.completedTs ?? (item.status === "completed" ? (item.endTs ?? ts) : undefined),
|
|
189
251
|
retryCount: typeof item.retryCount === "number" && Number.isInteger(item.retryCount) && item.retryCount >= 0
|
|
190
252
|
? item.retryCount
|
|
191
253
|
: 0,
|
|
@@ -213,6 +275,57 @@ export async function readDelegationLedger(projectRoot) {
|
|
|
213
275
|
return { runId: activeRunId, entries: [] };
|
|
214
276
|
}
|
|
215
277
|
}
|
|
278
|
+
export async function readDelegationEvents(projectRoot) {
|
|
279
|
+
const filePath = delegationEventsPath(projectRoot);
|
|
280
|
+
if (!(await exists(filePath))) {
|
|
281
|
+
return { events: [], corruptLines: [] };
|
|
282
|
+
}
|
|
283
|
+
const events = [];
|
|
284
|
+
const corruptLines = [];
|
|
285
|
+
const text = await fs.readFile(filePath, "utf8").catch(() => "");
|
|
286
|
+
const lines = text.split(/\r?\n/gu);
|
|
287
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
288
|
+
const line = lines[index]?.trim() ?? "";
|
|
289
|
+
if (line.length === 0)
|
|
290
|
+
continue;
|
|
291
|
+
try {
|
|
292
|
+
const parsed = JSON.parse(line);
|
|
293
|
+
if (isDelegationEvent(parsed)) {
|
|
294
|
+
events.push(parsed);
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
corruptLines.push(index + 1);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
catch {
|
|
301
|
+
corruptLines.push(index + 1);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return { events, corruptLines };
|
|
305
|
+
}
|
|
306
|
+
async function appendDelegationEvent(projectRoot, event) {
|
|
307
|
+
const filePath = delegationEventsPath(projectRoot);
|
|
308
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
309
|
+
await fs.appendFile(filePath, `${JSON.stringify(event)}\n`, { encoding: "utf8", mode: 0o600 });
|
|
310
|
+
}
|
|
311
|
+
async function writeSubagentTracker(projectRoot, entries) {
|
|
312
|
+
const active = entries
|
|
313
|
+
.filter((entry) => entry.status === "scheduled" || entry.status === "launched" || entry.status === "acknowledged")
|
|
314
|
+
.map((entry) => ({
|
|
315
|
+
spanId: entry.spanId,
|
|
316
|
+
dispatchId: entry.dispatchId,
|
|
317
|
+
workerRunId: entry.workerRunId,
|
|
318
|
+
stage: entry.stage,
|
|
319
|
+
agent: entry.agent,
|
|
320
|
+
status: entry.status,
|
|
321
|
+
dispatchSurface: entry.dispatchSurface,
|
|
322
|
+
agentDefinitionPath: entry.agentDefinitionPath,
|
|
323
|
+
startedAt: entry.startTs,
|
|
324
|
+
launchedAt: entry.launchedTs,
|
|
325
|
+
acknowledgedAt: entry.ackTs
|
|
326
|
+
}));
|
|
327
|
+
await writeFileSafe(subagentsStatePath(projectRoot), `${JSON.stringify({ active, updatedAt: new Date().toISOString() }, null, 2)}\n`, { mode: 0o600 });
|
|
328
|
+
}
|
|
216
329
|
export async function appendDelegation(projectRoot, entry) {
|
|
217
330
|
const { activeRunId } = await readFlowState(projectRoot);
|
|
218
331
|
await withDirectoryLock(delegationLockPath(projectRoot), async () => {
|
|
@@ -222,10 +335,19 @@ export async function appendDelegation(projectRoot, entry) {
|
|
|
222
335
|
if (entry.status === "waived" && !hasValidWaiverReason(entry.waiverReason)) {
|
|
223
336
|
throw new Error("waived delegation entries require a non-empty waiverReason");
|
|
224
337
|
}
|
|
225
|
-
const stamped = { ...entry, runId: entry.runId ?? activeRunId };
|
|
338
|
+
const stamped = statusTimestampPatch({ ...entry, runId: entry.runId ?? activeRunId }, startTs);
|
|
226
339
|
stamped.spanId = entry.spanId ?? createSpanId();
|
|
227
340
|
stamped.startTs = startTs;
|
|
228
341
|
stamped.ts = startTs;
|
|
342
|
+
if (TERMINAL_DELEGATION_STATUSES.has(stamped.status) && !stamped.endTs) {
|
|
343
|
+
stamped.endTs = new Date().toISOString();
|
|
344
|
+
}
|
|
345
|
+
if (stamped.status === "completed") {
|
|
346
|
+
stamped.completedTs = stamped.completedTs ?? stamped.endTs ?? new Date().toISOString();
|
|
347
|
+
}
|
|
348
|
+
if (stamped.status === "scheduled") {
|
|
349
|
+
delete stamped.endTs;
|
|
350
|
+
}
|
|
229
351
|
stamped.schemaVersion = 1;
|
|
230
352
|
if (stamped.retryCount === undefined ||
|
|
231
353
|
!Number.isInteger(stamped.retryCount) ||
|
|
@@ -247,18 +369,19 @@ export async function appendDelegation(projectRoot, entry) {
|
|
|
247
369
|
stamped.fulfillmentMode = expectedFulfillmentMode(fallbacks);
|
|
248
370
|
}
|
|
249
371
|
}
|
|
250
|
-
// Idempotency:
|
|
251
|
-
//
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
if (prior.entries.some((existing) => existing.spanId === stamped.spanId)) {
|
|
372
|
+
// Idempotency: a retried hook may replay the same lifecycle row. Allow a
|
|
373
|
+
// terminal row to close an existing scheduled span, but drop exact same
|
|
374
|
+
// span/status duplicates so checks do not mis-count repeated writes.
|
|
375
|
+
if (prior.entries.some((existing) => existing.spanId === stamped.spanId && existing.status === stamped.status)) {
|
|
255
376
|
return;
|
|
256
377
|
}
|
|
378
|
+
await appendDelegationEvent(projectRoot, eventFromEntry(stamped));
|
|
257
379
|
const ledger = {
|
|
258
380
|
runId: activeRunId,
|
|
259
381
|
entries: [...prior.entries, stamped]
|
|
260
382
|
};
|
|
261
383
|
await writeFileSafe(filePath, `${JSON.stringify(ledger, null, 2)}\n`, { mode: 0o600 });
|
|
384
|
+
await writeSubagentTracker(projectRoot, ledger.entries);
|
|
262
385
|
});
|
|
263
386
|
}
|
|
264
387
|
/**
|
|
@@ -285,6 +408,7 @@ export async function checkMandatoryDelegations(projectRoot, stage, options = {}
|
|
|
285
408
|
const mandatory = stageSchema(stage, flowState.track).mandatoryDelegations;
|
|
286
409
|
const { activeRunId } = flowState;
|
|
287
410
|
const ledger = await readDelegationLedger(projectRoot);
|
|
411
|
+
const events = await readDelegationEvents(projectRoot);
|
|
288
412
|
const forStage = ledger.entries.filter((e) => e.stage === stage);
|
|
289
413
|
const forRun = forStage.filter((e) => e.runId === activeRunId);
|
|
290
414
|
const staleIgnored = forStage
|
|
@@ -293,6 +417,14 @@ export async function checkMandatoryDelegations(projectRoot, stage, options = {}
|
|
|
293
417
|
const missing = [];
|
|
294
418
|
const waived = [];
|
|
295
419
|
const missingEvidence = [];
|
|
420
|
+
const missingDispatchProof = [];
|
|
421
|
+
const legacyInferredCompletions = [];
|
|
422
|
+
const terminalSpanIds = new Set(forRun
|
|
423
|
+
.filter((entry) => TERMINAL_DELEGATION_STATUSES.has(entry.status) && entry.spanId)
|
|
424
|
+
.map((entry) => entry.spanId));
|
|
425
|
+
const staleWorkers = forRun
|
|
426
|
+
.filter((entry) => entry.status === "scheduled" && entry.spanId && !terminalSpanIds.has(entry.spanId))
|
|
427
|
+
.map((entry) => `${entry.agent}(spanId=${entry.spanId})`);
|
|
296
428
|
const config = await readConfig(projectRoot).catch(() => null);
|
|
297
429
|
const harnesses = config?.harnesses ?? [];
|
|
298
430
|
const configuredFallbacks = harnesses.map((h) => HARNESS_ADAPTERS[h].capabilities.subagentFallback);
|
|
@@ -322,13 +454,41 @@ export async function checkMandatoryDelegations(projectRoot, stage, options = {}
|
|
|
322
454
|
!completedRows.some((e) => Array.isArray(e.evidenceRefs) && e.evidenceRefs.length > 0)) {
|
|
323
455
|
missingEvidence.push(agent);
|
|
324
456
|
}
|
|
457
|
+
for (const row of completedRows) {
|
|
458
|
+
const mode = row.fulfillmentMode ?? "isolated";
|
|
459
|
+
if (mode === "isolated") {
|
|
460
|
+
const spanEvents = events.events.filter((event) => event.runId === activeRunId &&
|
|
461
|
+
event.stage === stage &&
|
|
462
|
+
event.agent === agent &&
|
|
463
|
+
event.spanId === row.spanId);
|
|
464
|
+
const dispatchId = row.dispatchId ?? row.workerRunId ?? spanEvents.find((event) => event.dispatchId || event.workerRunId)?.dispatchId ?? spanEvents.find((event) => event.workerRunId)?.workerRunId;
|
|
465
|
+
const dispatchSurface = row.dispatchSurface ?? spanEvents.find((event) => event.dispatchSurface)?.dispatchSurface;
|
|
466
|
+
const agentDefinitionPath = row.agentDefinitionPath ?? spanEvents.find((event) => event.agentDefinitionPath)?.agentDefinitionPath;
|
|
467
|
+
const hasAck = Boolean(row.ackTs || spanEvents.some((event) => event.event === "acknowledged" && event.ackTs));
|
|
468
|
+
const hasCompleted = Boolean(row.completedTs || spanEvents.some((event) => event.event === "completed" && event.completedTs));
|
|
469
|
+
const hasDispatchProof = Boolean(row.spanId && dispatchId && dispatchSurface && agentDefinitionPath && hasAck && hasCompleted);
|
|
470
|
+
if (!hasDispatchProof) {
|
|
471
|
+
const proofEraSignal = Boolean(row.dispatchId || row.workerRunId || row.dispatchSurface || row.agentDefinitionPath || spanEvents.some((event) => event.dispatchId || event.workerRunId || event.dispatchSurface || event.agentDefinitionPath || event.event === "acknowledged" || event.event === "launched"));
|
|
472
|
+
if (proofEraSignal) {
|
|
473
|
+
missingDispatchProof.push(agent);
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
legacyInferredCompletions.push(`${agent}(spanId=${row.spanId ?? "unknown"})`);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
325
481
|
}
|
|
326
482
|
return {
|
|
327
|
-
satisfied: missing.length === 0 && missingEvidence.length === 0,
|
|
483
|
+
satisfied: missing.length === 0 && missingEvidence.length === 0 && missingDispatchProof.length === 0 && staleWorkers.length === 0 && events.corruptLines.length === 0,
|
|
328
484
|
missing,
|
|
329
485
|
waived,
|
|
330
486
|
staleIgnored,
|
|
331
487
|
missingEvidence,
|
|
488
|
+
missingDispatchProof,
|
|
489
|
+
legacyInferredCompletions,
|
|
490
|
+
corruptEventLines: events.corruptLines,
|
|
491
|
+
staleWorkers,
|
|
332
492
|
expectedMode
|
|
333
493
|
};
|
|
334
494
|
}
|
package/dist/doctor-registry.js
CHANGED
|
@@ -107,6 +107,15 @@ const RULES = [
|
|
|
107
107
|
docRef: ref("harnesses.md")
|
|
108
108
|
}
|
|
109
109
|
},
|
|
110
|
+
{
|
|
111
|
+
test: /^harness:reality:/,
|
|
112
|
+
metadata: {
|
|
113
|
+
severity: "info",
|
|
114
|
+
summary: "Harness reality label for dispatch/proof support.",
|
|
115
|
+
fix: "No action required; use this label to interpret native/generic/role-switch proof requirements.",
|
|
116
|
+
docRef: ref("harnesses.md")
|
|
117
|
+
}
|
|
118
|
+
},
|
|
110
119
|
{
|
|
111
120
|
test: /^delegation:/,
|
|
112
121
|
metadata: {
|