@cleocode/contracts 2026.9.20 → 2026.9.22

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 (165) hide show
  1. package/README.md +1 -1
  2. package/dist/__tests__/decide.test.d.ts +7 -0
  3. package/dist/__tests__/decide.test.d.ts.map +1 -0
  4. package/dist/__tests__/decide.test.js +91 -0
  5. package/dist/__tests__/decide.test.js.map +1 -0
  6. package/dist/__tests__/evidence-atom-schema.test.js +19 -1
  7. package/dist/__tests__/evidence-atom-schema.test.js.map +1 -1
  8. package/dist/__tests__/memory-wire-shapes.test.js.map +1 -1
  9. package/dist/acceptance-gate-schema.d.ts +5 -0
  10. package/dist/acceptance-gate-schema.d.ts.map +1 -1
  11. package/dist/acceptance-gate-schema.js +26 -0
  12. package/dist/acceptance-gate-schema.js.map +1 -1
  13. package/dist/acceptance-gate.d.ts +70 -3
  14. package/dist/acceptance-gate.d.ts.map +1 -1
  15. package/dist/backup-manifest.d.ts +13 -2
  16. package/dist/backup-manifest.d.ts.map +1 -1
  17. package/dist/branch-lock.d.ts +11 -0
  18. package/dist/branch-lock.d.ts.map +1 -1
  19. package/dist/branch-lock.js +31 -4
  20. package/dist/branch-lock.js.map +1 -1
  21. package/dist/cloud/api.d.ts +607 -0
  22. package/dist/cloud/api.d.ts.map +1 -0
  23. package/dist/cloud/api.js +417 -0
  24. package/dist/cloud/api.js.map +1 -0
  25. package/dist/cloud/envelope.d.ts +84 -0
  26. package/dist/cloud/envelope.d.ts.map +1 -0
  27. package/dist/cloud/envelope.js +54 -0
  28. package/dist/cloud/envelope.js.map +1 -0
  29. package/dist/cloud/ids.d.ts +30 -0
  30. package/dist/cloud/ids.d.ts.map +1 -0
  31. package/dist/cloud/ids.js +32 -0
  32. package/dist/cloud/ids.js.map +1 -0
  33. package/dist/cloud/index.d.ts +17 -0
  34. package/dist/cloud/index.d.ts.map +1 -0
  35. package/dist/cloud/index.js +17 -0
  36. package/dist/cloud/index.js.map +1 -0
  37. package/dist/data-accessor.d.ts +23 -3
  38. package/dist/data-accessor.d.ts.map +1 -1
  39. package/dist/data-accessor.js.map +1 -1
  40. package/dist/decide.d.ts +333 -0
  41. package/dist/decide.d.ts.map +1 -0
  42. package/dist/decide.js +184 -0
  43. package/dist/decide.js.map +1 -0
  44. package/dist/dispatch/operations-registry.d.ts.map +1 -1
  45. package/dist/dispatch/operations-registry.js +56 -0
  46. package/dist/dispatch/operations-registry.js.map +1 -1
  47. package/dist/done-plan.d.ts +330 -0
  48. package/dist/done-plan.d.ts.map +1 -0
  49. package/dist/done-plan.js +17 -0
  50. package/dist/done-plan.js.map +1 -0
  51. package/dist/evidence-atom-schema.d.ts +29 -44
  52. package/dist/evidence-atom-schema.d.ts.map +1 -1
  53. package/dist/evidence-atom-schema.js +48 -8
  54. package/dist/evidence-atom-schema.js.map +1 -1
  55. package/dist/exit-codes.d.ts +40 -0
  56. package/dist/exit-codes.d.ts.map +1 -1
  57. package/dist/exit-codes.js +41 -0
  58. package/dist/exit-codes.js.map +1 -1
  59. package/dist/graph.d.ts +40 -0
  60. package/dist/graph.d.ts.map +1 -1
  61. package/dist/index.d.ts +13 -6
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +4 -0
  64. package/dist/index.js.map +1 -1
  65. package/dist/memory/observe.d.ts +16 -0
  66. package/dist/memory/observe.d.ts.map +1 -1
  67. package/dist/operations/__tests__/release-shared-surface.test.d.ts +10 -0
  68. package/dist/operations/__tests__/release-shared-surface.test.d.ts.map +1 -0
  69. package/dist/operations/__tests__/release-shared-surface.test.js +84 -0
  70. package/dist/operations/__tests__/release-shared-surface.test.js.map +1 -0
  71. package/dist/operations/dialectic.d.ts +16 -0
  72. package/dist/operations/dialectic.d.ts.map +1 -1
  73. package/dist/operations/docs.d.ts +5 -2
  74. package/dist/operations/docs.d.ts.map +1 -1
  75. package/dist/operations/docs.js.map +1 -1
  76. package/dist/operations/memory.d.ts +48 -0
  77. package/dist/operations/memory.d.ts.map +1 -1
  78. package/dist/operations/nexus-scope-map.d.ts +8 -0
  79. package/dist/operations/nexus-scope-map.d.ts.map +1 -1
  80. package/dist/operations/nexus-scope-map.js +8 -0
  81. package/dist/operations/nexus-scope-map.js.map +1 -1
  82. package/dist/operations/nexus-user-profile.d.ts +27 -1
  83. package/dist/operations/nexus-user-profile.d.ts.map +1 -1
  84. package/dist/operations/nexus-user-profile.js +11 -1
  85. package/dist/operations/nexus-user-profile.js.map +1 -1
  86. package/dist/operations/nexus.d.ts +394 -3
  87. package/dist/operations/nexus.d.ts.map +1 -1
  88. package/dist/operations/release.d.ts +259 -0
  89. package/dist/operations/release.d.ts.map +1 -1
  90. package/dist/operations/release.js +130 -1
  91. package/dist/operations/release.js.map +1 -1
  92. package/dist/operations/session.d.ts +6 -4
  93. package/dist/operations/session.d.ts.map +1 -1
  94. package/dist/operations/tasks.d.ts +6 -0
  95. package/dist/operations/tasks.d.ts.map +1 -1
  96. package/dist/operations/tasks.js +1 -0
  97. package/dist/operations/tasks.js.map +1 -1
  98. package/dist/operations/validate.d.ts +5 -0
  99. package/dist/operations/validate.d.ts.map +1 -1
  100. package/dist/operations/worktree.d.ts +125 -1
  101. package/dist/operations/worktree.d.ts.map +1 -1
  102. package/dist/process-probe.d.ts +17 -0
  103. package/dist/process-probe.d.ts.map +1 -0
  104. package/dist/process-probe.js +21 -0
  105. package/dist/process-probe.js.map +1 -0
  106. package/dist/project-context.d.ts +44 -2
  107. package/dist/project-context.d.ts.map +1 -1
  108. package/dist/project-lifecycle.d.ts +143 -0
  109. package/dist/project-lifecycle.d.ts.map +1 -0
  110. package/dist/project-lifecycle.js +21 -0
  111. package/dist/project-lifecycle.js.map +1 -0
  112. package/dist/release/plan.d.ts +6 -2
  113. package/dist/release/plan.d.ts.map +1 -1
  114. package/dist/release/plan.js +8 -2
  115. package/dist/release/plan.js.map +1 -1
  116. package/dist/release/plan.test.js +6 -1
  117. package/dist/release/plan.test.js.map +1 -1
  118. package/dist/table-classification.d.ts +208 -0
  119. package/dist/table-classification.d.ts.map +1 -0
  120. package/dist/table-classification.js +23 -0
  121. package/dist/table-classification.js.map +1 -0
  122. package/dist/task.d.ts +97 -1
  123. package/dist/task.d.ts.map +1 -1
  124. package/package.json +6 -2
  125. package/schemas/gate-result.schema.json +43 -0
  126. package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
  127. package/src/__tests__/decide.test.ts +139 -0
  128. package/src/__tests__/evidence-atom-schema.test.ts +24 -1
  129. package/src/__tests__/memory-wire-shapes.test.ts +3 -0
  130. package/src/acceptance-gate-schema.ts +29 -0
  131. package/src/acceptance-gate.ts +68 -3
  132. package/src/backup-manifest.ts +13 -2
  133. package/src/branch-lock.ts +36 -4
  134. package/src/cloud/api.ts +512 -0
  135. package/src/cloud/envelope.ts +63 -0
  136. package/src/cloud/ids.ts +51 -0
  137. package/src/cloud/index.ts +16 -0
  138. package/src/data-accessor.ts +24 -3
  139. package/src/decide.ts +355 -0
  140. package/src/dispatch/operations-registry.ts +59 -0
  141. package/src/done-plan.ts +370 -0
  142. package/src/evidence-atom-schema.ts +51 -9
  143. package/src/exit-codes.ts +42 -0
  144. package/src/graph.ts +44 -0
  145. package/src/index.ts +118 -0
  146. package/src/memory/observe.ts +16 -0
  147. package/src/operations/__tests__/release-shared-surface.test.ts +112 -0
  148. package/src/operations/dialectic.ts +17 -0
  149. package/src/operations/docs.ts +5 -2
  150. package/src/operations/memory.ts +48 -0
  151. package/src/operations/nexus-scope-map.ts +9 -0
  152. package/src/operations/nexus-user-profile.ts +29 -1
  153. package/src/operations/nexus.ts +416 -3
  154. package/src/operations/release.ts +197 -0
  155. package/src/operations/session.ts +6 -4
  156. package/src/operations/tasks.ts +7 -0
  157. package/src/operations/validate.ts +5 -0
  158. package/src/operations/worktree.ts +133 -1
  159. package/src/process-probe.ts +21 -0
  160. package/src/project-context.ts +42 -2
  161. package/src/project-lifecycle.ts +149 -0
  162. package/src/release/plan.test.ts +6 -1
  163. package/src/release/plan.ts +8 -2
  164. package/src/table-classification.ts +232 -0
  165. package/src/task.ts +102 -2
@@ -1569,6 +1569,12 @@ export interface TasksUpdateQueryParams {
1569
1569
  addRelates?: Array<{ taskId: string; type: string; reason?: string }>;
1570
1570
  /** Remove related tasks by taskId. @task T9327 */
1571
1571
  removeRelates?: string[];
1572
+ /**
1573
+ * Optimistic-concurrency guard: the task `updatedAt` the caller read. When
1574
+ * the stored version differs, the update fails with `E_CONFLICT` and the
1575
+ * current version, instead of overwriting a newer write. @task T12503
1576
+ */
1577
+ expectedUpdatedAt?: string;
1572
1578
  }
1573
1579
  /**
1574
1580
  * Result of `tasks.update` — the updated task record with change list.
@@ -2602,6 +2608,7 @@ export const TASKS_UPDATE_INPUT_SCHEMA: JsonSchema = {
2602
2608
  },
2603
2609
  },
2604
2610
  removeRelates: { type: 'array', items: { type: 'string' } },
2611
+ expectedUpdatedAt: { type: 'string', minLength: 1 },
2605
2612
  },
2606
2613
  };
2607
2614
 
@@ -223,6 +223,11 @@ export interface ValidateGateParams {
223
223
  * `CLEO_STRICT_EVIDENCE=1`).
224
224
  */
225
225
  sharedEvidence?: boolean;
226
+ /**
227
+ * `--no-run` (T12621): serve typed gates from the result cache and execute
228
+ * none; a gate without a fresh cached pass refuses with `E_GATE_NOT_CACHED`.
229
+ */
230
+ noRun?: boolean;
226
231
  }
227
232
  export interface ValidateGateResult {
228
233
  taskId: string;
@@ -28,7 +28,7 @@
28
28
  * @task T1161
29
29
  */
30
30
  export interface WorktreeHook {
31
- /** Shell command to run (executed via `sh -c` in the worktree dir). */
31
+ /** Shell command to run in the worktree dir (`/bin/sh -c` on POSIX, cmd.exe on Windows). */
32
32
  command: string;
33
33
  /**
34
34
  * When to run the hook.
@@ -97,6 +97,107 @@ export interface WorktreeIncludePattern {
97
97
  negated: boolean;
98
98
  }
99
99
 
100
+ // ---------------------------------------------------------------------------
101
+ // Per-task worktree lock (T12506)
102
+ // ---------------------------------------------------------------------------
103
+
104
+ /**
105
+ * Identity of the process that holds (or asks for) a task's worktree lock.
106
+ *
107
+ * Every field is optional on input: `createWorktree` fills `pid` and
108
+ * `processStartedAt` from the calling process when the caller does not name a
109
+ * longer-lived owner process. The spawn pipeline passes the per-agent session
110
+ * id, agent id, stable device id and the nearest non-launcher ancestor process
111
+ * (the harness that outlives the short `cleo` invocation).
112
+ *
113
+ * @task T12506
114
+ */
115
+ export interface WorktreeLockHolder {
116
+ /** CLEO session id of the holder (`CLEO_SESSION_ID`). */
117
+ sessionId?: string | null;
118
+ /** Agent id of the holder (`CLEO_AGENT_ID`). */
119
+ agentId?: string | null;
120
+ /** Stable device id (`getStableDeviceId()`); pids are only comparable on one device. */
121
+ deviceId?: string | null;
122
+ /** Owner process id whose liveness keeps the lock held. */
123
+ pid?: number;
124
+ /**
125
+ * Owner process start time as rendered by `ps -o lstart=` under
126
+ * `PS_STABLE_ENV` (UTC, C locale). A live pid with a different start time is
127
+ * a recycled pid, i.e. the holder is dead.
128
+ */
129
+ processStartedAt?: string | null;
130
+ }
131
+
132
+ /**
133
+ * On-disk record of a held per-task worktree lock
134
+ * (`<cleoHome>/locks/worktrees/<projectHash>/<taskId>.lock`).
135
+ *
136
+ * Published atomically (write temp file, then hard-link into place, which
137
+ * fails with `EEXIST` when another holder won), so a reader never sees a
138
+ * partially written record.
139
+ *
140
+ * @task T12506
141
+ */
142
+ export interface WorktreeLockRecord {
143
+ /** Record schema version. */
144
+ schemaVersion: 1;
145
+ /** Random token identifying this acquisition. */
146
+ token: string;
147
+ /** Task the lock guards. */
148
+ taskId: string;
149
+ /** Project hash scoping the worktree. */
150
+ projectHash: string;
151
+ /** Holder session id, or `null` when unknown. */
152
+ sessionId: string | null;
153
+ /** Holder agent id, or `null` when unknown. */
154
+ agentId: string | null;
155
+ /** Holder device id, or `null` when unknown. */
156
+ deviceId: string | null;
157
+ /** Holder owner process id. */
158
+ pid: number;
159
+ /** Holder owner process start time (`ps lstart`, UTC), or `null` when unprobeable. */
160
+ processStartedAt: string | null;
161
+ /** Host name of the holder. */
162
+ hostname: string;
163
+ /** ISO 8601 time the lock was acquired. */
164
+ acquiredAt: string;
165
+ /** ISO 8601 time of the holder's latest heartbeat. */
166
+ heartbeatAt: string;
167
+ }
168
+
169
+ /**
170
+ * How `createWorktree` obtained the per-task lock.
171
+ *
172
+ * - `acquired`: no lock existed.
173
+ * - `reclaimed`: a lock existed but its holder was provably dead (pid gone or
174
+ * start time differs) or its heartbeat was older than the TTL.
175
+ * - `reentered`: the lock is held by the SAME caller (session id, agent id,
176
+ * owner pid and start time all match), e.g. an orchestrator retrying its own
177
+ * spawn; the existing worktree is re-attached.
178
+ *
179
+ * @task T12506
180
+ */
181
+ export type WorktreeLockAcquisitionStatus = 'acquired' | 'reclaimed' | 'reentered';
182
+
183
+ /**
184
+ * Lock outcome reported on {@link CreateWorktreeResult.lock}.
185
+ *
186
+ * @task T12506
187
+ */
188
+ export interface WorktreeLockAcquisition {
189
+ /** How the lock was obtained. */
190
+ status: WorktreeLockAcquisitionStatus;
191
+ /** Absolute path of the lock file. */
192
+ lockPath: string;
193
+ /** The record now on disk. */
194
+ record: WorktreeLockRecord;
195
+ /** The previous holder's record when the lock was reclaimed. */
196
+ reclaimedFrom?: WorktreeLockRecord;
197
+ /** Why the previous holder was judged dead (`reclaimed` only). */
198
+ reclaimReason?: 'pid-gone' | 'pid-recycled' | 'heartbeat-stale' | 'unreadable';
199
+ }
200
+
100
201
  // ---------------------------------------------------------------------------
101
202
  // Create operation
102
203
  // ---------------------------------------------------------------------------
@@ -178,6 +279,20 @@ export interface CreateWorktreeOptions {
178
279
  * @task T9807
179
280
  */
180
281
  spawnScope?: string;
282
+ /**
283
+ * Identity recorded in the per-task worktree lock (T12506). Missing fields
284
+ * default to the calling process (pid + `ps` start time).
285
+ *
286
+ * @task T12506
287
+ */
288
+ holder?: WorktreeLockHolder;
289
+ /**
290
+ * Heartbeat age (ms) beyond which an existing lock is reclaimable even when
291
+ * its owner pid is alive. Defaults to `CLEO_WORKTREE_LOCK_TTL_MS` or 4 hours.
292
+ *
293
+ * @task T12506
294
+ */
295
+ lockTtlMs?: number;
181
296
  }
182
297
 
183
298
  /**
@@ -211,8 +326,15 @@ export interface CreateWorktreeResult {
211
326
  * branch behind; false on a clean first-time creation.
212
327
  *
213
328
  * @task T1878
329
+ * @task T12506 — also true when an existing worktree directory was
330
+ * re-attached instead of recreated (never force-removed).
214
331
  */
215
332
  reused: boolean;
333
+ /**
334
+ * Per-task lock acquired before `git worktree add` (T12506). Absent only on
335
+ * results synthesised by callers that skip provisioning (e.g. `--resume`).
336
+ */
337
+ lock?: WorktreeLockAcquisition;
216
338
  /** Environment variables to inject into the spawned agent process. */
217
339
  envVars: Record<string, string>;
218
340
  /** Prompt preamble text for agent isolation context. */
@@ -258,6 +380,16 @@ export interface DestroyWorktreeOptions {
258
380
  * @default true
259
381
  */
260
382
  deleteBranch?: boolean;
383
+ /**
384
+ * When true, `deleteBranch` also deletes a branch that carries commits on
385
+ * neither `origin/main` nor the project's current `HEAD`. Without it such a
386
+ * branch is kept and `error` explains why (T12506: unmerged agent work is
387
+ * never deleted implicitly).
388
+ *
389
+ * @default false
390
+ * @task T12506
391
+ */
392
+ forceDeleteUnmergedBranch?: boolean;
261
393
  /**
262
394
  * When true, force destruction even if the worktree has uncommitted changes.
263
395
  *
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Shared constants for probing the OS process table with `ps`.
3
+ *
4
+ * @module process-probe
5
+ * @task T12500
6
+ * @task T12506
7
+ */
8
+
9
+ /**
10
+ * Environment overrides for every `ps` probe (T12500): UTC and the C locale make
11
+ * the `lstart` start time independent of the caller's `TZ` / `LANG`. The
12
+ * terminal-identity ppid-chain keys (core) and the per-task worktree lock
13
+ * holder identity (`@cleocode/worktree`, T12506) both compare `lstart` strings
14
+ * written by one process and read by another, so both must render them the
15
+ * same way.
16
+ */
17
+ export const PS_STABLE_ENV: Readonly<Record<'TZ' | 'LC_ALL' | 'LANG', string>> = {
18
+ TZ: 'UTC',
19
+ LC_ALL: 'C',
20
+ LANG: 'C',
21
+ };
@@ -70,14 +70,48 @@ export interface ProjectEvidenceContext {
70
70
  * `commit:` atom whose SHA exists in exactly one sibling, without help.
71
71
  */
72
72
  gitRoot?: string;
73
+ /**
74
+ * Accept required CI green on a merged PR's merge commit as `testsPassed` /
75
+ * `qaPassed` evidence (`ci:<pr>` atom, owner decision D11149). Only `true`
76
+ * enables it.
77
+ *
78
+ * @task T12634
79
+ */
80
+ ciSatisfies?: boolean;
81
+ /**
82
+ * Which required checks attest each gate for `ci:<pr>`. Each list must be a
83
+ * subset of the required-check list; a gate without a list refuses `ci:`.
84
+ *
85
+ * @task T12634
86
+ */
87
+ ciChecks?: {
88
+ tests?: string[];
89
+ qa?: string[];
90
+ /** Job-name globs that must have run and succeeded for a code task's gate. */
91
+ jobs?: { tests?: string[]; qa?: string[] };
92
+ };
93
+ }
94
+
95
+ /**
96
+ * A required check named in `release.prRequiredWorkflows`, optionally pinned to
97
+ * the GitHub App that must post it and (for a workflow) its workflow file, so a
98
+ * same-named check from another app never counts (T12634).
99
+ */
100
+ export interface RequiredCheckPin {
101
+ /** Check-run (job) or workflow name. */
102
+ name: string;
103
+ /** GitHub App slug (e.g. `github-actions`) or numeric app id. */
104
+ app?: string | number;
105
+ /** Workflow file path (e.g. `.github/workflows/ci.yml`). */
106
+ workflow?: string;
73
107
  }
74
108
 
75
109
  /** Release and evidence behavior configured per project. */
76
110
  export interface ProjectReleaseContext {
77
111
  /** Branches whose reachable commits may satisfy `commit:` evidence atoms. */
78
112
  integrationBranches?: string[];
79
- /** Required successful checks for `pr:` evidence atoms. */
80
- prRequiredWorkflows?: string[];
113
+ /** Required successful checks for `pr:` / `ci:` evidence atoms; an entry may pin its app/workflow. */
114
+ prRequiredWorkflows?: Array<string | RequiredCheckPin>;
81
115
  }
82
116
 
83
117
  /** Schema-compliant project context for LLM agent consumption. */
@@ -90,6 +124,12 @@ export interface ProjectContext {
90
124
  testing?: {
91
125
  framework?: TestFramework;
92
126
  command?: string;
127
+ /**
128
+ * Affected-scope test command template for `tool:test-affected` (D11150,
129
+ * T12635): `{projects}`, `{filters}` and `{packages}` expand per affected
130
+ * package, e.g. `pnpm exec vitest run {projects}`.
131
+ */
132
+ affectedCommand?: string;
93
133
  testFilePatterns?: string[];
94
134
  directories?: {
95
135
  unit?: string;
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Project relocation contracts — `cleo project move` and `cleo project reroot`
3
+ * (T12552 · T12558).
4
+ *
5
+ * Both relocations RENAME; nothing is copied, so the live database, its WAL
6
+ * sidecars and `.git` move atomically and there is never a second copy to
7
+ * diverge from the first.
8
+ *
9
+ * - `move` renames the whole project root to a new path on the SAME device.
10
+ * A cross-device target is refused (`E_CROSS_DEVICE`).
11
+ * - `reroot` renames `.cleo/` (and CLEO's own top-level files) into a child
12
+ * directory of the current root and leaves a {@link ProjectMovedTombstone}.
13
+ *
14
+ * Both support a dry run that returns a {@link ProjectRelocationPlan} and
15
+ * touches neither the disk nor the registry.
16
+ *
17
+ * @task T12552
18
+ * @task T12558
19
+ */
20
+
21
+ /** Which relocation a plan or result describes. */
22
+ export type ProjectRelocationKind = 'move' | 'reroot';
23
+
24
+ /** The registry change a relocation makes. */
25
+ export interface ProjectRelocationRegistryAction {
26
+ /**
27
+ * `rebind` points the project's registry row at the target (registering the
28
+ * project first when it has no row). Always explicit — never inferred.
29
+ */
30
+ action: 'rebind';
31
+ /** Location that becomes `live`. */
32
+ livePath: string;
33
+ /** Location demoted to `missing` — it no longer holds the project. */
34
+ demotedPath: string;
35
+ /** Always `missing`: after a rename the old location holds no store. */
36
+ demotedState: 'missing';
37
+ /** Always `carried`: the checkout nonce moves with the renamed `.cleo/`. */
38
+ nonce: 'carried';
39
+ }
40
+
41
+ /** A top-level entry a relocation leaves behind, and why. */
42
+ export interface ProjectRelocationExclusion {
43
+ /** Entry name, relative to the source root. */
44
+ entry: string;
45
+ /** Why it is not moved. */
46
+ reason: string;
47
+ }
48
+
49
+ /**
50
+ * What a relocation WOULD do, returned by a dry run. Producing it writes
51
+ * nothing to disk and opens no database.
52
+ */
53
+ export interface ProjectRelocationPlan {
54
+ /** Discriminant: always `true` for a plan. */
55
+ dryRun: true;
56
+ /** Which relocation. */
57
+ kind: ProjectRelocationKind;
58
+ /** Stable project id — preserved by every relocation. */
59
+ projectId: string;
60
+ /** Current absolute project root. */
61
+ source: string;
62
+ /** Absolute project root after the relocation. */
63
+ target: string;
64
+ /** Always `rename`: relocations never copy. */
65
+ transfer: 'rename';
66
+ /**
67
+ * Entries renamed, relative to `source`. `move` renames the root itself
68
+ * (`['.']`); `reroot` lists each top-level entry it moves into `target`.
69
+ */
70
+ entries: string[];
71
+ /** Top-level entries left behind, with the reason (reroot only). */
72
+ excluded: ProjectRelocationExclusion[];
73
+ /** Absolute paths of files the relocation writes. */
74
+ writes: string[];
75
+ /** The registry change. */
76
+ registry: ProjectRelocationRegistryAction;
77
+ /** Where the required pre-change checkpoint is written. */
78
+ checkpoint: string;
79
+ /**
80
+ * Conditions that make the real run refuse (`E_CROSS_DEVICE`, CLEO or git
81
+ * worktrees). Empty when none were found.
82
+ */
83
+ blockers: string[];
84
+ /** Checks only the real run performs (they would open a database). */
85
+ deferredChecks: string[];
86
+ }
87
+
88
+ /** Result of a completed `cleo project move`. */
89
+ export interface MoveProjectResult {
90
+ /** Discriminant: always `false` for a completed move. */
91
+ dryRun: false;
92
+ /** Stable project id — unchanged. */
93
+ projectId: string;
94
+ /** The old absolute project root (no longer exists). */
95
+ oldPath: string;
96
+ /** The new absolute project root. */
97
+ newPath: string;
98
+ /** Id of the checkpoint taken before the rename. */
99
+ checkpointId: string;
100
+ /** Directory holding the checkpoint, outside the moved tree. */
101
+ checkpointPath: string;
102
+ /** Registry status after the rebind. */
103
+ reconcileStatus: 'ok' | 'path_updated' | 'auto_registered' | 'candidate';
104
+ }
105
+
106
+ /** Result of a completed (or resumed) `cleo project reroot`. */
107
+ export interface RerootProjectResult {
108
+ /** Discriminant: always `false` for a completed reroot. */
109
+ dryRun: false;
110
+ /** Stable project id — unchanged. */
111
+ projectId: string;
112
+ /** Previous absolute project root (now holds only the tombstone). */
113
+ oldRoot: string;
114
+ /** New absolute project root (the child directory). */
115
+ newRoot: string;
116
+ /**
117
+ * `true` when this run finished a reroot an earlier run left half done
118
+ * (`.cleo/` already in the child, registry still on the old root).
119
+ */
120
+ resumed: boolean;
121
+ /** Id of the checkpoint taken before any change (empty on a resume). */
122
+ checkpointId: string;
123
+ /** Directory holding the checkpoint, outside the moved tree (empty on a resume). */
124
+ checkpointPath: string;
125
+ /** Top-level entries renamed from the old root into the new one. */
126
+ renamed: string[];
127
+ /** Whether `.cleo/project-id` was already present or had to be written. */
128
+ projectIdFile: 'present' | 'written';
129
+ /** Absolute path of the tombstone left at the old root. */
130
+ tombstone: string;
131
+ /** Registry status after the rebind. */
132
+ reconcileStatus: 'ok' | 'path_updated' | 'auto_registered' | 'candidate';
133
+ /** Follow-up the operator must do by hand (git bookkeeping). */
134
+ notes: string[];
135
+ }
136
+
137
+ /**
138
+ * `<oldRoot>/.cleo-moved.json` — left by `cleo project reroot` so a command
139
+ * run at the old root refuses with `E_PROJECT_MOVED` instead of silently
140
+ * creating an empty store there.
141
+ */
142
+ export interface ProjectMovedTombstone {
143
+ /** The project that used to live here. */
144
+ projectId: string;
145
+ /** Absolute path of its new root. */
146
+ movedTo: string;
147
+ /** ISO 8601 timestamp of the relocation. */
148
+ at: string;
149
+ }
@@ -187,7 +187,12 @@ describe('Release plan constant tuples', () => {
187
187
  });
188
188
 
189
189
  it('exports resolved-source attribution per ADR-061', () => {
190
- expect(RESOLVED_SOURCE).toEqual(['project-context', 'language-default', 'legacy-alias']);
190
+ expect(RESOLVED_SOURCE).toEqual([
191
+ 'project-context',
192
+ 'package-script',
193
+ 'language-default',
194
+ 'legacy-alias',
195
+ ]);
191
196
  });
192
197
 
193
198
  it('exposes a stable schema version + URL', () => {
@@ -137,9 +137,15 @@ export const TASK_KIND = [
137
137
  export const IMPACT = ['major', 'minor', 'patch'] as const;
138
138
 
139
139
  /**
140
- * Source attribution for resolved tool commands. Mirrors ADR-061 §1 surfaces.
140
+ * Source attribution for resolved tool commands. Mirrors ADR-061 §1 surfaces;
141
+ * `package-script` is a project `package.json` script (T12633).
141
142
  */
142
- export const RESOLVED_SOURCE = ['project-context', 'language-default', 'legacy-alias'] as const;
143
+ export const RESOLVED_SOURCE = [
144
+ 'project-context',
145
+ 'package-script',
146
+ 'language-default',
147
+ 'legacy-alias',
148
+ ] as const;
143
149
 
144
150
  // ─── Enum schemas ────────────────────────────────────────────────────────────
145
151