@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.
- package/dist/__tests__/operation-def.test.js.map +1 -1
- package/dist/dispatch/operation-def.d.ts +42 -0
- package/dist/dispatch/operation-def.d.ts.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/llm/provider-profile.d.ts +54 -0
- package/dist/llm/provider-profile.d.ts.map +1 -1
- package/dist/operations/__tests__/output-contracts.test.d.ts +16 -0
- package/dist/operations/__tests__/output-contracts.test.d.ts.map +1 -0
- package/dist/operations/__tests__/output-contracts.test.js +103 -0
- package/dist/operations/__tests__/output-contracts.test.js.map +1 -0
- package/dist/operations/output-contract.d.ts +127 -0
- package/dist/operations/output-contract.d.ts.map +1 -0
- package/dist/operations/output-contract.js +41 -0
- package/dist/operations/output-contract.js.map +1 -0
- package/dist/operations/output-contracts-data.d.ts +35 -0
- package/dist/operations/output-contracts-data.d.ts.map +1 -0
- package/dist/operations/output-contracts-data.js +328 -0
- package/dist/operations/output-contracts-data.js.map +1 -0
- package/dist/operations/tasks.d.ts +14 -4
- package/dist/operations/tasks.d.ts.map +1 -1
- package/dist/operations/tasks.js.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/operation-def.test.ts +5 -0
- package/src/dispatch/operation-def.ts +44 -0
- package/src/index.ts +9 -0
- package/src/llm/provider-profile.ts +59 -0
- package/src/operations/__tests__/output-contracts.test.ts +125 -0
- package/src/operations/output-contract.ts +140 -0
- package/src/operations/output-contracts-data.ts +356 -0
- 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
|
+
};
|
package/src/operations/tasks.ts
CHANGED
|
@@ -959,8 +959,18 @@ export interface TasksSyncLinksRemoveResult {
|
|
|
959
959
|
export interface TasksCancelParams {
|
|
960
960
|
taskId: string;
|
|
961
961
|
reason?: string;
|
|
962
|
-
/**
|
|
963
|
-
|
|
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' | '
|
|
996
|
-
/** Child or descendant tasks affected by cascade/
|
|
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;
|