@cleocode/contracts 2026.5.101 → 2026.5.103
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/boundary.d.ts +166 -0
- package/dist/boundary.d.ts.map +1 -0
- package/dist/boundary.js +663 -0
- package/dist/boundary.js.map +1 -0
- package/dist/dispatch/operations-registry.d.ts.map +1 -1
- package/dist/dispatch/operations-registry.js +82 -0
- package/dist/dispatch/operations-registry.js.map +1 -1
- package/dist/doctor.d.ts +76 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/errors.d.ts +54 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +44 -0
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/operations/tasks.d.ts +170 -0
- package/dist/operations/tasks.d.ts.map +1 -1
- package/dist/operations/worktree.d.ts +30 -0
- package/dist/operations/worktree.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
- package/src/boundary.ts +875 -0
- package/src/dispatch/operations-registry.ts +85 -0
- package/src/doctor.ts +84 -0
- package/src/errors.ts +61 -0
- package/src/index.ts +21 -0
- package/src/operations/tasks.ts +179 -0
- package/src/operations/worktree.ts +30 -0
|
@@ -2395,6 +2395,39 @@ export const OPERATIONS: OperationDef[] = [
|
|
|
2395
2395
|
},
|
|
2396
2396
|
] satisfies ParamDef[],
|
|
2397
2397
|
},
|
|
2398
|
+
{
|
|
2399
|
+
gateway: 'mutate',
|
|
2400
|
+
domain: 'tasks',
|
|
2401
|
+
operation: 'saga.detach',
|
|
2402
|
+
description:
|
|
2403
|
+
'tasks.saga.detach (mutate) — remove a Saga member relation (task_relations type=groups); idempotent; appends to .cleo/audit/saga-detach.jsonl (T10118)',
|
|
2404
|
+
tier: 0,
|
|
2405
|
+
idempotent: true,
|
|
2406
|
+
sessionRequired: false,
|
|
2407
|
+
requiredParams: ['sagaId', 'memberId'],
|
|
2408
|
+
params: [
|
|
2409
|
+
{
|
|
2410
|
+
name: 'sagaId',
|
|
2411
|
+
type: 'string',
|
|
2412
|
+
required: true,
|
|
2413
|
+
description: 'Saga task ID',
|
|
2414
|
+
cli: { positional: true },
|
|
2415
|
+
},
|
|
2416
|
+
{
|
|
2417
|
+
name: 'memberId',
|
|
2418
|
+
type: 'string',
|
|
2419
|
+
required: true,
|
|
2420
|
+
description: 'Member task ID to detach from the Saga',
|
|
2421
|
+
cli: { positional: true },
|
|
2422
|
+
},
|
|
2423
|
+
{
|
|
2424
|
+
name: 'reason',
|
|
2425
|
+
type: 'string',
|
|
2426
|
+
required: false,
|
|
2427
|
+
description: 'Human-readable reason recorded in the audit log entry',
|
|
2428
|
+
},
|
|
2429
|
+
] satisfies ParamDef[],
|
|
2430
|
+
},
|
|
2398
2431
|
{
|
|
2399
2432
|
gateway: 'query',
|
|
2400
2433
|
domain: 'tasks',
|
|
@@ -2445,6 +2478,58 @@ export const OPERATIONS: OperationDef[] = [
|
|
|
2445
2478
|
},
|
|
2446
2479
|
] satisfies ParamDef[],
|
|
2447
2480
|
},
|
|
2481
|
+
{
|
|
2482
|
+
// T10117 — detach I5-violating parentId and re-attach via task_relations
|
|
2483
|
+
// type=groups (ADR-073 §1.2 invariant I5). Idempotent.
|
|
2484
|
+
gateway: 'mutate',
|
|
2485
|
+
domain: 'tasks',
|
|
2486
|
+
operation: 'saga.repair',
|
|
2487
|
+
description:
|
|
2488
|
+
'tasks.saga.repair (mutate) — detach I5-violating parentId and re-attach via task_relations type=groups; idempotent',
|
|
2489
|
+
tier: 0,
|
|
2490
|
+
idempotent: true,
|
|
2491
|
+
sessionRequired: false,
|
|
2492
|
+
requiredParams: ['sagaId'],
|
|
2493
|
+
params: [
|
|
2494
|
+
{
|
|
2495
|
+
name: 'sagaId',
|
|
2496
|
+
type: 'string',
|
|
2497
|
+
required: true,
|
|
2498
|
+
description: 'Saga task ID (must have label=saga)',
|
|
2499
|
+
cli: { positional: true },
|
|
2500
|
+
},
|
|
2501
|
+
] satisfies ParamDef[],
|
|
2502
|
+
},
|
|
2503
|
+
{
|
|
2504
|
+
// T10121 — idempotent cron-safe saga auto-close repair. Re-applies
|
|
2505
|
+
// T10116 logic for sagas whose members reached 100% terminal via paths
|
|
2506
|
+
// OTHER than completeTask (bulk SQL, crash recovery, manual edits).
|
|
2507
|
+
// Supersedes T10098 standalone scope.
|
|
2508
|
+
gateway: 'mutate',
|
|
2509
|
+
domain: 'tasks',
|
|
2510
|
+
operation: 'saga.reconcile',
|
|
2511
|
+
description:
|
|
2512
|
+
'tasks.saga.reconcile (mutate) — idempotent cron-safe saga auto-close repair; per-saga advisory lock + audit log; supersedes T10098',
|
|
2513
|
+
tier: 0,
|
|
2514
|
+
idempotent: true,
|
|
2515
|
+
sessionRequired: false,
|
|
2516
|
+
requiredParams: [],
|
|
2517
|
+
params: [
|
|
2518
|
+
{
|
|
2519
|
+
name: 'sagaId',
|
|
2520
|
+
type: 'string',
|
|
2521
|
+
required: false,
|
|
2522
|
+
description: 'Optional saga task ID. Omit to walk every saga.',
|
|
2523
|
+
cli: { positional: true },
|
|
2524
|
+
},
|
|
2525
|
+
{
|
|
2526
|
+
name: 'dryRun',
|
|
2527
|
+
type: 'boolean',
|
|
2528
|
+
required: false,
|
|
2529
|
+
description: 'Report what would happen without mutating rows or writing audit log',
|
|
2530
|
+
},
|
|
2531
|
+
] satisfies ParamDef[],
|
|
2532
|
+
},
|
|
2448
2533
|
{
|
|
2449
2534
|
gateway: 'mutate',
|
|
2450
2535
|
domain: 'tasks',
|
package/src/doctor.ts
CHANGED
|
@@ -202,6 +202,90 @@ export interface OrphanScanResult {
|
|
|
202
202
|
softWarnMessage?: string;
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
// ============================================================================
|
|
206
|
+
// Saga Hierarchy Audit (T10119 — ADR-073 §1.2 invariant + drift detection)
|
|
207
|
+
// ============================================================================
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Stable invariant identifiers surfaced by the saga hierarchy audit
|
|
211
|
+
* (ADR-073 §1.2). `I5` and `I7` mirror the runtime guards in
|
|
212
|
+
* `packages/core/src/sagas/enforcement.ts`. `depth` covers the I5/I7 depth
|
|
213
|
+
* ladder (saga → member-Epic → Task → Subtask = 3 hops max). `auto-close-drift`
|
|
214
|
+
* is a soft-drift detector (no invariant strictly broken — the saga is
|
|
215
|
+
* structurally valid, but every member is done while the saga still says
|
|
216
|
+
* pending; T10116 fixes the root cause).
|
|
217
|
+
*/
|
|
218
|
+
export type SagaAuditViolationKind = 'I5' | 'I7' | 'depth' | 'auto-close-drift';
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* One violation surfaced by {@link auditSagaHierarchy}.
|
|
222
|
+
*
|
|
223
|
+
* Each violation is actionable: the `repairCommand` field names the
|
|
224
|
+
* canonical `cleo` invocation an operator should run to resolve it.
|
|
225
|
+
*/
|
|
226
|
+
export interface SagaAuditViolation {
|
|
227
|
+
/** Stable violation identifier (`I5`, `I7`, `depth`, `auto-close-drift`). */
|
|
228
|
+
kind: SagaAuditViolationKind;
|
|
229
|
+
/** Saga task ID where the violation was detected. */
|
|
230
|
+
sagaId: string;
|
|
231
|
+
/**
|
|
232
|
+
* The offending member task ID. Equal to `sagaId` for I5 violations
|
|
233
|
+
* (the saga row itself is the offender). For I7 violations, the
|
|
234
|
+
* nested-saga candidate. For `auto-close-drift`, equal to `sagaId`.
|
|
235
|
+
*/
|
|
236
|
+
offendingId: string;
|
|
237
|
+
/** Human-readable message naming both IDs and the repair command. */
|
|
238
|
+
message: string;
|
|
239
|
+
/** Canonical `cleo` command an operator should run to resolve. */
|
|
240
|
+
repairCommand: string;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Per-saga audit summary returned by {@link auditSagaHierarchy}.
|
|
245
|
+
*
|
|
246
|
+
* Carries enough context for the doctor CLI to render a one-line summary
|
|
247
|
+
* (`Saga T#### · status=… · 4/5 members done · 0 violations`) without a
|
|
248
|
+
* second database round-trip.
|
|
249
|
+
*/
|
|
250
|
+
export interface SagaAuditEntry {
|
|
251
|
+
/** Saga task ID. */
|
|
252
|
+
sagaId: string;
|
|
253
|
+
/** Saga title (for human-readable rendering). */
|
|
254
|
+
title: string;
|
|
255
|
+
/** Saga status (`pending`, `active`, `done`, …). */
|
|
256
|
+
status: string;
|
|
257
|
+
/** Total member-Epic count. */
|
|
258
|
+
memberCount: number;
|
|
259
|
+
/** Member-Epics with `status='done'`. */
|
|
260
|
+
doneCount: number;
|
|
261
|
+
/**
|
|
262
|
+
* Violations attributable to this saga. Always returned (possibly
|
|
263
|
+
* empty) so callers can render a stable rows-then-violations layout.
|
|
264
|
+
*/
|
|
265
|
+
violations: SagaAuditViolation[];
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Aggregated result returned by {@link auditSagaHierarchy}.
|
|
270
|
+
*
|
|
271
|
+
* `count` is the total number of `I5`/`I7`/`depth` invariant violations
|
|
272
|
+
* across all sagas — the doctor CLI uses this to decide whether to set
|
|
273
|
+
* a non-zero exit code (`auto-close-drift` is a soft warning and does
|
|
274
|
+
* NOT alone trigger exit≠0; tests pass with drift but no I-invariant
|
|
275
|
+
* failure).
|
|
276
|
+
*
|
|
277
|
+
* `driftCount` is the number of `auto-close-drift` warnings (kept
|
|
278
|
+
* separate so CI gates can opt-in to treating drift as failure).
|
|
279
|
+
*/
|
|
280
|
+
export interface SagaAuditResult {
|
|
281
|
+
/** All sagas audited, sorted by `sagaId` ascending. */
|
|
282
|
+
sagas: SagaAuditEntry[];
|
|
283
|
+
/** Total I5/I7/depth invariant violations across all sagas. */
|
|
284
|
+
count: number;
|
|
285
|
+
/** Total auto-close-drift warnings across all sagas. */
|
|
286
|
+
driftCount: number;
|
|
287
|
+
}
|
|
288
|
+
|
|
205
289
|
/**
|
|
206
290
|
* One line appended to `.cleo/audit/worktree-prune.jsonl` per prune
|
|
207
291
|
* operation. Extends the existing audit-log schema (timestamp +
|
package/src/errors.ts
CHANGED
|
@@ -233,6 +233,67 @@ export class LeadBypassDetectedError extends Error {
|
|
|
233
233
|
}
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
+
/**
|
|
237
|
+
* LAFS error code emitted when a saga-labeled epic carries forbidden parent or
|
|
238
|
+
* `depends` edges — ADR-073 §1.2 invariant **I3** violation.
|
|
239
|
+
*
|
|
240
|
+
* Sagas MUST link to member Epics exclusively via
|
|
241
|
+
* `task_relations.type='groups'`; a parent or depends edge re-introduces the
|
|
242
|
+
* depth-budget consumption the saga tier was created to avoid.
|
|
243
|
+
*
|
|
244
|
+
* Runtime guard: `assertSagaInvariantI3` (in `@cleocode/core` saga module).
|
|
245
|
+
*
|
|
246
|
+
* @task T10115
|
|
247
|
+
* @saga T10113
|
|
248
|
+
* @see ADR-073-above-epic-naming.md §1.2
|
|
249
|
+
*/
|
|
250
|
+
export const E_SAGA_INVARIANT_VIOLATION_I3 = 'E_SAGA_INVARIANT_VIOLATION_I3' as const;
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* LAFS error code emitted when a saga-labeled row has a non-null `parentId` —
|
|
254
|
+
* ADR-073 §1.2 invariant **I5** violation.
|
|
255
|
+
*
|
|
256
|
+
* Sagas are top-level groupings; they do not nest under any task. A non-null
|
|
257
|
+
* `parentId` on a saga-labeled row indicates storage corruption or a faulty
|
|
258
|
+
* import path.
|
|
259
|
+
*
|
|
260
|
+
* Runtime guard: `assertSagaInvariantI5` (in `@cleocode/core` saga module).
|
|
261
|
+
*
|
|
262
|
+
* @task T10115
|
|
263
|
+
* @saga T10113
|
|
264
|
+
* @see ADR-073-above-epic-naming.md §1.2
|
|
265
|
+
*/
|
|
266
|
+
export const E_SAGA_INVARIANT_VIOLATION_I5 = 'E_SAGA_INVARIANT_VIOLATION_I5' as const;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* LAFS error code emitted when a saga-member candidate is itself a saga —
|
|
270
|
+
* ADR-073 §1.2 invariant **I7** violation (no nested sagas).
|
|
271
|
+
*
|
|
272
|
+
* Nested sagas would re-introduce the multi-release grouping depth the tier
|
|
273
|
+
* was created to flatten.
|
|
274
|
+
*
|
|
275
|
+
* Runtime guard: `assertSagaInvariantI7` (in `@cleocode/core` saga module).
|
|
276
|
+
*
|
|
277
|
+
* @task T10115
|
|
278
|
+
* @saga T10113
|
|
279
|
+
* @see ADR-073-above-epic-naming.md §1.2
|
|
280
|
+
*/
|
|
281
|
+
export const E_SAGA_INVARIANT_VIOLATION_I7 = 'E_SAGA_INVARIANT_VIOLATION_I7' as const;
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Union of saga-invariant LAFS error codes (I3 / I5 / I7).
|
|
285
|
+
*
|
|
286
|
+
* Useful for narrowing thrown error codes from
|
|
287
|
+
* `assertSagaInvariantI{3,5,7}` in dispatch handlers.
|
|
288
|
+
*
|
|
289
|
+
* @task T10115
|
|
290
|
+
* @saga T10113
|
|
291
|
+
*/
|
|
292
|
+
export type SagaInvariantErrorCode =
|
|
293
|
+
| typeof E_SAGA_INVARIANT_VIOLATION_I3
|
|
294
|
+
| typeof E_SAGA_INVARIANT_VIOLATION_I5
|
|
295
|
+
| typeof E_SAGA_INVARIANT_VIOLATION_I7;
|
|
296
|
+
|
|
236
297
|
/**
|
|
237
298
|
* Normalize any thrown value into a standardized error object.
|
|
238
299
|
*
|
package/src/index.ts
CHANGED
|
@@ -128,6 +128,16 @@ export type {
|
|
|
128
128
|
BackupMetadata,
|
|
129
129
|
BackupScope,
|
|
130
130
|
} from './backup-manifest.js';
|
|
131
|
+
// === Boundary Registry (SSoT for Rust/TS layering — ADR-078, Saga T10176) ===
|
|
132
|
+
export type {
|
|
133
|
+
BoundaryEntry,
|
|
134
|
+
CanonicalHome,
|
|
135
|
+
PerfBudget,
|
|
136
|
+
SafetyBudget,
|
|
137
|
+
ThroughputThreshold,
|
|
138
|
+
WorkloadIntent,
|
|
139
|
+
} from './boundary.js';
|
|
140
|
+
export { BOUNDARY_REGISTRY } from './boundary.js';
|
|
131
141
|
// === Brain/Memory Types ===
|
|
132
142
|
export type {
|
|
133
143
|
BrainCognitiveType,
|
|
@@ -302,12 +312,17 @@ export {
|
|
|
302
312
|
DocKindRegistry,
|
|
303
313
|
} from './docs-taxonomy.js';
|
|
304
314
|
// === Doctor: Worktree-Orphan Audit + Prune Types (T9790, T9808, T9962) ===
|
|
315
|
+
// === Doctor: Saga Hierarchy Audit (T10119 — ADR-073 §1.2) ===
|
|
305
316
|
export type {
|
|
306
317
|
ComprehensiveAuditResult,
|
|
307
318
|
OrphanEntry,
|
|
308
319
|
OrphanScanResult,
|
|
309
320
|
PruneAuditEntry,
|
|
310
321
|
PruneResult,
|
|
322
|
+
SagaAuditEntry,
|
|
323
|
+
SagaAuditResult,
|
|
324
|
+
SagaAuditViolation,
|
|
325
|
+
SagaAuditViolationKind,
|
|
311
326
|
WorktreeAnomaly,
|
|
312
327
|
WorktreeAnomalyKind,
|
|
313
328
|
} from './doctor.js';
|
|
@@ -334,12 +349,16 @@ export {
|
|
|
334
349
|
TASK_SEVERITIES,
|
|
335
350
|
TASK_SIZES,
|
|
336
351
|
} from './enums.js';
|
|
352
|
+
export type { SagaInvariantErrorCode } from './errors.js';
|
|
337
353
|
// === Error Utilities ===
|
|
338
354
|
export {
|
|
339
355
|
ClassifierUnregisteredAgentError,
|
|
340
356
|
createErrorResult,
|
|
341
357
|
createSuccessResult,
|
|
342
358
|
DecisionValidatorFailedError,
|
|
359
|
+
E_SAGA_INVARIANT_VIOLATION_I3,
|
|
360
|
+
E_SAGA_INVARIANT_VIOLATION_I5,
|
|
361
|
+
E_SAGA_INVARIANT_VIOLATION_I7,
|
|
343
362
|
formatError,
|
|
344
363
|
getErrorMessage,
|
|
345
364
|
isErrorResult,
|
|
@@ -1156,6 +1175,8 @@ export type {
|
|
|
1156
1175
|
TasksSagaAddResult,
|
|
1157
1176
|
TasksSagaCreateParams,
|
|
1158
1177
|
TasksSagaCreateResult,
|
|
1178
|
+
TasksSagaDetachParams,
|
|
1179
|
+
TasksSagaDetachResult,
|
|
1159
1180
|
TasksSagaListParams,
|
|
1160
1181
|
TasksSagaListResult,
|
|
1161
1182
|
TasksSagaMembersParams,
|
package/src/operations/tasks.ts
CHANGED
|
@@ -1048,19 +1048,75 @@ export interface TasksSagaAddResult {
|
|
|
1048
1048
|
added: boolean;
|
|
1049
1049
|
}
|
|
1050
1050
|
|
|
1051
|
+
/**
|
|
1052
|
+
* Params for `tasks.saga.detach` — remove a Saga member relation
|
|
1053
|
+
* (`task_relations.type='groups'`). Idempotent.
|
|
1054
|
+
*
|
|
1055
|
+
* @task T10118
|
|
1056
|
+
* @see ADR-073-above-epic-naming.md §1.2 invariant I7
|
|
1057
|
+
*/
|
|
1058
|
+
export interface TasksSagaDetachParams {
|
|
1059
|
+
/** Saga task ID (the `from` side of the groups relation). */
|
|
1060
|
+
sagaId: string;
|
|
1061
|
+
/** Member task ID (the `to` side of the groups relation). */
|
|
1062
|
+
memberId: string;
|
|
1063
|
+
/** Optional human-readable reason recorded in the audit log entry. */
|
|
1064
|
+
reason?: string;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
/**
|
|
1068
|
+
* Result of `tasks.saga.detach` — relation removal confirmation + audit
|
|
1069
|
+
* record (always appended even on idempotent no-op).
|
|
1070
|
+
*
|
|
1071
|
+
* @task T10118
|
|
1072
|
+
*/
|
|
1073
|
+
export interface TasksSagaDetachResult {
|
|
1074
|
+
/** Saga task ID. */
|
|
1075
|
+
sagaId: string;
|
|
1076
|
+
/** Member task ID that was detached. */
|
|
1077
|
+
memberId: string;
|
|
1078
|
+
/** True when a row was actually removed; false on idempotent no-op. */
|
|
1079
|
+
removed: boolean;
|
|
1080
|
+
/** Reason recorded in the audit log entry. */
|
|
1081
|
+
reason: string;
|
|
1082
|
+
/** ISO 8601 timestamp recorded in the audit log entry. */
|
|
1083
|
+
timestamp: string;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1051
1086
|
/** Params for `tasks.saga.list` — list all Sagas. */
|
|
1052
1087
|
export type TasksSagaListParams = Record<string, never>;
|
|
1053
1088
|
|
|
1089
|
+
/**
|
|
1090
|
+
* A single I5-violation warning entry returned by `tasks.saga.list` when a
|
|
1091
|
+
* saga row carries a non-null `parentId` (ADR-073 §1.2 invariant I5).
|
|
1092
|
+
*
|
|
1093
|
+
* @task T10117
|
|
1094
|
+
*/
|
|
1095
|
+
export interface TasksSagaInvariantI5Warning {
|
|
1096
|
+
/** Fixed warning code. */
|
|
1097
|
+
code: 'E_SAGA_INVARIANT_VIOLATION_I5';
|
|
1098
|
+
/** Saga task ID whose `parentId` violates invariant I5. */
|
|
1099
|
+
sagaId: string;
|
|
1100
|
+
/** The non-null `parentId` value found on the saga row. */
|
|
1101
|
+
offendingParentId: string;
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1054
1104
|
/**
|
|
1055
1105
|
* Result of `tasks.saga.list` — all labeled top-level Epics.
|
|
1056
1106
|
*
|
|
1057
1107
|
* @task T9521
|
|
1108
|
+
* @task T10117 — added optional `warnings` array for I5 violations
|
|
1058
1109
|
*/
|
|
1059
1110
|
export interface TasksSagaListResult {
|
|
1060
1111
|
/** Array of Saga task records. */
|
|
1061
1112
|
sagas: TaskRecord[];
|
|
1062
1113
|
/** Total count. */
|
|
1063
1114
|
total: number;
|
|
1115
|
+
/**
|
|
1116
|
+
* One entry per saga with a non-null `parentId`. Omitted entirely when no
|
|
1117
|
+
* violations were observed (pre-T10117 envelope shape preserved).
|
|
1118
|
+
*/
|
|
1119
|
+
warnings?: TasksSagaInvariantI5Warning[];
|
|
1064
1120
|
}
|
|
1065
1121
|
|
|
1066
1122
|
/** Params for `tasks.saga.members` — list member Epics linked to a Saga. */
|
|
@@ -1083,6 +1139,124 @@ export interface TasksSagaMembersResult {
|
|
|
1083
1139
|
total: number;
|
|
1084
1140
|
}
|
|
1085
1141
|
|
|
1142
|
+
/**
|
|
1143
|
+
* Params for `tasks.saga.repair` — detach an I5-violating `parentId` from a
|
|
1144
|
+
* Saga and re-attach via `task_relations.type='groups'`.
|
|
1145
|
+
*
|
|
1146
|
+
* @task T10117
|
|
1147
|
+
* @see ADR-073-above-epic-naming.md §1.2 — invariant I5
|
|
1148
|
+
*/
|
|
1149
|
+
export interface TasksSagaRepairParams {
|
|
1150
|
+
/** Saga task ID (must have `label='saga'`). */
|
|
1151
|
+
sagaId: string;
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
/**
|
|
1155
|
+
* Result of `tasks.saga.repair` — idempotent detach + re-attach.
|
|
1156
|
+
*
|
|
1157
|
+
* @task T10117
|
|
1158
|
+
*/
|
|
1159
|
+
export interface TasksSagaRepairResult {
|
|
1160
|
+
/** Saga task ID that was inspected. */
|
|
1161
|
+
sagaId: string;
|
|
1162
|
+
/** `true` when the call performed a state change; `false` for no-op. */
|
|
1163
|
+
repaired: boolean;
|
|
1164
|
+
/** The detached `parentId` value, or `null` when no detach was needed. */
|
|
1165
|
+
detachedParentId: string | null;
|
|
1166
|
+
/**
|
|
1167
|
+
* The `groups` edge that was written, or `null` for the idempotent no-op
|
|
1168
|
+
* and the missing-former-parent edge case.
|
|
1169
|
+
*/
|
|
1170
|
+
attachedRelation: {
|
|
1171
|
+
from: string;
|
|
1172
|
+
to: string;
|
|
1173
|
+
type: 'groups';
|
|
1174
|
+
} | null;
|
|
1175
|
+
/** Free-form notes the CLI renderer can surface alongside the result. */
|
|
1176
|
+
note?: string;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
/**
|
|
1180
|
+
* Per-saga reconciliation action returned by `tasks.saga.reconcile`.
|
|
1181
|
+
*
|
|
1182
|
+
* @task T10121
|
|
1183
|
+
*/
|
|
1184
|
+
export type TasksSagaReconcileAction = 'close' | 'no-op' | 'blocked' | 'error';
|
|
1185
|
+
|
|
1186
|
+
/**
|
|
1187
|
+
* Params for `tasks.saga.reconcile` — idempotent cron-safe saga auto-close
|
|
1188
|
+
* repair. Re-applies the T10116 auto-close logic for sagas whose members
|
|
1189
|
+
* reached 100% terminal status via paths OTHER than `completeTask` (bulk
|
|
1190
|
+
* SQL repair, crash recovery, manual state edits).
|
|
1191
|
+
*
|
|
1192
|
+
* @task T10121
|
|
1193
|
+
* @see ADR-073-above-epic-naming.md §1.3
|
|
1194
|
+
*/
|
|
1195
|
+
export interface TasksSagaReconcileParams {
|
|
1196
|
+
/**
|
|
1197
|
+
* Optional single saga to reconcile. When omitted, the verb walks every
|
|
1198
|
+
* saga returned by `taskList({ type: 'epic', label: 'saga' })`.
|
|
1199
|
+
*/
|
|
1200
|
+
sagaId?: string;
|
|
1201
|
+
/**
|
|
1202
|
+
* When `true`, run in report-only mode — log what would happen without
|
|
1203
|
+
* mutating any rows or writing to the audit log.
|
|
1204
|
+
*/
|
|
1205
|
+
dryRun?: boolean;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
/**
|
|
1209
|
+
* Per-saga reconciliation outcome surfaced inside
|
|
1210
|
+
* {@link TasksSagaReconcileResult}.
|
|
1211
|
+
*
|
|
1212
|
+
* @task T10121
|
|
1213
|
+
*/
|
|
1214
|
+
export interface TasksSagaReconcileEntry {
|
|
1215
|
+
/** Saga task ID this entry refers to. */
|
|
1216
|
+
sagaId: string;
|
|
1217
|
+
/** The action the reconciler took for this saga. */
|
|
1218
|
+
action: TasksSagaReconcileAction;
|
|
1219
|
+
/** Member task IDs considered by the closure check. */
|
|
1220
|
+
members: string[];
|
|
1221
|
+
/** Members that satisfied the terminal-status predicate. */
|
|
1222
|
+
terminalMembers: string[];
|
|
1223
|
+
/** Members that did NOT satisfy the terminal-status predicate. */
|
|
1224
|
+
pendingMembers: string[];
|
|
1225
|
+
/** Saga status BEFORE this run. */
|
|
1226
|
+
statusBefore: string;
|
|
1227
|
+
/** Saga status AFTER this run (== `statusBefore` for no-op/blocked/error). */
|
|
1228
|
+
statusAfter: string;
|
|
1229
|
+
/** Free-form human-readable reason recorded for the audit entry. */
|
|
1230
|
+
reason: string;
|
|
1231
|
+
/** ISO 8601 timestamp the decision was recorded. */
|
|
1232
|
+
timestamp: string;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
/**
|
|
1236
|
+
* Result of `tasks.saga.reconcile` — aggregate counters + per-saga entries
|
|
1237
|
+
* in stable id order.
|
|
1238
|
+
*
|
|
1239
|
+
* @task T10121
|
|
1240
|
+
*/
|
|
1241
|
+
export interface TasksSagaReconcileResult {
|
|
1242
|
+
/** Total number of sagas inspected (== `entries.length`). */
|
|
1243
|
+
total: number;
|
|
1244
|
+
/** Number of sagas the run flipped to `status='done'`. */
|
|
1245
|
+
closed: number;
|
|
1246
|
+
/** Number of sagas already in the correct terminal state. */
|
|
1247
|
+
noOp: number;
|
|
1248
|
+
/** Number of sagas blocked behind a concurrent lock holder. */
|
|
1249
|
+
blocked: number;
|
|
1250
|
+
/** Number of sagas with pending non-terminal members (not closed). */
|
|
1251
|
+
pending: number;
|
|
1252
|
+
/** Number of sagas that errored out during reconciliation. */
|
|
1253
|
+
errors: number;
|
|
1254
|
+
/** Whether this run ran in dry-run mode. */
|
|
1255
|
+
dryRun: boolean;
|
|
1256
|
+
/** Detailed per-saga entries in stable id order. */
|
|
1257
|
+
entries: TasksSagaReconcileEntry[];
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1086
1260
|
/** Params for `tasks.saga.rollup` — aggregate member Epic statuses. */
|
|
1087
1261
|
export interface TasksSagaRollupParams {
|
|
1088
1262
|
/** Saga task ID. */
|
|
@@ -1169,7 +1343,12 @@ export type TasksOps = {
|
|
|
1169
1343
|
// Saga sub-domain ops (ADR-073)
|
|
1170
1344
|
readonly 'saga.create': readonly [TasksSagaCreateParams, TasksSagaCreateResult];
|
|
1171
1345
|
readonly 'saga.add': readonly [TasksSagaAddParams, TasksSagaAddResult];
|
|
1346
|
+
readonly 'saga.detach': readonly [TasksSagaDetachParams, TasksSagaDetachResult];
|
|
1172
1347
|
readonly 'saga.list': readonly [TasksSagaListParams, TasksSagaListResult];
|
|
1173
1348
|
readonly 'saga.members': readonly [TasksSagaMembersParams, TasksSagaMembersResult];
|
|
1174
1349
|
readonly 'saga.rollup': readonly [TasksSagaRollupParams, TasksSagaRollupResult];
|
|
1350
|
+
/** T10117 — repair an I5-violating saga. */
|
|
1351
|
+
readonly 'saga.repair': readonly [TasksSagaRepairParams, TasksSagaRepairResult];
|
|
1352
|
+
/** T10121 — idempotent cron-safe auto-close repair (supersedes T10098 scope). */
|
|
1353
|
+
readonly 'saga.reconcile': readonly [TasksSagaReconcileParams, TasksSagaReconcileResult];
|
|
1175
1354
|
};
|
|
@@ -449,8 +449,38 @@ export interface WorktreeInfo {
|
|
|
449
449
|
owningAgent: string | null;
|
|
450
450
|
/** ISO-8601 timestamp of last activity (newest commit OR mtime of working tree). */
|
|
451
451
|
lastActivity: string;
|
|
452
|
+
/**
|
|
453
|
+
* ISO-8601 timestamp recording when this worktree was first created.
|
|
454
|
+
*
|
|
455
|
+
* Resolution order:
|
|
456
|
+
* 1. For git-native entries — mtime of the per-worktree admin file
|
|
457
|
+
* `<gitCommonDir>/worktrees/<basename(path)>/HEAD`. Git writes this once
|
|
458
|
+
* at `git worktree add` time and never rewrites it during normal usage,
|
|
459
|
+
* making it a faithful proxy for creation time.
|
|
460
|
+
* 2. For sentinel-only entries — the `adoptedAt` timestamp persisted in
|
|
461
|
+
* `.cleo/worktrees.json`.
|
|
462
|
+
* 3. Fallback — mtime of the worktree directory itself.
|
|
463
|
+
*
|
|
464
|
+
* Distinct from {@link lastActivity}, which moves with each new commit on
|
|
465
|
+
* the branch. The two values diverge for any worktree that has had at least
|
|
466
|
+
* one commit since creation.
|
|
467
|
+
*
|
|
468
|
+
* @task T9546
|
|
469
|
+
*/
|
|
470
|
+
createdAt: string;
|
|
452
471
|
/** Whether `git worktree list --porcelain` reports the worktree as locked. */
|
|
453
472
|
isLocked: boolean;
|
|
473
|
+
/**
|
|
474
|
+
* Lock-state string mirroring {@link isLocked} as a discriminated literal.
|
|
475
|
+
*
|
|
476
|
+
* Provided for downstream consumers that want a single human-readable token
|
|
477
|
+
* rather than a boolean — `'locked'` when porcelain reports the worktree as
|
|
478
|
+
* locked, `'unlocked'` otherwise. Always equivalent to
|
|
479
|
+
* `isLocked ? 'locked' : 'unlocked'`.
|
|
480
|
+
*
|
|
481
|
+
* @task T9546
|
|
482
|
+
*/
|
|
483
|
+
lockState: 'locked' | 'unlocked';
|
|
454
484
|
/** Whether the worktree is stale: no activity > N days AND (task done/cancelled OR branch merged). */
|
|
455
485
|
isStale: boolean;
|
|
456
486
|
/** Whether the branch is reachable from `main` (already integrated). */
|