@cleocode/contracts 2026.5.114 → 2026.5.121
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__/changesets.test.d.ts +8 -0
- package/dist/__tests__/changesets.test.d.ts.map +1 -0
- package/dist/__tests__/changesets.test.js +146 -0
- package/dist/__tests__/changesets.test.js.map +1 -0
- package/dist/__tests__/evidence-atom-schema.test.js +75 -1
- package/dist/__tests__/evidence-atom-schema.test.js.map +1 -1
- package/dist/__tests__/meta-tokens.test.d.ts +16 -0
- package/dist/__tests__/meta-tokens.test.d.ts.map +1 -0
- package/dist/__tests__/meta-tokens.test.js +99 -0
- package/dist/__tests__/meta-tokens.test.js.map +1 -0
- package/dist/boundary.js +3 -3
- package/dist/boundary.js.map +1 -1
- package/dist/changesets.d.ts +91 -0
- package/dist/changesets.d.ts.map +1 -1
- package/dist/changesets.js +72 -2
- package/dist/changesets.js.map +1 -1
- package/dist/dispatch/operations-registry.d.ts.map +1 -1
- package/dist/dispatch/operations-registry.js +148 -0
- package/dist/dispatch/operations-registry.js.map +1 -1
- package/dist/evidence-atom-schema.d.ts +52 -8
- package/dist/evidence-atom-schema.d.ts.map +1 -1
- package/dist/evidence-atom-schema.js +96 -0
- package/dist/evidence-atom-schema.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/operations/docs.d.ts +166 -8
- package/dist/operations/docs.d.ts.map +1 -1
- package/dist/operations/docs.js +9 -7
- package/dist/operations/docs.js.map +1 -1
- package/dist/operations/tasks.d.ts +39 -0
- package/dist/operations/tasks.d.ts.map +1 -1
- package/dist/operations/tasks.js.map +1 -1
- package/dist/task-record.d.ts +7 -0
- package/dist/task-record.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
- package/src/__tests__/changesets.test.ts +176 -0
- package/src/__tests__/evidence-atom-schema.test.ts +88 -0
- package/src/__tests__/meta-tokens.test.ts +107 -0
- package/src/boundary.ts +3 -3
- package/src/changesets.ts +93 -2
- package/src/dispatch/operations-registry.ts +155 -0
- package/src/evidence-atom-schema.ts +131 -7
- package/src/index.ts +2 -0
- package/src/operations/docs.ts +187 -9
- package/src/operations/tasks.ts +39 -0
- package/src/task-record.ts +7 -0
|
@@ -2335,6 +2335,41 @@ export const OPERATIONS: OperationDef[] = [
|
|
|
2335
2335
|
},
|
|
2336
2336
|
] satisfies ParamDef[],
|
|
2337
2337
|
},
|
|
2338
|
+
{
|
|
2339
|
+
gateway: 'mutate',
|
|
2340
|
+
domain: 'tasks',
|
|
2341
|
+
operation: 'relates.remove',
|
|
2342
|
+
description:
|
|
2343
|
+
'tasks.relates.remove (mutate) — delete a relation between two tasks. Pass `type` to scope deletion to a single relation kind; omit to remove any type.',
|
|
2344
|
+
tier: 1,
|
|
2345
|
+
idempotent: true,
|
|
2346
|
+
sessionRequired: false,
|
|
2347
|
+
requiredParams: ['taskId', 'relatedId'],
|
|
2348
|
+
params: [
|
|
2349
|
+
{
|
|
2350
|
+
name: 'taskId',
|
|
2351
|
+
type: 'string',
|
|
2352
|
+
required: true,
|
|
2353
|
+
description: 'Source task ID',
|
|
2354
|
+
cli: { positional: true },
|
|
2355
|
+
},
|
|
2356
|
+
{
|
|
2357
|
+
name: 'relatedId',
|
|
2358
|
+
type: 'string',
|
|
2359
|
+
required: true,
|
|
2360
|
+
description: 'Target task ID to remove the relation to',
|
|
2361
|
+
cli: { positional: true },
|
|
2362
|
+
},
|
|
2363
|
+
{
|
|
2364
|
+
name: 'type',
|
|
2365
|
+
type: 'string',
|
|
2366
|
+
required: false,
|
|
2367
|
+
description:
|
|
2368
|
+
'Optional relation type (blocks|related|duplicates|absorbs|fixes|extends|supersedes). Omit to remove any type.',
|
|
2369
|
+
cli: { flag: 'type' },
|
|
2370
|
+
},
|
|
2371
|
+
] satisfies ParamDef[],
|
|
2372
|
+
},
|
|
2338
2373
|
// === saga sub-domain (ADR-073 — above-epic grouping tier) ===
|
|
2339
2374
|
{
|
|
2340
2375
|
gateway: 'mutate',
|
|
@@ -6771,6 +6806,40 @@ export const OPERATIONS: OperationDef[] = [
|
|
|
6771
6806
|
},
|
|
6772
6807
|
],
|
|
6773
6808
|
},
|
|
6809
|
+
// ── docs.supersede (T10162) ──────────────────────────────────────────────
|
|
6810
|
+
{
|
|
6811
|
+
gateway: 'mutate',
|
|
6812
|
+
domain: 'docs',
|
|
6813
|
+
operation: 'supersede',
|
|
6814
|
+
description:
|
|
6815
|
+
'docs.supersede (mutate) — atomically flip an older doc to `superseded` and link both rows via the supersedes/superseded_by self-FK pointers (T10162 · Saga T9855)',
|
|
6816
|
+
tier: 1,
|
|
6817
|
+
idempotent: true,
|
|
6818
|
+
sessionRequired: false,
|
|
6819
|
+
requiredParams: ['oldSlug', 'newSlug'],
|
|
6820
|
+
params: [
|
|
6821
|
+
{
|
|
6822
|
+
name: 'oldSlug',
|
|
6823
|
+
type: 'string' as const,
|
|
6824
|
+
required: true,
|
|
6825
|
+
description: 'Slug of the doc being replaced',
|
|
6826
|
+
cli: { positional: true },
|
|
6827
|
+
},
|
|
6828
|
+
{
|
|
6829
|
+
name: 'newSlug',
|
|
6830
|
+
type: 'string' as const,
|
|
6831
|
+
required: true,
|
|
6832
|
+
description: 'Slug of the doc that replaces oldSlug',
|
|
6833
|
+
cli: { positional: true },
|
|
6834
|
+
},
|
|
6835
|
+
{
|
|
6836
|
+
name: 'reason',
|
|
6837
|
+
type: 'string' as const,
|
|
6838
|
+
required: false,
|
|
6839
|
+
description: 'Optional human-readable reason carried back on the response',
|
|
6840
|
+
},
|
|
6841
|
+
],
|
|
6842
|
+
},
|
|
6774
6843
|
// ── docs.generate (T798) ─────────────────────────────────────────────────
|
|
6775
6844
|
{
|
|
6776
6845
|
gateway: 'query',
|
|
@@ -6797,6 +6866,61 @@ export const OPERATIONS: OperationDef[] = [
|
|
|
6797
6866
|
},
|
|
6798
6867
|
],
|
|
6799
6868
|
},
|
|
6869
|
+
// ── docs.update (T10161 — Epic T10157 / Saga T9855) ──────────────────────
|
|
6870
|
+
{
|
|
6871
|
+
gateway: 'mutate',
|
|
6872
|
+
domain: 'docs',
|
|
6873
|
+
operation: 'update',
|
|
6874
|
+
description:
|
|
6875
|
+
'docs.update (mutate) — replace blob content for an existing slug while preserving the slug. ' +
|
|
6876
|
+
'New sha256 + new attachment row; the old row stays reachable by ID for history. ' +
|
|
6877
|
+
'NO supersession edge (use `cleo docs supersede` for that). ' +
|
|
6878
|
+
'Audit log entry appended to .cleo/audit/docs-versioning.jsonl (squashed within a 5-min window).',
|
|
6879
|
+
tier: 1,
|
|
6880
|
+
idempotent: false,
|
|
6881
|
+
sessionRequired: false,
|
|
6882
|
+
requiredParams: ['slug'],
|
|
6883
|
+
params: [
|
|
6884
|
+
{
|
|
6885
|
+
name: 'slug',
|
|
6886
|
+
type: 'string' as const,
|
|
6887
|
+
required: true,
|
|
6888
|
+
description: 'Slug of the existing attachment to update',
|
|
6889
|
+
},
|
|
6890
|
+
{
|
|
6891
|
+
name: 'file',
|
|
6892
|
+
type: 'string' as const,
|
|
6893
|
+
required: false,
|
|
6894
|
+
description: 'Path to a local file containing the new content',
|
|
6895
|
+
},
|
|
6896
|
+
{
|
|
6897
|
+
name: 'content',
|
|
6898
|
+
type: 'string' as const,
|
|
6899
|
+
required: false,
|
|
6900
|
+
description: 'Inline UTF-8 content (mutually exclusive with file)',
|
|
6901
|
+
},
|
|
6902
|
+
{
|
|
6903
|
+
name: 'message',
|
|
6904
|
+
type: 'string' as const,
|
|
6905
|
+
required: false,
|
|
6906
|
+
description: 'One-line summary describing the change (recorded in the audit log)',
|
|
6907
|
+
},
|
|
6908
|
+
{
|
|
6909
|
+
name: 'status',
|
|
6910
|
+
type: 'string' as const,
|
|
6911
|
+
required: false,
|
|
6912
|
+
description:
|
|
6913
|
+
'Override the new lifecycle status. Defaults to "draft" on every update. ' +
|
|
6914
|
+
'Valid: draft|proposed|accepted|superseded|archived|deprecated.',
|
|
6915
|
+
},
|
|
6916
|
+
{
|
|
6917
|
+
name: 'attachedBy',
|
|
6918
|
+
type: 'string' as const,
|
|
6919
|
+
required: false,
|
|
6920
|
+
description: 'Agent identity that performed the update (defaults to "human")',
|
|
6921
|
+
},
|
|
6922
|
+
],
|
|
6923
|
+
},
|
|
6800
6924
|
// ── playbook.* HITL CLI surface (T935) ───────────────────────────────────
|
|
6801
6925
|
{
|
|
6802
6926
|
gateway: 'mutate',
|
|
@@ -7505,6 +7629,37 @@ export const OPERATIONS: OperationDef[] = [
|
|
|
7505
7629
|
] satisfies ParamDef[],
|
|
7506
7630
|
},
|
|
7507
7631
|
|
|
7632
|
+
// release query: validate-changelog (T9937 / Saga T9862 — canonical CHANGELOG
|
|
7633
|
+
// header validator that replaces the brittle inline grep step in
|
|
7634
|
+
// .github/workflows/release.yml). Read-only — exposed only under the query
|
|
7635
|
+
// gateway because no DB or filesystem mutation occurs.
|
|
7636
|
+
{
|
|
7637
|
+
gateway: 'query',
|
|
7638
|
+
domain: 'release',
|
|
7639
|
+
operation: 'validate-changelog',
|
|
7640
|
+
description:
|
|
7641
|
+
'release.validate-changelog (query) — validate that CHANGELOG.md contains the canonical `## [VERSION]` header per ADR-028 §2.5. Replaces the brittle inline grep in .github/workflows/release.yml (T9937 / Saga T9862).',
|
|
7642
|
+
tier: 1,
|
|
7643
|
+
idempotent: true,
|
|
7644
|
+
sessionRequired: false,
|
|
7645
|
+
requiredParams: ['version'],
|
|
7646
|
+
params: [
|
|
7647
|
+
{
|
|
7648
|
+
name: 'version',
|
|
7649
|
+
type: 'string',
|
|
7650
|
+
required: true,
|
|
7651
|
+
description: 'Release version (accepts v2026.5.94 or 2026.5.94)',
|
|
7652
|
+
cli: { positional: true },
|
|
7653
|
+
},
|
|
7654
|
+
{
|
|
7655
|
+
name: 'changelogPath',
|
|
7656
|
+
type: 'string',
|
|
7657
|
+
required: false,
|
|
7658
|
+
description: 'Override the CHANGELOG file path (default: <projectRoot>/CHANGELOG.md)',
|
|
7659
|
+
},
|
|
7660
|
+
] satisfies ParamDef[],
|
|
7661
|
+
},
|
|
7662
|
+
|
|
7508
7663
|
// ---------------------------------------------------------------------------
|
|
7509
7664
|
// Provenance domain — `cleo provenance` CLI surface (T9528 · Phase 2 of T9493)
|
|
7510
7665
|
// ---------------------------------------------------------------------------
|
|
@@ -332,14 +332,25 @@ export const GATE_EVIDENCE_REQUIREMENTS: Readonly<
|
|
|
332
332
|
* Result of {@link validateEvidenceForGate}.
|
|
333
333
|
*
|
|
334
334
|
* Success carries no extra payload (the caller already holds the atom list).
|
|
335
|
-
* Failure carries
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
335
|
+
* Failure carries:
|
|
336
|
+
* - {@link EvidenceValidationFailure.message} — the legacy single-line
|
|
337
|
+
* `Gate '<gate>' requires evidence: [<combo>] OR [<combo>]` format,
|
|
338
|
+
* preserved byte-for-byte for backward-compat with surfaces and tests
|
|
339
|
+
* that match against it.
|
|
340
|
+
* - {@link EvidenceValidationFailure.hint} — a richer, multi-line,
|
|
341
|
+
* example-bearing remediation hint suitable for CLI `fix:` surfaces.
|
|
342
|
+
* Added by T9949 so `E_EVIDENCE_INSUFFICIENT` errors point clearly at
|
|
343
|
+
* the alternative atom the caller needs (e.g. "this gate requires
|
|
344
|
+
* real proof — note: alone is not accepted; use commit+files,
|
|
345
|
+
* commit+note, or pr instead").
|
|
339
346
|
*/
|
|
340
|
-
export
|
|
341
|
-
|
|
342
|
-
|
|
347
|
+
export interface EvidenceValidationFailure {
|
|
348
|
+
readonly ok: false;
|
|
349
|
+
readonly message: string;
|
|
350
|
+
readonly hint: string;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export type EvidenceValidationResult = { readonly ok: true } | EvidenceValidationFailure;
|
|
343
354
|
|
|
344
355
|
/**
|
|
345
356
|
* Format the satisfying-combinations spec for a gate as the legacy
|
|
@@ -354,6 +365,114 @@ export function formatGateRequirement(gate: VerificationGate): string {
|
|
|
354
365
|
return requirement.oneOf.map((set) => `[${set.join(' AND ')}]`).join(' OR ');
|
|
355
366
|
}
|
|
356
367
|
|
|
368
|
+
/**
|
|
369
|
+
* Example `--evidence` strings for each atom kind. Used by
|
|
370
|
+
* {@link formatGateRequirementHint} to render copy-pasteable remediation
|
|
371
|
+
* commands in CLI error surfaces.
|
|
372
|
+
*
|
|
373
|
+
* @task T9949
|
|
374
|
+
*/
|
|
375
|
+
const ATOM_EXAMPLES: Readonly<Record<EvidenceAtomKind, string>> = Object.freeze({
|
|
376
|
+
commit: 'commit:<sha>',
|
|
377
|
+
files: 'files:path/a.ts,path/b.ts',
|
|
378
|
+
'test-run': 'test-run:/tmp/vitest-out.json',
|
|
379
|
+
tool: 'tool:test',
|
|
380
|
+
url: 'url:https://example.com/docs',
|
|
381
|
+
note: 'note:<short description>',
|
|
382
|
+
decision: 'decision:D-arch-001',
|
|
383
|
+
pr: 'pr:357',
|
|
384
|
+
'loc-drop': 'loc-drop:<fromLines>:<toLines>',
|
|
385
|
+
'callsite-coverage': 'callsite-coverage:<symbolName>:<relativeSourcePath>',
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Render a satisfying atom combination as a copy-pasteable `--evidence`
|
|
390
|
+
* string fragment, e.g. `['commit', 'files']` → `commit:<sha>;files:path/a.ts,path/b.ts`.
|
|
391
|
+
*
|
|
392
|
+
* @internal
|
|
393
|
+
*/
|
|
394
|
+
function renderCombinationAsEvidence(combination: ReadonlyArray<EvidenceAtomKind>): string {
|
|
395
|
+
return combination.map((kind) => ATOM_EXAMPLES[kind]).join(';');
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Produce a multi-line, example-bearing remediation hint for a gate.
|
|
400
|
+
*
|
|
401
|
+
* Designed for CLI `fix:` surfaces so `E_EVIDENCE_INSUFFICIENT` errors point
|
|
402
|
+
* the caller at the specific alternative atom shape they need. The hint lists
|
|
403
|
+
* every satisfying combination as a copy-pasteable `cleo verify` invocation
|
|
404
|
+
* plus a note-specific clarification when the gate does NOT accept `note:`
|
|
405
|
+
* alone.
|
|
406
|
+
*
|
|
407
|
+
* The motivating bug (T9949) was that note-only deliverables (decision-only
|
|
408
|
+
* tasks, deletion-only refactors) hit `E_EVIDENCE_INSUFFICIENT` with the
|
|
409
|
+
* legacy single-line error and had to consult ADR-051 to figure out which
|
|
410
|
+
* additional atom kind the gate required. The richer hint inlines the answer.
|
|
411
|
+
*
|
|
412
|
+
* @param gate - Verification gate the hint is for.
|
|
413
|
+
* @returns Multi-line remediation hint suitable for an `engineError({fix:})`
|
|
414
|
+
* surface. Always ends in a clarifying sentence about whether `note:` alone
|
|
415
|
+
* is accepted for the gate.
|
|
416
|
+
*
|
|
417
|
+
* @example
|
|
418
|
+
* ```ts
|
|
419
|
+
* formatGateRequirementHint('implemented');
|
|
420
|
+
* // "Gate 'implemented' requires programmatic evidence. Use ONE of:
|
|
421
|
+
* // - cleo verify T#### --gate implemented --evidence 'commit:<sha>;files:path/a.ts,path/b.ts'
|
|
422
|
+
* // - cleo verify T#### --gate implemented --evidence 'commit:<sha>;note:<short description>'
|
|
423
|
+
* // - cleo verify T#### --gate implemented --evidence 'decision:D-arch-001;files:path/a.ts,path/b.ts'
|
|
424
|
+
* // - cleo verify T#### --gate implemented --evidence 'decision:D-arch-001;note:<short description>'
|
|
425
|
+
* // - cleo verify T#### --gate implemented --evidence 'pr:357'
|
|
426
|
+
* // Note: 'note:' alone is NOT accepted for this gate — pair it with 'commit:' or 'decision:'."
|
|
427
|
+
* ```
|
|
428
|
+
*
|
|
429
|
+
* @task T9949
|
|
430
|
+
* @adr ADR-051 §2.3
|
|
431
|
+
*/
|
|
432
|
+
export function formatGateRequirementHint(gate: VerificationGate): string {
|
|
433
|
+
const requirement = GATE_EVIDENCE_REQUIREMENTS[gate];
|
|
434
|
+
if (!requirement) {
|
|
435
|
+
// Unknown gate — match validateEvidenceForGate's defensive default.
|
|
436
|
+
return `Gate '${gate}' has no documented evidence requirements.`;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const lines: string[] = [
|
|
440
|
+
`Gate '${gate}' requires programmatic evidence. Use ONE of:`,
|
|
441
|
+
...requirement.oneOf.map(
|
|
442
|
+
(combo) =>
|
|
443
|
+
` - cleo verify T#### --gate ${gate} --evidence '${renderCombinationAsEvidence(combo)}'`,
|
|
444
|
+
),
|
|
445
|
+
];
|
|
446
|
+
|
|
447
|
+
// Note-specific clarification — T9949's headline pain point.
|
|
448
|
+
// The gate accepts note: alone IFF some single-element combination is ['note'].
|
|
449
|
+
const noteAloneAccepted = requirement.oneOf.some(
|
|
450
|
+
(combo) => combo.length === 1 && combo[0] === 'note',
|
|
451
|
+
);
|
|
452
|
+
if (noteAloneAccepted) {
|
|
453
|
+
lines.push(`Note: 'note:' alone IS accepted for this gate (waiver-style evidence).`);
|
|
454
|
+
} else {
|
|
455
|
+
// Surface every alternative pairing involving note for at-a-glance routing.
|
|
456
|
+
const notePairings = requirement.oneOf.filter(
|
|
457
|
+
(combo) => combo.length > 1 && combo.includes('note'),
|
|
458
|
+
);
|
|
459
|
+
if (notePairings.length > 0) {
|
|
460
|
+
const partners = Array.from(
|
|
461
|
+
new Set(notePairings.flatMap((combo) => combo.filter((k) => k !== 'note'))),
|
|
462
|
+
).map((k) => `'${k}:'`);
|
|
463
|
+
lines.push(
|
|
464
|
+
`Note: 'note:' alone is NOT accepted for this gate — pair it with ${partners.join(' or ')}.`,
|
|
465
|
+
);
|
|
466
|
+
} else {
|
|
467
|
+
lines.push(
|
|
468
|
+
`Note: 'note:' is NOT accepted for this gate (even when paired). See ADR-051 for the full grammar.`,
|
|
469
|
+
);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
return lines.join('\n');
|
|
474
|
+
}
|
|
475
|
+
|
|
357
476
|
/**
|
|
358
477
|
* Check whether a set of parsed atoms satisfies the requirement spec for
|
|
359
478
|
* `gate`.
|
|
@@ -401,6 +520,11 @@ export function validateEvidenceForGate(
|
|
|
401
520
|
return {
|
|
402
521
|
ok: false,
|
|
403
522
|
message: `Gate '${gate}' requires evidence: ${formatGateRequirement(gate)}`,
|
|
523
|
+
// T9949: rich, example-bearing remediation hint suitable for CLI `fix:`.
|
|
524
|
+
// Always populated alongside `message` so consumers can choose between
|
|
525
|
+
// the legacy single-line surface and the multi-line hint without re-
|
|
526
|
+
// computing the spec.
|
|
527
|
+
hint: formatGateRequirementHint(gate),
|
|
404
528
|
};
|
|
405
529
|
}
|
|
406
530
|
|
package/src/index.ts
CHANGED
|
@@ -475,6 +475,7 @@ export {
|
|
|
475
475
|
export type {
|
|
476
476
|
EvidenceAtom as EvidenceAtomInput,
|
|
477
477
|
EvidenceAtomKind,
|
|
478
|
+
EvidenceValidationFailure,
|
|
478
479
|
EvidenceValidationResult,
|
|
479
480
|
GateEvidenceRequirement,
|
|
480
481
|
} from './evidence-atom-schema.js';
|
|
@@ -486,6 +487,7 @@ export {
|
|
|
486
487
|
EvidenceParseError,
|
|
487
488
|
filesAtomSchema,
|
|
488
489
|
formatGateRequirement,
|
|
490
|
+
formatGateRequirementHint,
|
|
489
491
|
GATE_EVIDENCE_REQUIREMENTS,
|
|
490
492
|
locDropAtomSchema,
|
|
491
493
|
noteAtomSchema,
|
package/src/operations/docs.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Docs Domain Operations Contract (
|
|
2
|
+
* Docs Domain Operations Contract (7 operations)
|
|
3
3
|
*
|
|
4
4
|
* Query operations: 3
|
|
5
|
-
* Mutate operations:
|
|
5
|
+
* Mutate operations: 4
|
|
6
6
|
*
|
|
7
7
|
* Docs domain handles attachment management and document generation via `cleo docs`:
|
|
8
|
-
* - add
|
|
9
|
-
* - list
|
|
10
|
-
* - generate
|
|
11
|
-
* - fetch
|
|
12
|
-
* - remove
|
|
8
|
+
* - add — attach a local file or URL to a CLEO owner entity (task, session, observation)
|
|
9
|
+
* - list — list attachments for an owner entity
|
|
10
|
+
* - generate — generate llms.txt summary for a task/epic, optionally attach as blob
|
|
11
|
+
* - fetch — retrieve attachment bytes and metadata by ID or SHA-256
|
|
12
|
+
* - remove — remove an attachment ref; purges blob when refCount hits zero
|
|
13
|
+
* - update — UPDATE-in-place via slug (T10161)
|
|
14
|
+
* - supersede — atomically flip an older doc to `superseded` and link it to its successor (T10162)
|
|
13
15
|
*
|
|
14
16
|
* Owner type is auto-detected from the owner ID prefix:
|
|
15
17
|
* T### → 'task'
|
|
@@ -398,6 +400,15 @@ export interface DocsAddParams {
|
|
|
398
400
|
* @task T9636 (T-DOCS-SLUG-1)
|
|
399
401
|
*/
|
|
400
402
|
slug?: string;
|
|
403
|
+
/**
|
|
404
|
+
* Optional human-readable title — used to derive the kebab-slug tail when
|
|
405
|
+
* auto-allocating an ADR slug. REQUIRED when `type === 'adr'` AND `slug`
|
|
406
|
+
* is omitted; ignored otherwise. The allocator slugifies the title via
|
|
407
|
+
* the shared kebabize helper.
|
|
408
|
+
*
|
|
409
|
+
* @task T10360 (closes T10153)
|
|
410
|
+
*/
|
|
411
|
+
title?: string;
|
|
401
412
|
/**
|
|
402
413
|
* Optional taxonomy classification. Must match one of
|
|
403
414
|
* {@link DOCS_TYPE_VALUES}; an invalid value returns `E_INVALID_TYPE`.
|
|
@@ -447,6 +458,13 @@ export interface DocsAddResult {
|
|
|
447
458
|
slug?: string;
|
|
448
459
|
/** Type classification recorded for this attachment, when provided (T9637). */
|
|
449
460
|
type?: DocsType;
|
|
461
|
+
/**
|
|
462
|
+
* Auto-allocated ADR number, present iff the slug was minted by
|
|
463
|
+
* {@link allocateAdrSlug} (i.e. `--type adr` without `--slug`).
|
|
464
|
+
*
|
|
465
|
+
* @task T10360
|
|
466
|
+
*/
|
|
467
|
+
adrNumber?: number;
|
|
450
468
|
}
|
|
451
469
|
|
|
452
470
|
// --------------------------------------------------------------------------
|
|
@@ -481,6 +499,157 @@ export interface DocsRemoveResult {
|
|
|
481
499
|
attachmentBackend?: AttachmentBackend;
|
|
482
500
|
}
|
|
483
501
|
|
|
502
|
+
// --------------------------------------------------------------------------
|
|
503
|
+
// docs.update — UPDATE-in-place via slug (T10161)
|
|
504
|
+
// --------------------------------------------------------------------------
|
|
505
|
+
|
|
506
|
+
/**
|
|
507
|
+
* Allowed `lifecycle_status` values mirrored from the attachments-table enum
|
|
508
|
+
* (`ATTACHMENT_LIFECYCLE_STATUSES` in
|
|
509
|
+
* `packages/core/src/store/schema/attachments.ts`). Kept inline here so the
|
|
510
|
+
* contract surface stays self-contained — the dispatch handler narrows raw
|
|
511
|
+
* `--status` input against this set before touching the store.
|
|
512
|
+
*
|
|
513
|
+
* @task T10161 (Epic T10157 / Saga T9855)
|
|
514
|
+
*/
|
|
515
|
+
export type DocsLifecycleStatus =
|
|
516
|
+
| 'draft'
|
|
517
|
+
| 'proposed'
|
|
518
|
+
| 'accepted'
|
|
519
|
+
| 'superseded'
|
|
520
|
+
| 'archived'
|
|
521
|
+
| 'deprecated';
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Parameters for `docs.update`.
|
|
525
|
+
*
|
|
526
|
+
* Replaces the blob content for an existing slug — the slug is transferred
|
|
527
|
+
* from the old `attachments` row to a new row carrying the new sha256, and
|
|
528
|
+
* the old row remains reachable by attachment-id for history. Unlike
|
|
529
|
+
* {@link DocsSupersedeParams}-style flows, NO supersession edge is created
|
|
530
|
+
* (callers wanting an explicit lineage edge should use `cleo docs supersede`).
|
|
531
|
+
*
|
|
532
|
+
* Exactly one of `file` or `content` MUST be provided.
|
|
533
|
+
*
|
|
534
|
+
* @task T10161 (Epic T10157 / Saga T9855 — E12.C4)
|
|
535
|
+
*/
|
|
536
|
+
export interface DocsUpdateParams {
|
|
537
|
+
/** Slug of the existing attachment to update (required). */
|
|
538
|
+
slug: string;
|
|
539
|
+
/** Path to a local file containing the new content. */
|
|
540
|
+
file?: string;
|
|
541
|
+
/** Inline UTF-8 content (mutually exclusive with `file`). */
|
|
542
|
+
content?: string;
|
|
543
|
+
/** Optional one-line summary describing the change (recorded in the audit log). */
|
|
544
|
+
message?: string;
|
|
545
|
+
/**
|
|
546
|
+
* Override the new lifecycle status. Defaults to `'draft'` on every update
|
|
547
|
+
* so an explicit `accepted` doc gets back-pressured to draft on edit. Pass
|
|
548
|
+
* `--status accepted` (or any other valid status) to override.
|
|
549
|
+
*/
|
|
550
|
+
status?: DocsLifecycleStatus;
|
|
551
|
+
/** Agent identity that performed the update (default: `'human'`). */
|
|
552
|
+
attachedBy?: string;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Result of `docs.update`.
|
|
557
|
+
*
|
|
558
|
+
* Identifies the new row (active) and the row it replaced. When the supplied
|
|
559
|
+
* content is byte-identical to the current content the operation is a noop —
|
|
560
|
+
* `previousSha256` will equal `sha256` and `changed` will be `false`.
|
|
561
|
+
*
|
|
562
|
+
* @task T10161
|
|
563
|
+
*/
|
|
564
|
+
export interface DocsUpdateResult {
|
|
565
|
+
/** Slug that now points at the new content. */
|
|
566
|
+
slug: string;
|
|
567
|
+
/** Type/kind of the row, preserved from the prior version. */
|
|
568
|
+
type?: DocsType;
|
|
569
|
+
/** New attachment ID now bearing the slug. */
|
|
570
|
+
attachmentId: string;
|
|
571
|
+
/** Previous attachment ID; retains its row + bytes for history. */
|
|
572
|
+
previousAttachmentId: string;
|
|
573
|
+
/** SHA-256 hex of the new content. */
|
|
574
|
+
sha256: string;
|
|
575
|
+
/** SHA-256 hex of the prior content (equals `sha256` on a noop). */
|
|
576
|
+
previousSha256: string;
|
|
577
|
+
/** Whether the bytes actually changed (false ⇒ noop). */
|
|
578
|
+
changed: boolean;
|
|
579
|
+
/** Lifecycle status now stored on the new row. */
|
|
580
|
+
lifecycleStatus: DocsLifecycleStatus;
|
|
581
|
+
/** ISO 8601 timestamp the new row was registered. */
|
|
582
|
+
updatedAt: string;
|
|
583
|
+
/**
|
|
584
|
+
* Best-effort 1-indexed version number for the slug (count of historical
|
|
585
|
+
* rows that ever carried this slug, including the current one). When the
|
|
586
|
+
* underlying store cannot enumerate history this falls back to `2` for the
|
|
587
|
+
* first update and increments by 1 thereafter.
|
|
588
|
+
*/
|
|
589
|
+
version: number;
|
|
590
|
+
/**
|
|
591
|
+
* True when this update was squashed into an existing audit entry within
|
|
592
|
+
* the 5-minute squash window (no new audit line was written).
|
|
593
|
+
*/
|
|
594
|
+
squashed: boolean;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// --------------------------------------------------------------------------
|
|
598
|
+
// docs.supersede — flip an old doc to `superseded` and point at its successor
|
|
599
|
+
// --------------------------------------------------------------------------
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Parameters for `docs.supersede`.
|
|
603
|
+
*
|
|
604
|
+
* Both `oldSlug` and `newSlug` reference rows on `attachments.slug`. The
|
|
605
|
+
* operation is atomic: either both rows update (and the lifecycle flip lands)
|
|
606
|
+
* or neither does.
|
|
607
|
+
*
|
|
608
|
+
* @task T10162 (Saga T9855 · Epic T10157 · ADR-078)
|
|
609
|
+
*/
|
|
610
|
+
export interface DocsSupersedeParams {
|
|
611
|
+
/** Slug of the doc being replaced. */
|
|
612
|
+
oldSlug: string;
|
|
613
|
+
/** Slug of the doc that replaces {@link oldSlug}. */
|
|
614
|
+
newSlug: string;
|
|
615
|
+
/**
|
|
616
|
+
* Optional human-readable reason surfaced on the response envelope and
|
|
617
|
+
* preserved by future provenance-graph reads (T10166 `summary` field on
|
|
618
|
+
* the `supersedes` edge). Not persisted to a dedicated column today.
|
|
619
|
+
*/
|
|
620
|
+
reason?: string;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
/**
|
|
624
|
+
* Result of `docs.supersede`.
|
|
625
|
+
*
|
|
626
|
+
* @task T10162 (Saga T9855 · Epic T10157 · ADR-078)
|
|
627
|
+
*/
|
|
628
|
+
export interface DocsSupersedeResult {
|
|
629
|
+
/** Slug of the doc that was just superseded. */
|
|
630
|
+
oldSlug: string;
|
|
631
|
+
/** Slug of the new doc that supersedes {@link oldSlug}. */
|
|
632
|
+
newSlug: string;
|
|
633
|
+
/** Attachment ID resolved from {@link oldSlug}. */
|
|
634
|
+
oldAttachmentId: string;
|
|
635
|
+
/** Attachment ID resolved from {@link newSlug}. */
|
|
636
|
+
newAttachmentId: string;
|
|
637
|
+
/** ISO-8601 timestamp the supersession was recorded. */
|
|
638
|
+
supersededAt: string;
|
|
639
|
+
/**
|
|
640
|
+
* Stable identifier for the lineage edge minted by this call.
|
|
641
|
+
*
|
|
642
|
+
* The edge itself is not stored in a dedicated table — it is reconstructed
|
|
643
|
+
* at read time from `attachments.supersedes` / `attachments.superseded_by`
|
|
644
|
+
* by `cleo docs provenance` (T10166). The ID is deterministic
|
|
645
|
+
* (`supersedes:<newAttachmentId>->&<oldAttachmentId>`) so concurrent callers
|
|
646
|
+
* that win the transaction race observe a stable value.
|
|
647
|
+
*/
|
|
648
|
+
edgeId: string;
|
|
649
|
+
/** Optional reason carried through from the request. */
|
|
650
|
+
reason?: string;
|
|
651
|
+
}
|
|
652
|
+
|
|
484
653
|
// ============================================================================
|
|
485
654
|
// Discriminated Union (DocsOps)
|
|
486
655
|
// ============================================================================
|
|
@@ -500,7 +669,9 @@ export type DocsOps =
|
|
|
500
669
|
| { op: 'docs.fetch'; params: DocsFetchParams; result: DocsFetchResult }
|
|
501
670
|
| { op: 'docs.generate'; params: DocsGenerateParams; result: DocsGenerateResult }
|
|
502
671
|
| { op: 'docs.add'; params: DocsAddParams; result: DocsAddResult }
|
|
503
|
-
| { op: 'docs.remove'; params: DocsRemoveParams; result: DocsRemoveResult }
|
|
672
|
+
| { op: 'docs.remove'; params: DocsRemoveParams; result: DocsRemoveResult }
|
|
673
|
+
| { op: 'docs.update'; params: DocsUpdateParams; result: DocsUpdateResult }
|
|
674
|
+
| { op: 'docs.supersede'; params: DocsSupersedeParams; result: DocsSupersedeResult };
|
|
504
675
|
|
|
505
676
|
/**
|
|
506
677
|
* Enumeration of all docs domain operation names.
|
|
@@ -509,4 +680,11 @@ export type DocsOps =
|
|
|
509
680
|
* Useful for dynamic operation dispatch, type narrowing, or documentation.
|
|
510
681
|
* Kept in sync with the `DocsOps` discriminated union above.
|
|
511
682
|
*/
|
|
512
|
-
export type DocsOp =
|
|
683
|
+
export type DocsOp =
|
|
684
|
+
| 'docs.list'
|
|
685
|
+
| 'docs.fetch'
|
|
686
|
+
| 'docs.generate'
|
|
687
|
+
| 'docs.add'
|
|
688
|
+
| 'docs.remove'
|
|
689
|
+
| 'docs.update'
|
|
690
|
+
| 'docs.supersede';
|
package/src/operations/tasks.ts
CHANGED
|
@@ -125,6 +125,45 @@ export interface TasksFindParams {
|
|
|
125
125
|
* @task T9072
|
|
126
126
|
*/
|
|
127
127
|
kind?: string;
|
|
128
|
+
/**
|
|
129
|
+
* Unified urgency surface (T9905).
|
|
130
|
+
*
|
|
131
|
+
* When `true`, the predicate is
|
|
132
|
+
*
|
|
133
|
+
* `priority IN ('critical','high') OR severity IN ('P0','P1')`
|
|
134
|
+
*
|
|
135
|
+
* combining the two orthogonal urgency axes (priority + severity) into a
|
|
136
|
+
* single filter. Composes with other filters via AND.
|
|
137
|
+
*
|
|
138
|
+
* @task T9905
|
|
139
|
+
*/
|
|
140
|
+
urgent?: boolean;
|
|
141
|
+
/**
|
|
142
|
+
* Filter by label — selects tasks whose `labels` array contains this
|
|
143
|
+
* value. Composes with other filters via AND.
|
|
144
|
+
*
|
|
145
|
+
* Closes GH#393 — gives `cleo find --label <name>` parity with the
|
|
146
|
+
* positional `cleo labels <name>` surface.
|
|
147
|
+
*
|
|
148
|
+
* @task T9904
|
|
149
|
+
*/
|
|
150
|
+
label?: string;
|
|
151
|
+
/**
|
|
152
|
+
* Filter by parent task ID — restricts the result set to tasks whose
|
|
153
|
+
* `parentId` equals this value. Mirrors the `--parent` axis on
|
|
154
|
+
* `cleo list`. When the parent task is a Saga (Epic with
|
|
155
|
+
* `label='saga'`), routing goes through `task_relations.type='groups'`
|
|
156
|
+
* member IDs instead of the `parentId` column (ADR-073 §1).
|
|
157
|
+
*
|
|
158
|
+
* Composes with other filters via AND. Closes T10108 — pre-fix,
|
|
159
|
+
* `cleo find "" --parent <id>` returned every task in the project
|
|
160
|
+
* because the empty-string query bypassed all filters via
|
|
161
|
+
* `fuzzyScore('', '<title>')===80`.
|
|
162
|
+
*
|
|
163
|
+
* @task T10108
|
|
164
|
+
* @saga T9862
|
|
165
|
+
*/
|
|
166
|
+
parent?: string;
|
|
128
167
|
}
|
|
129
168
|
export type TasksFindResult = MinimalTask[];
|
|
130
169
|
|
package/src/task-record.ts
CHANGED
|
@@ -103,4 +103,11 @@ export interface MinimalTaskRecord {
|
|
|
103
103
|
type?: string;
|
|
104
104
|
/** Scope size estimate — helps agents decide if decomposition is needed. @task T091 */
|
|
105
105
|
size?: string;
|
|
106
|
+
/**
|
|
107
|
+
* Bug severity axis — P0|P1|P2|P3 (string-widened). Surfaced so the unified
|
|
108
|
+
* urgency surface (`cleo find --urgent`) can identify P0/P1 rows without a
|
|
109
|
+
* follow-up `cleo show` per row.
|
|
110
|
+
* @task T9905
|
|
111
|
+
*/
|
|
112
|
+
severity?: string | null;
|
|
106
113
|
}
|