@cleocode/contracts 2026.6.6 → 2026.6.7

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 (33) hide show
  1. package/dist/__tests__/operation-def.test.js.map +1 -1
  2. package/dist/dispatch/operation-def.d.ts +42 -0
  3. package/dist/dispatch/operation-def.d.ts.map +1 -1
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/llm/provider-profile.d.ts +54 -0
  9. package/dist/llm/provider-profile.d.ts.map +1 -1
  10. package/dist/operations/__tests__/output-contracts.test.d.ts +16 -0
  11. package/dist/operations/__tests__/output-contracts.test.d.ts.map +1 -0
  12. package/dist/operations/__tests__/output-contracts.test.js +103 -0
  13. package/dist/operations/__tests__/output-contracts.test.js.map +1 -0
  14. package/dist/operations/output-contract.d.ts +127 -0
  15. package/dist/operations/output-contract.d.ts.map +1 -0
  16. package/dist/operations/output-contract.js +41 -0
  17. package/dist/operations/output-contract.js.map +1 -0
  18. package/dist/operations/output-contracts-data.d.ts +35 -0
  19. package/dist/operations/output-contracts-data.d.ts.map +1 -0
  20. package/dist/operations/output-contracts-data.js +328 -0
  21. package/dist/operations/output-contracts-data.js.map +1 -0
  22. package/dist/operations/tasks.d.ts +14 -4
  23. package/dist/operations/tasks.d.ts.map +1 -1
  24. package/dist/operations/tasks.js.map +1 -1
  25. package/package.json +2 -2
  26. package/src/__tests__/operation-def.test.ts +5 -0
  27. package/src/dispatch/operation-def.ts +44 -0
  28. package/src/index.ts +9 -0
  29. package/src/llm/provider-profile.ts +59 -0
  30. package/src/operations/__tests__/output-contracts.test.ts +125 -0
  31. package/src/operations/output-contract.ts +140 -0
  32. package/src/operations/output-contracts-data.ts +356 -0
  33. package/src/operations/tasks.ts +14 -4
@@ -0,0 +1,356 @@
1
+ /**
2
+ * OUTPUT_CONTRACTS data — the proof-set of per-operation OUTPUT contracts.
3
+ *
4
+ * SSoT for the OUTPUT (result) shape of CLEO's highest-traffic operations
5
+ * (DHQ-057 · T11692). Each entry is an {@link OperationOutputContract}: a
6
+ * JSON Schema draft-07 document for the LAFS envelope's `data` payload plus
7
+ * the curated list of valid `--field` JSON pointers.
8
+ *
9
+ * Populated INCREMENTALLY — high-traffic operations first. This is the OUTPUT
10
+ * mirror of `INPUT_CONTRACTS` (which lives in core). The data lives here in
11
+ * `contracts` (a leaf package, zero runtime deps) so every consumer — CLI
12
+ * `--describe`, the SDK `describeOperation`, REST clients — resolves against
13
+ * one source of truth.
14
+ *
15
+ * Each `dataSchema` is grounded in the concrete result interface declared in
16
+ * `./tasks.js` (e.g. {@link TasksShowResult}, {@link MinimalMutateEnvelope}) so
17
+ * the schema can never drift from the typed shape without a compile-time touch
18
+ * to this file.
19
+ *
20
+ * @packageDocumentation
21
+ * @module @cleocode/contracts/operations/output-contracts-data
22
+ *
23
+ * @epic T11679
24
+ * @task T11692 — DHQ-057: per-operation output schema SSoT
25
+ */
26
+
27
+ import type {
28
+ OperationOutputContract,
29
+ OperationOutputContractRegistry,
30
+ } from './output-contract.js';
31
+
32
+ // ---------------------------------------------------------------------------
33
+ // tasks.show — the operation that bit us (DHQ-057 reproduction)
34
+ // ---------------------------------------------------------------------------
35
+
36
+ /**
37
+ * OUTPUT contract for `tasks.show`.
38
+ *
39
+ * Grounded in {@link TasksShowResult}: `{ task, view, attachments, acRows?,
40
+ * relations? }`. The task body is nested under `task` — so the canonical
41
+ * pointer for the title is `/data/task/title`, NOT `/data/title`. This is the
42
+ * exact shape whose absence produced `cleo show --field /data/title` →
43
+ * `E_FIELD_NOT_FOUND`.
44
+ */
45
+ const tasksShowOutputContract: OperationOutputContract = {
46
+ operation: 'tasks.show',
47
+ shapeNote:
48
+ 'The task record is nested under `task` — use /data/task/<field>, not /data/<field>. ' +
49
+ '`view` may be null. `acRows` and `relations` are conditional.',
50
+ dataSchema: {
51
+ type: 'object',
52
+ required: ['task', 'view', 'attachments'],
53
+ additionalProperties: true,
54
+ properties: {
55
+ task: {
56
+ type: 'object',
57
+ description: 'Full task record (id, title, status, priority, type, parentId, ...).',
58
+ required: ['id', 'title', 'status'],
59
+ properties: {
60
+ id: { type: 'string' },
61
+ title: { type: 'string' },
62
+ status: { type: 'string' },
63
+ priority: { type: 'string' },
64
+ type: { type: 'string' },
65
+ parentId: { type: ['string', 'null'] },
66
+ },
67
+ },
68
+ view: {
69
+ type: ['object', 'null'],
70
+ description:
71
+ 'Canonical task view projection produced by computeTaskView. Null when unavailable.',
72
+ },
73
+ attachments: {
74
+ type: 'array',
75
+ description: 'Docs attachments linked to this task. Always an array (empty when none).',
76
+ },
77
+ acRows: {
78
+ type: 'array',
79
+ description: 'Acceptance-criterion rows (id, alias AC<n>, ordinal, text). Optional.',
80
+ },
81
+ relations: {
82
+ type: 'object',
83
+ description: 'Expanded relation/doc lists. Present only with --relations.',
84
+ },
85
+ },
86
+ },
87
+ fieldPointers: [
88
+ '/data/task/id',
89
+ '/data/task/title',
90
+ '/data/task/status',
91
+ '/data/task/priority',
92
+ '/data/task/type',
93
+ '/data/task/parentId',
94
+ '/data/view/id',
95
+ '/data/view/title',
96
+ ],
97
+ };
98
+
99
+ // ---------------------------------------------------------------------------
100
+ // tasks.list — array-of-tasks plus counts
101
+ // ---------------------------------------------------------------------------
102
+
103
+ /**
104
+ * OUTPUT contract for `tasks.list`.
105
+ *
106
+ * Grounded in {@link TasksListResult}: `{ tasks: TaskOp[], total, filtered }`.
107
+ */
108
+ const tasksListOutputContract: OperationOutputContract = {
109
+ operation: 'tasks.list',
110
+ shapeNote: 'Rows are under /data/tasks (an array); counts are /data/total and /data/filtered.',
111
+ dataSchema: {
112
+ type: 'object',
113
+ required: ['tasks', 'total', 'filtered'],
114
+ additionalProperties: true,
115
+ properties: {
116
+ tasks: {
117
+ type: 'array',
118
+ description: 'Task rows matching the filters.',
119
+ items: {
120
+ type: 'object',
121
+ properties: {
122
+ id: { type: 'string' },
123
+ title: { type: 'string' },
124
+ status: { type: 'string' },
125
+ },
126
+ },
127
+ },
128
+ total: { type: 'number', description: 'Total tasks before filtering.' },
129
+ filtered: { type: 'number', description: 'Number of tasks after filters applied.' },
130
+ },
131
+ },
132
+ fieldPointers: [
133
+ '/data/tasks/0/id',
134
+ '/data/tasks/0/title',
135
+ '/data/tasks/0/status',
136
+ '/data/total',
137
+ '/data/filtered',
138
+ ],
139
+ };
140
+
141
+ // ---------------------------------------------------------------------------
142
+ // tasks.find — { results: MinimalTask[], total, query, searchType }
143
+ // ---------------------------------------------------------------------------
144
+
145
+ /**
146
+ * OUTPUT contract for `tasks.find`.
147
+ *
148
+ * Grounded in the actual return shape from `findTasks()` (find.ts:503-505)
149
+ * and the MVI projection plan (mvi-projection.ts:330 `path: 'results'`):
150
+ * `{ results: MinimalTask[], total: number, query: string, searchType: string }`.
151
+ *
152
+ * The `data` payload is a **wrapper object** (NOT a bare array). Results live
153
+ * under `/data/results`; the total count is at `/data/total`.
154
+ */
155
+ const tasksFindOutputContract: OperationOutputContract = {
156
+ operation: 'tasks.find',
157
+ shapeNote:
158
+ 'Results are wrapped: /data/results (array of matches), /data/total (count). ' +
159
+ 'Use /data/results/0/id — NOT /data/0/id.',
160
+ dataSchema: {
161
+ type: 'object',
162
+ required: ['results', 'total'],
163
+ additionalProperties: true,
164
+ properties: {
165
+ results: {
166
+ type: 'array',
167
+ description: 'Array of minimal task matches.',
168
+ items: {
169
+ type: 'object',
170
+ required: ['id', 'title'],
171
+ properties: {
172
+ id: { type: 'string' },
173
+ title: { type: 'string' },
174
+ status: { type: 'string' },
175
+ },
176
+ },
177
+ },
178
+ total: { type: 'number', description: 'Total matching tasks.' },
179
+ query: { type: 'string', description: 'The query string that was searched.' },
180
+ searchType: { type: 'string', description: 'Kind of search performed (fts, semantic, ...).' },
181
+ },
182
+ },
183
+ fieldPointers: [
184
+ '/data/results/0/id',
185
+ '/data/results/0/title',
186
+ '/data/results/0/status',
187
+ '/data/total',
188
+ ],
189
+ };
190
+
191
+ // ---------------------------------------------------------------------------
192
+ // Mutation envelope shape (T9931 / T10608) — shared by add / add-batch /
193
+ // update / complete / delete.
194
+ //
195
+ // DEFAULT (minimal) shape — what `--field` resolves against — is
196
+ // `MinimalMutateEnvelope` from `mutate-projection.ts`:
197
+ // { count, created: string[], updated: string[], deleted: string[], ids: string[], ... }
198
+ //
199
+ // `created`, `updated`, and `deleted` contain BARE TASK ID STRINGS, not
200
+ // objects. `/data/created/0` resolves to a string like "T11692" directly.
201
+ // The object-array shape (`TaskMutationEnvelope`) is the `--full` shape only.
202
+ // ---------------------------------------------------------------------------
203
+
204
+ /**
205
+ * The shared JSON Schema for the minimal mutate projection envelope
206
+ * (`MinimalMutateEnvelope` — T9931 / mutate-projection.ts). `created`,
207
+ * `updated`, and `deleted` are always present arrays of **bare task ID strings**
208
+ * (not objects). The gateway populates only the relevant bucket. Pointers:
209
+ * - `/data/created/0` → first created task ID string (e.g. "T11692")
210
+ * - `/data/updated/0` → first updated task ID string
211
+ * - `/data/deleted/0` → first deleted task ID string
212
+ *
213
+ * This is the DEFAULT shape (applies unless caller passes `--full`).
214
+ */
215
+ const TASK_MUTATION_DATA_SCHEMA = {
216
+ type: 'object',
217
+ required: ['count', 'created', 'updated', 'deleted'],
218
+ additionalProperties: true,
219
+ properties: {
220
+ count: { type: 'number', description: 'Number of records the mutation affected.' },
221
+ created: {
222
+ type: 'array',
223
+ description:
224
+ 'Task IDs created by the mutation (bare strings, e.g. "T11692"). ' +
225
+ 'Empty for update/delete-only mutations.',
226
+ items: { type: 'string' },
227
+ },
228
+ updated: {
229
+ type: 'array',
230
+ description:
231
+ 'Task IDs updated by the mutation (bare strings). ' +
232
+ 'Empty for create/delete-only mutations.',
233
+ items: { type: 'string' },
234
+ },
235
+ deleted: {
236
+ type: 'array',
237
+ description:
238
+ 'Task IDs deleted by the mutation (bare strings). ' +
239
+ 'Empty for create/update-only mutations.',
240
+ items: { type: 'string' },
241
+ },
242
+ ids: {
243
+ type: 'array',
244
+ description: 'Deprecated alias for the non-empty bucket. Prefer created/updated/deleted.',
245
+ items: { type: 'string' },
246
+ },
247
+ dryRun: { type: 'boolean', description: 'True when this was a preview-only mutation.' },
248
+ status: { type: 'string', description: 'Post-mutation task status (add/update/complete).' },
249
+ },
250
+ } as const;
251
+
252
+ /**
253
+ * OUTPUT contract for `tasks.add`.
254
+ *
255
+ * Grounded in `MinimalMutateEnvelope` (mutate-projection.ts) — the created
256
+ * task ID lands as a bare string in `created[0]`.
257
+ * Use `/data/created/0` (a string like "T11692"), NOT `/data/created/0/id`.
258
+ */
259
+ const tasksAddOutputContract: OperationOutputContract = {
260
+ operation: 'tasks.add',
261
+ shapeNote:
262
+ 'The created task ID (bare string) is at /data/created/0 — NOT /data/created/0/id. ' +
263
+ 'Example: /data/created/0 → "T11692".',
264
+ dataSchema: { ...TASK_MUTATION_DATA_SCHEMA },
265
+ fieldPointers: ['/data/created/0', '/data/count'],
266
+ };
267
+
268
+ /**
269
+ * OUTPUT contract for `tasks.add-batch`.
270
+ *
271
+ * Grounded in `MinimalMutateEnvelope` (mutate-projection.ts). For dry-run,
272
+ * `wouldCreate` and `insertedCount` are projected to the **root** of the
273
+ * envelope data (NOT under `dryRunSummary`) — confirmed in
274
+ * mutate-projection.ts lines 224/232.
275
+ */
276
+ const tasksAddBatchOutputContract: OperationOutputContract = {
277
+ operation: 'tasks.add-batch',
278
+ shapeNote:
279
+ 'Atomic batch insert. Each created task ID (bare string) is in /data/created (array). ' +
280
+ 'Dry-run projections are at root: /data/wouldCreate and /data/insertedCount (=0). ' +
281
+ 'NOT under /data/dryRunSummary.',
282
+ dataSchema: {
283
+ type: 'object',
284
+ required: ['count', 'created', 'updated', 'deleted'],
285
+ additionalProperties: true,
286
+ properties: {
287
+ ...TASK_MUTATION_DATA_SCHEMA.properties,
288
+ wouldCreate: {
289
+ type: 'number',
290
+ description: 'Dry-run: predicted write count. Present only when dryRun=true.',
291
+ },
292
+ insertedCount: {
293
+ type: 'number',
294
+ description: 'Dry-run: always 0 (no DB write). Present only when dryRun=true.',
295
+ },
296
+ wouldAffect: {
297
+ type: 'number',
298
+ description: 'Dry-run: generic affected count. Present only when dryRun=true.',
299
+ },
300
+ },
301
+ },
302
+ fieldPointers: ['/data/created/0', '/data/count', '/data/wouldCreate', '/data/insertedCount'],
303
+ };
304
+
305
+ /**
306
+ * OUTPUT contract for `tasks.update` (and the `complete` alias, which is an
307
+ * update to `status: done`).
308
+ *
309
+ * Grounded in `MinimalMutateEnvelope` (mutate-projection.ts) — the changed
310
+ * task ID lands as a bare string in `updated[0]`.
311
+ * Use `/data/updated/0` (a string like "T11692"), NOT `/data/updated/0/id`.
312
+ */
313
+ const tasksUpdateOutputContract: OperationOutputContract = {
314
+ operation: 'tasks.update',
315
+ shapeNote:
316
+ 'The updated task ID (bare string) is at /data/updated/0 — NOT /data/updated/0/id. ' +
317
+ 'Use /data/status for the post-mutation status.',
318
+ dataSchema: { ...TASK_MUTATION_DATA_SCHEMA },
319
+ fieldPointers: ['/data/updated/0', '/data/status', '/data/count'],
320
+ };
321
+
322
+ /**
323
+ * OUTPUT contract for `tasks.complete`.
324
+ *
325
+ * Identical envelope to `tasks.update` — completion is a status mutation that
326
+ * lands the task ID in `updated[0]` as a bare string.
327
+ */
328
+ const tasksCompleteOutputContract: OperationOutputContract = {
329
+ operation: 'tasks.complete',
330
+ shapeNote:
331
+ 'Completion is a status mutation — the task ID (bare string) is at /data/updated/0 ' +
332
+ '(status=done). Use /data/status for the post-mutation status.',
333
+ dataSchema: { ...TASK_MUTATION_DATA_SCHEMA },
334
+ fieldPointers: ['/data/updated/0', '/data/status', '/data/count'],
335
+ };
336
+
337
+ // ---------------------------------------------------------------------------
338
+ // Registry
339
+ // ---------------------------------------------------------------------------
340
+
341
+ /**
342
+ * SSoT registry of per-operation OUTPUT contracts, keyed by the canonical
343
+ * `<domain>.<verb>` operation id. Extend this map as operations are migrated
344
+ * to the schema-first OUTPUT surface (high-traffic first).
345
+ *
346
+ * @task T11692
347
+ */
348
+ export const OUTPUT_CONTRACTS: OperationOutputContractRegistry = {
349
+ 'tasks.show': tasksShowOutputContract,
350
+ 'tasks.list': tasksListOutputContract,
351
+ 'tasks.find': tasksFindOutputContract,
352
+ 'tasks.add': tasksAddOutputContract,
353
+ 'tasks.add-batch': tasksAddBatchOutputContract,
354
+ 'tasks.update': tasksUpdateOutputContract,
355
+ 'tasks.complete': tasksCompleteOutputContract,
356
+ };
@@ -959,8 +959,18 @@ export interface TasksSyncLinksRemoveResult {
959
959
  export interface TasksCancelParams {
960
960
  taskId: string;
961
961
  reason?: string;
962
- /** Explicit child handling mode. Defaults to 'block' so propagation is never implicit. */
963
- children?: 'block' | 'cascade' | 'orphan';
962
+ /**
963
+ * Explicit child handling mode. Defaults to 'block' so propagation is never
964
+ * implicit. `reparent` moves the children under {@link reparentTo}; the
965
+ * legacy `orphan`/detach mode was removed by T11811 (it manufactured the
966
+ * exact orphan the containment guard rejects).
967
+ */
968
+ children?: 'block' | 'cascade' | 'reparent';
969
+ /**
970
+ * Target parent ID for the `reparent` strategy (T11811). REQUIRED when
971
+ * `children='reparent'`.
972
+ */
973
+ reparentTo?: string;
964
974
  /** Operator waiver for large cascade cancellation. */
965
975
  force?: boolean;
966
976
  /** Large-subtree cascade guard threshold. Defaults to 10 descendants. */
@@ -992,8 +1002,8 @@ export interface TasksCancelResult {
992
1002
  */
993
1003
  alreadyCancelled?: boolean;
994
1004
  /** Explicit child handling mode applied by the operation. */
995
- childStrategy?: 'block' | 'cascade' | 'orphan';
996
- /** Child or descendant tasks affected by cascade/orphan handling. */
1005
+ childStrategy?: 'block' | 'cascade' | 'reparent';
1006
+ /** Child or descendant tasks affected by cascade/reparent handling. */
997
1007
  affectedTasks?: string[];
998
1008
  /** Count of affected child/descendant tasks. */
999
1009
  affectedCount?: number;