@cleocode/contracts 2026.9.19 → 2026.9.21
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/README.md +1 -1
- package/dist/__tests__/decide.test.d.ts +7 -0
- package/dist/__tests__/decide.test.d.ts.map +1 -0
- package/dist/__tests__/decide.test.js +91 -0
- package/dist/__tests__/decide.test.js.map +1 -0
- package/dist/__tests__/evidence-atom-schema.test.js +2 -1
- package/dist/__tests__/evidence-atom-schema.test.js.map +1 -1
- package/dist/__tests__/memory-wire-shapes.test.js.map +1 -1
- package/dist/acceptance-gate-schema.d.ts +5 -0
- package/dist/acceptance-gate-schema.d.ts.map +1 -1
- package/dist/acceptance-gate-schema.js +26 -0
- package/dist/acceptance-gate-schema.js.map +1 -1
- package/dist/acceptance-gate.d.ts +70 -3
- package/dist/acceptance-gate.d.ts.map +1 -1
- package/dist/backup-manifest.d.ts +13 -2
- package/dist/backup-manifest.d.ts.map +1 -1
- package/dist/boundary.js +1 -1
- package/dist/boundary.js.map +1 -1
- package/dist/branch-lock.d.ts +11 -0
- package/dist/branch-lock.d.ts.map +1 -1
- package/dist/branch-lock.js +31 -4
- package/dist/branch-lock.js.map +1 -1
- package/dist/cloud/api.d.ts +607 -0
- package/dist/cloud/api.d.ts.map +1 -0
- package/dist/cloud/api.js +417 -0
- package/dist/cloud/api.js.map +1 -0
- package/dist/cloud/envelope.d.ts +84 -0
- package/dist/cloud/envelope.d.ts.map +1 -0
- package/dist/cloud/envelope.js +54 -0
- package/dist/cloud/envelope.js.map +1 -0
- package/dist/cloud/ids.d.ts +30 -0
- package/dist/cloud/ids.d.ts.map +1 -0
- package/dist/cloud/ids.js +32 -0
- package/dist/cloud/ids.js.map +1 -0
- package/dist/cloud/index.d.ts +17 -0
- package/dist/cloud/index.d.ts.map +1 -0
- package/dist/cloud/index.js +17 -0
- package/dist/cloud/index.js.map +1 -0
- package/dist/data-accessor.d.ts +23 -3
- package/dist/data-accessor.d.ts.map +1 -1
- package/dist/data-accessor.js.map +1 -1
- package/dist/decide.d.ts +333 -0
- package/dist/decide.d.ts.map +1 -0
- package/dist/decide.js +184 -0
- package/dist/decide.js.map +1 -0
- package/dist/dispatch/operations-registry.d.ts.map +1 -1
- package/dist/dispatch/operations-registry.js +55 -0
- package/dist/dispatch/operations-registry.js.map +1 -1
- package/dist/done-plan.d.ts +311 -0
- package/dist/done-plan.d.ts.map +1 -0
- package/dist/done-plan.js +17 -0
- package/dist/done-plan.js.map +1 -0
- package/dist/evidence-atom-schema.d.ts +18 -1
- package/dist/evidence-atom-schema.d.ts.map +1 -1
- package/dist/evidence-atom-schema.js +27 -2
- package/dist/evidence-atom-schema.js.map +1 -1
- package/dist/exit-codes.d.ts +40 -0
- package/dist/exit-codes.d.ts.map +1 -1
- package/dist/exit-codes.js +41 -0
- package/dist/exit-codes.js.map +1 -1
- package/dist/graph.d.ts +40 -0
- package/dist/graph.d.ts.map +1 -1
- package/dist/index.d.ts +14 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/memory/observe.d.ts +16 -0
- package/dist/memory/observe.d.ts.map +1 -1
- package/dist/operations/__tests__/release-shared-surface.test.d.ts +10 -0
- package/dist/operations/__tests__/release-shared-surface.test.d.ts.map +1 -0
- package/dist/operations/__tests__/release-shared-surface.test.js +84 -0
- package/dist/operations/__tests__/release-shared-surface.test.js.map +1 -0
- package/dist/operations/dialectic.d.ts +16 -0
- package/dist/operations/dialectic.d.ts.map +1 -1
- package/dist/operations/memory.d.ts +48 -0
- package/dist/operations/memory.d.ts.map +1 -1
- package/dist/operations/nexus-scope-map.d.ts +8 -0
- package/dist/operations/nexus-scope-map.d.ts.map +1 -1
- package/dist/operations/nexus-scope-map.js +8 -0
- package/dist/operations/nexus-scope-map.js.map +1 -1
- package/dist/operations/nexus-user-profile.d.ts +27 -1
- package/dist/operations/nexus-user-profile.d.ts.map +1 -1
- package/dist/operations/nexus-user-profile.js +11 -1
- package/dist/operations/nexus-user-profile.js.map +1 -1
- package/dist/operations/nexus.d.ts +394 -3
- package/dist/operations/nexus.d.ts.map +1 -1
- package/dist/operations/release.d.ts +259 -0
- package/dist/operations/release.d.ts.map +1 -1
- package/dist/operations/release.js +130 -1
- package/dist/operations/release.js.map +1 -1
- package/dist/operations/session.d.ts +6 -4
- package/dist/operations/session.d.ts.map +1 -1
- package/dist/operations/tasks.d.ts +6 -0
- package/dist/operations/tasks.d.ts.map +1 -1
- package/dist/operations/tasks.js +1 -0
- package/dist/operations/tasks.js.map +1 -1
- package/dist/operations/validate.d.ts +5 -0
- package/dist/operations/validate.d.ts.map +1 -1
- package/dist/operations/worktree.d.ts +125 -1
- package/dist/operations/worktree.d.ts.map +1 -1
- package/dist/process-probe.d.ts +17 -0
- package/dist/process-probe.d.ts.map +1 -0
- package/dist/process-probe.js +21 -0
- package/dist/process-probe.js.map +1 -0
- package/dist/project-context.d.ts +44 -2
- package/dist/project-context.d.ts.map +1 -1
- package/dist/project-lifecycle.d.ts +143 -0
- package/dist/project-lifecycle.d.ts.map +1 -0
- package/dist/project-lifecycle.js +21 -0
- package/dist/project-lifecycle.js.map +1 -0
- package/dist/release/plan.d.ts +6 -2
- package/dist/release/plan.d.ts.map +1 -1
- package/dist/release/plan.js +8 -2
- package/dist/release/plan.js.map +1 -1
- package/dist/release/plan.test.js +6 -1
- package/dist/release/plan.test.js.map +1 -1
- package/dist/skills/install-gate.d.ts +109 -0
- package/dist/skills/install-gate.d.ts.map +1 -0
- package/dist/skills/install-gate.js +18 -0
- package/dist/skills/install-gate.js.map +1 -0
- package/dist/table-classification.d.ts +208 -0
- package/dist/table-classification.d.ts.map +1 -0
- package/dist/table-classification.js +23 -0
- package/dist/table-classification.js.map +1 -0
- package/dist/task.d.ts +79 -0
- package/dist/task.d.ts.map +1 -1
- package/package.json +6 -2
- package/schemas/gate-result.schema.json +43 -0
- package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
- package/src/__tests__/decide.test.ts +139 -0
- package/src/__tests__/evidence-atom-schema.test.ts +2 -1
- package/src/__tests__/memory-wire-shapes.test.ts +3 -0
- package/src/acceptance-gate-schema.ts +29 -0
- package/src/acceptance-gate.ts +68 -3
- package/src/backup-manifest.ts +13 -2
- package/src/boundary.ts +1 -1
- package/src/branch-lock.ts +36 -4
- package/src/cloud/api.ts +512 -0
- package/src/cloud/envelope.ts +63 -0
- package/src/cloud/ids.ts +51 -0
- package/src/cloud/index.ts +16 -0
- package/src/data-accessor.ts +24 -3
- package/src/decide.ts +355 -0
- package/src/dispatch/operations-registry.ts +58 -0
- package/src/done-plan.ts +350 -0
- package/src/evidence-atom-schema.ts +31 -2
- package/src/exit-codes.ts +42 -0
- package/src/graph.ts +44 -0
- package/src/index.ts +133 -0
- package/src/memory/observe.ts +16 -0
- package/src/operations/__tests__/release-shared-surface.test.ts +112 -0
- package/src/operations/dialectic.ts +17 -0
- package/src/operations/memory.ts +48 -0
- package/src/operations/nexus-scope-map.ts +9 -0
- package/src/operations/nexus-user-profile.ts +29 -1
- package/src/operations/nexus.ts +416 -3
- package/src/operations/release.ts +197 -0
- package/src/operations/session.ts +6 -4
- package/src/operations/tasks.ts +7 -0
- package/src/operations/validate.ts +5 -0
- package/src/operations/worktree.ts +133 -1
- package/src/process-probe.ts +21 -0
- package/src/project-context.ts +42 -2
- package/src/project-lifecycle.ts +149 -0
- package/src/release/plan.test.ts +6 -1
- package/src/release/plan.ts +8 -2
- package/src/skills/install-gate.ts +134 -0
- package/src/table-classification.ts +232 -0
- package/src/task.ts +84 -1
package/src/operations/tasks.ts
CHANGED
|
@@ -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
|
|
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
|
+
};
|
package/src/project-context.ts
CHANGED
|
@@ -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
|
+
}
|
package/src/release/plan.test.ts
CHANGED
|
@@ -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([
|
|
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', () => {
|
package/src/release/plan.ts
CHANGED
|
@@ -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 = [
|
|
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
|
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill install security gate — wire shapes shared by core and CAAMP.
|
|
3
|
+
*
|
|
4
|
+
* The gate's functions are DEFINED in `@cleocode/core` (`skills/skills-guard.ts`,
|
|
5
|
+
* `skills/federation-install-gate.ts`, `skills/skills-guard-audit.ts`) and
|
|
6
|
+
* CALLED by CAAMP's install pipeline, which every skill install goes through
|
|
7
|
+
* (T12384). CAAMP cannot import core's declarations — core depends on CAAMP,
|
|
8
|
+
* and a core declaration in CAAMP's public types becomes an input to core's
|
|
9
|
+
* own build (TS5055) — so the shapes both sides agree on live here.
|
|
10
|
+
*
|
|
11
|
+
* Each type is structurally identical to core's counterpart, so core's
|
|
12
|
+
* functions are assignable to the signatures CAAMP declares against these
|
|
13
|
+
* types.
|
|
14
|
+
*
|
|
15
|
+
* @task T12384
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** Trust tier of a skill source (core `SkillTrustLevel`). */
|
|
19
|
+
export type SkillGateTrustLevel = 'builtin' | 'trusted' | 'community' | 'agent-created';
|
|
20
|
+
|
|
21
|
+
/** Overall scan verdict (core `ScanVerdict`). */
|
|
22
|
+
export type SkillGateVerdict = 'safe' | 'caution' | 'dangerous';
|
|
23
|
+
|
|
24
|
+
/** Severity of one finding (core `FindingSeverity`). */
|
|
25
|
+
export type SkillGateFindingSeverity = 'critical' | 'high' | 'medium' | 'low';
|
|
26
|
+
|
|
27
|
+
/** Threat category of one finding (core `FindingCategory`). */
|
|
28
|
+
export type SkillGateFindingCategory =
|
|
29
|
+
| 'exfiltration'
|
|
30
|
+
| 'injection'
|
|
31
|
+
| 'destructive'
|
|
32
|
+
| 'persistence'
|
|
33
|
+
| 'network'
|
|
34
|
+
| 'obfuscation'
|
|
35
|
+
| 'execution'
|
|
36
|
+
| 'traversal'
|
|
37
|
+
| 'mining'
|
|
38
|
+
| 'supply_chain'
|
|
39
|
+
| 'privilege_escalation'
|
|
40
|
+
| 'credential_exposure'
|
|
41
|
+
| 'structural';
|
|
42
|
+
|
|
43
|
+
/** One scanner finding (core `Finding`). */
|
|
44
|
+
export interface SkillGateFinding {
|
|
45
|
+
/** Stable identifier of the pattern or structural check. */
|
|
46
|
+
readonly patternId: string;
|
|
47
|
+
/** Severity of the hit. */
|
|
48
|
+
readonly severity: SkillGateFindingSeverity;
|
|
49
|
+
/** Threat category. */
|
|
50
|
+
readonly category: SkillGateFindingCategory;
|
|
51
|
+
/** Path relative to the scanned skill root. */
|
|
52
|
+
readonly file: string;
|
|
53
|
+
/** 1-based line number (`0` for directory-level findings). */
|
|
54
|
+
readonly line: number;
|
|
55
|
+
/** Matched content excerpt. */
|
|
56
|
+
readonly match: string;
|
|
57
|
+
/** Human-readable description. */
|
|
58
|
+
readonly description: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Result of scanning a skill directory (core `ScanResult`). */
|
|
62
|
+
export interface SkillGateScanResult {
|
|
63
|
+
/** Skill identifier (basename of the scanned path). */
|
|
64
|
+
readonly skillName: string;
|
|
65
|
+
/** Source identifier the trust level was resolved from. */
|
|
66
|
+
readonly source: string;
|
|
67
|
+
/** Resolved trust tier. */
|
|
68
|
+
readonly trustLevel: SkillGateTrustLevel;
|
|
69
|
+
/** Overall verdict. */
|
|
70
|
+
readonly verdict: SkillGateVerdict;
|
|
71
|
+
/** Every finding, in discovery order. */
|
|
72
|
+
readonly findings: readonly SkillGateFinding[];
|
|
73
|
+
/** ISO-8601 scan time. */
|
|
74
|
+
readonly scannedAt: string;
|
|
75
|
+
/** One-line summary. */
|
|
76
|
+
readonly summary: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Install-policy action (core `InstallDecision`). */
|
|
80
|
+
export type SkillGatePolicyAction = 'allow' | 'block' | 'ask';
|
|
81
|
+
|
|
82
|
+
/** Install-policy decision over a scan (core `InstallGateDecision`). */
|
|
83
|
+
export interface SkillGatePolicyDecision {
|
|
84
|
+
/** Final action. */
|
|
85
|
+
readonly decision: SkillGatePolicyAction;
|
|
86
|
+
/** Human-readable rationale. */
|
|
87
|
+
readonly reason: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Operator trust assigned to a federation peer (core `FederationTrustLevel`). */
|
|
91
|
+
export type SkillGateFederationTrust = 'verified' | 'unverified' | 'blocked';
|
|
92
|
+
|
|
93
|
+
/** A registered federation peer (core `FederationEntry`). */
|
|
94
|
+
export interface SkillGateFederationPeer {
|
|
95
|
+
/** Normalised peer URL. */
|
|
96
|
+
readonly url: string;
|
|
97
|
+
/** Operator-assigned trust. */
|
|
98
|
+
readonly trust: SkillGateFederationTrust;
|
|
99
|
+
/** ISO-8601 time the peer was added. */
|
|
100
|
+
readonly addedAt: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Federation gate action (core `FederationInstallDecision`). */
|
|
104
|
+
export type SkillGateFederationAction = 'allow' | 'block-checksum' | 'prompt-first-install';
|
|
105
|
+
|
|
106
|
+
/** Input to the federation gate (core `FederationInstallGateOptions`). */
|
|
107
|
+
export interface SkillGateFederationInput {
|
|
108
|
+
/** Source identifier (URL, owner/repo, `library:<name>`). */
|
|
109
|
+
readonly source: string;
|
|
110
|
+
/** Path of the fetched artefact; checksum validation needs it. */
|
|
111
|
+
readonly artefactPath?: string;
|
|
112
|
+
/** Expected sha256, when the source declares one. */
|
|
113
|
+
readonly expectedChecksum?: string | null;
|
|
114
|
+
/** Operator approval of a first install from an unverified peer. */
|
|
115
|
+
readonly approveNewSource?: boolean;
|
|
116
|
+
/** Federation index override (tests). */
|
|
117
|
+
readonly federationIndexPath?: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Result of the federation gate (core `FederationInstallGateResult`). */
|
|
121
|
+
export interface SkillGateFederationResult {
|
|
122
|
+
/** Final action. */
|
|
123
|
+
readonly decision: SkillGateFederationAction;
|
|
124
|
+
/** Human-readable rationale. */
|
|
125
|
+
readonly reason: string;
|
|
126
|
+
/** Matched peer, when the source is a known federation URL. */
|
|
127
|
+
readonly peer: SkillGateFederationPeer | null;
|
|
128
|
+
/** Whether the source resolves to a federation peer. */
|
|
129
|
+
readonly isFederationSource: boolean;
|
|
130
|
+
/** Computed sha256 of the artefact, when checked. */
|
|
131
|
+
readonly computedChecksum: string | null;
|
|
132
|
+
/** Expected sha256, when declared. */
|
|
133
|
+
readonly expectedChecksum: string | null;
|
|
134
|
+
}
|