@cleocode/contracts 2026.5.129 → 2026.5.131

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 (175) hide show
  1. package/dist/__tests__/atomic-tools-contract.test.d.ts +14 -0
  2. package/dist/__tests__/atomic-tools-contract.test.d.ts.map +1 -0
  3. package/dist/__tests__/atomic-tools-contract.test.js +55 -0
  4. package/dist/__tests__/atomic-tools-contract.test.js.map +1 -0
  5. package/dist/__tests__/gateway-contract.test.d.ts +18 -0
  6. package/dist/__tests__/gateway-contract.test.d.ts.map +1 -0
  7. package/dist/__tests__/gateway-contract.test.js +187 -0
  8. package/dist/__tests__/gateway-contract.test.js.map +1 -0
  9. package/dist/__tests__/mvi.test.d.ts +17 -0
  10. package/dist/__tests__/mvi.test.d.ts.map +1 -0
  11. package/dist/__tests__/mvi.test.js +147 -0
  12. package/dist/__tests__/mvi.test.js.map +1 -0
  13. package/dist/__tests__/operation-def.test.js +1 -1
  14. package/dist/__tests__/operation-def.test.js.map +1 -1
  15. package/dist/boundary.d.ts +26 -0
  16. package/dist/boundary.d.ts.map +1 -1
  17. package/dist/boundary.js +42 -4
  18. package/dist/boundary.js.map +1 -1
  19. package/dist/branch-lock.d.ts +35 -1
  20. package/dist/branch-lock.d.ts.map +1 -1
  21. package/dist/branch-lock.js +23 -2
  22. package/dist/branch-lock.js.map +1 -1
  23. package/dist/daemon/__tests__/health-roundtrip.test.d.ts +13 -0
  24. package/dist/daemon/__tests__/health-roundtrip.test.d.ts.map +1 -0
  25. package/dist/daemon/__tests__/health-roundtrip.test.js +69 -0
  26. package/dist/daemon/__tests__/health-roundtrip.test.js.map +1 -0
  27. package/dist/daemon/health.d.ts +108 -0
  28. package/dist/daemon/health.d.ts.map +1 -0
  29. package/dist/daemon/health.js +95 -0
  30. package/dist/daemon/health.js.map +1 -0
  31. package/dist/daemon/index.d.ts +24 -0
  32. package/dist/daemon/index.d.ts.map +1 -0
  33. package/dist/daemon/index.js +22 -0
  34. package/dist/daemon/index.js.map +1 -0
  35. package/dist/daemon/subsystem.d.ts +121 -0
  36. package/dist/daemon/subsystem.d.ts.map +1 -0
  37. package/dist/daemon/subsystem.js +28 -0
  38. package/dist/daemon/subsystem.js.map +1 -0
  39. package/dist/daemon-ipc/__tests__/daemon-ipc-freeze.test.d.ts +15 -0
  40. package/dist/daemon-ipc/__tests__/daemon-ipc-freeze.test.d.ts.map +1 -0
  41. package/dist/daemon-ipc/__tests__/daemon-ipc-freeze.test.js +40 -0
  42. package/dist/daemon-ipc/__tests__/daemon-ipc-freeze.test.js.map +1 -0
  43. package/dist/daemon-ipc/index.d.ts +44 -0
  44. package/dist/daemon-ipc/index.d.ts.map +1 -0
  45. package/dist/daemon-ipc/index.js +43 -0
  46. package/dist/daemon-ipc/index.js.map +1 -0
  47. package/dist/db-inventory.json +8 -8
  48. package/dist/dispatch/identity.d.ts +1 -1
  49. package/dist/dispatch/identity.d.ts.map +1 -1
  50. package/dist/dispatch/identity.js +3 -0
  51. package/dist/dispatch/identity.js.map +1 -1
  52. package/dist/dispatch/operation-def.d.ts +16 -0
  53. package/dist/dispatch/operation-def.d.ts.map +1 -1
  54. package/dist/dispatch/operations-registry.d.ts.map +1 -1
  55. package/dist/dispatch/operations-registry.js +133 -0
  56. package/dist/dispatch/operations-registry.js.map +1 -1
  57. package/dist/exit-codes.d.ts +1 -1
  58. package/dist/exit-codes.js +1 -1
  59. package/dist/gateway/rpc/__tests__/freeze.test.d.ts +20 -0
  60. package/dist/gateway/rpc/__tests__/freeze.test.d.ts.map +1 -0
  61. package/dist/gateway/rpc/__tests__/freeze.test.js +122 -0
  62. package/dist/gateway/rpc/__tests__/freeze.test.js.map +1 -0
  63. package/dist/gateway/rpc/index.d.ts +23 -0
  64. package/dist/gateway/rpc/index.d.ts.map +1 -0
  65. package/dist/gateway/rpc/index.js +22 -0
  66. package/dist/gateway/rpc/index.js.map +1 -0
  67. package/dist/gateway/rpc/messages.d.ts +263 -0
  68. package/dist/gateway/rpc/messages.d.ts.map +1 -0
  69. package/dist/gateway/rpc/messages.js +152 -0
  70. package/dist/gateway/rpc/messages.js.map +1 -0
  71. package/dist/gateway/rpc/version.d.ts +39 -0
  72. package/dist/gateway/rpc/version.d.ts.map +1 -0
  73. package/dist/gateway/rpc/version.js +39 -0
  74. package/dist/gateway/rpc/version.js.map +1 -0
  75. package/dist/gateway.d.ts +239 -0
  76. package/dist/gateway.d.ts.map +1 -0
  77. package/dist/gateway.js +171 -0
  78. package/dist/gateway.js.map +1 -0
  79. package/dist/goal.d.ts +314 -0
  80. package/dist/goal.d.ts.map +1 -0
  81. package/dist/goal.js +118 -0
  82. package/dist/goal.js.map +1 -0
  83. package/dist/index.d.ts +9 -0
  84. package/dist/index.d.ts.map +1 -1
  85. package/dist/index.js +10 -0
  86. package/dist/index.js.map +1 -1
  87. package/dist/mvi.d.ts +209 -0
  88. package/dist/mvi.d.ts.map +1 -0
  89. package/dist/mvi.js +99 -0
  90. package/dist/mvi.js.map +1 -0
  91. package/dist/operations/attention.d.ts +114 -0
  92. package/dist/operations/attention.d.ts.map +1 -0
  93. package/dist/operations/attention.js +22 -0
  94. package/dist/operations/attention.js.map +1 -0
  95. package/dist/operations/docs.d.ts +7 -1
  96. package/dist/operations/docs.d.ts.map +1 -1
  97. package/dist/operations/docs.js.map +1 -1
  98. package/dist/operations/focus.d.ts +25 -0
  99. package/dist/operations/focus.d.ts.map +1 -1
  100. package/dist/operations/index.d.ts +1 -0
  101. package/dist/operations/index.d.ts.map +1 -1
  102. package/dist/operations/index.js +1 -0
  103. package/dist/operations/index.js.map +1 -1
  104. package/dist/operations/session.d.ts +11 -0
  105. package/dist/operations/session.d.ts.map +1 -1
  106. package/dist/sdk-tool.d.ts +1 -1
  107. package/dist/sdk-tool.js +1 -1
  108. package/dist/supervisor-ipc/__tests__/freeze.test.d.ts +15 -0
  109. package/dist/supervisor-ipc/__tests__/freeze.test.d.ts.map +1 -0
  110. package/dist/supervisor-ipc/__tests__/freeze.test.js +159 -0
  111. package/dist/supervisor-ipc/__tests__/freeze.test.js.map +1 -0
  112. package/dist/supervisor-ipc/index.d.ts +15 -0
  113. package/dist/supervisor-ipc/index.d.ts.map +1 -0
  114. package/dist/supervisor-ipc/index.js +14 -0
  115. package/dist/supervisor-ipc/index.js.map +1 -0
  116. package/dist/supervisor-ipc/messages.d.ts +457 -0
  117. package/dist/supervisor-ipc/messages.d.ts.map +1 -0
  118. package/dist/supervisor-ipc/messages.js +312 -0
  119. package/dist/supervisor-ipc/messages.js.map +1 -0
  120. package/dist/supervisor-ipc/version.d.ts +29 -0
  121. package/dist/supervisor-ipc/version.d.ts.map +1 -0
  122. package/dist/supervisor-ipc/version.js +29 -0
  123. package/dist/supervisor-ipc/version.js.map +1 -0
  124. package/dist/tools/__tests__/composite.test.d.ts +14 -0
  125. package/dist/tools/__tests__/composite.test.d.ts.map +1 -0
  126. package/dist/tools/__tests__/composite.test.js +75 -0
  127. package/dist/tools/__tests__/composite.test.js.map +1 -0
  128. package/dist/tools/atomic.d.ts +195 -0
  129. package/dist/tools/atomic.d.ts.map +1 -0
  130. package/dist/tools/atomic.js +82 -0
  131. package/dist/tools/atomic.js.map +1 -0
  132. package/dist/tools/composite.d.ts +128 -0
  133. package/dist/tools/composite.d.ts.map +1 -0
  134. package/dist/tools/composite.js +88 -0
  135. package/dist/tools/composite.js.map +1 -0
  136. package/package.json +35 -3
  137. package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
  138. package/src/__tests__/atomic-tools-contract.test.ts +67 -0
  139. package/src/__tests__/gateway-contract.test.ts +219 -0
  140. package/src/__tests__/mvi.test.ts +175 -0
  141. package/src/__tests__/operation-def.test.ts +2 -1
  142. package/src/boundary.ts +74 -4
  143. package/src/branch-lock.ts +49 -2
  144. package/src/daemon/__tests__/health-roundtrip.test.ts +84 -0
  145. package/src/daemon/health.ts +121 -0
  146. package/src/daemon/index.ts +35 -0
  147. package/src/daemon/subsystem.ts +126 -0
  148. package/src/daemon-ipc/__tests__/daemon-ipc-freeze.test.ts +53 -0
  149. package/src/daemon-ipc/index.ts +90 -0
  150. package/src/db-inventory.json +8 -8
  151. package/src/dispatch/identity.ts +3 -0
  152. package/src/dispatch/operation-def.ts +16 -0
  153. package/src/dispatch/operations-registry.ts +140 -0
  154. package/src/exit-codes.ts +1 -1
  155. package/src/gateway/rpc/__tests__/freeze.test.ts +139 -0
  156. package/src/gateway/rpc/index.ts +43 -0
  157. package/src/gateway/rpc/messages.ts +187 -0
  158. package/src/gateway/rpc/version.ts +40 -0
  159. package/src/gateway.ts +252 -0
  160. package/src/goal.ts +348 -0
  161. package/src/index.ts +109 -0
  162. package/src/mvi.ts +236 -0
  163. package/src/operations/attention.ts +117 -0
  164. package/src/operations/docs.ts +7 -1
  165. package/src/operations/focus.ts +26 -0
  166. package/src/operations/index.ts +1 -0
  167. package/src/operations/session.ts +11 -0
  168. package/src/sdk-tool.ts +1 -1
  169. package/src/supervisor-ipc/__tests__/freeze.test.ts +180 -0
  170. package/src/supervisor-ipc/index.ts +62 -0
  171. package/src/supervisor-ipc/messages.ts +396 -0
  172. package/src/supervisor-ipc/version.ts +30 -0
  173. package/src/tools/__tests__/composite.test.ts +88 -0
  174. package/src/tools/atomic.ts +274 -0
  175. package/src/tools/composite.ts +173 -0
package/src/gateway.ts ADDED
@@ -0,0 +1,252 @@
1
+ /**
2
+ * Gateway contract — the transport-agnostic CQRS dispatch shapes.
3
+ *
4
+ * Every operation flows through:
5
+ * GatewayRequest → Middleware → DomainHandler → GatewayResponse
6
+ *
7
+ * These shapes were promoted verbatim from `packages/cleo/src/dispatch/types.ts`
8
+ * into `@cleocode/contracts/gateway` (R3-T2 · T11446 · SG-RUNTIME-UNIFICATION)
9
+ * so that EVERY transport adapter — CLI, MCP, RPC, HTTP — shares one canonical,
10
+ * zod-validated contract instead of the CLI owning a private copy. The CLI was
11
+ * the only historical transport (`Source = 'cli'`); {@link GatewaySource} now
12
+ * widens that to the four planned transports.
13
+ *
14
+ * Design: the zod schemas are the single source of truth (AC8 — runtime
15
+ * validation at untrusted transport boundaries such as MCP/RPC/HTTP). The TS
16
+ * types are derived via {@link https://zod.dev | z.infer}, so the static and
17
+ * runtime contracts can never drift. The behavioral contracts that zod cannot
18
+ * model (function types: {@link DomainHandler}, {@link Middleware},
19
+ * {@link DispatchNext}) remain TS-only and reference the inferred data types.
20
+ *
21
+ * @epic T4820
22
+ * @task T11446
23
+ * @saga T11243
24
+ */
25
+
26
+ import type { LAFSPage, MVILevel } from '@cleocode/lafs';
27
+ import { isMVILevel } from '@cleocode/lafs';
28
+ import { z } from 'zod';
29
+ import type { CanonicalDomain, Gateway, Tier } from './dispatch/identity.js';
30
+ import type { ProblemDetails } from './engine-result.js';
31
+
32
+ export { CANONICAL_DOMAINS } from './dispatch/identity.js';
33
+ export type { CanonicalDomain, Gateway, Tier };
34
+
35
+ /**
36
+ * Frozen version of the gateway contract. Bump on any breaking change to the
37
+ * request/response/error shapes; the freeze + spec are owned by R3-T8 (T11452).
38
+ */
39
+ export const GATEWAY_CONTRACT_VERSION = '1.0.0' as const;
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // GatewaySource — transport of origin
43
+ // ---------------------------------------------------------------------------
44
+
45
+ /** The four canonical transports a gateway request can originate from. */
46
+ export const GATEWAY_SOURCES = ['cli', 'mcp', 'rpc', 'http'] as const;
47
+
48
+ /** Where a gateway request originated. Widened from the historical CLI-only `'cli'`. */
49
+ export const gatewaySourceSchema = z.enum(GATEWAY_SOURCES);
50
+
51
+ /** Where a gateway request originated. */
52
+ export type GatewaySource = z.infer<typeof gatewaySourceSchema>;
53
+
54
+ /**
55
+ * @deprecated Use {@link GatewaySource}. Retained as a structural alias so the
56
+ * historical `import type { Source }` sites keep compiling unchanged.
57
+ */
58
+ export type Source = GatewaySource;
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // Foreign-type bridges (kept opaque to avoid re-deriving lafs/contracts schemas)
62
+ // ---------------------------------------------------------------------------
63
+
64
+ /** MVI verbosity level — validated through lafs's own {@link isMVILevel} guard (DRY SoT). */
65
+ const mviLevelSchema = z.custom<MVILevel>((v) => isMVILevel(v));
66
+
67
+ /** LAFS pagination envelope — produced internally; carried opaquely across transports. */
68
+ const lafsPageSchema = z.custom<LAFSPage>();
69
+
70
+ /** RFC 9457 Problem Details — produced internally from CleoError.toProblemDetails(). */
71
+ const problemDetailsSchema = z.custom<ProblemDetails>();
72
+
73
+ // ---------------------------------------------------------------------------
74
+ // DispatchRequest
75
+ // ---------------------------------------------------------------------------
76
+
77
+ /**
78
+ * Canonical request shape produced by every transport adapter. The dispatcher
79
+ * validates this against the OperationRegistry before passing it through the
80
+ * middleware pipeline and into a {@link DomainHandler}.
81
+ */
82
+ export const dispatchRequestSchema = z.object({
83
+ /** CQRS gateway (command/query). */
84
+ gateway: z.custom<Gateway>(),
85
+ /** Target domain (canonical name). */
86
+ domain: z.string(),
87
+ /** Domain-specific operation name. */
88
+ operation: z.string(),
89
+ /** Operation parameters (already sanitized by middleware). */
90
+ params: z.record(z.string(), z.unknown()).optional(),
91
+ /** Which transport this request came from. */
92
+ source: gatewaySourceSchema,
93
+ /** Unique request identifier for tracing. */
94
+ requestId: z.string(),
95
+ /** Bound session ID, if any. */
96
+ sessionId: z.string().optional(),
97
+ /** Root session that originated the workflow/saga, if any. */
98
+ originSessionId: z.string().optional(),
99
+ /** Specific execution instance for this command/step, if any. */
100
+ executionSessionId: z.string().optional(),
101
+ /** LAFS field selection: filter response data to these fields only. */
102
+ _fields: z.array(z.string()).optional(),
103
+ /** LAFS envelope verbosity. Defaults to 'standard'. 'custom' is server-set via _fields. */
104
+ _mvi: mviLevelSchema.optional(),
105
+ });
106
+
107
+ /** {@inheritDoc dispatchRequestSchema} */
108
+ export type DispatchRequest = z.infer<typeof dispatchRequestSchema>;
109
+
110
+ // ---------------------------------------------------------------------------
111
+ // DispatchResponse + supporting shapes
112
+ // ---------------------------------------------------------------------------
113
+
114
+ /** Rate-limit metadata attached to every response. */
115
+ export const rateLimitMetaSchema = z.object({
116
+ limit: z.number(),
117
+ remaining: z.number(),
118
+ resetMs: z.number(),
119
+ category: z.string(),
120
+ });
121
+
122
+ /** {@inheritDoc rateLimitMetaSchema} */
123
+ export type RateLimitMeta = z.infer<typeof rateLimitMetaSchema>;
124
+
125
+ /** Structured error shape (LAFS-compatible). */
126
+ export const dispatchErrorSchema = z.object({
127
+ /** Machine-readable error code (E_NOT_FOUND, E_VALIDATION_FAILED, …). */
128
+ code: z.string(),
129
+ /** LAFS exit code (1-99). */
130
+ exitCode: z.number().optional(),
131
+ /** Human-readable message. */
132
+ message: z.string(),
133
+ /** Additional structured details. */
134
+ details: z.record(z.string(), z.unknown()).optional(),
135
+ /** Copy-paste fix command. */
136
+ fix: z.string().optional(),
137
+ /** Alternative actions the caller can try. */
138
+ alternatives: z.array(z.object({ action: z.string(), command: z.string() })).optional(),
139
+ /** RFC 9457 Problem Details (populated from CleoError.toProblemDetails()). */
140
+ problemDetails: problemDetailsSchema.optional(),
141
+ });
142
+
143
+ /** {@inheritDoc dispatchErrorSchema} */
144
+ export type DispatchError = z.infer<typeof dispatchErrorSchema>;
145
+
146
+ /**
147
+ * Always-present metadata block on every {@link DispatchResponse}.
148
+ *
149
+ * The catch-all (`[key: string]: unknown`) makes this assignable to
150
+ * `Record<string, unknown>` in covariant positions, eliminating the T9767
151
+ * cast-chain anti-pattern at every responseMeta call site.
152
+ *
153
+ * @task T9767
154
+ */
155
+ export const dispatchResponseMetaSchema = z
156
+ .object({
157
+ gateway: z.custom<Gateway>(),
158
+ domain: z.string(),
159
+ operation: z.string(),
160
+ timestamp: z.string(),
161
+ duration_ms: z.number(),
162
+ source: gatewaySourceSchema,
163
+ requestId: z.string(),
164
+ rateLimit: rateLimitMetaSchema.optional(),
165
+ /** Session ID that processed this request (T4959). */
166
+ sessionId: z.string().optional(),
167
+ /** Root session that originated the workflow/saga. */
168
+ originSessionId: z.string().optional(),
169
+ /** Specific execution instance for this command/step. */
170
+ executionSessionId: z.string().optional(),
171
+ /** Preserves protocol-level version for backward compat. */
172
+ version: z.string().optional(),
173
+ })
174
+ .catchall(z.unknown());
175
+
176
+ /** {@inheritDoc dispatchResponseMetaSchema} */
177
+ export type DispatchResponseMeta = z.infer<typeof dispatchResponseMetaSchema>;
178
+
179
+ /**
180
+ * Canonical response shape returned by the dispatcher. The CLI adapter
181
+ * translates this into cliOutput() / cliError() + process.exit(); other
182
+ * transports serialize it onto their wire.
183
+ */
184
+ export const dispatchResponseSchema = z.object({
185
+ /** Always-present metadata for every dispatch response. */
186
+ meta: dispatchResponseMetaSchema,
187
+ success: z.boolean(),
188
+ data: z.unknown().optional(),
189
+ page: lafsPageSchema.optional(),
190
+ partial: z.boolean().optional(),
191
+ error: dispatchErrorSchema.optional(),
192
+ });
193
+
194
+ /** {@inheritDoc dispatchResponseSchema} */
195
+ export type DispatchResponse = z.infer<typeof dispatchResponseSchema>;
196
+
197
+ // ---------------------------------------------------------------------------
198
+ // GatewayStreamEvent — for streaming transports (SSE/HTTP, RPC server-push)
199
+ // ---------------------------------------------------------------------------
200
+
201
+ /**
202
+ * A single frame emitted by a streaming gateway operation. Unary transports
203
+ * (CLI) never see these; streaming transports (Studio SSE, RPC server-push)
204
+ * emit a sequence terminated by a `done` or `error` event.
205
+ */
206
+ export const gatewayStreamEventSchema = z.object({
207
+ /** Frame kind: incremental data, terminal success, or terminal error. */
208
+ kind: z.enum(['data', 'done', 'error']),
209
+ /** Monotonic sequence number within the stream (0-based). */
210
+ seq: z.number(),
211
+ /** Payload for `data`/`done` frames. */
212
+ data: z.unknown().optional(),
213
+ /** Error detail for `error` frames. */
214
+ error: dispatchErrorSchema.optional(),
215
+ /** Correlates the stream back to its originating request. */
216
+ requestId: z.string(),
217
+ });
218
+
219
+ /** {@inheritDoc gatewayStreamEventSchema} */
220
+ export type GatewayStreamEvent = z.infer<typeof gatewayStreamEventSchema>;
221
+
222
+ // ---------------------------------------------------------------------------
223
+ // Behavioral contracts (function types — TS-only; zod cannot model functions)
224
+ // ---------------------------------------------------------------------------
225
+
226
+ /**
227
+ * Contract for domain handlers.
228
+ *
229
+ * Each canonical domain (tasks, session, memory, check, pipeline, orchestrate,
230
+ * tools, admin, nexus, sticky, intelligence) implements this interface.
231
+ */
232
+ export interface DomainHandler {
233
+ /** Execute a read-only query operation. */
234
+ query(operation: string, params?: Record<string, unknown>): Promise<DispatchResponse>;
235
+ /** Execute a state-modifying mutation operation. */
236
+ mutate(operation: string, params?: Record<string, unknown>): Promise<DispatchResponse>;
237
+ /** Declared operations for introspection and validation. */
238
+ getSupportedOperations(): { query: string[]; mutate: string[] };
239
+ }
240
+
241
+ /** Async continuation that produces a {@link DispatchResponse}. */
242
+ export type DispatchNext = () => Promise<DispatchResponse>;
243
+
244
+ /**
245
+ * Middleware function signature. Receives the request and a `next`
246
+ * continuation; can short-circuit (e.g. rate-limit exceeded) or modify the
247
+ * request/response.
248
+ */
249
+ export type Middleware = (
250
+ request: DispatchRequest,
251
+ next: DispatchNext,
252
+ ) => Promise<DispatchResponse>;
package/src/goal.ts ADDED
@@ -0,0 +1,348 @@
1
+ /**
2
+ * CLEO-native goal-system contracts (Layer 4 of SG-COGNITIVE-SUBSTRATE).
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.
30
+ *
31
+ * @packageDocumentation
32
+ * @module @cleocode/contracts/goal
33
+ *
34
+ * @epic T11290 EP-CLEO-GOAL-SYSTEM
35
+ * @task T11376
36
+ * @saga T11283 SG-COGNITIVE-SUBSTRATE
37
+ */
38
+
39
+ /**
40
+ * Canonical task-id shape recognized by a {@link TaskCompletionGoal}.
41
+ *
42
+ * Mirrors the ADR-079-r2 `SATISFIES_TASK_ID_REGEX` / evidence-atom task-id
43
+ * grammar (`T` + 1–7 digits). Exported so the store, judge, and CLI layers
44
+ * validate target ids against ONE pattern rather than re-deriving it.
45
+ *
46
+ * @public
47
+ */
48
+ export const GOAL_TARGET_TASK_ID_REGEX = /^T[0-9]{1,7}$/;
49
+
50
+ /**
51
+ * Lifecycle status of a {@link GoalRecord}.
52
+ *
53
+ * Transitions are owned by the turn-budgeted loop (`advanceGoal`):
54
+ * - `active` — the goal is being pursued; the loop consumes turns.
55
+ * - `paused` — auto-paused after a judge/parse failure (Hermes pattern);
56
+ * carries a {@link GoalRecord.pausedReason}. No turns are consumed while
57
+ * paused; a human/agent must resume.
58
+ * - `satisfied` — the judge returned `ok: true` (evidence-backed for task
59
+ * goals, LLM-confirmed for fuzzy goals). Terminal.
60
+ * - `abandoned` — the turn budget was exhausted before satisfaction. Terminal.
61
+ * - `impossible` — the judge proved the goal can never be satisfied
62
+ * (e.g. the target task was cancelled/deleted). Terminal; no further turns.
63
+ *
64
+ * @public
65
+ */
66
+ export type GoalStatus = 'active' | 'paused' | 'satisfied' | 'abandoned' | 'impossible';
67
+
68
+ /**
69
+ * The set of all {@link GoalStatus} values, in lifecycle order.
70
+ *
71
+ * Use for runtime validation (`GOAL_STATUSES.includes(x)`) and to drive
72
+ * exhaustive switch checks without hand-maintaining a parallel array.
73
+ *
74
+ * @public
75
+ */
76
+ export const GOAL_STATUSES: readonly GoalStatus[] = Object.freeze([
77
+ 'active',
78
+ 'paused',
79
+ 'satisfied',
80
+ 'abandoned',
81
+ 'impossible',
82
+ ]);
83
+
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
+ /**
100
+ * Discriminant tag for the {@link GoalKind} union.
101
+ *
102
+ * @public
103
+ */
104
+ export type GoalKindTag = 'task-completion' | 'fuzzy';
105
+
106
+ /**
107
+ * A goal whose satisfaction is decided by the **evidence-gate-aware** judge.
108
+ *
109
+ * The judge resolves `targetTaskId`'s actual status and re-uses the ADR-051
110
+ * evidence path — NEVER transcript text. This is CLEO's core differentiator
111
+ * over Claude-Code and Hermes.
112
+ *
113
+ * @public
114
+ */
115
+ export interface TaskCompletionGoal {
116
+ /** Discriminant: judged via task status + evidence atoms (ADR-051). */
117
+ readonly kind: 'task-completion';
118
+ /**
119
+ * The task to complete. MUST match {@link GOAL_TARGET_TASK_ID_REGEX}
120
+ * (`T` + 1–7 digits). The judge loads this task and inspects its real
121
+ * `status` and gate evidence.
122
+ */
123
+ readonly targetTaskId: string;
124
+ }
125
+
126
+ /**
127
+ * A goal with no machine-checkable completion artifact — satisfaction is
128
+ * decided by the injectable LLM judge ({@link GoalJudge}) as a fallback.
129
+ *
130
+ * The fuzzy path NEVER touches the evidence infrastructure; it exists only for
131
+ * intents that cannot be reduced to a task + gates (e.g. "explore the auth
132
+ * module and summarize the risks").
133
+ *
134
+ * @public
135
+ */
136
+ export interface FuzzyGoal {
137
+ /** Discriminant: judged by the fallback LLM judge (no evidence path). */
138
+ readonly kind: 'fuzzy';
139
+ }
140
+
141
+ /**
142
+ * Discriminated union distinguishing an evidence-judged task-completion goal
143
+ * from an LLM-judged fuzzy goal. The `kind` tag routes the judge: see
144
+ * {@link TaskCompletionGoal} (evidence) vs {@link FuzzyGoal} (LLM fallback).
145
+ *
146
+ * @public
147
+ */
148
+ export type GoalKind = TaskCompletionGoal | FuzzyGoal;
149
+
150
+ /**
151
+ * A DB-persisted, per-agent goal record (survives process restart).
152
+ *
153
+ * Persisted in `tasks.db` under the `tasks_goal` table (Pattern A — single file
154
+ * per scope + domain-prefixed table). Keyed per agent by the resolved
155
+ * `sessionId` + `agentId` from E0 (`resolveSessionIdFromEnv` /
156
+ * `resolveAgentIdFromEnv`) so two concurrent agents never collide on one global
157
+ * row — the session-bleed class this saga exists to kill.
158
+ *
159
+ * @public
160
+ */
161
+ export interface GoalRecord {
162
+ /**
163
+ * Stable, content-independent goal id (the idempotency key / primary key in
164
+ * the store). Generated by the store on create.
165
+ */
166
+ readonly id: string;
167
+ /**
168
+ * Resolved session id that owns this goal (from `resolveSessionIdFromEnv`).
169
+ * `null` only for the global-scope / no-session case.
170
+ */
171
+ readonly sessionId: string | null;
172
+ /**
173
+ * Resolved agent handle that owns this goal (from `resolveAgentIdFromEnv`).
174
+ * `null` when spawn did not inject an agent identity.
175
+ */
176
+ readonly agentId: string | null;
177
+ /**
178
+ * Parent goal id when this is a sub-goal, else `null`. Sub-goals let an agent
179
+ * decompose a large intent without losing the turn budget of the parent.
180
+ */
181
+ readonly parentGoalId: string | null;
182
+ /**
183
+ * The kind discriminator + its payload (target task id for task goals).
184
+ * Drives whether the judge takes the evidence path or the LLM-fallback path.
185
+ */
186
+ readonly goalKind: GoalKind;
187
+ /** Human-readable statement of what the agent is trying to achieve. */
188
+ readonly intent: string;
189
+ /**
190
+ * Outstanding, append-only acceptance criteria. The continuation builder
191
+ * embeds these so the agent knows what remains. Append via
192
+ * `appendCriteria` — never mutated in place.
193
+ */
194
+ readonly criteria: readonly string[];
195
+ /** Current lifecycle status. */
196
+ readonly status: GoalStatus;
197
+ /**
198
+ * Maximum number of turns the loop may consume before the goal is abandoned.
199
+ * A hard cap that bounds runaway loops (the protection Claude-Code lacks).
200
+ */
201
+ readonly turnBudget: number;
202
+ /** Turns consumed so far. Never exceeds {@link turnBudget}. */
203
+ readonly turnsUsed: number;
204
+ /**
205
+ * Why the goal was auto-paused, when `status === 'paused'`. `null` otherwise.
206
+ * Set on judge/parse failure so the resume path can surface the cause.
207
+ */
208
+ readonly pausedReason: string | null;
209
+ /**
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.
212
+ */
213
+ readonly lastVerdict: GoalJudgeVerdict | null;
214
+ /** ISO-8601 creation timestamp. */
215
+ readonly createdAt: string;
216
+ /** ISO-8601 last-update timestamp. */
217
+ readonly updatedAt: string;
218
+ }
219
+
220
+ /**
221
+ * The verdict returned by every judge path — evidence-gate-aware OR LLM
222
+ * fallback — for a single goal evaluation.
223
+ *
224
+ * The shape is uniform across both paths (the loop never branches on which
225
+ * judge produced it):
226
+ * - `ok` — the goal is satisfied right now.
227
+ * - `reason` — human-readable explanation (embedded into the continuation
228
+ * nudge so the agent knows WHY it must continue, or why it succeeded).
229
+ * - `impossible` — the goal can NEVER be satisfied (e.g. target task cancelled);
230
+ * the loop transitions the goal to `impossible` and stops consuming turns.
231
+ * `impossible` implies `ok === false`.
232
+ * - `evidence` — optional list of evidence-atom strings (ADR-051) the
233
+ * evidence judge consulted, for audit/telemetry. Absent on the LLM path.
234
+ *
235
+ * @public
236
+ */
237
+ export interface GoalJudgeVerdict {
238
+ /** True when the goal is satisfied. Mutually exclusive with `impossible`. */
239
+ readonly ok: boolean;
240
+ /** Human-readable explanation of the verdict (for the continuation nudge). */
241
+ readonly reason: string;
242
+ /** True when the goal can never be satisfied (terminal `impossible`). */
243
+ readonly impossible: boolean;
244
+ /**
245
+ * Evidence-atom strings the evidence judge inspected (ADR-051). Present only
246
+ * on the task-completion path; omitted by the LLM fallback.
247
+ */
248
+ readonly evidence?: readonly string[];
249
+ }
250
+
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
+ /**
328
+ * Test whether a string is a valid goal target task id (`T` + 1–7 digits).
329
+ *
330
+ * @param value - Candidate task id.
331
+ * @returns `true` when `value` matches {@link GOAL_TARGET_TASK_ID_REGEX}.
332
+ * @public
333
+ */
334
+ export function isValidGoalTargetTaskId(value: string): boolean {
335
+ return GOAL_TARGET_TASK_ID_REGEX.test(value);
336
+ }
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
+ }