@cleocode/contracts 2026.9.22 → 2026.9.24

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 (102) hide show
  1. package/dist/__tests__/evidence-atom-schema.test.js +3 -2
  2. package/dist/__tests__/evidence-atom-schema.test.js.map +1 -1
  3. package/dist/acceptance-gate-schema.d.ts.map +1 -1
  4. package/dist/acceptance-gate-schema.js +4 -1
  5. package/dist/acceptance-gate-schema.js.map +1 -1
  6. package/dist/branch-lock.d.ts +9 -0
  7. package/dist/branch-lock.d.ts.map +1 -1
  8. package/dist/branch-lock.js +25 -3
  9. package/dist/branch-lock.js.map +1 -1
  10. package/dist/cloud/api.d.ts +1 -1
  11. package/dist/data-accessor.d.ts +209 -16
  12. package/dist/data-accessor.d.ts.map +1 -1
  13. package/dist/data-accessor.js.map +1 -1
  14. package/dist/decide-sites.d.ts +157 -0
  15. package/dist/decide-sites.d.ts.map +1 -0
  16. package/dist/decide-sites.js +28 -0
  17. package/dist/decide-sites.js.map +1 -0
  18. package/dist/decide.d.ts +214 -0
  19. package/dist/decide.d.ts.map +1 -1
  20. package/dist/decide.js +54 -0
  21. package/dist/decide.js.map +1 -1
  22. package/dist/dispatch/operations-registry.d.ts.map +1 -1
  23. package/dist/dispatch/operations-registry.js +174 -8
  24. package/dist/dispatch/operations-registry.js.map +1 -1
  25. package/dist/doctor.d.ts +9 -0
  26. package/dist/doctor.d.ts.map +1 -1
  27. package/dist/evidence-atom-schema.d.ts.map +1 -1
  28. package/dist/evidence-atom-schema.js +3 -0
  29. package/dist/evidence-atom-schema.js.map +1 -1
  30. package/dist/exit-codes.d.ts +7 -0
  31. package/dist/exit-codes.d.ts.map +1 -1
  32. package/dist/exit-codes.js +7 -0
  33. package/dist/exit-codes.js.map +1 -1
  34. package/dist/index.d.ts +12 -7
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +4 -1
  37. package/dist/index.js.map +1 -1
  38. package/dist/knowledge-health.d.ts +21 -0
  39. package/dist/knowledge-health.d.ts.map +1 -1
  40. package/dist/nexus-account.d.ts +160 -0
  41. package/dist/nexus-account.d.ts.map +1 -0
  42. package/dist/nexus-account.js +68 -0
  43. package/dist/nexus-account.js.map +1 -0
  44. package/dist/operations/admin.d.ts +5 -0
  45. package/dist/operations/admin.d.ts.map +1 -1
  46. package/dist/operations/focus.d.ts +7 -3
  47. package/dist/operations/focus.d.ts.map +1 -1
  48. package/dist/operations/nexus-scope-map.d.ts +8 -0
  49. package/dist/operations/nexus-scope-map.d.ts.map +1 -1
  50. package/dist/operations/nexus-scope-map.js +8 -0
  51. package/dist/operations/nexus-scope-map.js.map +1 -1
  52. package/dist/operations/nexus.d.ts +265 -0
  53. package/dist/operations/nexus.d.ts.map +1 -1
  54. package/dist/operations/nexus.js +9 -1
  55. package/dist/operations/nexus.js.map +1 -1
  56. package/dist/operations/orchestrate.d.ts +6 -1
  57. package/dist/operations/orchestrate.d.ts.map +1 -1
  58. package/dist/operations/output-contracts-data.d.ts.map +1 -1
  59. package/dist/operations/output-contracts-data.js +115 -1
  60. package/dist/operations/output-contracts-data.js.map +1 -1
  61. package/dist/operations/tasks.d.ts +97 -9
  62. package/dist/operations/tasks.d.ts.map +1 -1
  63. package/dist/operations/tasks.js.map +1 -1
  64. package/dist/project-lifecycle.d.ts +6 -2
  65. package/dist/project-lifecycle.d.ts.map +1 -1
  66. package/dist/session.d.ts +19 -1
  67. package/dist/session.d.ts.map +1 -1
  68. package/dist/session.js +9 -2
  69. package/dist/session.js.map +1 -1
  70. package/dist/task.d.ts +49 -1
  71. package/dist/task.d.ts.map +1 -1
  72. package/dist/tools/index.d.ts +1 -1
  73. package/dist/tools/index.d.ts.map +1 -1
  74. package/dist/tools/score-task-priority.d.ts +28 -3
  75. package/dist/tools/score-task-priority.d.ts.map +1 -1
  76. package/package.json +2 -2
  77. package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
  78. package/src/__tests__/evidence-atom-schema.test.ts +3 -2
  79. package/src/acceptance-gate-schema.ts +4 -1
  80. package/src/branch-lock.ts +38 -3
  81. package/src/data-accessor.ts +215 -16
  82. package/src/decide-sites.ts +179 -0
  83. package/src/decide.ts +235 -0
  84. package/src/dispatch/operations-registry.ts +183 -8
  85. package/src/doctor.ts +9 -0
  86. package/src/evidence-atom-schema.ts +3 -0
  87. package/src/exit-codes.ts +7 -0
  88. package/src/index.ts +72 -0
  89. package/src/knowledge-health.ts +22 -0
  90. package/src/nexus-account.ts +181 -0
  91. package/src/operations/admin.ts +5 -0
  92. package/src/operations/focus.ts +7 -3
  93. package/src/operations/nexus-scope-map.ts +9 -0
  94. package/src/operations/nexus.ts +281 -0
  95. package/src/operations/orchestrate.ts +6 -2
  96. package/src/operations/output-contracts-data.ts +125 -1
  97. package/src/operations/tasks.ts +104 -12
  98. package/src/project-lifecycle.ts +6 -2
  99. package/src/session.ts +20 -2
  100. package/src/task.ts +51 -1
  101. package/src/tools/index.ts +1 -0
  102. package/src/tools/score-task-priority.ts +29 -3
package/src/decide.ts CHANGED
@@ -143,6 +143,17 @@ export interface DecisionRequest {
143
143
  readonly state: DecisionState;
144
144
  /** Question name → question (1–32 entries). Names key the answers in {@link DecisionOutcome}. */
145
145
  readonly questions: Readonly<Record<string, DecisionQuestion>>;
146
+ /**
147
+ * Language hint (e.g. `en`). Sent only to a provider whose capabilities
148
+ * include `langHint`; others never see it (T12664).
149
+ */
150
+ readonly lang?: string;
151
+ /**
152
+ * Provider-side answer cache. `false` asks a provider with `cacheControl`
153
+ * not to answer from its cache (real latency, sensitive text). Ignored by
154
+ * providers without the capability.
155
+ */
156
+ readonly cache?: boolean;
146
157
  }
147
158
 
148
159
  /** Answer to a {@link NoulDecisionQuestion}. */
@@ -196,14 +207,226 @@ export interface DecisionOutcome {
196
207
  readonly latencyMs: number;
197
208
  /** Provider-reported cost in US dollars, when known. */
198
209
  readonly costUsd?: number;
210
+ /** Provider-reported cost in integer micro-dollars, when known (T12664). */
211
+ readonly costMicros?: number;
212
+ /**
213
+ * Account balance in integer micro-dollars reported with this answer, when
214
+ * known. The Jev wire adapter never sets it (the layahost OpenAPI reports
215
+ * balance only via `GET /v1/usage`, T12715); other providers may.
216
+ */
217
+ readonly balanceMicros?: number;
218
+ /** Provider model checkpoint that answered, when reported. */
219
+ readonly checkpoint?: string;
199
220
  /** Provider-reported input-token count, when known. */
200
221
  readonly inputTokens?: number;
201
222
  }
202
223
 
224
+ // ─── Provider kinds (T12713) ─────────────────────────────────────────────────
225
+
226
+ /**
227
+ * Every supported decision-provider kind, recommended first.
228
+ *
229
+ * - `layahost` — the hosted provider at {@link LAYAHOST_BASE_URL}; the user
230
+ * supplies only an API key.
231
+ * - `jev` — any other Jev-compatible endpoint; the user supplies the URL.
232
+ */
233
+ export const DECISION_PROVIDER_KINDS = ['layahost', 'jev'] as const;
234
+
235
+ /** A decision-provider kind. See {@link DECISION_PROVIDER_KINDS}. */
236
+ export type DecisionProviderKind = (typeof DECISION_PROVIDER_KINDS)[number];
237
+
238
+ /** Base URL of the layahost API (its OpenAPI `servers[0].url`). */
239
+ export const LAYAHOST_BASE_URL = 'https://layahost.com';
240
+
241
+ /**
242
+ * Base URL of Jev's own API (TypeSafe AI). Source: the canonical spec
243
+ * `system-one-integration`, "Provider research: capability map" ("Jev itself
244
+ * (TypeSafe AI, `https://api.typesafe.ai`, OpenAPI v0.2.0)").
245
+ */
246
+ export const JEV_DEFAULT_BASE_URL = 'https://api.typesafe.ai';
247
+
248
+ /** layahost's routing model, the default for a layahost connection. */
249
+ export const LAYAHOST_DEFAULT_MODEL = 'laya-auto';
250
+
251
+ // ─── Named provider profiles (T12733) ────────────────────────────────────────
252
+
253
+ /**
254
+ * A valid profile name: 1-32 characters of lowercase letters, digits and
255
+ * `-`, starting and ending with a letter or digit (e.g. `default`, `work`,
256
+ * `team-b`). A profile is addressed as `<provider>/<name>`, e.g.
257
+ * `layahost/default` or `jev/team`; one provider may hold several profiles
258
+ * (accounts), each with its own key.
259
+ */
260
+ export const DECIDE_PROFILE_NAME_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,30}[a-z0-9])?$/;
261
+
262
+ /** The profile name used when none is given: `<provider>/default`. */
263
+ export const DECIDE_DEFAULT_PROFILE_NAME = 'default';
264
+
265
+ /**
266
+ * The stored `baseUrl` of a profile that uses its provider's preset URL. It
267
+ * is resolved when the profile is used, so a changed preset URL flows
268
+ * through to every profile that kept the default.
269
+ */
270
+ export const DECIDE_PROFILE_DEFAULT_URL = 'default';
271
+
272
+ /** How a profile's base URL is set: the provider's preset URL, or an override. */
273
+ export type DecideProfileUrlSource = 'default' | 'override';
274
+
275
+ /**
276
+ * Reachability of a decision provider, as `GET /v1/models` reports it.
277
+ *
278
+ * - `reachable` — the listing answered 2xx.
279
+ * - `unauthorized` — the provider rejected the key.
280
+ * - `key_limit_reached` — the key's monthly decision limit is reached.
281
+ * - `unconfigured` — no valid base URL + key stored.
282
+ * - `unreachable` — network failure, timeout, or any other HTTP status.
283
+ */
284
+ export type DecideProviderState =
285
+ | 'reachable'
286
+ | 'unauthorized'
287
+ | 'key_limit_reached'
288
+ | 'unconfigured'
289
+ | 'unreachable';
290
+
291
+ /** Result of probing one profile (`cleo decide profiles --probe`). Secret-free. */
292
+ export interface DecideProfileProbe {
293
+ /** Reachability verdict. */
294
+ readonly state: DecideProviderState;
295
+ /** HTTP status of the probe, when one was received. */
296
+ readonly httpStatus?: number;
297
+ /** Probe latency, ms. */
298
+ readonly latencyMs?: number;
299
+ /** Secret-free explanation, when there is one. */
300
+ readonly detail?: string;
301
+ }
302
+
303
+ /** One named System One provider profile, as listed. Carries no key, only its masked preview. */
304
+ export interface DecideProfileSummary {
305
+ /** Profile id, `<provider>/<name>`, e.g. `layahost/work`. */
306
+ readonly id: string;
307
+ /** Profile name within its provider, e.g. `work`. */
308
+ readonly name: string;
309
+ /** Whether everyday decisions use this profile. Exactly one profile is active when any is. */
310
+ readonly active: boolean;
311
+ /** Whether the profile holds an acceptable base URL and a non-blank key. */
312
+ readonly configured: boolean;
313
+ /** Provider kind. */
314
+ readonly provider: DecisionProviderKind;
315
+ /** Resolved base URL (userinfo removed). */
316
+ readonly baseUrl: string;
317
+ /** Whether the URL is the provider's preset (`default`) or an override. */
318
+ readonly urlSource: DecideProfileUrlSource;
319
+ /** Default model, when stored. */
320
+ readonly model?: string;
321
+ /** Masked key preview (`…abcd`). */
322
+ readonly keyPreview: string;
323
+ /** ISO timestamp of the profile's last write, when known. */
324
+ readonly updatedAt?: string;
325
+ /** Reachability, when probed. */
326
+ readonly probe?: DecideProfileProbe;
327
+ }
328
+
329
+ /**
330
+ * A profile resolved for use (`resolveDecideProfile`): its id, provider, base
331
+ * URL (a stored `default` already resolved to the preset), plaintext API key
332
+ * and default model. Contains the key: pass it to the wire and never log or
333
+ * emit it. `profile` repeats `name` so the value can be used directly as a
334
+ * decision connection whose provider state is cached per profile.
335
+ */
336
+ export interface DecideProfileConnection {
337
+ /** Profile id, `<provider>/<name>`. */
338
+ readonly name: string;
339
+ /** Same as {@link DecideProfileConnection.name}. */
340
+ readonly profile: string;
341
+ /** Provider kind. */
342
+ readonly provider: DecisionProviderKind;
343
+ /** Resolved base URL. */
344
+ readonly baseUrl: string;
345
+ /** Plaintext API key. SECRET. */
346
+ readonly apiKey: string;
347
+ /** Default model, when stored. */
348
+ readonly model?: string;
349
+ }
350
+
351
+ /** Result of listing the System One profiles. Secret-free. */
352
+ export interface DecideProfileListResult {
353
+ /** Absolute path of the credential store. */
354
+ readonly path: string;
355
+ /** Id (`<provider>/<name>`) of the active profile, or `null` when none is configured. */
356
+ readonly active: string | null;
357
+ /** Every stored profile, sorted by id. */
358
+ readonly profiles: readonly DecideProfileSummary[];
359
+ /**
360
+ * True when the top-level settings disagreed with the active profile's
361
+ * entry (an older CLEO rewrote the file) and were taken as authoritative.
362
+ */
363
+ readonly reconciled: boolean;
364
+ }
365
+
203
366
  /** Connection settings for a decision provider. */
204
367
  export interface DecisionProviderConfig {
205
368
  /** Absolute base URL of the provider's API (no trailing path to the endpoint). */
206
369
  readonly baseUrl: string;
370
+ /** Provider kind; absent means a custom Jev-compatible endpoint (`jev`). */
371
+ readonly provider?: DecisionProviderKind;
372
+ }
373
+
374
+ // ─── Provider capabilities (T12664) ──────────────────────────────────────────
375
+
376
+ /**
377
+ * What a decision provider can do beyond the Jev minimum
378
+ * (`POST /v1/systemone`, 32 questions, 32,000-character state). A caller asks
379
+ * for a capability; the client degrades when it is absent (spec
380
+ * `system-one-integration` §2.2).
381
+ */
382
+ export interface DecisionProviderCapabilities {
383
+ /** Wire protocol the provider speaks. */
384
+ readonly wire: string;
385
+ /** Most questions one request may carry. */
386
+ readonly maxQuestionsPerRequest: number;
387
+ /** Largest state, in characters. */
388
+ readonly maxStateChars: number;
389
+ /** Batch endpoint limits, when the provider has one. */
390
+ readonly batch?: { readonly maxRequests: number; readonly maxQuestions: number };
391
+ /** Built-in template names, when the provider has templates. */
392
+ readonly templates?: readonly string[];
393
+ /** Server-side flows are available. */
394
+ readonly flows?: boolean;
395
+ /** A usage/balance endpoint and cost headers are available. */
396
+ readonly usage?: boolean;
397
+ /** The request's `cache` field is honoured. */
398
+ readonly cacheControl?: boolean;
399
+ /** The request's `lang` field is honoured. */
400
+ readonly langHint?: boolean;
401
+ /** How the provider reports cost. */
402
+ readonly reportsCost?: 'micros' | 'usd' | false;
403
+ }
404
+
405
+ /** The Jev minimum every provider supports; the default when a provider reports nothing. */
406
+ export const JEV_MINIMUM_CAPABILITIES: DecisionProviderCapabilities = {
407
+ wire: 'jev-systemone/1',
408
+ maxQuestionsPerRequest: 32,
409
+ maxStateChars: 32_000,
410
+ reportsCost: 'usd',
411
+ };
412
+
413
+ /** One item of a batch call: its outcome, or why it failed. */
414
+ export type DecisionBatchItem =
415
+ | { readonly ok: true; readonly outcome: DecisionOutcome }
416
+ | { readonly ok: false; readonly status?: number; readonly errorKind: string };
417
+
418
+ /** Account usage and balance as a provider reports it. */
419
+ export interface DecisionProviderUsage {
420
+ /** Balance in integer micro-dollars. */
421
+ readonly balanceMicros?: number;
422
+ /** Decisions the balance still covers. */
423
+ readonly decisionsLeft?: number;
424
+ /** Plan name. */
425
+ readonly plan?: string;
426
+ /** End of the current plan period, ISO-8601. */
427
+ readonly periodEnd?: string;
428
+ /** Decisions and cost over the queried window. */
429
+ readonly totals?: { readonly decisions?: number; readonly costMicros?: number };
207
430
  }
208
431
 
209
432
  // ─── Zod schemas ──────────────────────────────────────────────────────────────
@@ -290,6 +513,11 @@ export const decisionQuestionSchema = z.discriminatedUnion('type', [
290
513
  */
291
514
  export const decisionRequestSchema = z.object({
292
515
  model: z.string().min(1).optional(),
516
+ lang: z
517
+ .string()
518
+ .regex(/^[a-z]{2,3}(-[A-Za-z0-9]{2,8})?$/)
519
+ .optional(),
520
+ cache: z.boolean().optional(),
293
521
  state: decisionStateSchema,
294
522
  questions: z.record(z.string().min(1), decisionQuestionSchema).refine(
295
523
  (questions) => {
@@ -346,10 +574,17 @@ export const decisionOutcomeSchema = z.object({
346
574
  requestId: z.string().min(1).optional(),
347
575
  latencyMs: z.number().nonnegative(),
348
576
  costUsd: z.number().nonnegative().optional(),
577
+ costMicros: z.number().int().nonnegative().optional(),
578
+ balanceMicros: z.number().int().optional(),
579
+ checkpoint: z.string().min(1).optional(),
349
580
  inputTokens: z.number().int().nonnegative().optional(),
350
581
  });
351
582
 
583
+ /** Zod schema for {@link DecisionProviderKind}. */
584
+ export const decisionProviderKindSchema = z.enum(DECISION_PROVIDER_KINDS);
585
+
352
586
  /** Zod schema for {@link DecisionProviderConfig}; `baseUrl` must be an absolute URL. */
353
587
  export const decisionProviderConfigSchema = z.object({
354
588
  baseUrl: z.url(),
589
+ provider: decisionProviderKindSchema.optional(),
355
590
  });
@@ -814,12 +814,22 @@ export const OPERATIONS: OperationDef[] = [
814
814
  gateway: 'query' as const,
815
815
  domain: 'tasks' as const,
816
816
  operation: 'history',
817
- description: 'Show task work history (time tracked per task)',
817
+ description:
818
+ 'Show task work history; with taskId, the task audit trail; with ranking, who changed its priority/severity/kind/depends and why (T12693)',
818
819
  tier: 1,
819
820
  idempotent: true,
820
821
  sessionRequired: false,
821
822
  requiredParams: [],
822
- params: [],
823
+ params: [
824
+ { name: 'taskId', type: 'string', required: false, description: 'Task to show history for' },
825
+ { name: 'limit', type: 'number', required: false, description: 'Maximum entries' },
826
+ {
827
+ name: 'ranking',
828
+ type: 'boolean',
829
+ required: false,
830
+ description: 'Only ranking-input changes (actor, session, reason, before/after)',
831
+ },
832
+ ] satisfies ParamDef[],
823
833
  },
824
834
  {
825
835
  gateway: 'query',
@@ -3062,6 +3072,14 @@ export const OPERATIONS: OperationDef[] = [
3062
3072
  description: 'taskId parameter',
3063
3073
  cli: { positional: true },
3064
3074
  },
3075
+ {
3076
+ name: 'expectedUpdatedAt',
3077
+ type: 'string',
3078
+ required: false,
3079
+ description:
3080
+ 'Optimistic-concurrency guard: fail with E_CONFLICT unless the task updatedAt still equals this value',
3081
+ cli: { flag: 'if-match' },
3082
+ },
3065
3083
  ] satisfies ParamDef[],
3066
3084
  },
3067
3085
  {
@@ -3364,6 +3382,33 @@ export const OPERATIONS: OperationDef[] = [
3364
3382
  ] satisfies ParamDef[],
3365
3383
  outputSchema: OUTPUT_CONTRACTS['tasks.assignee'],
3366
3384
  },
3385
+ {
3386
+ gateway: 'mutate',
3387
+ domain: 'tasks',
3388
+ operation: 'ranking.revert',
3389
+ description:
3390
+ 'tasks.ranking.revert (mutate) — undo one recorded ranking change (priority/severity/kind/depends) as a new audited change (T12693, D11161)',
3391
+ tier: 1,
3392
+ idempotent: false,
3393
+ sessionRequired: false,
3394
+ requiredParams: ['entryId'],
3395
+ params: [
3396
+ {
3397
+ name: 'entryId',
3398
+ type: 'string',
3399
+ required: true,
3400
+ description: 'ranking_changed audit row id (cleo history ranking <taskId>)',
3401
+ cli: { positional: true },
3402
+ },
3403
+ { name: 'reason', type: 'string', required: false, description: 'Why it is reverted' },
3404
+ {
3405
+ name: 'force',
3406
+ type: 'boolean',
3407
+ required: false,
3408
+ description: 'Revert even though a field changed again since',
3409
+ },
3410
+ ] satisfies ParamDef[],
3411
+ },
3367
3412
  {
3368
3413
  gateway: 'mutate',
3369
3414
  domain: 'tasks',
@@ -3711,7 +3756,8 @@ export const OPERATIONS: OperationDef[] = [
3711
3756
  gateway: 'mutate',
3712
3757
  domain: 'tasks',
3713
3758
  operation: 'start',
3714
- description: 'tasks.start (mutate)',
3759
+ description:
3760
+ "tasks.start (mutate) — start work and take the caller session's leased claim (E_TASK_CLAIMED when another session holds it)",
3715
3761
  tier: 0,
3716
3762
  idempotent: false,
3717
3763
  sessionRequired: false,
@@ -3724,6 +3770,20 @@ export const OPERATIONS: OperationDef[] = [
3724
3770
  description: 'taskId parameter',
3725
3771
  cli: { positional: true },
3726
3772
  },
3773
+ {
3774
+ name: 'takeOver',
3775
+ type: 'boolean',
3776
+ required: false,
3777
+ description: "Take over another session's EXPIRED claim lease (audited)",
3778
+ cli: { flag: 'take-over' },
3779
+ },
3780
+ {
3781
+ name: 'forceClaim',
3782
+ type: 'boolean',
3783
+ required: false,
3784
+ description: "Take over another session's LIVE claim lease (audited)",
3785
+ cli: { flag: 'force-claim' },
3786
+ },
3727
3787
  ] satisfies ParamDef[],
3728
3788
  },
3729
3789
  {
@@ -3832,11 +3892,12 @@ export const OPERATIONS: OperationDef[] = [
3832
3892
  gateway: 'mutate',
3833
3893
  domain: 'tasks',
3834
3894
  operation: 'claim',
3835
- description: 'tasks.claim (mutate) — claim a task by assigning it to the current session',
3895
+ description:
3896
+ "tasks.claim (mutate) — take, renew or override the caller session's leased claim on a task (never the human assignee)",
3836
3897
  tier: 0,
3837
3898
  idempotent: false,
3838
3899
  sessionRequired: true,
3839
- requiredParams: ['taskId', 'agentId'],
3900
+ requiredParams: ['taskId'],
3840
3901
  params: [
3841
3902
  {
3842
3903
  name: 'taskId',
@@ -3847,8 +3908,27 @@ export const OPERATIONS: OperationDef[] = [
3847
3908
  {
3848
3909
  name: 'agentId',
3849
3910
  type: 'string',
3850
- required: true,
3851
- description: 'Agent ID to assign the task to',
3911
+ required: false,
3912
+ description:
3913
+ "Agent ID recorded with the lease (default: the session's agent / CLEO_AGENT_ID)",
3914
+ },
3915
+ {
3916
+ name: 'renew',
3917
+ type: 'boolean',
3918
+ required: false,
3919
+ description: "Renew the caller's own lease",
3920
+ },
3921
+ {
3922
+ name: 'takeOver',
3923
+ type: 'boolean',
3924
+ required: false,
3925
+ description: "Take over another session's EXPIRED lease (audited)",
3926
+ },
3927
+ {
3928
+ name: 'forceClaim',
3929
+ type: 'boolean',
3930
+ required: false,
3931
+ description: "Take over another session's LIVE lease (audited)",
3852
3932
  },
3853
3933
  ],
3854
3934
  },
@@ -3856,7 +3936,8 @@ export const OPERATIONS: OperationDef[] = [
3856
3936
  gateway: 'mutate',
3857
3937
  domain: 'tasks',
3858
3938
  operation: 'unclaim',
3859
- description: 'tasks.unclaim (mutate) — unclaim a task by removing the current assignee',
3939
+ description:
3940
+ "tasks.unclaim (mutate) — release the caller session's claim lease (never the human assignee)",
3860
3941
  tier: 0,
3861
3942
  idempotent: false,
3862
3943
  sessionRequired: true,
@@ -3868,6 +3949,12 @@ export const OPERATIONS: OperationDef[] = [
3868
3949
  required: true,
3869
3950
  description: 'Task ID to unclaim',
3870
3951
  },
3952
+ {
3953
+ name: 'forceClaim',
3954
+ type: 'boolean',
3955
+ required: false,
3956
+ description: "Release another session's lease (audited)",
3957
+ },
3871
3958
  ],
3872
3959
  },
3873
3960
  {
@@ -4127,6 +4214,20 @@ export const OPERATIONS: OperationDef[] = [
4127
4214
  description:
4128
4215
  'When true (default), adds toTaskId as a dependency on fromTaskId so it cannot complete before the pivot resolves',
4129
4216
  },
4217
+ {
4218
+ name: 'takeOver',
4219
+ type: 'boolean',
4220
+ required: false,
4221
+ description: "Take over another session's EXPIRED claim on toTaskId (audited)",
4222
+ cli: { flag: 'take-over' },
4223
+ },
4224
+ {
4225
+ name: 'forceClaim',
4226
+ type: 'boolean',
4227
+ required: false,
4228
+ description: "Take over another session's LIVE claim on toTaskId (audited)",
4229
+ cli: { flag: 'force-claim' },
4230
+ },
4130
4231
  ],
4131
4232
  },
4132
4233
  // parallel.start and parallel.end merged into orchestrate.parallel via action param (T5615)
@@ -6870,6 +6971,80 @@ export const OPERATIONS: OperationDef[] = [
6870
6971
  params: [],
6871
6972
  },
6872
6973
 
6974
+ {
6975
+ gateway: 'query' as const,
6976
+ domain: 'nexus',
6977
+ operation: 'projects.fleet',
6978
+ description:
6979
+ 'nexus.projects.fleet (query) — fleet view: every project with its location on each device, last recorded git state (local, and remote as of the last fetch) and staleness; paged, counts first, read-only',
6980
+ tier: 1,
6981
+ idempotent: true,
6982
+ sessionRequired: false,
6983
+ requiredParams: [],
6984
+ params: [
6985
+ {
6986
+ name: 'device',
6987
+ type: 'string',
6988
+ required: false,
6989
+ description: 'Only locations on this device (device id, hostname, or current)',
6990
+ },
6991
+ {
6992
+ name: 'missing',
6993
+ type: 'boolean',
6994
+ required: false,
6995
+ description: 'Only projects with a missing location',
6996
+ },
6997
+ {
6998
+ name: 'dirty',
6999
+ type: 'boolean',
7000
+ required: false,
7001
+ description: 'Only projects with a dirty location',
7002
+ },
7003
+ {
7004
+ name: 'behind',
7005
+ type: 'boolean',
7006
+ required: false,
7007
+ description: 'Only projects with a location behind its upstream (as of the last fetch)',
7008
+ },
7009
+ {
7010
+ name: 'ahead',
7011
+ type: 'boolean',
7012
+ required: false,
7013
+ description: 'Only projects with a location ahead of its upstream (as of the last fetch)',
7014
+ },
7015
+ {
7016
+ name: 'stale',
7017
+ type: 'boolean',
7018
+ required: false,
7019
+ description: 'Only projects with a never-probed, old-probe or old-fetch location',
7020
+ },
7021
+ {
7022
+ name: 'errored',
7023
+ type: 'boolean',
7024
+ required: false,
7025
+ description: 'Only projects whose last probe recorded an error',
7026
+ },
7027
+ {
7028
+ name: 'staleAfterMs',
7029
+ type: 'number',
7030
+ required: false,
7031
+ description: 'Staleness window for probes, fetches and heartbeats (default 24h)',
7032
+ },
7033
+ {
7034
+ name: 'limit',
7035
+ type: 'number',
7036
+ required: false,
7037
+ description: 'Page size in projects (default 50, max 500; 0 = all)',
7038
+ },
7039
+ {
7040
+ name: 'offset',
7041
+ type: 'number',
7042
+ required: false,
7043
+ description: 'Projects skipped before the page',
7044
+ },
7045
+ ],
7046
+ },
7047
+
6873
7048
  {
6874
7049
  gateway: 'mutate' as const,
6875
7050
  domain: 'nexus',
package/src/doctor.ts CHANGED
@@ -880,6 +880,15 @@ export interface DbSubstrateSurveyOptions {
880
880
  * `false` → leave the corrupt DB in place; only report it.
881
881
  */
882
882
  autoQuarantine?: boolean;
883
+ /**
884
+ * Directory the survey was invoked from (T12708). Quarantine moves a
885
+ * project's live store; from a linked worktree of that project it needs
886
+ * {@link DbSubstrateSurveyOptions.confirmOwnerStore}, and it never moves a
887
+ * store inside a worktree. Defaults to the process directory.
888
+ */
889
+ cwd?: string;
890
+ /** Confirm quarantining the owning project's LIVE store from a worktree (T12708). */
891
+ confirmOwnerStore?: boolean;
883
892
  }
884
893
 
885
894
  /**
@@ -500,6 +500,9 @@ export const GATE_EVIDENCE_REQUIREMENTS: Readonly<
500
500
  ['decision', 'files'],
501
501
  ['decision', 'note'],
502
502
  ['pr', 'files'],
503
+ // T12689: a deletion-only PR (no bytes to hash). Contextual validation
504
+ // accepts this only when every change the PR made is a deletion.
505
+ ['pr', 'note'],
503
506
  ],
504
507
  },
505
508
  testsPassed: { oneOf: [['test-run'], ['tool'], ['ci']] },
package/src/exit-codes.ts CHANGED
@@ -75,6 +75,13 @@ export enum ExitCode {
75
75
  SCOPE_CONFLICT = 32,
76
76
  SCOPE_INVALID = 33,
77
77
  TASK_NOT_IN_SCOPE = 34,
78
+ /**
79
+ * Another session holds the claim lease on the task (`cleo start`,
80
+ * `cleo claim`, spawn). LAFS code `E_TASK_CLAIMED`; the details
81
+ * (`TaskClaimedDetails`) name the holder session and agent, the lease
82
+ * expiry, and the explicit, audited override: `--take-over` for an expired
83
+ * lease, `--force-claim` for a live one. @task T12502
84
+ */
78
85
  TASK_CLAIMED = 35,
79
86
  SESSION_REQUIRED = 36,
80
87
  SESSION_CLOSE_BLOCKED = 37,