@cleocode/contracts 2026.10.12 → 2026.10.13

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 (59) hide show
  1. package/dist/attachment.d.ts +5 -2
  2. package/dist/attachment.d.ts.map +1 -1
  3. package/dist/branch-lock.d.ts +3 -0
  4. package/dist/branch-lock.d.ts.map +1 -1
  5. package/dist/branch-lock.js.map +1 -1
  6. package/dist/config.d.ts +7 -0
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/dispatch/__tests__/operation-envelope-snapshots.test.js +8 -0
  9. package/dist/dispatch/__tests__/operation-envelope-snapshots.test.js.map +1 -1
  10. package/dist/dispatch/operations-registry.d.ts.map +1 -1
  11. package/dist/dispatch/operations-registry.js +84 -2
  12. package/dist/dispatch/operations-registry.js.map +1 -1
  13. package/dist/done-plan.d.ts +1 -1
  14. package/dist/done-plan.d.ts.map +1 -1
  15. package/dist/graph.d.ts +24 -4
  16. package/dist/graph.d.ts.map +1 -1
  17. package/dist/index.d.ts +4 -3
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/invariants/sync-write-invariants.d.ts.map +1 -1
  21. package/dist/invariants/sync-write-invariants.js +30 -4
  22. package/dist/invariants/sync-write-invariants.js.map +1 -1
  23. package/dist/lean.d.ts +34 -0
  24. package/dist/lean.d.ts.map +1 -0
  25. package/dist/lean.js +14 -0
  26. package/dist/lean.js.map +1 -0
  27. package/dist/lease-ipc/messages.d.ts +10 -10
  28. package/dist/ledger/index.d.ts +4 -4
  29. package/dist/operations/docs.d.ts +259 -6
  30. package/dist/operations/docs.d.ts.map +1 -1
  31. package/dist/operations/docs.js.map +1 -1
  32. package/dist/operations/worktree.d.ts +50 -0
  33. package/dist/operations/worktree.d.ts.map +1 -1
  34. package/dist/project-hooks.d.ts +1 -1
  35. package/dist/resource-governor.d.ts +6 -0
  36. package/dist/resource-governor.d.ts.map +1 -1
  37. package/dist/resource-governor.js +6 -0
  38. package/dist/resource-governor.js.map +1 -1
  39. package/dist/templates/manifest.d.ts +1 -1
  40. package/dist/tools/atomic.d.ts +5 -2
  41. package/dist/tools/atomic.d.ts.map +1 -1
  42. package/dist/tools/atomic.js.map +1 -1
  43. package/dist/workgraph.d.ts +4 -4
  44. package/package.json +2 -2
  45. package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
  46. package/src/attachment.ts +5 -2
  47. package/src/branch-lock.ts +4 -0
  48. package/src/config.ts +7 -0
  49. package/src/dispatch/__tests__/operation-envelope-snapshots.test.ts +8 -0
  50. package/src/dispatch/operations-registry.ts +89 -2
  51. package/src/done-plan.ts +1 -0
  52. package/src/graph.ts +25 -4
  53. package/src/index.ts +7 -0
  54. package/src/invariants/sync-write-invariants.ts +56 -29
  55. package/src/lean.ts +36 -0
  56. package/src/operations/docs.ts +266 -6
  57. package/src/operations/worktree.ts +49 -0
  58. package/src/resource-governor.ts +7 -0
  59. package/src/tools/atomic.ts +5 -2
package/src/attachment.ts CHANGED
@@ -177,7 +177,9 @@ export type AttachmentKind = Attachment['kind'];
177
177
 
178
178
  /**
179
179
  * Persisted metadata row for one attachment in the attachment registry
180
- * (`.cleo/attachments/index.db`).
180
+ * (the `attachments` table of the project `cleo.db`, twin-carried into
181
+ * `docs_attachments`; the legacy `.cleo/attachments/index.db` file is
182
+ * retired — T13362).
181
183
  *
182
184
  * The full {@link Attachment} value is stored as a JSON blob in the `attachment`
183
185
  * field so the registry does not need a column per variant.
@@ -217,7 +219,8 @@ export interface AttachmentMetadata {
217
219
  /**
218
220
  * Junction row linking one attachment to one owner.
219
221
  *
220
- * Stored in the `attachment_refs` table of `.cleo/attachments/index.db`.
222
+ * Stored in the `attachment_refs` table of the project `cleo.db`
223
+ * (twin-carried into `docs_attachment_refs`).
221
224
  * A single attachment can be referenced by multiple owners simultaneously
222
225
  * (e.g., the same RFC PDF attached to an epic and to every child task).
223
226
  */
@@ -17,6 +17,8 @@
17
17
  * @adr ADR-062
18
18
  */
19
19
 
20
+ import type { WorktreeDisposalBlockers } from './operations/worktree.js';
21
+
20
22
  // ---------------------------------------------------------------------------
21
23
  // L1 — Worktree lifecycle types
22
24
  // ---------------------------------------------------------------------------
@@ -85,6 +87,8 @@ export interface WorktreeMergeResult {
85
87
  rebased: boolean;
86
88
  /** Whether the worktree filesystem entry was removed. */
87
89
  worktreeRemoved: boolean;
90
+ /** Set when the worktree was kept because something still needs its files (T13696). */
91
+ worktreeKept?: WorktreeDisposalBlockers;
88
92
  /** Whether `task/<taskId>` was deleted post-merge. */
89
93
  branchDeleted: boolean;
90
94
  /** Error message if any step failed (non-fatal — caller decides). */
package/src/config.ts CHANGED
@@ -10,6 +10,7 @@
10
10
  */
11
11
 
12
12
  import type { HeavyCommandHookMode } from './heavy-command-hook.js';
13
+ import type { LeanConfig } from './lean.js';
13
14
  import type { ModelTransport } from './operations/llm.js';
14
15
 
15
16
  /** Output format options. */
@@ -895,6 +896,12 @@ export interface ResourcesConfig {
895
896
 
896
897
  /** CLEO project configuration (config.json). */
897
898
  export interface CleoConfig {
899
+ /**
900
+ * ct-lean rule level (T13663).
901
+ *
902
+ * @defaultValue undefined (level `full`)
903
+ */
904
+ lean?: LeanConfig;
898
905
  /** Configuration schema version string. */
899
906
  version: string;
900
907
  /** Output formatting preferences. */
@@ -112,6 +112,14 @@ describe('operation envelope registry snapshots (T10615)', () => {
112
112
  "required": false,
113
113
  "type": "string",
114
114
  },
115
+ {
116
+ "cli": {
117
+ "flag": "base-version-uid",
118
+ },
119
+ "name": "baseVersionUid",
120
+ "required": false,
121
+ "type": "string",
122
+ },
115
123
  {
116
124
  "cli": undefined,
117
125
  "name": "message",
@@ -5038,6 +5038,35 @@ export const OPERATIONS: OperationDef[] = [
5038
5038
  },
5039
5039
  ] satisfies ParamDef[],
5040
5040
  },
5041
+ {
5042
+ gateway: 'mutate',
5043
+ domain: 'pipeline',
5044
+ operation: 'manifest.update',
5045
+ description:
5046
+ 'pipeline.manifest.update (mutate) — change only the given fields of a manifest entry; linkTask adds a linked task (T13678)',
5047
+ tier: 1,
5048
+ idempotent: true,
5049
+ sessionRequired: false,
5050
+ requiredParams: ['id'],
5051
+ params: [
5052
+ { name: 'id', type: 'string', required: true, description: 'Manifest entry id' },
5053
+ {
5054
+ name: 'linkTask',
5055
+ type: 'string',
5056
+ required: false,
5057
+ description: 'Task id to add to linked_tasks',
5058
+ },
5059
+ { name: 'title', type: 'string', required: false, description: 'New title' },
5060
+ { name: 'topics', type: 'array', required: false, description: 'Replacement topics' },
5061
+ {
5062
+ name: 'key_findings',
5063
+ type: 'array',
5064
+ required: false,
5065
+ description: 'Replacement key findings',
5066
+ },
5067
+ { name: 'status', type: 'string', required: false, description: 'New status' },
5068
+ ] satisfies ParamDef[],
5069
+ },
5041
5070
  {
5042
5071
  gateway: 'mutate',
5043
5072
  domain: 'pipeline',
@@ -8690,6 +8719,22 @@ export const OPERATIONS: OperationDef[] = [
8690
8719
  },
8691
8720
  ],
8692
8721
  },
8722
+ // ── docs.stats (T13363 — Epic T13340 / Saga T13339) ───────────────────────
8723
+ {
8724
+ gateway: 'query',
8725
+ domain: 'docs',
8726
+ operation: 'stats',
8727
+ description:
8728
+ 'docs.stats (query) — per-project CleoDocs adoption metrics (read-only): add:update ' +
8729
+ 'ratio from the docs audit log, loose-markdown count with scanned/excluded dirs, ' +
8730
+ 'docs_wikilinks edge count, and live decision_adr_links count. Emitted for the ' +
8731
+ 'Council pilot baseline comparisons.',
8732
+ tier: 1,
8733
+ idempotent: true,
8734
+ sessionRequired: false,
8735
+ requiredParams: [],
8736
+ params: [],
8737
+ },
8693
8738
  {
8694
8739
  gateway: 'mutate',
8695
8740
  domain: 'docs',
@@ -8697,8 +8742,10 @@ export const OPERATIONS: OperationDef[] = [
8697
8742
  description:
8698
8743
  'docs.doctor (mutate) — apply the docs doctor repairs: set docVersion from the audit ' +
8699
8744
  'log, backfill topics/related_tasks from content, archive dangling local-file rows, ' +
8700
- 'rebuild docs_wikilinks. The dispatch layer creates a fresh backup (cleo backup add ' +
8701
- 'path) and requires its receipt before any write.',
8745
+ 'rebuild docs_wikilinks, and backfill hash-verified legacy bytes into the blob store. ' +
8746
+ 'The dispatch layer creates a fresh backup (cleo backup add ' +
8747
+ 'path) and requires its receipt before any write. repair:true restricts the run to ' +
8748
+ 'the legacy-blob-coverage backfill only (T13653).',
8702
8749
  tier: 1,
8703
8750
  idempotent: false,
8704
8751
  sessionRequired: false,
@@ -8710,6 +8757,14 @@ export const OPERATIONS: OperationDef[] = [
8710
8757
  required: false,
8711
8758
  description: 'Execute repairs (default false — dry-run plan only)',
8712
8759
  },
8760
+ {
8761
+ name: 'repair',
8762
+ type: 'boolean' as const,
8763
+ required: false,
8764
+ description:
8765
+ 'Execute ONLY the legacy-blob-coverage backfill: mirror hash-verified legacy ' +
8766
+ 'bytes into the blob store so unmigrated docs become readable (T13653)',
8767
+ },
8713
8768
  {
8714
8769
  name: 'olderThanDays',
8715
8770
  type: 'number' as const,
@@ -8718,6 +8773,24 @@ export const OPERATIONS: OperationDef[] = [
8718
8773
  },
8719
8774
  ],
8720
8775
  },
8776
+ // ── docs.migrate (T13360 — Epic T13340 / Saga T13339) ─────────────────────
8777
+ {
8778
+ gateway: 'mutate',
8779
+ domain: 'docs',
8780
+ operation: 'migrate',
8781
+ description:
8782
+ 'docs.migrate (mutate) — the T13360 legacy docs migration (spec §5): import legacy ' +
8783
+ 'docs + history into the re-keyed conduit store inside the T13448 safety harness ' +
8784
+ '(pre-step VACUUM INTO backup, before/after fingerprints, COMMIT only on full ' +
8785
+ 'verification). Imported rows are labeled origin:migrated; dangling-pointer docs are ' +
8786
+ 'content-captured or recorded dangling in conduit_doc_migration_ledger. Idempotent — ' +
8787
+ 'a re-run is a verified no-op.',
8788
+ tier: 1,
8789
+ idempotent: true,
8790
+ sessionRequired: false,
8791
+ requiredParams: [],
8792
+ params: [],
8793
+ },
8721
8794
  // ── docs.generate (T798) ─────────────────────────────────────────────────
8722
8795
  {
8723
8796
  gateway: 'query',
@@ -8826,6 +8899,20 @@ export const OPERATIONS: OperationDef[] = [
8826
8899
  required: false,
8827
8900
  description: 'Inline UTF-8 content (mutually exclusive with file)',
8828
8901
  },
8902
+ {
8903
+ name: 'baseVersionUid',
8904
+ type: 'string' as const,
8905
+ required: false,
8906
+ description:
8907
+ 'Expected base version uid — the metadata.headVersionUid from the `cleo docs fetch <slug>` envelope the edit was authored against (T13674). ' +
8908
+ 'When the stored head has moved past it: append-shaped section edits are three-way merged through the section CRDT (both writers survive); ' +
8909
+ 'a non-mergeable section edit is recorded as a named section conflict — nothing is silently dropped. ' +
8910
+ "A base that does not resolve to this doc's version history is refused with E_DOC_BASE_UNKNOWN before any write. " +
8911
+ 'Works with --content and with a UTF-8 text --file; a non-text --file with a base is refused with E_DOC_BASE_UNSUPPORTED. ' +
8912
+ 'The update result carries the new headVersionUid, so edits can chain without a re-fetch. ' +
8913
+ 'Omit only when the edit was not based on an earlier fetch (the current head is then assumed).',
8914
+ cli: { flag: 'base-version-uid' },
8915
+ },
8829
8916
  {
8830
8917
  name: 'message',
8831
8918
  type: 'string' as const,
package/src/done-plan.ts CHANGED
@@ -66,6 +66,7 @@ export type DonePlanBlockerCode =
66
66
  | 'tool-unresolved'
67
67
  | 'tool-failed'
68
68
  | 'typed-gate-failed'
69
+ | 'typed-gate-not-run'
69
70
  | 'test-run-needed'
70
71
  | 'qa-run-needed'
71
72
  | 'ac-mapping-needed'
package/src/graph.ts CHANGED
@@ -769,15 +769,36 @@ export interface GraphPublicationRows {
769
769
  generation?: string;
770
770
  /** Source provenance and coverage belonging to this generation. */
771
771
  assessment?: GraphIndexAssessment;
772
- /** Complete replacement node generation. */
773
- nodes: NexusNodeInsertRow[];
774
- /** Complete replacement relationship generation. */
775
- relations: NexusRelationInsertRow[];
772
+ /**
773
+ * Complete replacement node generation. May be serialized lazily as the
774
+ * publisher iterates it (once), so the graph is not held twice (T13656).
775
+ */
776
+ nodes: Iterable<NexusNodeInsertRow>;
777
+ /** Complete replacement relationship generation; may be lazy like `nodes`. */
778
+ relations: Iterable<NexusRelationInsertRow>;
776
779
  /**
777
780
  * Per-file extraction memo changes committed in the same transaction as the rows.
778
781
  * Absent when the producer does not maintain a parse cache.
779
782
  */
780
783
  parseCache?: GraphParseCacheUpdate;
784
+ /**
785
+ * The generation's retained references, already in their stored encoding
786
+ * (T13656). When present, `assessment.references` is absent and
787
+ * `assessment.referenceCount` equals `references.count`.
788
+ */
789
+ references?: GraphStoredReferences;
790
+ }
791
+
792
+ /**
793
+ * A reference list in its stored encoding: the JSON array, one reference per
794
+ * line, as concatenated gzip members (T13326). Producers stream references into
795
+ * it as they are reported, so the list is never held as objects (T13656).
796
+ */
797
+ export interface GraphStoredReferences {
798
+ /** Number of references encoded. */
799
+ count: number;
800
+ /** The compressed list, stored verbatim. */
801
+ payload: Uint8Array;
781
802
  }
782
803
 
783
804
  /**
package/src/index.ts CHANGED
@@ -774,6 +774,7 @@ export type {
774
774
  GraphPublicationRows,
775
775
  GraphRelation,
776
776
  GraphRelationType,
777
+ GraphStoredReferences,
777
778
  ImpactResult,
778
779
  InferredProvenance,
779
780
  KnowledgeGraph,
@@ -896,6 +897,8 @@ export {
896
897
  isLafsError,
897
898
  isLafsSuccess,
898
899
  } from './lafs.js';
900
+ // === ct-lean levels (T13663 / Epic T13418) ===
901
+ export type { LeanConfig, LeanLevel, LeanLevelSource } from './lean.js';
899
902
  // === Lease-IPC v1.1 (PARALLEL DbWriterLease contract — T11627 · SG-RUNTIME-UNIFICATION) ===
900
903
  export type {
901
904
  ChildKilledUnresponsiveResponse,
@@ -1357,6 +1360,8 @@ export type {
1357
1360
  export {
1358
1361
  DOCS_LIFECYCLE_STATUSES,
1359
1362
  type DocsLifecycleStatus,
1363
+ type DocsSectionConflict,
1364
+ type DocsSectionLeaseNotice,
1360
1365
  type DocsUpdateParams,
1361
1366
  } from './operations/docs.js';
1362
1367
  // === Ensures-schema Zod registry (T11762 ST-1 / T11900 — cantbook ensures.schema SSoT) ===
@@ -2028,11 +2033,13 @@ export type {
2028
2033
  PruneOrphanedWorktreesResult,
2029
2034
  PruneWorktreesOptions,
2030
2035
  PruneWorktreesResult,
2036
+ WorktreeDisposalBlockers,
2031
2037
  WorktreeHook,
2032
2038
  WorktreeHookResult,
2033
2039
  WorktreeIncludePattern,
2034
2040
  WorktreeInfo,
2035
2041
  WorktreeInstallOutcome,
2042
+ WorktreeKeepMarker,
2036
2043
  WorktreeLifecycleAction,
2037
2044
  WorktreeLifecycleAuditEntry,
2038
2045
  WorktreeListEntry,
@@ -462,21 +462,25 @@ export const SYNC_WRITE_INVARIANTS: readonly SyncWriteInvariant[] = Object.freez
462
462
  ['tasks_audit_log', 'audit_log'],
463
463
  ),
464
464
  ),
465
- rule(
466
- 'sticky.status.one-way',
467
- 'T12942',
468
- 'brain_sticky_notes',
469
- ['status', 'converted_to_json'],
470
- ['SK2', 'SK3', 'B24'],
471
- 'active→{converted, archived} is one-way; converted_to_json is write-once',
465
+ implemented(
466
+ rule(
467
+ 'sticky.status.one-way',
468
+ 'T12942',
469
+ 'brain_sticky_notes',
470
+ ['status', 'converted_to_json'],
471
+ ['SK2', 'SK3', 'B24'],
472
+ 'active→{converted, archived} is one-way; converted_to_json is write-once',
473
+ ),
472
474
  ),
473
- rule(
474
- 'release.status.absorbing',
475
- 'T12943',
476
- 'tasks_releases',
477
- ['status'],
478
- ['REL-2', 'REL-3', 'REL-4', 'REL-5', 'REL-6', 'REL-7', 'REL-8'],
479
- 'shipped release statuses are absorbing',
475
+ implemented(
476
+ rule(
477
+ 'release.status.absorbing',
478
+ 'T12943',
479
+ 'tasks_releases',
480
+ ['status'],
481
+ ['REL-2', 'REL-3', 'REL-4', 'REL-5', 'REL-6', 'REL-7', 'REL-8'],
482
+ 'shipped release statuses are absorbing',
483
+ ),
480
484
  ),
481
485
  implemented(
482
486
  rule(
@@ -489,13 +493,15 @@ export const SYNC_WRITE_INVARIANTS: readonly SyncWriteInvariant[] = Object.freez
489
493
  ['tasks_playbook_approvals', 'playbook_approvals'],
490
494
  ),
491
495
  ),
492
- rule(
493
- 'docs.lifecycle.transitions',
494
- 'T12945',
495
- 'docs_attachments',
496
- ['lifecycle_status'],
497
- ['D04', 'D05', 'D31', 'D32', 'D39', 'D50'],
498
- 'superseded is sticky; lifecycle transitions follow a matrix',
496
+ implemented(
497
+ rule(
498
+ 'docs.lifecycle.transitions',
499
+ 'T12945',
500
+ 'docs_attachments',
501
+ ['lifecycle_status'],
502
+ ['D04', 'D05', 'D31', 'D32', 'D39', 'D50'],
503
+ 'superseded is sticky; lifecycle transitions follow a matrix',
504
+ ),
499
505
  ),
500
506
  // T13354 (D11165) — CleoDocs re-keyed history family; the typed merge rules
501
507
  // land with the write path (T13355).
@@ -523,14 +529,35 @@ export const SYNC_WRITE_INVARIANTS: readonly SyncWriteInvariant[] = Object.freez
523
529
  [],
524
530
  'alias rows carry natural identity over (slug, doc_uid); identical aliases merge to one row',
525
531
  ),
526
- rule(
527
- 'docs.head.max-hlc',
528
- 'T13355',
529
- 'conduit_doc_heads',
530
- ['*'],
531
- [],
532
- 'the projection keeps the max-HLC head; a losing concurrent head is recorded (conflict_count/last_conflict_json), never silently shadowed',
533
- ),
532
+ // T13355 (D11165): the write-path behavior is implemented at the writer
533
+ // chokepoint (`upsertConduitDocHead`, now reachable from the docs update
534
+ // write path); cross-device convergence rides the Stage C journal merge
535
+ // engine (T12344) and needs no second rule.
536
+ {
537
+ id: 'docs.head.max-hlc',
538
+ class: 'monotonic-merge-rule',
539
+ tables: ['conduit_doc_heads'],
540
+ sites: [],
541
+ mergeRule: { table: 'conduit_doc_heads', columns: ['*'] },
542
+ inventory: [],
543
+ runtimeGate: {
544
+ module: 'packages/core/src/store/conduit-doc-history.ts',
545
+ functionName: 'upsertConduitDocHead',
546
+ },
547
+ reason:
548
+ 'the projection keeps the max-HLC head; a losing concurrent head is recorded (conflict_count/last_conflict_json), never silently shadowed',
549
+ },
550
+ // T13355 — the docs write path adopts a legacy doc into the conduit family
551
+ // once (identity shell keyed by the immutable uid); the read-back guard is
552
+ // a local write-confirmation, not a merge rule.
553
+ {
554
+ id: 'docs.doc.adopt-once',
555
+ class: 'not-sync-relevant',
556
+ tables: ['conduit_attachments'],
557
+ sites: [],
558
+ reason:
559
+ 'adopting a legacy doc inserts one immutable-uid identity shell; losing the write is refused loudly at the writer, not merged',
560
+ },
534
561
  // T13359 (D11165) — decisions-vs-ADRs link table; the natural composite key
535
562
  // makes identical links merge to one row, and the revoke is a one-way latch.
536
563
  // T13597: enforced at merge, not just by the local accessor — 'revoked' is an
package/src/lean.ts ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * ct-lean levels — shared types (T13663).
3
+ *
4
+ * The lean-change rules (the `ct-lean` skill, adapted from Ponytail 5.1.0,
5
+ * DietrichGebert, MIT) run at one of four levels. The level resolves from the
6
+ * `CLEO_LEAN_LEVEL` environment variable, then the session-scoped level set by
7
+ * `cleo lean level <l> --session`, then the `lean.level` config key (project
8
+ * over global), then `full`.
9
+ *
10
+ * @task T13663
11
+ * @epic T13418
12
+ */
13
+
14
+ /**
15
+ * How strictly the lean-change rules apply.
16
+ *
17
+ * - `off` — no lean rules are emitted.
18
+ * - `lite` — build what was asked; name the smaller option in one line.
19
+ * - `full` — DEFAULT. The smallest complete change (the full ct-lean rules).
20
+ * - `ultra` — `full`, and also question the request before building.
21
+ */
22
+ export type LeanLevel = 'off' | 'lite' | 'full' | 'ultra';
23
+
24
+ /** Where a resolved lean level came from. */
25
+ export type LeanLevelSource = 'env' | 'session' | 'config' | 'default';
26
+
27
+ /** The `lean` section of `.cleo/config.json`. */
28
+ export interface LeanConfig {
29
+ /**
30
+ * Default lean level for this project (or, in the global config, every
31
+ * project). `CLEO_LEAN_LEVEL` and a session-scoped level override it.
32
+ *
33
+ * @defaultValue 'full'
34
+ */
35
+ level?: LeanLevel;
36
+ }