@cleocode/contracts 2026.5.60 → 2026.5.62

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.
Files changed (75) hide show
  1. package/dist/__tests__/nexus-scope-map.test.d.ts +8 -0
  2. package/dist/__tests__/nexus-scope-map.test.d.ts.map +1 -0
  3. package/dist/__tests__/nexus-scope-map.test.js +145 -0
  4. package/dist/__tests__/nexus-scope-map.test.js.map +1 -0
  5. package/dist/branch-lock.d.ts +11 -0
  6. package/dist/branch-lock.d.ts.map +1 -1
  7. package/dist/branch-lock.js +11 -0
  8. package/dist/branch-lock.js.map +1 -1
  9. package/dist/config.d.ts +21 -0
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/data-accessor.d.ts +2 -0
  12. package/dist/data-accessor.d.ts.map +1 -1
  13. package/dist/data-accessor.js.map +1 -1
  14. package/dist/engine-result.d.ts +170 -0
  15. package/dist/engine-result.d.ts.map +1 -0
  16. package/dist/engine-result.js +123 -0
  17. package/dist/engine-result.js.map +1 -0
  18. package/dist/errors.d.ts +24 -0
  19. package/dist/errors.d.ts.map +1 -1
  20. package/dist/errors.js +31 -0
  21. package/dist/errors.js.map +1 -1
  22. package/dist/exit-codes.d.ts +8 -1
  23. package/dist/exit-codes.d.ts.map +1 -1
  24. package/dist/exit-codes.js +7 -0
  25. package/dist/exit-codes.js.map +1 -1
  26. package/dist/graph.d.ts +88 -0
  27. package/dist/graph.d.ts.map +1 -1
  28. package/dist/graph.js +35 -0
  29. package/dist/graph.js.map +1 -1
  30. package/dist/index.d.ts +10 -5
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +6 -2
  33. package/dist/index.js.map +1 -1
  34. package/dist/operations/nexus-scope-map.d.ts +540 -0
  35. package/dist/operations/nexus-scope-map.d.ts.map +1 -0
  36. package/dist/operations/nexus-scope-map.js +556 -0
  37. package/dist/operations/nexus-scope-map.js.map +1 -0
  38. package/dist/operations/nexus-scope.d.ts +185 -0
  39. package/dist/operations/nexus-scope.d.ts.map +1 -0
  40. package/dist/operations/nexus-scope.js +9 -0
  41. package/dist/operations/nexus-scope.js.map +1 -0
  42. package/dist/operations/session.d.ts +79 -0
  43. package/dist/operations/session.d.ts.map +1 -1
  44. package/dist/operations/tasks.d.ts +38 -0
  45. package/dist/operations/tasks.d.ts.map +1 -1
  46. package/dist/operations/worktree.d.ts +33 -0
  47. package/dist/operations/worktree.d.ts.map +1 -1
  48. package/dist/spawn.d.ts +94 -0
  49. package/dist/spawn.d.ts.map +1 -1
  50. package/dist/spawn.js +65 -1
  51. package/dist/spawn.js.map +1 -1
  52. package/dist/task.d.ts +17 -2
  53. package/dist/task.d.ts.map +1 -1
  54. package/dist/task.js +11 -1
  55. package/dist/task.js.map +1 -1
  56. package/dist/tasks.d.ts +0 -2
  57. package/dist/tasks.d.ts.map +1 -1
  58. package/package.json +2 -2
  59. package/src/__tests__/nexus-scope-map.test.ts +183 -0
  60. package/src/branch-lock.ts +11 -0
  61. package/src/config.ts +22 -0
  62. package/src/data-accessor.ts +3 -0
  63. package/src/engine-result.ts +220 -0
  64. package/src/errors.ts +34 -0
  65. package/src/exit-codes.ts +8 -0
  66. package/src/graph.ts +112 -0
  67. package/src/index.ts +49 -2
  68. package/src/operations/nexus-scope-map.ts +597 -0
  69. package/src/operations/nexus-scope.ts +217 -0
  70. package/src/operations/session.ts +87 -0
  71. package/src/operations/tasks.ts +40 -0
  72. package/src/operations/worktree.ts +33 -0
  73. package/src/spawn.ts +141 -0
  74. package/src/task.ts +30 -1
  75. package/src/tasks.ts +0 -2
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Nexus operation scope contracts — discriminated unions and descriptor
3
+ * interface used by the NEXUS_SCOPE_MAP SSoT.
4
+ *
5
+ * @task T9145
6
+ * @module operations/nexus-scope
7
+ */
8
+
9
+ // ---------------------------------------------------------------------------
10
+ // NexusScope — five-state discriminated union
11
+ // ---------------------------------------------------------------------------
12
+
13
+ /**
14
+ * Five-state scope classification for Nexus operations.
15
+ *
16
+ * - `project` — Operates on a single registered project graph.
17
+ * - `living-brain` — Reads/writes the BRAIN (memory) store.
18
+ * - `cross` — Spans multiple project graphs or compares them.
19
+ * - `hybrid` — Touches both the project graph AND BRAIN.
20
+ * - `global` — Operates on the global Nexus registry (all projects).
21
+ */
22
+ export type NexusScope = 'project' | 'living-brain' | 'cross' | 'hybrid' | 'global';
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // NexusEffect — read / write / admin axis
26
+ // ---------------------------------------------------------------------------
27
+
28
+ /**
29
+ * Side-effect classification for a Nexus operation.
30
+ *
31
+ * - `read` — Pure query; no persistent state change.
32
+ * - `write` — Mutates the target store(s).
33
+ * - `admin` — Administrative operation (register/unregister/permission).
34
+ */
35
+ export type NexusEffect = 'read' | 'write' | 'admin';
36
+
37
+ // ---------------------------------------------------------------------------
38
+ // NexusStore — target data stores
39
+ // ---------------------------------------------------------------------------
40
+
41
+ /**
42
+ * The persistent stores a Nexus operation may touch.
43
+ *
44
+ * - `nexus-graph` — The graph DB (nodes + relations for a project).
45
+ * - `nexus-registry` — The global project registry.
46
+ * - `brain` — The BRAIN memory / observation store.
47
+ * - `tasks` — The task store (nexus → task bridge operations).
48
+ * - `fs` — The local filesystem (scan / walk / snapshot).
49
+ */
50
+ export type NexusStore = 'nexus-graph' | 'nexus-registry' | 'brain' | 'tasks' | 'fs';
51
+
52
+ // ---------------------------------------------------------------------------
53
+ // ScopeBinding — links an operation key to scope + effect metadata
54
+ // ---------------------------------------------------------------------------
55
+
56
+ /**
57
+ * A single scope binding, attaching NexusScope and NexusEffect metadata to
58
+ * a named Nexus operation.
59
+ */
60
+ export interface ScopeBinding {
61
+ /** The operation key as declared in {@link NexusOps}. */
62
+ readonly op: string;
63
+ /** Scope classification of this operation. */
64
+ readonly scope: NexusScope;
65
+ /** Side-effect classification. */
66
+ readonly effect: NexusEffect;
67
+ /** Stores this operation reads or writes. */
68
+ readonly stores: ReadonlyArray<NexusStore>;
69
+ }
70
+
71
+ // ---------------------------------------------------------------------------
72
+ // NexusOperationDescriptor — full operation metadata
73
+ // ---------------------------------------------------------------------------
74
+
75
+ /**
76
+ * Rich metadata descriptor for a single Nexus operation.
77
+ *
78
+ * Used by the NEXUS_SCOPE_MAP SSoT to provide compile-time exhaustiveness
79
+ * checking and runtime helpers (`getNexusDescriptor`, `listOpsByScope`).
80
+ */
81
+ export interface NexusOperationDescriptor {
82
+ /** The operation key as declared in {@link NexusOps}. */
83
+ readonly op: string;
84
+ /** Human-readable summary of the operation. */
85
+ readonly description: string;
86
+ /** Scope classification. */
87
+ readonly scope: NexusScope;
88
+ /** Side-effect classification. */
89
+ readonly effect: NexusEffect;
90
+ /** Stores this operation reads or writes. */
91
+ readonly stores: ReadonlyArray<NexusStore>;
92
+ /**
93
+ * Whether this operation requires a `projectId` parameter.
94
+ * Operations with `scope === 'global'` typically do NOT require one.
95
+ * @defaultValue `true`
96
+ */
97
+ readonly requiresProject: boolean;
98
+ /**
99
+ * When `true`, the decorator stamps `meta._nexus.indexFreshness` on the
100
+ * response envelope (top-level CLI invocations only; sub-calls inherit via
101
+ * `meta.requestId` lineage to avoid per-call git-status shell-outs).
102
+ *
103
+ * @defaultValue `false`
104
+ * @task T9146
105
+ */
106
+ readonly indexSensitive?: boolean;
107
+ }
108
+
109
+ // ---------------------------------------------------------------------------
110
+ // W2: NexusScopeMeta — namespaced _nexus block stamped on every response
111
+ // ---------------------------------------------------------------------------
112
+
113
+ /**
114
+ * Source of the `projectId` binding for a nexus operation.
115
+ *
116
+ * - `arg-project-id` — caller supplied `--project-id` explicitly.
117
+ * - `arg-path` — resolved from `--path` argument.
118
+ * - `cwd` — derived from current working directory.
119
+ * - `registry` — looked up via the global project registry.
120
+ * - `none` — operation does not require a project binding.
121
+ *
122
+ * @task T9146
123
+ */
124
+ export type NexusBindingSource = 'arg-project-id' | 'arg-path' | 'cwd' | 'registry' | 'none';
125
+
126
+ /**
127
+ * A structured suggestion for the next action an agent should take after a
128
+ * Nexus operation completes.
129
+ *
130
+ * Machine-readable — display strings are derived from these fields.
131
+ *
132
+ * @task T9146
133
+ */
134
+ export interface SuggestedNextOp {
135
+ /** Nexus operation key (must exist in NEXUS_SCOPE_MAP). */
136
+ readonly op: string;
137
+ /** Arguments to pass to the operation. */
138
+ readonly args: Readonly<Record<string, unknown>>;
139
+ /** Scope of the suggested operation. */
140
+ readonly scope: NexusScope;
141
+ /** Side-effect classification of the suggested operation. */
142
+ readonly effect: NexusEffect;
143
+ /** Whether the agent should confirm with the user before executing. */
144
+ readonly requiresConfirmation: boolean;
145
+ /** Human-readable rationale for why this next step is suggested. */
146
+ readonly reason: string;
147
+ }
148
+
149
+ /**
150
+ * Namespaced `_nexus` block attached to every nexus-domain dispatch response
151
+ * under `meta._nexus`.
152
+ *
153
+ * Consumed by agents, renderers, and downstream middleware for scope-aware
154
+ * routing and display.
155
+ *
156
+ * @task T9146
157
+ */
158
+ export interface NexusScopeMeta {
159
+ /** Scope classification of the operation that produced this envelope. */
160
+ readonly scope: NexusScope;
161
+ /** Side-effect classification of the operation. */
162
+ readonly effect: NexusEffect;
163
+ /**
164
+ * Resolved project identifier (undefined for `global` / `living-brain`
165
+ * scope operations that do not require a project).
166
+ */
167
+ readonly projectId?: string;
168
+ /** Human-readable project name from the registry (if available). */
169
+ readonly projectName?: string;
170
+ /** Absolute filesystem path to the project root. */
171
+ readonly projectPath?: string;
172
+ /** Absolute path to the nexus DB or registry file. */
173
+ readonly registryPath?: string;
174
+ /** How the `projectId` was resolved for this request. */
175
+ readonly bindingSource: NexusBindingSource;
176
+ /**
177
+ * For `hybrid`-scope operations: the ID of the secondary project
178
+ * (the one whose data is being compared or merged with the primary).
179
+ */
180
+ readonly counterpartProjectId?: string;
181
+ /**
182
+ * Whether the nexus index was considered fresh at the time of this call.
183
+ * Only present when `descriptor.indexSensitive === true` AND this was a
184
+ * top-level CLI invocation (sub-calls inherit via `meta.requestId` lineage).
185
+ */
186
+ readonly indexFreshness?: 'fresh' | 'stale' | 'unknown';
187
+ /**
188
+ * The canonical CLI command string for this operation (e.g. `"cleo nexus context"`).
189
+ */
190
+ readonly canonicalCommand: string;
191
+ /**
192
+ * If this operation is a legacy alias, the canonical op key it maps to.
193
+ * Absent when the operation is already canonical.
194
+ */
195
+ readonly legacyAliasFor?: string;
196
+ /** Non-fatal warnings surfaced from descriptor or runtime resolution. */
197
+ readonly warnings?: ReadonlyArray<string>;
198
+ /**
199
+ * Structured suggestions for what the agent should do next.
200
+ * Every entry's `.op` MUST resolve to a known NEXUS_SCOPE_MAP entry
201
+ * (enforced at build time by the typed-registry gate in nexus-decorator.ts).
202
+ */
203
+ readonly suggestedNext?: ReadonlyArray<SuggestedNextOp>;
204
+ }
205
+
206
+ /**
207
+ * Utility type that intersects {@link NexusScopeMeta} into `meta._nexus` for
208
+ * nexus-domain dispatch responses.
209
+ *
210
+ * Use this to narrow the `meta` field when processing nexus responses.
211
+ *
212
+ * @task T9146
213
+ */
214
+ export interface MetaWithNexusScope {
215
+ _nexus: NexusScopeMeta;
216
+ [key: string]: unknown;
217
+ }
@@ -200,6 +200,91 @@ export interface SessionHandoffShowResult {
200
200
  handoff: unknown;
201
201
  }
202
202
 
203
+ // ---------------------------------------------------------------------------
204
+ // BriefingFieldContract — per-field staleness + dedup rules (T1905 / BBTT-W1-3)
205
+ // ---------------------------------------------------------------------------
206
+
207
+ /**
208
+ * Provenance category for briefing observation exclusion.
209
+ *
210
+ * @task T1905
211
+ */
212
+ export type BriefingExcludeProvenance = 'test-fixture' | 'synthetic' | 'imported';
213
+
214
+ /**
215
+ * Per-field contract rule for a single briefing field.
216
+ *
217
+ * Defines staleness and deduplication constraints for one named section
218
+ * of the `SessionBriefingShowResult`. `assertBriefingContract` evaluates
219
+ * these rules and emits a `ContractViolation` for each breach.
220
+ *
221
+ * @task T1905
222
+ */
223
+ export interface BriefingFieldRule {
224
+ /**
225
+ * Maximum acceptable age (in days) for any observation surfaced in this field.
226
+ * A violation is emitted when `now - capturedAt > maxAgeDays * 86_400_000`.
227
+ */
228
+ maxAgeDays?: number;
229
+ /**
230
+ * Deduplication key — field path within each list item used to detect
231
+ * duplicate entries (e.g. `'id'` deduplicates by task ID).
232
+ * When set, the contract checker warns if two items share the same key value.
233
+ */
234
+ dedupBy?: string;
235
+ /**
236
+ * Provenance tags whose observations must be excluded from this field.
237
+ * Violations are emitted when an item carries one of these provenance values.
238
+ */
239
+ excludeProvenance?: BriefingExcludeProvenance[];
240
+ }
241
+
242
+ /**
243
+ * Full briefing field contract — maps each named briefing section to its rule.
244
+ *
245
+ * Pass to `assertBriefingContract` together with the computed briefing to
246
+ * obtain `ContractViolation[]`.
247
+ *
248
+ * @example
249
+ * ```ts
250
+ * const contract: BriefingFieldContract = {
251
+ * recentObservations: { maxAgeDays: 7, excludeProvenance: ['test-fixture'] },
252
+ * nextTasks: { dedupBy: 'id' },
253
+ * };
254
+ * ```
255
+ *
256
+ * @task T1905
257
+ */
258
+ export interface BriefingFieldContract {
259
+ recentObservations?: BriefingFieldRule;
260
+ nextTasks?: BriefingFieldRule;
261
+ openBugs?: BriefingFieldRule;
262
+ blockedTasks?: BriefingFieldRule;
263
+ activeEpics?: BriefingFieldRule;
264
+ [field: string]: BriefingFieldRule | undefined;
265
+ }
266
+
267
+ /**
268
+ * A single contract violation emitted by `assertBriefingContract`.
269
+ *
270
+ * @task T1905
271
+ */
272
+ export interface ContractViolation {
273
+ /** Name of the briefing field that violated its rule. */
274
+ field: string;
275
+ /** Human-readable description of the violation. */
276
+ message: string;
277
+ /**
278
+ * Violation kind for programmatic handling.
279
+ * - `stale` — field data exceeds `maxAgeDays`.
280
+ * - `duplicate` — two items share the same `dedupBy` key value.
281
+ * - `excluded-provenance` — item carries a banned provenance tag.
282
+ */
283
+ kind: 'stale' | 'duplicate' | 'excluded-provenance';
284
+ /** Severity — P0 violations block `cleo briefing --strict`. */
285
+ severity: 'P0' | 'P1';
286
+ }
287
+
203
288
  // session.briefing.show
204
289
  /** Parameters for `session.briefing.show`. */
205
290
  export interface SessionBriefingShowParams {
@@ -208,6 +293,8 @@ export interface SessionBriefingShowParams {
208
293
  maxBlocked?: number;
209
294
  maxEpics?: number;
210
295
  scope?: string;
296
+ /** When true, exit non-zero if any contract violation is detected (T1905). */
297
+ strict?: boolean;
211
298
  }
212
299
 
213
300
  /** Compact task entry in a session briefing's next-tasks list. */
@@ -630,6 +630,29 @@ export interface TasksRelatesAddResult {
630
630
  added: boolean;
631
631
  }
632
632
 
633
+ // tasks.relates.remove
634
+ export interface TasksRelatesRemoveParams {
635
+ /** Source task ID. */
636
+ taskId: string;
637
+ /** Target task ID to remove the relation to. */
638
+ relatedId: string;
639
+ /** Optional relation type to narrow the deletion (omit to remove any type). */
640
+ type?: string;
641
+ }
642
+ /**
643
+ * Result of `tasks.relates.remove` — relation deletion confirmation.
644
+ *
645
+ * @task T9240
646
+ */
647
+ export interface TasksRelatesRemoveResult {
648
+ /** Source task ID. */
649
+ from: string;
650
+ /** Target (related) task ID. */
651
+ to: string;
652
+ /** Whether a relation was actually deleted. */
653
+ removed: boolean;
654
+ }
655
+
633
656
  // tasks.add (dispatch-level params — extends TasksCreateParams)
634
657
  export interface TasksAddParams {
635
658
  title: string;
@@ -661,6 +684,16 @@ export interface TasksAddParams {
661
684
  * @task T1633
662
685
  */
663
686
  forceDuplicate?: boolean;
687
+ /**
688
+ * Path to an existing verifier script for this task (T9218 / ADR-070).
689
+ *
690
+ * Required when priority=critical OR size=large OR type=epic. The path
691
+ * must point to an existing `.mjs` file. Omitting this on high-consequence
692
+ * tasks causes rejection with E_VERIFIER_REQUIRED.
693
+ *
694
+ * Use `cleo verify backfill <taskId>` to auto-generate a stub verifier.
695
+ */
696
+ verifier?: string;
664
697
  }
665
698
  /**
666
699
  * Result of `tasks.add` — the newly created task.
@@ -698,6 +731,10 @@ export interface TasksUpdateQueryParams {
698
731
  type?: string; // SSoT-EXEMPT:kind≠type — 'type' is hierarchy(epic|task|subtask), 'kind' is intent(work|bug|...) — separate axes T944
699
732
  size?: string;
700
733
  files?: string[];
734
+ /** Add files incrementally (mirrors --add-labels pattern). @task T9242 */
735
+ addFiles?: string[];
736
+ /** Remove files incrementally (mirrors --remove-labels pattern). @task T9242 */
737
+ removeFiles?: string[];
701
738
  pipelineStage?: string;
702
739
  /** Canonical wire field for task kind axis (T9072: renamed from role). */
703
740
  kind?: string;
@@ -715,6 +752,8 @@ export interface TasksUpdateQueryParams {
715
752
  reason?: string;
716
753
  /** Dependency declaration waiver for critical-priority tasks (T1856). */
717
754
  dependsWaiver?: string;
755
+ /** Clear the blockedBy free-text reason (set to undefined). @task T9241 */
756
+ clearBlockedBy?: boolean;
718
757
  }
719
758
  /**
720
759
  * Result of `tasks.update` — the updated task record with change list.
@@ -907,6 +946,7 @@ export type TasksOps = {
907
946
  readonly reparent: readonly [TasksReparentQueryParams, TasksReparentDispatchResult];
908
947
  readonly reorder: readonly [TasksReorderQueryParams, TasksReorderDispatchResult];
909
948
  readonly 'relates.add': readonly [TasksRelatesAddParams, TasksRelatesAddResult];
949
+ readonly 'relates.remove': readonly [TasksRelatesRemoveParams, TasksRelatesRemoveResult];
910
950
  readonly start: readonly [TasksStartQueryParams, TasksStartQueryResult];
911
951
  readonly stop: readonly [TasksStopQueryParams, TasksStopQueryResult];
912
952
  readonly 'sync.reconcile': readonly [TasksSyncReconcileParams, TasksSyncReconcileResult];
@@ -135,6 +135,39 @@ export interface CreateWorktreeOptions {
135
135
  * @default true
136
136
  */
137
137
  lockWorktree?: boolean;
138
+ /**
139
+ * Glob patterns to exclude from the worktree via sparse-checkout after
140
+ * creation (T9226 spawn-clone-exclude filter).
141
+ *
142
+ * When set, `createWorktree` enables sparse-checkout on the new worktree
143
+ * and hides all files matching any pattern. Callers should also supply
144
+ * `spawnCloneExcludeExempt` to preserve the task-scoped file.
145
+ *
146
+ * @task T9226
147
+ */
148
+ spawnCloneExclude?: readonly string[];
149
+ /**
150
+ * Specific file paths to re-include after the exclude filter is applied.
151
+ *
152
+ * Used to preserve the task's own verifier (`scripts/verify-<taskId>.mjs`)
153
+ * after the broad `scripts/verify-*.mjs` exclusion.
154
+ *
155
+ * @task T9226
156
+ */
157
+ spawnCloneExcludeExempt?: readonly string[];
158
+ /**
159
+ * When `true`, forcibly reset an existing `task/<taskId>` branch that has
160
+ * orphan commits (commits not reachable from `baseRef`).
161
+ *
162
+ * Without this flag, `createWorktree` throws `E_DIRTY_BRANCH` when an orphan
163
+ * branch is detected so the caller can investigate before losing history.
164
+ * Set to `true` only when you are certain the prior branch state is stale and
165
+ * safe to discard (e.g. CI retry or integration-test re-runs).
166
+ *
167
+ * @default false
168
+ * @task T1927
169
+ */
170
+ forceReset?: boolean;
138
171
  }
139
172
 
140
173
  /**
package/src/spawn.ts CHANGED
@@ -2,6 +2,8 @@
2
2
  * Spawn provider interface for CLEO provider adapters.
3
3
  *
4
4
  * @task T5240
5
+ * @task T9214 — orchestrator-defer waiver field (W4 UX hardening)
6
+ * @task T9215 — DelegateTaskEnvelope discriminated-union type (W1 wiring)
5
7
  */
6
8
 
7
9
  export interface AdapterSpawnProvider {
@@ -16,6 +18,21 @@ export interface SpawnContext {
16
18
  prompt: string;
17
19
  workingDirectory?: string;
18
20
  options?: Record<string, unknown>;
21
+ /**
22
+ * Atomicity scope declaration for the spawned worker.
23
+ *
24
+ * When set to `'orchestrator-defer'`, a tier-1+ orchestrator signals that
25
+ * the spawned worker will declare its own file scope at commit time. This
26
+ * bypasses `E_ATOMICITY_NO_SCOPE` for the child task while preserving
27
+ * auditability via the `atomicity_waiver` field in the returned
28
+ * {@link AtomicityResult}.
29
+ *
30
+ * MUST NOT be set by tier-0 (direct user / CLI) callers — only by
31
+ * orchestrators making delegated tier-1 dispatch calls.
32
+ *
33
+ * @task T9214
34
+ */
35
+ scope?: 'orchestrator-defer';
19
36
  }
20
37
 
21
38
  export interface SpawnResult {
@@ -32,3 +49,127 @@ export interface SpawnResult {
32
49
  /** Error message when status is 'failed'. Contains details about what went wrong. */
33
50
  error?: string;
34
51
  }
52
+
53
+ // =============================================================================
54
+ // delegate_task JSON envelope (T9215 / W1 — ADR-070 hierarchical orchestration)
55
+ // =============================================================================
56
+
57
+ /**
58
+ * Descriptor for a single child task within a `delegate_task` batch.
59
+ *
60
+ * Each child carries its task ID, agent role, and optional spawn parameters.
61
+ * The adapter recognizer uses this to call `orchestrateSpawnExecute` for each.
62
+ *
63
+ * @task T9215
64
+ */
65
+ export interface DelegateTaskChild {
66
+ /** Task ID of the child to spawn (e.g. 'T9101'). */
67
+ taskId: string;
68
+ /** Agent role assigned to the child ('leaf' | 'worker'). */
69
+ role: 'leaf' | 'worker';
70
+ /** Subagent type for the spawn provider (e.g. 'cleo-subagent'). */
71
+ subagent_type?: string;
72
+ /** LLM model to use for this child (e.g. 'sonnet'). */
73
+ model?: string;
74
+ }
75
+
76
+ /**
77
+ * Parent identity block within a `delegate_task` envelope.
78
+ *
79
+ * Identifies the calling Lead agent for hierarchy validation.
80
+ *
81
+ * @task T9215
82
+ */
83
+ export interface DelegateTaskParent {
84
+ /** Task ID of the calling Lead agent. */
85
+ taskId: string;
86
+ /** Role of the calling agent ('orchestrator' | 'lead'). */
87
+ role: 'orchestrator' | 'lead';
88
+ }
89
+
90
+ /**
91
+ * Discriminated-union JSON envelope emitted by Lead agents to fan out workers.
92
+ *
93
+ * The `tool` field discriminates this type from arbitrary JSON in adapter
94
+ * stdout. Adapters scan their stdout stream for lines containing this sentinel
95
+ * and dispatch via `orchestrateSpawnExecute` for each child in `tasks`.
96
+ *
97
+ * Shape aligned with `ct-lead/references/spawn-pattern.md` (T9082).
98
+ *
99
+ * @task T9215 — W1 delegate_task wiring
100
+ * @adr ADR-070
101
+ */
102
+ export interface DelegateTaskEnvelope {
103
+ /** Discriminant — always 'delegate_task'. */
104
+ tool: 'delegate_task';
105
+ args: {
106
+ /** Identity of the calling Lead agent. Used for hierarchy validation. */
107
+ parent: DelegateTaskParent;
108
+ /** Conduit topic for wave coordination (e.g. 'epic-T9080.wave-2'). */
109
+ conduitTopic?: string;
110
+ /** Total timeout in seconds for the entire batch. */
111
+ timeoutSeconds?: number;
112
+ /** Array of child task descriptors to spawn in parallel. */
113
+ tasks: DelegateTaskChild[];
114
+ };
115
+ }
116
+
117
+ /**
118
+ * Parse and validate a `delegate_task` JSON envelope from adapter stdout.
119
+ *
120
+ * Scans a line of stdout for the sentinel `"tool":"delegate_task"` and
121
+ * attempts to parse the full envelope. Returns `null` if the line is not
122
+ * a valid `DelegateTaskEnvelope` — callers should pass through non-matching
123
+ * lines without transformation.
124
+ *
125
+ * Validation checks:
126
+ * - `tool` equals `'delegate_task'`
127
+ * - `args.tasks` is a non-empty array
128
+ * - Each task has a non-empty `taskId`
129
+ * - `args.parent` has a valid `taskId` and `role`
130
+ *
131
+ * @param line - Single line of stdout from the spawned agent process.
132
+ * @returns Parsed envelope or `null` if the line is not a sentinel.
133
+ *
134
+ * @task T9215
135
+ */
136
+ export function parseDelegateTaskEnvelope(line: string): DelegateTaskEnvelope | null {
137
+ if (!line.includes('"delegate_task"')) return null;
138
+
139
+ let parsed: unknown;
140
+ try {
141
+ // Find the first JSON object on the line
142
+ const start = line.indexOf('{');
143
+ const end = line.lastIndexOf('}');
144
+ if (start === -1 || end === -1 || end <= start) return null;
145
+ parsed = JSON.parse(line.slice(start, end + 1));
146
+ } catch {
147
+ return null;
148
+ }
149
+
150
+ if (typeof parsed !== 'object' || parsed === null) return null;
151
+ const obj = parsed as Record<string, unknown>;
152
+
153
+ if (obj['tool'] !== 'delegate_task') return null;
154
+
155
+ const args = obj['args'];
156
+ if (typeof args !== 'object' || args === null) return null;
157
+ const argsObj = args as Record<string, unknown>;
158
+
159
+ const tasks = argsObj['tasks'];
160
+ if (!Array.isArray(tasks) || tasks.length === 0) return null;
161
+
162
+ for (const t of tasks) {
163
+ if (typeof t !== 'object' || t === null) return null;
164
+ const task = t as Record<string, unknown>;
165
+ if (typeof task['taskId'] !== 'string' || task['taskId'].length === 0) return null;
166
+ }
167
+
168
+ const parent = argsObj['parent'];
169
+ if (typeof parent !== 'object' || parent === null) return null;
170
+ const parentObj = parent as Record<string, unknown>;
171
+ if (typeof parentObj['taskId'] !== 'string' || parentObj['taskId'].length === 0) return null;
172
+ if (parentObj['role'] !== 'orchestrator' && parentObj['role'] !== 'lead') return null;
173
+
174
+ return parsed as DelegateTaskEnvelope;
175
+ }
package/src/task.ts CHANGED
@@ -87,8 +87,12 @@ export type TaskSize = 'small' | 'medium' | 'large';
87
87
  /** Epic lifecycle states. */
88
88
  export type EpicLifecycle = 'backlog' | 'planning' | 'active' | 'review' | 'released' | 'archived';
89
89
 
90
- /** Task origin (provenance). */
90
+ /** Task origin (provenance). T1899: added production/test-fixture/imported/migrated. */
91
91
  export type TaskOrigin =
92
+ | 'production'
93
+ | 'test-fixture'
94
+ | 'imported'
95
+ | 'migrated'
92
96
  | 'internal'
93
97
  | 'bug-report'
94
98
  | 'feature-request'
@@ -97,6 +101,19 @@ export type TaskOrigin =
97
101
  | 'dependency'
98
102
  | 'regression';
99
103
 
104
+ /** Canonical origin values for the T1899 CHECK constraint. */
105
+ export const TASK_ORIGIN_CANONICAL = [
106
+ 'production',
107
+ 'test-fixture',
108
+ 'imported',
109
+ 'migrated',
110
+ ] as const satisfies readonly TaskOrigin[];
111
+
112
+ /** Whether an origin value is a test-fixture (should be excluded from live briefing). */
113
+ export function isTestFixtureOrigin(origin: string | null | undefined): boolean {
114
+ return origin === 'test-fixture';
115
+ }
116
+
100
117
  /** Verification agent types. */
101
118
  export type VerificationAgent =
102
119
  | 'planner'
@@ -463,6 +480,18 @@ export interface Task {
463
480
 
464
481
  /** Agent ID that has claimed/is assigned to this task. Null when unclaimed. @defaultValue undefined */
465
482
  assignee?: string | null;
483
+
484
+ /**
485
+ * Abort reason when a worker was stopped due to runaway detection (T1658).
486
+ *
487
+ * Set by the sentient monitor when a worker exceeds its size-based budget
488
+ * (`small` ≥ 30 min, `medium` ≥ 2 h, `large` ≥ 4 h). The field is
489
+ * informational — callers may surface it in `cleo sentient monitor` output.
490
+ *
491
+ * @task T1658
492
+ * @defaultValue undefined
493
+ */
494
+ abortReason?: string | null;
466
495
  }
467
496
 
468
497
  // ---------------------------------------------------------------------------
package/src/tasks.ts CHANGED
@@ -24,8 +24,6 @@ import type { TaskPriority, TaskStatus } from './task.js';
24
24
  /**
25
25
  * RCASD-IVTR+C pipeline stage values valid for `TaskView.pipelineStage`.
26
26
  *
27
- * Mirrors `TaskViewPipelineStage` from `packages/core/src/tasks/compute-task-view.ts`.
28
- *
29
27
  * @task T1703
30
28
  */
31
29
  export type TaskViewPipelineStage =