@cleocode/contracts 2026.10.18 → 2026.10.19

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 (56) hide show
  1. package/dist/branch-lock.d.ts +14 -3
  2. package/dist/branch-lock.d.ts.map +1 -1
  3. package/dist/branch-lock.js +8 -3
  4. package/dist/branch-lock.js.map +1 -1
  5. package/dist/dispatch/operations-registry.d.ts.map +1 -1
  6. package/dist/dispatch/operations-registry.js +7 -0
  7. package/dist/dispatch/operations-registry.js.map +1 -1
  8. package/dist/done-plan.d.ts +69 -0
  9. package/dist/done-plan.d.ts.map +1 -1
  10. package/dist/goal-contract.d.ts +44 -4
  11. package/dist/goal-contract.d.ts.map +1 -1
  12. package/dist/goal-contract.js +2 -2
  13. package/dist/goal-contract.js.map +1 -1
  14. package/dist/goal-runtime.d.ts +89 -0
  15. package/dist/goal-runtime.d.ts.map +1 -0
  16. package/dist/goal-runtime.js +13 -0
  17. package/dist/goal-runtime.js.map +1 -0
  18. package/dist/goal.d.ts +12 -116
  19. package/dist/goal.d.ts.map +1 -1
  20. package/dist/goal.js +9 -72
  21. package/dist/goal.js.map +1 -1
  22. package/dist/heavy-command-hook.d.ts +6 -0
  23. package/dist/heavy-command-hook.d.ts.map +1 -1
  24. package/dist/index.d.ts +6 -4
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +2 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/invariants/sync-write-invariants.js +6 -5
  29. package/dist/invariants/sync-write-invariants.js.map +1 -1
  30. package/dist/operations/docs.d.ts +2 -2
  31. package/dist/operations/docs.d.ts.map +1 -1
  32. package/dist/project-hooks.d.ts +15 -2
  33. package/dist/project-hooks.d.ts.map +1 -1
  34. package/dist/project-hooks.js +8 -0
  35. package/dist/project-hooks.js.map +1 -1
  36. package/dist/release/hook-pilot.d.ts +141 -2
  37. package/dist/release/hook-pilot.d.ts.map +1 -1
  38. package/dist/release/hook-pilot.js +75 -3
  39. package/dist/release/hook-pilot.js.map +1 -1
  40. package/dist/task.d.ts +23 -0
  41. package/dist/task.d.ts.map +1 -1
  42. package/package.json +2 -2
  43. package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
  44. package/src/branch-lock.ts +14 -3
  45. package/src/dispatch/operations-registry.ts +8 -0
  46. package/src/done-plan.ts +73 -0
  47. package/src/goal-contract.ts +48 -4
  48. package/src/goal-runtime.ts +105 -0
  49. package/src/goal.ts +12 -133
  50. package/src/heavy-command-hook.ts +9 -0
  51. package/src/index.ts +18 -7
  52. package/src/invariants/sync-write-invariants.ts +5 -5
  53. package/src/operations/docs.ts +3 -2
  54. package/src/project-hooks.ts +15 -1
  55. package/src/release/hook-pilot.ts +85 -3
  56. package/src/task.ts +23 -0
@@ -294,6 +294,12 @@ export interface OwnerOverrideAuditRecord {
294
294
  reason: string;
295
295
  /** Process ID. */
296
296
  pid: number;
297
+ /** The owner setting a set or refused set targeted (T13787). */
298
+ setting?: string;
299
+ /** The setting's value before the attempt (T13787). */
300
+ old_value?: number;
301
+ /** The value the attempt asked for, or set (T13787). */
302
+ new_value?: number;
297
303
  }
298
304
 
299
305
  /**
@@ -340,9 +346,14 @@ export const BRANCH_LOCK_ERROR_CODES = {
340
346
  /** L4d: session override limit exceeded. */
341
347
  E_OVERRIDE_RATE_LIMIT: 'E_OVERRIDE_RATE_LIMIT',
342
348
  /**
343
- * T1501 / P0-5: per-session cap (default 3) exceeded without a valid waiver doc.
344
- * Set CLEO_OWNER_OVERRIDE_WAIVER=<absolute path> to a file containing
345
- * `cap-waiver: true` in its frontmatter to bypass the cap.
349
+ * T13790: CLEO_OWNER_OVERRIDE from a caller that is not the owner — no
350
+ * owner-marked session (`cleo session start … --owner-auth`), or not on the
351
+ * owner's own terminal.
352
+ */
353
+ E_OVERRIDE_NOT_OWNER: 'E_OVERRIDE_NOT_OWNER',
354
+ /**
355
+ * T1501 / P0-5: per-session cap exceeded. The cap is the owner's sealed
356
+ * `evidence.overrideCapPerSession` (default 10, T13787); no waiver lifts it.
346
357
  */
347
358
  E_OVERRIDE_CAP_EXCEEDED: 'E_OVERRIDE_CAP_EXCEEDED',
348
359
  /**
@@ -9705,6 +9705,14 @@ export const OPERATIONS: OperationDef[] = [
9705
9705
  description:
9706
9706
  'Comma-separated task IDs forwarded as the plan scope for a task-scoped release',
9707
9707
  },
9708
+ // T13825: the single release PR's plan branch.
9709
+ {
9710
+ name: 'ref',
9711
+ type: 'string',
9712
+ required: false,
9713
+ description:
9714
+ 'Branch to dispatch release-prepare on (release/plan-<version>: the plan + CHANGELOG commit on top of the default branch), so the bump PR carries the plan too',
9715
+ },
9708
9716
  ] satisfies ParamDef[],
9709
9717
  },
9710
9718
 
package/src/done-plan.ts CHANGED
@@ -89,6 +89,8 @@ export interface DonePlanBlocker {
89
89
  next: DoneNextStep;
90
90
  /** Original evidence error code when the blocker wraps a validator refusal. */
91
91
  cause?: string;
92
+ /** The verification gate the blocker holds back, when it is one gate's (T13795). */
93
+ gate?: VerificationGate;
92
94
  }
93
95
 
94
96
  /** A merged PR that cites the task. */
@@ -373,3 +375,74 @@ export interface DoneBlockedDetails {
373
375
  /** The plan, for tooling. */
374
376
  plan: DonePlan;
375
377
  }
378
+
379
+ /** A test file a covering test-run report must run, and why. */
380
+ export interface OwedTestFile {
381
+ /** Repo-relative test file. */
382
+ file: string;
383
+ /** Why the binding requires it. */
384
+ reason: string;
385
+ }
386
+
387
+ /** An affected package a covering report must run at least one test file of. */
388
+ export interface OwedTestPackage {
389
+ /** Workspace package name. */
390
+ name: string;
391
+ /** Changed itself, or depends on a changed package. */
392
+ reason: 'changed' | 'depends on a changed package';
393
+ /** Test files of this package the change adds or edits (the natural choice). */
394
+ changedTestFiles: string[];
395
+ }
396
+
397
+ /**
398
+ * The tests a task's change owes, as the `test-run:` binding and
399
+ * `cleo done --plan` enforce them (T13795).
400
+ *
401
+ * - `targeted`: a passing report of every `files` entry plus at least one test
402
+ * file of every `packages` entry binds (config files and typecheck-only
403
+ * dependents are owed their own results).
404
+ * - `full`: the change is workspace-wide; only `tool:test` (or `ci:<pr>`) binds.
405
+ * - `none`: no changed path to judge (no origin default branch, or no change).
406
+ */
407
+ export interface TaskTestPlan {
408
+ /** Changed paths judged, or null when none could be read. */
409
+ changedPaths: string[] | null;
410
+ /** How the change can be tested. */
411
+ scope: 'targeted' | 'full' | 'none';
412
+ /** Why only a full run binds (`scope === 'full'`). */
413
+ reason?: string;
414
+ /** Exact test files a covering report must run. */
415
+ files: OwedTestFile[];
416
+ /** Packages a covering report must run at least one test file of. */
417
+ packages: OwedTestPackage[];
418
+ /** Affected packages with no test file at all. */
419
+ untestedPackages: string[];
420
+ /** CLEO config files the change edits, owed `tool:config-schema` instead of a test. */
421
+ configPaths: string[];
422
+ /** Typecheck-only dependent package dirs, owed a typecheck instead of a test. */
423
+ typecheckDirs: string[];
424
+ /**
425
+ * Whether the change has merged. A merged change is judged as `cleo complete`
426
+ * judges it: the task's own part of the landed change, and every test file it
427
+ * adds or edits owed by a test-run recorded on the default branch.
428
+ */
429
+ merged: boolean;
430
+ /** Whether the task already holds a standing testsPassed gate. */
431
+ gatePassed: boolean;
432
+ /** The testsPassed tool runs `cleo done` would use. */
433
+ toolRuns: DonePlanToolRun[];
434
+ /** The testsPassed blockers `cleo done --plan` reports. */
435
+ blockers: DonePlanBlocker[];
436
+ }
437
+
438
+ /** Result of `cleo tasks changeset <id> [--test-plan]` (T13795). */
439
+ export interface TaskChangeSetReport {
440
+ /** Task id. */
441
+ taskId: string;
442
+ /** The change set `cleo done` derives. */
443
+ changeSet: TaskChangeSet;
444
+ /** Commits of the change, oldest first. */
445
+ commits: string[];
446
+ /** The tests owed (with `--test-plan`). */
447
+ testPlan?: TaskTestPlan;
448
+ }
@@ -2,7 +2,7 @@
2
2
  * goal/v1 — the contract a goal runtime executes against, and what CLEO reads
3
3
  * back from a run.
4
4
  *
5
- * CLEO owns the goal protocol; OpenCode-Goal (OCG) is the goal runtime (owner
5
+ * CLEO owns the goal protocol; CLEO Goal (`@cleocode/goal`, bin `cleo-goal`, alias `ocg`) is the goal runtime (owner
6
6
  * decision 2026-10-16, epic T13791). The two stay separate packages and talk
7
7
  * only through the `cleo` CLI and LAFS envelopes:
8
8
  *
@@ -13,7 +13,7 @@
13
13
  * 3. `cleo goal close` reads the outcome and records evidence for each passing
14
14
  * criterion on the gate it demonstrates, via {@link GoalCriterion.proves}.
15
15
  *
16
- * The shapes mirror OCG's `src/contract/types.ts` field for field, in OCG's
16
+ * The shapes mirror CLEO Goal's `src/contract/types.ts` field for field, in CLEO Goal's
17
17
  * snake_case YAML spelling, so one file means the same thing on both sides.
18
18
  * The only addition is the optional per-criterion `proves` list.
19
19
  *
@@ -31,6 +31,8 @@
31
31
  * @task T13792
32
32
  */
33
33
 
34
+ import type { GoalRuntimeStatus } from './goal-runtime.js';
35
+
34
36
  /** The schema tag every goal/v1 contract carries. */
35
37
  export const GOAL_CONTRACT_SCHEMA = 'goal/v1' as const;
36
38
 
@@ -243,9 +245,9 @@ export type GoalVerificationMode = 'host' | 'host+verifier' | 'strict';
243
245
  export type GoalCleoGate = 'implemented' | 'testsPassed' | 'qaPassed';
244
246
 
245
247
  /**
246
- * A CLEO task the goal serves (OCG `CleoTaskLink`).
248
+ * A CLEO task the goal serves (CLEO Goal `CleoTaskLink`).
247
249
  *
248
- * The bare id links the task for reporting. `acs` is OCG's older per-gate
250
+ * The bare id links the task for reporting. `acs` is CLEO Goal's older per-gate
249
251
  * claim of which AC aliases a gate's evidence proves; per-criterion
250
252
  * {@link GoalCriterion.proves} is the precise form and what `cleo goal draft`
251
253
  * writes. An AC neither form claims stays open.
@@ -436,3 +438,45 @@ export interface GoalOutcome {
436
438
  /** Per-criterion and per-invariant verdicts. */
437
439
  results: GoalCriterionOutcome[];
438
440
  }
441
+
442
+ /** A drafted goal and the CLEO tasks it serves, from its `goal.yaml` (T13821). */
443
+ export interface GoalDraftEntry {
444
+ /** Goal slug (its directory under `.opencode/goals/`). */
445
+ slug: string;
446
+ /** Contract title, when the contract parses. */
447
+ title: string | null;
448
+ /** CLEO tasks the goal serves: `cleo.tasks` plus every task a criterion proves. */
449
+ tasks: string[];
450
+ /** Why the contract could not be read, when it could not. */
451
+ problem?: string;
452
+ }
453
+
454
+ /**
455
+ * `cleo goal status` (T13821): the drafted goals and the live run, which CLEO
456
+ * reads only through `cleo-goal status --json`, never the runtime's files.
457
+ */
458
+ export interface GoalStatusReport {
459
+ /** Whether the CLEO Goal bin (`cleo-goal`, alias `ocg`) is installed. */
460
+ runtime: 'installed' | 'not-installed';
461
+ /** The commands that install it, when it is not installed. */
462
+ install?: string[];
463
+ /** The live run, from `cleo-goal status --json`; null when not installed. */
464
+ live: GoalRuntimeStatus | null;
465
+ /** The drafted goals (filtered to one task with `--task`). */
466
+ goals: GoalDraftEntry[];
467
+ }
468
+
469
+ /**
470
+ * The goal runtime (CLEO Goal) as `cleo go` finds it, and the exact next step
471
+ * in the current harness (T13821).
472
+ */
473
+ export interface GoalRuntimeHandoff {
474
+ /** Whether the CLEO Goal bin (`cleo-goal`, then `ocg`) resolves. */
475
+ installed: boolean;
476
+ /** The resolved bin, when installed. */
477
+ bin?: string;
478
+ /** The commands that install it, when it is not installed. */
479
+ install?: string[];
480
+ /** The one next step, in plain words. */
481
+ next: string;
482
+ }
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Wire shapes of the CLEO Goal runtime bin (`@cleocode/goal`, bin `cleo-goal`,
3
+ * alias `ocg`) that CLEO parses. CLEO talks to the runtime only through these
4
+ * JSON outputs (T13791); an unknown `schemaVersion` is refused, never guessed.
5
+ *
6
+ * @task T13819
7
+ * @task T13820
8
+ * @task T13821
9
+ * @epic T13791
10
+ */
11
+
12
+ /** The only `schemaVersion` this CLEO understands for every shape below. */
13
+ export const GOAL_RUNTIME_SCHEMA_VERSION = 1;
14
+
15
+ /** Run state reported by `cleo-goal status --json`; `none` when no goal is live. */
16
+ export type GoalRuntimeRunStatus =
17
+ | 'running'
18
+ | 'waiting'
19
+ | 'verifying'
20
+ | 'paused'
21
+ | 'blocked'
22
+ | 'needs_review'
23
+ | 'budget_limited'
24
+ | 'complete'
25
+ | 'failed'
26
+ | 'aborted'
27
+ | 'superseded'
28
+ | 'none';
29
+
30
+ /** One criterion of the live goal. */
31
+ export interface GoalRuntimeCriterion {
32
+ readonly id: string;
33
+ readonly statement: string;
34
+ readonly status: string;
35
+ readonly essential: boolean;
36
+ readonly invariant: boolean;
37
+ readonly by: string;
38
+ readonly detail: string;
39
+ }
40
+
41
+ /** One budget dimension (turns, tokens, time, …). */
42
+ export interface GoalRuntimeBudgetPart {
43
+ readonly name: string;
44
+ readonly used: number;
45
+ readonly limit: number;
46
+ }
47
+
48
+ /**
49
+ * `cleo-goal status --json` stdout. The bin exits 0 whenever the project was
50
+ * readable, including with no goal (`goal: null`, `status: 'none'`).
51
+ */
52
+ export interface GoalRuntimeStatus {
53
+ readonly schemaVersion: typeof GOAL_RUNTIME_SCHEMA_VERSION;
54
+ readonly goal: string | null;
55
+ readonly runId: string | null;
56
+ readonly title: string;
57
+ readonly status: GoalRuntimeRunStatus;
58
+ readonly priority?: string;
59
+ readonly turn: number;
60
+ readonly reason: string | null;
61
+ readonly criteria: readonly GoalRuntimeCriterion[];
62
+ readonly proven: number;
63
+ readonly total: number;
64
+ readonly budget: { readonly ratio: number; readonly parts: readonly GoalRuntimeBudgetPart[] };
65
+ readonly verdict: {
66
+ readonly passed: boolean;
67
+ readonly lines: readonly string[];
68
+ readonly turn: number;
69
+ } | null;
70
+ readonly blocker: {
71
+ readonly key: string;
72
+ readonly count: number;
73
+ readonly reason: string;
74
+ } | null;
75
+ /** Epoch milliseconds. */
76
+ readonly updatedAt: number;
77
+ }
78
+
79
+ /**
80
+ * One OpenCode v2 `plugins` entry: a spec, a `[spec, options]` tuple, or a
81
+ * `{ package, options }` object.
82
+ */
83
+ export type GoalRuntimePluginEntry =
84
+ | string
85
+ | readonly [string, Readonly<Record<string, unknown>>]
86
+ | { readonly package: string; readonly options?: Readonly<Record<string, unknown>> };
87
+
88
+ /**
89
+ * `cleo-goal install --project --json` stdout on exit 0. Writes only the
90
+ * project's `.opencode/opencode.json` (OpenCode v2 `plugins` key).
91
+ */
92
+ export interface GoalRuntimeInstall {
93
+ readonly schemaVersion: typeof GOAL_RUNTIME_SCHEMA_VERSION;
94
+ readonly action: 'installed' | 'already-present' | 'updated' | 'dry-run';
95
+ readonly file: string;
96
+ /** The npm or git spec registered (`github:…#vX.Y.Z` until npm publish). */
97
+ readonly spec: string;
98
+ readonly plugins: readonly GoalRuntimePluginEntry[];
99
+ }
100
+
101
+ /** `cleo-goal` stdout on exit 1 (exit 2 is a usage error on stderr only). */
102
+ export interface GoalRuntimeError {
103
+ readonly schemaVersion: typeof GOAL_RUNTIME_SCHEMA_VERSION;
104
+ readonly error: string;
105
+ }
package/src/goal.ts CHANGED
@@ -1,38 +1,20 @@
1
1
  /**
2
- * CLEO-native goal-system contracts (Layer 4 of SG-COGNITIVE-SUBSTRATE).
2
+ * CLEO goal record contracts.
3
3
  *
4
- * A *goal* is a DB-persisted, per-agent, turn-budgeted intent that CLEO drives
5
- * to completion by judging progress after every agent turn. CLEO's goal loop is
6
- * designed to beat two prior-art systems:
7
- *
8
- * - **Claude-Code** — a `Stop` hook + a Haiku *transcript judge* (the model
9
- * reads the conversation and guesses whether the task is done). It has no
10
- * turn budget and no persistence: a fresh process forgets the goal.
11
- * - **Hermes** — a post-turn loop with a turn budget and parse-failure
12
- * auto-pause, but it still judges *fuzzy* intent against transcript text.
13
- *
14
- * CLEO's differentiator is the **evidence-gate-aware judge**: when a goal is to
15
- * *complete a task* (`complete T123`), the judge does NOT read the transcript.
16
- * It resolves the target task's actual `status` and re-uses the shipped ADR-051
17
- * evidence infrastructure ({@link GateEvidenceRequirement} +
18
- * `validateEvidenceForGate`) — the goal is satisfied ONLY when the task is
19
- * `done` with its required gates backed by real, validated evidence atoms
20
- * (commit reachable, file sha256 match, test-run hash match). Transcript text
21
- * can never satisfy a task-completion goal. Fuzzy goals fall back to an
22
- * injectable LLM judge (see {@link GoalJudge}) so the model is consulted only
23
- * where no machine-checkable artifact exists.
24
- *
25
- * This module is the LEAF contract: it declares the persisted record shape
26
- * ({@link GoalRecord}), the lifecycle status union ({@link GoalStatus}), the
27
- * discriminated goal-kind union ({@link GoalKind}), and the judge verdict shape
28
- * ({@link GoalJudgeVerdict}) that every layer (store, judge, loop, continuation,
29
- * CLI) agrees on. It has ZERO runtime dependencies beyond sibling contracts.
4
+ * CLEO owns the goal protocol and CLEO Goal is the goal runtime (owner
5
+ * decision 2026-10-16, epic T13791, ADR-097 `adr-097-goal-protocol-ocg-runtime`). The
6
+ * protocol's portable shapes (goal/v1 contract, task link, outcome) live in
7
+ * `./goal-contract.ts`. This module keeps the DB-persisted per-agent goal
8
+ * record ({@link GoalRecord}) and its status and kind unions. CLEO's own
9
+ * turn-budgeted goal loop (judge, continuation builder, Stop-hook advance) was
10
+ * retired in T13797, with its judge and continuation contracts.
30
11
  *
31
12
  * @packageDocumentation
32
13
  * @module @cleocode/contracts/goal
33
14
  *
34
15
  * @epic T11290 EP-CLEO-GOAL-SYSTEM
35
16
  * @task T11376
17
+ * @task T13797
36
18
  * @saga T11283 SG-COGNITIVE-SUBSTRATE
37
19
  */
38
20
 
@@ -81,21 +63,6 @@ export const GOAL_STATUSES: readonly GoalStatus[] = Object.freeze([
81
63
  'impossible',
82
64
  ]);
83
65
 
84
- /**
85
- * The terminal {@link GoalStatus} values a continuation builder must NOT nudge:
86
- * a satisfied/abandoned/impossible goal yields no next-turn message.
87
- *
88
- * `paused` is intentionally absent — a paused goal CAN be resumed, so the
89
- * continuation builder still emits a nudge for it (see `buildContinuation`).
90
- *
91
- * @public
92
- */
93
- export const GOAL_TERMINAL_STATUSES: readonly GoalStatus[] = Object.freeze([
94
- 'satisfied',
95
- 'abandoned',
96
- 'impossible',
97
- ]);
98
-
99
66
  /**
100
67
  * Discriminant tag for the {@link GoalKind} union.
101
68
  *
@@ -125,7 +92,7 @@ export interface TaskCompletionGoal {
125
92
 
126
93
  /**
127
94
  * A goal with no machine-checkable completion artifact — satisfaction is
128
- * decided by the injectable LLM judge ({@link GoalJudge}) as a fallback.
95
+ * decided by an LLM judge as a fallback.
129
96
  *
130
97
  * The fuzzy path NEVER touches the evidence infrastructure; it exists only for
131
98
  * intents that cannot be reduced to a task + gates (e.g. "explore the auth
@@ -207,8 +174,8 @@ export interface GoalRecord {
207
174
  */
208
175
  readonly pausedReason: string | null;
209
176
  /**
210
- * The most recent judge verdict, persisted so `cleo goal status` can show the
211
- * last reason without re-running the judge. `null` before the first judge.
177
+ * The most recent judge verdict, persisted with the record. `null` before
178
+ * the first judge.
212
179
  */
213
180
  readonly lastVerdict: GoalJudgeVerdict | null;
214
181
  /** ISO-8601 creation timestamp. */
@@ -248,82 +215,6 @@ export interface GoalJudgeVerdict {
248
215
  readonly evidence?: readonly string[];
249
216
  }
250
217
 
251
- /**
252
- * Injectable LLM-judge interface for {@link FuzzyGoal} evaluation.
253
- *
254
- * The goal judge depends on THIS interface, never on a concrete model client,
255
- * so vitest can pass a deterministic mock and stay fully offline/hermetic — CI
256
- * must never flake on a live model call. Production wires a real LLM-backed
257
- * implementation; tests wire a stub that returns a canned verdict.
258
- *
259
- * @public
260
- */
261
- export interface GoalJudge {
262
- /**
263
- * Judge a fuzzy goal from its record alone (no transcript dependency in the
264
- * contract — an implementation MAY consult external context, but the
265
- * interface keeps the judge a pure function of the goal for testability).
266
- *
267
- * @param goal - The goal record under evaluation.
268
- * @returns The verdict (same shape as the evidence path).
269
- */
270
- judge(goal: GoalRecord): Promise<GoalJudgeVerdict>;
271
- }
272
-
273
- /**
274
- * A single next-turn continuation message produced by the cache-safe builder.
275
- *
276
- * Critically, the role is ALWAYS `'user'`: the goal loop nudges the agent by
277
- * appending a user-style message, NEVER by mutating the system prompt. A stable
278
- * system-prompt prefix is what makes prompt-caching effective; rewriting it on
279
- * every turn would bust the cache. See `buildContinuation` for the full
280
- * prompt-cache-safety contract.
281
- *
282
- * @public
283
- */
284
- export interface GoalContinuation {
285
- /** Always `'user'` — the nudge is a user message, never a system mutation. */
286
- readonly role: 'user';
287
- /** The continuation text: goal intent + outstanding criteria + judge reason. */
288
- readonly content: string;
289
- }
290
-
291
- /**
292
- * Result of advancing a goal one turn through the turn-budgeted loop.
293
- *
294
- * @public
295
- */
296
- export interface GoalAdvanceResult {
297
- /** The verdict the judge returned this turn. */
298
- readonly verdict: GoalJudgeVerdict;
299
- /** The status the goal transitions to after this turn. */
300
- readonly nextStatus: GoalStatus;
301
- /** Turns left after this one. Never negative. */
302
- readonly turnsRemaining: number;
303
- }
304
-
305
- /**
306
- * Type guard: is this a {@link TaskCompletionGoal} (evidence-judged)?
307
- *
308
- * @param kind - A {@link GoalKind} discriminated union value.
309
- * @returns `true` when the goal is judged via task status + evidence atoms.
310
- * @public
311
- */
312
- export function isTaskCompletionGoal(kind: GoalKind): kind is TaskCompletionGoal {
313
- return kind.kind === 'task-completion';
314
- }
315
-
316
- /**
317
- * Type guard: is this a {@link FuzzyGoal} (LLM-judged fallback)?
318
- *
319
- * @param kind - A {@link GoalKind} discriminated union value.
320
- * @returns `true` when the goal is judged by the injectable LLM judge.
321
- * @public
322
- */
323
- export function isFuzzyGoal(kind: GoalKind): kind is FuzzyGoal {
324
- return kind.kind === 'fuzzy';
325
- }
326
-
327
218
  /**
328
219
  * Test whether a string is a valid goal target task id (`T` + 1–7 digits).
329
220
  *
@@ -334,15 +225,3 @@ export function isFuzzyGoal(kind: GoalKind): kind is FuzzyGoal {
334
225
  export function isValidGoalTargetTaskId(value: string): boolean {
335
226
  return GOAL_TARGET_TASK_ID_REGEX.test(value);
336
227
  }
337
-
338
- /**
339
- * Test whether a {@link GoalStatus} is terminal (no further turns / nudges that
340
- * advance work). A terminal status yields NO continuation.
341
- *
342
- * @param status - A goal status.
343
- * @returns `true` for `satisfied` / `abandoned` / `impossible`.
344
- * @public
345
- */
346
- export function isTerminalGoalStatus(status: GoalStatus): boolean {
347
- return GOAL_TERMINAL_STATUSES.includes(status);
348
- }
@@ -303,4 +303,13 @@ export interface HeavyHookDeliveryApi {
303
303
  projectDir: string,
304
304
  options?: HeavyHookDeliveryOptions,
305
305
  ) => HeavyHookCliProbe;
306
+ /**
307
+ * Install only the Stop dispatcher (`cleo hook stop`) for one provider,
308
+ * regardless of the heavy-command mode (T13820). Optional: an older
309
+ * adapters build lacks it. Never throws.
310
+ */
311
+ readonly syncProjectStopDispatch?: (
312
+ projectDir: string,
313
+ provider: 'claude-code' | 'codex',
314
+ ) => Promise<HeavyHookDeliveryOutcome>;
306
315
  }
package/src/index.ts CHANGED
@@ -551,7 +551,11 @@ export type {
551
551
  DonePlanTypedGate,
552
552
  DoneRecordResult,
553
553
  DoneToolResult,
554
+ OwedTestFile,
555
+ OwedTestPackage,
554
556
  TaskChangeSet,
557
+ TaskChangeSetReport,
558
+ TaskTestPlan,
555
559
  } from './done-plan.js';
556
560
  export type {
557
561
  EngineErrorPayload,
@@ -730,9 +734,6 @@ export {
730
734
  // === CLEO-native Goal System (T11376 · Epic T11290 · Saga T11283) ===
731
735
  export type {
732
736
  FuzzyGoal,
733
- GoalAdvanceResult,
734
- GoalContinuation,
735
- GoalJudge,
736
737
  GoalJudgeVerdict,
737
738
  GoalKind,
738
739
  GoalKindTag,
@@ -743,10 +744,6 @@ export type {
743
744
  export {
744
745
  GOAL_STATUSES,
745
746
  GOAL_TARGET_TASK_ID_REGEX,
746
- GOAL_TERMINAL_STATUSES,
747
- isFuzzyGoal,
748
- isTaskCompletionGoal,
749
- isTerminalGoalStatus,
750
747
  isValidGoalTargetTaskId,
751
748
  } from './goal.js';
752
749
  // === goal/v1 protocol: contract, task link, outcome (T13792 · Epic T13791) ===
@@ -769,12 +766,15 @@ export type {
769
766
  GoalCriterion,
770
767
  GoalCriterionOutcome,
771
768
  GoalDiffCheck,
769
+ GoalDraftEntry,
772
770
  GoalFileCheck,
773
771
  GoalHumanCheck,
774
772
  GoalHumanEvidence,
775
773
  GoalOutcome,
776
774
  GoalPathEvidence,
777
775
  GoalPriority,
776
+ GoalRuntimeHandoff,
777
+ GoalStatusReport,
778
778
  GoalStep,
779
779
  GoalTaskLink,
780
780
  GoalVerdictSource,
@@ -784,6 +784,17 @@ export type {
784
784
  GoalVerifierQuote,
785
785
  } from './goal-contract.js';
786
786
  export { GOAL_CONTRACT_SCHEMA, GOAL_HOST_CHECK_KINDS } from './goal-contract.js';
787
+ // === CLEO Goal runtime bin wire shapes (T13819 / T13820 / T13821) ===
788
+ export type {
789
+ GoalRuntimeBudgetPart,
790
+ GoalRuntimeCriterion,
791
+ GoalRuntimeError,
792
+ GoalRuntimeInstall,
793
+ GoalRuntimePluginEntry,
794
+ GoalRuntimeRunStatus,
795
+ GoalRuntimeStatus,
796
+ } from './goal-runtime.js';
797
+ export { GOAL_RUNTIME_SCHEMA_VERSION } from './goal-runtime.js';
787
798
  // === Graph Intelligence Types (T512, T529, T1862, T9145) ===
788
799
  export type {
789
800
  AmbiguousProvenance,
@@ -490,7 +490,6 @@ export const SYNC_WRITE_INVARIANTS: readonly SyncWriteInvariant[] = Object.freez
490
490
  ['status'],
491
491
  ['PB-1', 'PB-4', 'PB-7', 'P33'],
492
492
  'an approval goes pending→decided once',
493
- ['tasks_playbook_approvals', 'playbook_approvals'],
494
493
  ),
495
494
  ),
496
495
  implemented(
@@ -548,15 +547,16 @@ export const SYNC_WRITE_INVARIANTS: readonly SyncWriteInvariant[] = Object.freez
548
547
  'the projection keeps the max-HLC head; a losing concurrent head is recorded (conflict_count/last_conflict_json), never silently shadowed',
549
548
  },
550
549
  // 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.
550
+ // once (identity shell keyed by the immutable uid). T13586: the rule is
551
+ // about id minting across replicas, so it is identity-layer — the shell's
552
+ // uid and birth_fp derive from the legacy doc, not the adopting device.
553
553
  {
554
554
  id: 'docs.doc.adopt-once',
555
- class: 'not-sync-relevant',
555
+ class: 'identity-layer',
556
556
  tables: ['conduit_attachments'],
557
557
  sites: [],
558
558
  reason:
559
- 'adopting a legacy doc inserts one immutable-uid identity shell; losing the write is refused loudly at the writer, not merged',
559
+ 'two devices adopting one legacy doc must mint one uid: the shell takes the legacy birth and sentinel facts, so (uid, birth_fp) converge and receive places the peer copy as a duplicate; a lost local write is refused loudly at the writer',
560
560
  },
561
561
  // T13359 (D11165) — decisions-vs-ADRs link table; the natural composite key
562
562
  // makes identical links merge to one row, and the revoke is a one-way latch.
@@ -1114,7 +1114,8 @@ export interface DocsDoctorDiagnostic {
1114
1114
  | 'wikilinks-drift'
1115
1115
  | 'legacy-store-present'
1116
1116
  | 'legacy-blob-coverage'
1117
- | 'stale-drafts';
1117
+ | 'stale-drafts'
1118
+ | 'conduit-history-gaps';
1118
1119
  /** `error` should be fixed promptly; `info` is observational. */
1119
1120
  severity: 'info' | 'warn' | 'error';
1120
1121
  /** Total offenders found (may exceed `items.length`). */
@@ -1157,7 +1158,7 @@ export interface DocsDoctorResult {
1157
1158
  olderThanDays: number;
1158
1159
  /** Backup receipt that authorised this run (null on dry runs). */
1159
1160
  backupReceiptPath: string | null;
1160
- /** All seven diagnostics, in declaration order. */
1161
+ /** All eight diagnostics, in declaration order. */
1161
1162
  diagnostics: readonly DocsDoctorDiagnostic[];
1162
1163
  /** Repair plan (dry run) or executed repairs (apply). */
1163
1164
  repairs: readonly DocsDoctorRepairAction[];