@cleocode/contracts 2026.5.92 → 2026.5.94

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 (53) hide show
  1. package/dist/__tests__/docs-taxonomy.test.d.ts +16 -0
  2. package/dist/__tests__/docs-taxonomy.test.d.ts.map +1 -0
  3. package/dist/__tests__/docs-taxonomy.test.js +404 -0
  4. package/dist/__tests__/docs-taxonomy.test.js.map +1 -0
  5. package/dist/cli-category.d.ts +24 -0
  6. package/dist/cli-category.d.ts.map +1 -0
  7. package/dist/cli-category.js +32 -0
  8. package/dist/cli-category.js.map +1 -0
  9. package/dist/docs-taxonomy.d.ts +286 -0
  10. package/dist/docs-taxonomy.d.ts.map +1 -0
  11. package/dist/docs-taxonomy.js +489 -0
  12. package/dist/docs-taxonomy.js.map +1 -0
  13. package/dist/doctor.d.ts +120 -0
  14. package/dist/doctor.d.ts.map +1 -0
  15. package/dist/doctor.js +16 -0
  16. package/dist/doctor.js.map +1 -0
  17. package/dist/exit-codes.d.ts +29 -0
  18. package/dist/exit-codes.d.ts.map +1 -1
  19. package/dist/exit-codes.js +29 -0
  20. package/dist/exit-codes.js.map +1 -1
  21. package/dist/index.d.ts +11 -6
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +7 -3
  24. package/dist/index.js.map +1 -1
  25. package/dist/operations/docs.d.ts +89 -11
  26. package/dist/operations/docs.d.ts.map +1 -1
  27. package/dist/operations/docs.js +19 -12
  28. package/dist/operations/docs.js.map +1 -1
  29. package/dist/operations/release.d.ts +0 -25
  30. package/dist/operations/release.d.ts.map +1 -1
  31. package/dist/operations/session.d.ts +66 -0
  32. package/dist/operations/session.d.ts.map +1 -1
  33. package/dist/operations/tasks.d.ts +64 -0
  34. package/dist/operations/tasks.d.ts.map +1 -1
  35. package/dist/operations/validate.d.ts +32 -0
  36. package/dist/operations/validate.d.ts.map +1 -1
  37. package/dist/release/evidence-atoms.d.ts +37 -1
  38. package/dist/release/evidence-atoms.d.ts.map +1 -1
  39. package/dist/release/evidence-atoms.js +22 -0
  40. package/dist/release/evidence-atoms.js.map +1 -1
  41. package/package.json +2 -2
  42. package/src/__tests__/docs-taxonomy.test.ts +465 -0
  43. package/src/cli-category.ts +52 -0
  44. package/src/docs-taxonomy.ts +682 -0
  45. package/src/doctor.ts +130 -0
  46. package/src/exit-codes.ts +30 -0
  47. package/src/index.ts +36 -2
  48. package/src/operations/docs.ts +92 -18
  49. package/src/operations/release.ts +0 -28
  50. package/src/operations/session.ts +71 -0
  51. package/src/operations/tasks.ts +67 -0
  52. package/src/operations/validate.ts +34 -0
  53. package/src/release/evidence-atoms.ts +38 -0
package/src/doctor.ts ADDED
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Doctor domain contracts — types for `cleo doctor` worktree-orphan audit/prune.
3
+ *
4
+ * These types describe orphan `.cleo/` directories left behind under
5
+ * `<projectRoot>/.claude/worktrees/` by the T9550/T9580 SSoT bug (fixed in
6
+ * v2026.5.83) and the schema for the audit JSONL line written per prune.
7
+ *
8
+ * Consumed by:
9
+ * - `packages/core/src/doctor/worktree-orphans.ts` (scan + prune primitives)
10
+ * - `packages/cleo/src/cli/commands/doctor.ts` (CLI flags)
11
+ *
12
+ * @task T9790
13
+ * @epic T9790
14
+ */
15
+
16
+ /**
17
+ * One orphan `.cleo/` directory discovered under
18
+ * `<projectRoot>/.claude/worktrees/`.
19
+ *
20
+ * Worktrees are nested 1-3 levels deep — examples:
21
+ * - `<projectRoot>/.claude/worktrees/agent-X/.cleo/` (depth 1)
22
+ * - `<projectRoot>/.claude/worktrees/agent-X/T9220/.cleo/` (depth 2)
23
+ *
24
+ * Each entry carries full provenance so the operator can decide whether
25
+ * the orphan represents lost work before pruning.
26
+ */
27
+ export interface OrphanEntry {
28
+ /**
29
+ * The worktree root that contains the orphan — the first directory under
30
+ * `.claude/worktrees/` (e.g. `<projectRoot>/.claude/worktrees/agent-X`).
31
+ * Used as the boundary that `pruneWorktreeOrphans` validates against.
32
+ */
33
+ worktreePath: string;
34
+
35
+ /**
36
+ * Absolute path to the orphan `.cleo/` directory itself. Always lives
37
+ * under `worktreePath` (the security check rejects anything that doesn't).
38
+ */
39
+ orphanPath: string;
40
+
41
+ /**
42
+ * List of `tasks.db`, `brain.db`, `nexus.db`, or `config.json` paths
43
+ * found inside the orphan. Empty array means the orphan exists but is
44
+ * structurally empty (still pruned — it shouldn't be there at all).
45
+ */
46
+ dbFiles: string[];
47
+
48
+ /** Total byte size of the orphan tree (sum of regular file sizes). */
49
+ sizeBytes: number;
50
+
51
+ /** ISO-8601 timestamp of the most recent file modification under the orphan. */
52
+ lastModifiedAt: string;
53
+
54
+ /**
55
+ * Seconds since `lastModifiedAt` (relative to scan time). Surfaces "stale
56
+ * vs recent" without forcing the caller to compute date math.
57
+ */
58
+ ageSeconds: number;
59
+
60
+ /**
61
+ * `true` when the orphan contains more than just stray DB files — e.g. a
62
+ * full duplicate of `adrs/`, `agent-outputs/`, `rcasd/`, etc. These need
63
+ * heightened operator review before pruning.
64
+ */
65
+ isFullDuplicate: boolean;
66
+ }
67
+
68
+ /**
69
+ * Result of one prune operation. Reports the archive location, the per-entry
70
+ * outcome, and any entries rejected by the security gate.
71
+ */
72
+ export interface PruneResult {
73
+ /**
74
+ * Absolute path to the `.tar.gz` archive written before any deletion.
75
+ * `null` only when `dryRun: true` AND no archive was produced.
76
+ */
77
+ archivePath: string | null;
78
+
79
+ /** Whether this was a dry run (no archive, no rm, no audit-log line). */
80
+ dryRun: boolean;
81
+
82
+ /** Entries that were successfully pruned (or would be in dry-run mode). */
83
+ pruned: OrphanEntry[];
84
+
85
+ /**
86
+ * Entries that failed validation and were skipped. The `reason` is a
87
+ * stable machine-readable code (e.g. `path-outside-worktrees-root`,
88
+ * `path-not-found`, `rm-failed`).
89
+ */
90
+ rejected: Array<{ entry: OrphanEntry; reason: string }>;
91
+
92
+ /** Total bytes archived (sum of `pruned[].sizeBytes`). */
93
+ totalSizeBytes: number;
94
+
95
+ /** ISO-8601 timestamp the prune completed. */
96
+ prunedAt: string;
97
+ }
98
+
99
+ /**
100
+ * One line appended to `.cleo/audit/worktree-prune.jsonl` per prune
101
+ * operation. Extends the existing audit-log schema (timestamp +
102
+ * worktreePath + action + agent) with the orphan-specific fields needed
103
+ * to reconstruct what was removed.
104
+ *
105
+ * Existing fields preserved for schema continuity:
106
+ * - `timestamp`, `worktreePath`, `action`, `agent`
107
+ *
108
+ * New fields for orphan prune:
109
+ * - `orphanPath`, `sizeBytes`, `dbFileCount`, `archivePath`, `dryRun`
110
+ */
111
+ export interface PruneAuditEntry {
112
+ /** ISO-8601 timestamp. */
113
+ timestamp: string;
114
+ /** Worktree root that contained the orphan. */
115
+ worktreePath: string;
116
+ /** Absolute path to the orphan `.cleo/` directory removed. */
117
+ orphanPath: string;
118
+ /** Action code — fixed to `'prune-worktree-orphan'` for this flow. */
119
+ action: 'prune-worktree-orphan';
120
+ /** Always `'cleo'` — written by `cleo doctor`. */
121
+ agent: 'cleo';
122
+ /** Byte size of the pruned tree. */
123
+ sizeBytes: number;
124
+ /** Number of DB files found in the orphan (informational). */
125
+ dbFileCount: number;
126
+ /** Absolute path to the archive. `null` only on dry-run lines. */
127
+ archivePath: string | null;
128
+ /** Whether this entry represents a dry-run plan (no actual removal). */
129
+ dryRun: boolean;
130
+ }
package/src/exit-codes.ts CHANGED
@@ -244,6 +244,17 @@ export const E_DIRTY_TREE = 'E_DIRTY_TREE' as const;
244
244
  export const E_EPIC_EMPTY = 'E_EPIC_EMPTY' as const;
245
245
  /** Epic referenced by `--epic` does not exist (R-021). Exit code 10. */
246
246
  export const E_EPIC_NOT_FOUND = 'E_EPIC_NOT_FOUND' as const;
247
+ /**
248
+ * Leaf-Epic-as-Task ship (`--epic <id>` where the Epic has zero non-cancelled
249
+ * child tasks) was attempted but the Epic itself carries zero ADR-051 evidence
250
+ * atoms. Per ADR-073 the leaf-Epic-as-PR pattern is canonical for atomic ships,
251
+ * but the Epic still needs evidence (commit:sha, files, pr:<num>, etc.) to
252
+ * be promoted into a release. Exit code 83. `error.details.epicId` reports
253
+ * the offending Epic. `error.details.fix` points at `cleo verify`.
254
+ *
255
+ * @task T9838
256
+ */
257
+ export const E_EPIC_EMPTY_LEAF_NO_EVIDENCE = 'E_EPIC_EMPTY_LEAF_NO_EVIDENCE' as const;
247
258
  /** Channel + version scheme are incompatible (R-022). Exit code 6. */
248
259
  export const E_CHANNEL_MISMATCH = 'E_CHANNEL_MISMATCH' as const;
249
260
  /**
@@ -280,6 +291,25 @@ export const E_GH_NOT_AUTHENTICATED = 'E_GH_NOT_AUTHENTICATED' as const;
280
291
  */
281
292
  export const E_WORKFLOW_NOT_FOUND = 'E_WORKFLOW_NOT_FOUND' as const;
282
293
 
294
+ /**
295
+ * E_WRITE_CONTENTION — Persistent SQLITE_BUSY after exhausting application-level
296
+ * retry. Emitted by {@link withWriteRetry} (in `@cleocode/core`'s
297
+ * `store/with-retry.ts`) when a SQLite write transaction fails 4 consecutive
298
+ * attempts with `SQLITE_BUSY: database is locked` despite the engine-level
299
+ * `busy_timeout=5000ms` pragma.
300
+ *
301
+ * **Recovery**: retry the operation after a brief delay (the contended writer
302
+ * usually commits within 1-2 seconds). If the error reoccurs reliably, suspect
303
+ * a long-running writer holding a RESERVED lock — inspect with
304
+ * `sqlite3 .cleo/tasks.db "PRAGMA wal_checkpoint(FULL); SELECT * FROM sqlite_lock;"`.
305
+ *
306
+ * Maps to {@link ExitCode.LOCK_TIMEOUT} (7) at the CLI boundary.
307
+ *
308
+ * @bug gh-391 — parallel `cleo update --add-labels` losing ~50% of writes.
309
+ * @task T9839
310
+ */
311
+ export const E_WRITE_CONTENTION = 'E_WRITE_CONTENTION' as const;
312
+
283
313
  /** Check if an exit code represents an error (1-99). */
284
314
  export function isErrorCode(code: ExitCode): boolean {
285
315
  return code >= 1 && code < 100;
package/src/index.ts CHANGED
@@ -180,6 +180,9 @@ export type { AdapterCapabilities } from './capabilities.js';
180
180
  // === Changesets (CLEO-native task-anchored DSL — T9738) ===
181
181
  export type { ChangesetEntry, ChangesetKind } from './changesets.js';
182
182
  export { CHANGESET_KINDS, ChangesetEntrySchema } from './changesets.js';
183
+ // === CLI Category Types (help renderer grouping SSoT) ===
184
+ export type { CliCategory } from './cli-category.js';
185
+ export { CLI_CATEGORY_ORDER } from './cli-category.js';
183
186
  // === Code Symbol Types (tree-sitter AST) ===
184
187
  export type {
185
188
  BatchParseResult,
@@ -277,6 +280,22 @@ export type {
277
280
  StoreDocParams,
278
281
  StoreDocResult,
279
282
  } from './docs-accessor.js';
283
+ // === Canonical Doc-Kind Taxonomy Registry (T9788) ===
284
+ export type {
285
+ BuiltinDocKind,
286
+ DocKindConfigFile,
287
+ DocKindExtensionConfig,
288
+ DocKindMetadata,
289
+ SlugValidationResult,
290
+ } from './docs-taxonomy.js';
291
+ export {
292
+ BUILTIN_DOC_KIND_VALUES,
293
+ BUILTIN_DOC_KINDS,
294
+ DocKindConfigError,
295
+ DocKindRegistry,
296
+ } from './docs-taxonomy.js';
297
+ // === Doctor: Worktree-Orphan Audit + Prune Types (T9790) ===
298
+ export type { OrphanEntry, PruneAuditEntry, PruneResult } from './doctor.js';
280
299
  export type {
281
300
  EngineErrorPayload,
282
301
  EngineFailure,
@@ -339,6 +358,7 @@ export {
339
358
  E_CHANNEL_MISMATCH,
340
359
  E_DIRTY_TREE,
341
360
  E_EPIC_EMPTY,
361
+ E_EPIC_EMPTY_LEAF_NO_EVIDENCE,
342
362
  E_EPIC_NOT_FOUND,
343
363
  E_EVIDENCE_INSUFFICIENT,
344
364
  // SPEC-T9345 release pipeline v2 error code names (T9530)
@@ -347,6 +367,8 @@ export {
347
367
  E_PLAN_NOT_FOUND,
348
368
  E_RELEASE_PLAN_INVALID,
349
369
  E_WORKFLOW_NOT_FOUND,
370
+ // gh#391 — application-level SQLITE_BUSY retry exhaustion (T9839)
371
+ E_WRITE_CONTENTION,
350
372
  ExitCode,
351
373
  getExitCodeName,
352
374
  isErrorCode,
@@ -993,6 +1015,9 @@ export type {
993
1015
  SessionGcResult,
994
1016
  SessionHandoffShowParams,
995
1017
  SessionHandoffShowResult,
1018
+ SessionLintParams,
1019
+ SessionLintResult,
1020
+ SessionLintViolation,
996
1021
  SessionListParams,
997
1022
  SessionListResult,
998
1023
  SessionOps,
@@ -1015,6 +1040,8 @@ export type {
1015
1040
  DepGraphIssue,
1016
1041
  DepsTreeEdge,
1017
1042
  DepsTreeNode,
1043
+ TasksAddBatchParams,
1044
+ TasksAddBatchResult,
1018
1045
  TasksAddParams,
1019
1046
  TasksAddResult,
1020
1047
  TasksAnalyzeQueryParams,
@@ -1105,6 +1132,8 @@ export type {
1105
1132
  ComplianceMetrics,
1106
1133
  ValidateArchiveStatsParams,
1107
1134
  ValidateArchiveStatsResult,
1135
+ ValidateCanonDocsParams,
1136
+ ValidateCanonDocsResult,
1108
1137
  ValidateCanonParams,
1109
1138
  ValidateCanonResult,
1110
1139
  ValidateChainParams,
@@ -1247,13 +1276,18 @@ export type {
1247
1276
  export type { AdapterPathProvider } from './provider-paths.js';
1248
1277
  // === Release Channel ===
1249
1278
  export type { ChannelValidationResult, ReleaseChannel } from './release/channel.js';
1250
- // === Release Evidence Atoms (T9764) ===
1251
- export type { GhPrViewPayload, ParsedPrEvidenceAtom } from './release/evidence-atoms.js';
1279
+ // === Release Evidence Atoms (T9764 + T9838) ===
1280
+ export type {
1281
+ GhPrViewPayload,
1282
+ ParsedPrEvidenceAtom,
1283
+ PrEvidenceStateModifier,
1284
+ } from './release/evidence-atoms.js';
1252
1285
  export {
1253
1286
  ghPrViewSchema,
1254
1287
  PR_REQUIRED_WORKFLOWS,
1255
1288
  PR_REQUIRED_WORKFLOWS_ENV_VAR,
1256
1289
  parsedPrEvidenceAtomSchema,
1290
+ prEvidenceStateModifierSchema,
1257
1291
  } from './release/evidence-atoms.js';
1258
1292
  // === Release GitHub PR ===
1259
1293
  export type {
@@ -29,6 +29,7 @@
29
29
  */
30
30
 
31
31
  import type { AttachmentKind } from '../attachment.js';
32
+ import { BUILTIN_DOC_KIND_VALUES, type BuiltinDocKind } from '../docs-taxonomy.js';
32
33
 
33
34
  // ============================================================================
34
35
  // Shared Attachment Types (API wire format)
@@ -54,28 +55,32 @@ export type { AttachmentKind } from '../attachment.js';
54
55
  /**
55
56
  * Allowed values for the `--type` taxonomy on `cleo docs add`.
56
57
  *
57
- * The set is closed at the CLI surface; new values require a coordinated
58
- * update to (1) {@link DOCS_TYPE_VALUES}, (2) the dispatch-layer guard, and
59
- * (3) the citty CLI flag description. The DB column itself is open (no CHECK)
60
- * so older clients reading a forward-compatible value gracefully degrade.
58
+ * As of T9788 this is derived from the canonical {@link BUILTIN_DOC_KIND_VALUES}
59
+ * in `docs-taxonomy.ts` adding a kind there automatically widens this set
60
+ * without a duplicate edit here.
61
+ *
62
+ * Project-level extensions registered through `.cleo/docs-config.json` are
63
+ * NOT included in this constant (they are runtime-only, since the
64
+ * compile-time union must stay closed). Use {@link DocKindRegistry.list}
65
+ * to enumerate built-ins plus extensions at runtime.
61
66
  *
62
67
  * @task T9637 (T-DOCS-SLUG-2)
68
+ * @task T9788 (E-DOCS-TAXONOMY-V2 — registry consolidation)
63
69
  */
64
- export const DOCS_TYPE_VALUES = [
65
- 'spec',
66
- 'adr',
67
- 'research',
68
- 'handoff',
69
- 'note',
70
- 'llm-readme',
71
- ] as const;
70
+ export const DOCS_TYPE_VALUES: ReadonlyArray<BuiltinDocKind> =
71
+ BUILTIN_DOC_KIND_VALUES as ReadonlyArray<BuiltinDocKind>;
72
72
 
73
73
  /**
74
74
  * Closed-set type alias for {@link DOCS_TYPE_VALUES}.
75
75
  *
76
+ * As of T9788 this aliases {@link BuiltinDocKind} from the canonical
77
+ * registry — the union widens automatically when a new built-in kind
78
+ * is added to {@link BUILTIN_DOC_KINDS}.
79
+ *
76
80
  * @task T9637
81
+ * @task T9788
77
82
  */
78
- export type DocsType = (typeof DOCS_TYPE_VALUES)[number];
83
+ export type DocsType = BuiltinDocKind;
79
84
 
80
85
  /**
81
86
  * Flattened wire-format attachment row returned by docs query operations.
@@ -169,16 +174,38 @@ export interface AttachmentRecord {
169
174
  // docs.list — list attachments for an owner
170
175
  // --------------------------------------------------------------------------
171
176
 
177
+ /**
178
+ * Sort key for `docs.list` results.
179
+ *
180
+ * - `newest` — descending by `createdAt` (default — most recent first).
181
+ * - `sha` — ascending by `sha256` (stable lexicographic).
182
+ * - `slug` — ascending by `slug`; entries without a slug sort last.
183
+ *
184
+ * @task T9792
185
+ */
186
+ export type DocsListOrderBy = 'newest' | 'sha' | 'slug';
187
+
188
+ /**
189
+ * Default maximum number of rows returned by `docs.list` when the caller
190
+ * does not pass an explicit `limit`. Mirrored on the CLI flag default so the
191
+ * dispatch and CLI surfaces agree on the browsing window.
192
+ *
193
+ * @task T9792
194
+ */
195
+ export const DOCS_LIST_DEFAULT_LIMIT = 50;
196
+
172
197
  /**
173
198
  * Parameters for `docs.list`.
174
199
  *
175
- * Exactly one of `task`, `session`, `observation`, or `project` must be
176
- * provided. `project=true` lists ALL attachments in the project DB
177
- * regardless of owner. `type` is an optional filter applicable to every
178
- * mode and matches the {@link DocsType} taxonomy exactly.
200
+ * Scope is auto-promoted to whole-project when no owner-scope flag is set
201
+ * (T9792). Pre-T9792 callers MUST still pass `project: true` explicitly to
202
+ * stay forward-compatible the auto-promote happens at the CLI layer.
203
+ * `type` is an optional filter applicable to every mode and matches the
204
+ * {@link DocsType} taxonomy exactly.
179
205
  *
180
206
  * @task T9637 (T-DOCS-SLUG-2 — `type` filter)
181
207
  * @task T9638 (T-DOCS-SLUG-3 — `project` scope)
208
+ * @task T9792 (E-DOCS-LIST-UX-FIX — auto-promote project scope + limit + orderBy)
182
209
  */
183
210
  export interface DocsListParams {
184
211
  /** Task identifier to list attachments for. */
@@ -199,6 +226,20 @@ export interface DocsListParams {
199
226
  * @task T9637
200
227
  */
201
228
  type?: DocsType;
229
+ /**
230
+ * Maximum number of rows to return. Defaults to
231
+ * {@link DOCS_LIST_DEFAULT_LIMIT} when omitted. Values `<= 0` are treated
232
+ * as "no limit" so agents can opt-in to the full result set explicitly.
233
+ *
234
+ * @task T9792
235
+ */
236
+ limit?: number;
237
+ /**
238
+ * Sort key for the returned rows. Defaults to `newest` when omitted.
239
+ *
240
+ * @task T9792
241
+ */
242
+ orderBy?: DocsListOrderBy;
202
243
  }
203
244
 
204
245
  /**
@@ -218,8 +259,41 @@ export interface DocsListResult {
218
259
  project?: boolean;
219
260
  /** Type taxonomy filter, echoed back from the request when provided. */
220
261
  type?: DocsType;
221
- /** Count of attachments for this owner. */
262
+ /** Count of attachments for this owner (after limit + filters). */
222
263
  count: number;
264
+ /**
265
+ * Total number of attachments matching the scope + filters BEFORE the
266
+ * `limit` window was applied. Only emitted when `limit` truncated the
267
+ * result set so consumers can distinguish "fewer than limit" from
268
+ * "limit truncated".
269
+ *
270
+ * @task T9792
271
+ */
272
+ totalCount?: number;
273
+ /**
274
+ * Effective limit applied to this response. Mirrored from the request
275
+ * (or {@link DOCS_LIST_DEFAULT_LIMIT} when the request did not set one)
276
+ * so consumers can paginate without re-deriving the default.
277
+ *
278
+ * @task T9792
279
+ */
280
+ limit?: number;
281
+ /**
282
+ * Effective sort key applied to this response. Mirrored from the request
283
+ * (or `"newest"` when the request did not set one).
284
+ *
285
+ * @task T9792
286
+ */
287
+ orderBy?: DocsListOrderBy;
288
+ /**
289
+ * Optional human-readable hint surfaced when a default behaviour kicked
290
+ * in (e.g. project scope auto-promoted because no scope was passed). The
291
+ * CLI surfaces this through `meta.hint` so JSON consumers can detect that
292
+ * a narrower invocation may have been intended.
293
+ *
294
+ * @task T9792
295
+ */
296
+ hint?: string;
223
297
  /** Attachment metadata array. */
224
298
  attachments: DocsAttachmentRow[];
225
299
  /** Current attachment backend in use. */
@@ -16,14 +16,6 @@ export interface ReleaseGate {
16
16
  reason?: string;
17
17
  }
18
18
 
19
- export interface ChangelogSection {
20
- type: 'feat' | 'fix' | 'docs' | 'test' | 'refactor' | 'chore';
21
- entries: Array<{
22
- taskId: string;
23
- message: string;
24
- }>;
25
- }
26
-
27
19
  /**
28
20
  * Mutate Operations
29
21
  */
@@ -65,26 +57,6 @@ export interface ReleasePrepareResult {
65
57
  taskCount: number;
66
58
  }
67
59
 
68
- // release.changelog
69
- /** Parameters for `release.changelog`. @task T963 */
70
- export interface ReleaseChangelogParams {
71
- /** Version to build the changelog for (must match an existing manifest). @task T963 */
72
- version: string;
73
- /** Filter emitted sections. @task T963 */
74
- sections?: Array<'feat' | 'fix' | 'docs' | 'test' | 'refactor' | 'chore'>;
75
- }
76
- /** Result of `release.changelog`. @task T963 */
77
- export interface ReleaseChangelogResult {
78
- /** Version. @task T963 */
79
- version: string;
80
- /** Rendered changelog content (Markdown). @task T963 */
81
- content: string;
82
- /** Grouped changelog sections. @task T963 */
83
- sections: ChangelogSection[];
84
- /** Count of commits aggregated. @task T963 */
85
- commitCount: number;
86
- }
87
-
88
60
  // release.commit
89
61
  /** Parameters for `release.commit`. @task T963 */
90
62
  export interface ReleaseCommitParams {
@@ -587,6 +587,76 @@ export interface SessionRecordAssumptionResult {
587
587
  timestamp: string;
588
588
  }
589
589
 
590
+ // session.lint — agent-accountability harness (T9797)
591
+
592
+ /**
593
+ * Parameters for `session.lint`.
594
+ *
595
+ * Scans a Claude Code-style session transcript (`*.jsonl`) for raw
596
+ * markdown writes that bypass the docs SSoT. Flags any tool call whose
597
+ * `file_path` lands under a `rawMdPaths` entry whose owning DocKind has
598
+ * `rawMdAllowed: false` in `.cleo/canon.yml`.
599
+ *
600
+ * @task T9797
601
+ */
602
+ export interface SessionLintParams {
603
+ /**
604
+ * Absolute path to the `.jsonl` transcript to scan. Required.
605
+ */
606
+ transcript: string;
607
+ }
608
+
609
+ /**
610
+ * One violation surfaced by `session.lint`.
611
+ *
612
+ * Mirrors `CanonLintViolation` from
613
+ * `packages/core/src/session/canon-lint.ts` (the SDK-level engine).
614
+ *
615
+ * @task T9797
616
+ */
617
+ export interface SessionLintViolation {
618
+ /** Session id derived from the transcript filename. */
619
+ sessionId: string;
620
+ /** Anthropic `tool_use.id` (e.g. `toolu_01ABC...`). May be empty. */
621
+ toolUseId: string;
622
+ /** Tool name — `Write`, `Edit`, or `MultiEdit`. */
623
+ tool: string;
624
+ /** Repo-relative path the agent attempted to write. */
625
+ path: string;
626
+ /** Owning DocKind id (e.g. `adr`, `note`). */
627
+ docKind: string;
628
+ /** Matching `rawMdPaths` entry (e.g. `.cleo/adrs/`). */
629
+ matchedPath: string;
630
+ /** Categorical reason — always `'raw-md-canonical'` today. */
631
+ kind: 'raw-md-canonical';
632
+ /** First 200 chars of the violating content. */
633
+ evidence: string;
634
+ /** Suggested fix command. */
635
+ fix: string;
636
+ }
637
+
638
+ /**
639
+ * Result of `session.lint`.
640
+ *
641
+ * @task T9797
642
+ */
643
+ export interface SessionLintResult {
644
+ /** Absolute transcript path that was scanned. */
645
+ transcriptPath: string;
646
+ /** Session id derived from the transcript filename. */
647
+ sessionId: string;
648
+ /** True when no violations were flagged. */
649
+ passed: boolean;
650
+ /** Number of `Write`/`Edit`/`MultiEdit` tool calls inspected. */
651
+ scanned: number;
652
+ /** Violations in transcript order. Empty when `passed === true`. */
653
+ violations: SessionLintViolation[];
654
+ /** Non-fatal warnings (e.g. JSON parse failures on isolated lines). */
655
+ warnings: string[];
656
+ /** `'enforced'` when canon.yml present, `'no-canon'` when missing. */
657
+ mode: 'enforced' | 'no-canon';
658
+ }
659
+
590
660
  // ---------------------------------------------------------------------------
591
661
  // Typed operation record (Wave D adapter — T975)
592
662
  // ---------------------------------------------------------------------------
@@ -619,4 +689,5 @@ export type SessionOps = {
619
689
  SessionRecordAssumptionParams,
620
690
  SessionRecordAssumptionResult,
621
691
  ];
692
+ readonly lint: readonly [SessionLintParams, SessionLintResult];
622
693
  };
@@ -524,6 +524,7 @@ export interface TasksCancelParams {
524
524
  * Result of `tasks.cancel` — cancellation confirmation.
525
525
  *
526
526
  * @task T1703
527
+ * @task T9838 (idempotency — `alreadyCancelled` returned when re-cancelling)
527
528
  */
528
529
  export interface TasksCancelResult {
529
530
  /** The task ID that was cancelled. */
@@ -534,6 +535,14 @@ export interface TasksCancelResult {
534
535
  reason?: string;
535
536
  /** ISO 8601 timestamp of cancellation. */
536
537
  cancelledAt: string;
538
+ /**
539
+ * True when the task was already cancelled before this call — the
540
+ * operation is a no-op and the response reflects the pre-existing
541
+ * cancellation state. Idempotent re-cancellation (T9838).
542
+ *
543
+ * @defaultValue undefined
544
+ */
545
+ alreadyCancelled?: boolean;
537
546
  }
538
547
 
539
548
  // tasks.restore (with from routing: done → reopen, archived → unarchive)
@@ -653,6 +662,63 @@ export interface TasksRelatesRemoveResult {
653
662
  removed: boolean;
654
663
  }
655
664
 
665
+ // tasks.add-batch (atomic multi-task insert)
666
+ /**
667
+ * Parameters for `tasks.add-batch`.
668
+ *
669
+ * Wraps N `tasks.add` inserts in a single transaction; any failure rolls
670
+ * back ALL inserts. Closes the partial-batch bug in the CLI `add-batch`
671
+ * command (T9813 / T9814).
672
+ *
673
+ * @task T9814
674
+ */
675
+ export interface TasksAddBatchParams {
676
+ /**
677
+ * Array of task specs to insert. Must be non-empty.
678
+ * Each spec accepts the same fields as `tasks.add` (except `dryRun`
679
+ * which is hoisted to the batch level).
680
+ */
681
+ tasks: Array<{
682
+ title: string;
683
+ description?: string;
684
+ parent?: string;
685
+ depends?: string[];
686
+ priority?: string;
687
+ labels?: string[];
688
+ type?: string; // SSoT-EXEMPT:kind≠type — 'type' is hierarchy(epic|task|subtask), 'kind' is intent(work|bug|...) — separate axes T944
689
+ acceptance?: string[];
690
+ phase?: string;
691
+ size?: string;
692
+ notes?: string;
693
+ files?: string[];
694
+ kind?: string;
695
+ scope?: string;
696
+ severity?: string;
697
+ forceDuplicate?: boolean;
698
+ }>;
699
+ /** Optional default parent ID applied when a task spec omits `parent`. */
700
+ defaultParent?: string;
701
+ /**
702
+ * Dry-run mode: validate each spec and return predicted IDs without
703
+ * writing to the database. Created count is always 0 in dry-run.
704
+ */
705
+ dryRun?: boolean;
706
+ }
707
+
708
+ /**
709
+ * Result of `tasks.add-batch`.
710
+ *
711
+ * @task T9814
712
+ */
713
+ export interface TasksAddBatchResult {
714
+ /** Number of tasks actually created (0 on rollback or dry-run). */
715
+ created: number;
716
+ /** Per-task results in input order. */
717
+ tasks: TasksAddResult[];
718
+ /** Whether this was a dry run. */
719
+ dryRun?: boolean;
720
+ }
721
+
656
722
  // tasks.add (dispatch-level params — extends TasksCreateParams)
657
723
  export interface TasksAddParams {
658
724
  title: string;
@@ -1043,6 +1109,7 @@ export type TasksOps = {
1043
1109
  readonly 'sync.links': readonly [TasksSyncLinksParams, TasksSyncLinksResult];
1044
1110
  // Mutate ops
1045
1111
  readonly add: readonly [TasksAddParams, TasksAddResult];
1112
+ readonly 'add-batch': readonly [TasksAddBatchParams, TasksAddBatchResult];
1046
1113
  readonly update: readonly [TasksUpdateQueryParams, TasksUpdateQueryResult];
1047
1114
  readonly complete: readonly [TasksCompleteQueryParams, TasksCompleteQueryResult];
1048
1115
  readonly cancel: readonly [TasksCancelParams, TasksCancelResult];