@cleocode/core 2026.8.2 → 2026.8.4

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 (186) hide show
  1. package/dist/check/pr-gate.d.ts.map +1 -1
  2. package/dist/check/pr-gate.js +9 -0
  3. package/dist/check/pr-gate.js.map +1 -1
  4. package/dist/docs/export-document.js +66 -16
  5. package/dist/docs/export-document.js.map +3 -3
  6. package/dist/doctor/index.d.ts +2 -0
  7. package/dist/doctor/index.d.ts.map +1 -1
  8. package/dist/doctor/index.js +1 -0
  9. package/dist/doctor/index.js.map +1 -1
  10. package/dist/doctor/superseded-store.d.ts +101 -0
  11. package/dist/doctor/superseded-store.d.ts.map +1 -0
  12. package/dist/doctor/superseded-store.js +156 -0
  13. package/dist/doctor/superseded-store.js.map +1 -0
  14. package/dist/hooks/registry.d.ts +2 -2
  15. package/dist/hooks/registry.d.ts.map +1 -1
  16. package/dist/hooks/registry.js +5 -0
  17. package/dist/hooks/registry.js.map +1 -1
  18. package/dist/hooks/types.d.ts +59 -0
  19. package/dist/hooks/types.d.ts.map +1 -1
  20. package/dist/init/scaffold-workflows.d.ts +12 -0
  21. package/dist/init/scaffold-workflows.d.ts.map +1 -1
  22. package/dist/init/scaffold-workflows.js +16 -0
  23. package/dist/init/scaffold-workflows.js.map +1 -1
  24. package/dist/init/upgrade-workflows.d.ts.map +1 -1
  25. package/dist/init/upgrade-workflows.js +16 -0
  26. package/dist/init/upgrade-workflows.js.map +1 -1
  27. package/dist/init.d.ts.map +1 -1
  28. package/dist/init.js +16 -1
  29. package/dist/init.js.map +1 -1
  30. package/dist/internal.d.ts +1 -0
  31. package/dist/internal.d.ts.map +1 -1
  32. package/dist/internal.js +2 -0
  33. package/dist/internal.js.map +1 -1
  34. package/dist/llm/credentials.d.ts +16 -1
  35. package/dist/llm/credentials.d.ts.map +1 -1
  36. package/dist/llm/credentials.js.map +1 -1
  37. package/dist/llm/cross-provider-selector.d.ts +25 -0
  38. package/dist/llm/cross-provider-selector.d.ts.map +1 -1
  39. package/dist/llm/cross-provider-selector.js +76 -3
  40. package/dist/llm/cross-provider-selector.js.map +1 -1
  41. package/dist/llm/local-inference-probe.d.ts +101 -0
  42. package/dist/llm/local-inference-probe.d.ts.map +1 -0
  43. package/dist/llm/local-inference-probe.js +174 -0
  44. package/dist/llm/local-inference-probe.js.map +1 -0
  45. package/dist/llm/plugin-facade.js +199 -21
  46. package/dist/llm/plugin-facade.js.map +3 -3
  47. package/dist/llm/role-executor.d.ts.map +1 -1
  48. package/dist/llm/role-executor.js +95 -9
  49. package/dist/llm/role-executor.js.map +1 -1
  50. package/dist/llm/role-resolver.d.ts +10 -0
  51. package/dist/llm/role-resolver.d.ts.map +1 -1
  52. package/dist/llm/role-resolver.js +92 -1
  53. package/dist/llm/role-resolver.js.map +1 -1
  54. package/dist/llm/system-resolver.d.ts.map +1 -1
  55. package/dist/llm/system-resolver.js +2 -0
  56. package/dist/llm/system-resolver.js.map +1 -1
  57. package/dist/memory/brain-search.d.ts +109 -0
  58. package/dist/memory/brain-search.d.ts.map +1 -1
  59. package/dist/memory/brain-search.js +149 -37
  60. package/dist/memory/brain-search.js.map +1 -1
  61. package/dist/memory/brain-stub-prune.d.ts +109 -0
  62. package/dist/memory/brain-stub-prune.d.ts.map +1 -0
  63. package/dist/memory/brain-stub-prune.js +155 -0
  64. package/dist/memory/brain-stub-prune.js.map +1 -0
  65. package/dist/memory/engine-compat.d.ts.map +1 -1
  66. package/dist/memory/engine-compat.js +61 -3
  67. package/dist/memory/engine-compat.js.map +1 -1
  68. package/dist/nexus/analyze-orchestrator.d.ts.map +1 -1
  69. package/dist/nexus/analyze-orchestrator.js +28 -0
  70. package/dist/nexus/analyze-orchestrator.js.map +1 -1
  71. package/dist/nexus/context.d.ts.map +1 -1
  72. package/dist/nexus/context.js +3 -3
  73. package/dist/nexus/context.js.map +1 -1
  74. package/dist/nexus/impact.d.ts.map +1 -1
  75. package/dist/nexus/impact.js +3 -3
  76. package/dist/nexus/impact.js.map +1 -1
  77. package/dist/nexus/symbol-miss.d.ts +132 -0
  78. package/dist/nexus/symbol-miss.d.ts.map +1 -0
  79. package/dist/nexus/symbol-miss.js +168 -0
  80. package/dist/nexus/symbol-miss.js.map +1 -0
  81. package/dist/orchestrate/spawn-ops.d.ts +21 -1
  82. package/dist/orchestrate/spawn-ops.d.ts.map +1 -1
  83. package/dist/orchestrate/spawn-ops.js +39 -2
  84. package/dist/orchestrate/spawn-ops.js.map +1 -1
  85. package/dist/orchestrate/worker-verify.d.ts.map +1 -1
  86. package/dist/orchestrate/worker-verify.js +22 -0
  87. package/dist/orchestrate/worker-verify.js.map +1 -1
  88. package/dist/release/changesets-aggregator.d.ts.map +1 -1
  89. package/dist/release/changesets-aggregator.js +15 -1
  90. package/dist/release/changesets-aggregator.js.map +1 -1
  91. package/dist/release/open.d.ts +29 -2
  92. package/dist/release/open.d.ts.map +1 -1
  93. package/dist/release/open.js +48 -11
  94. package/dist/release/open.js.map +1 -1
  95. package/dist/resources/governor.d.ts +12 -2
  96. package/dist/resources/governor.d.ts.map +1 -1
  97. package/dist/resources/governor.js +12 -3
  98. package/dist/resources/governor.js.map +1 -1
  99. package/dist/scaffold/ensure-dirs.d.ts.map +1 -1
  100. package/dist/scaffold/ensure-dirs.js +14 -2
  101. package/dist/scaffold/ensure-dirs.js.map +1 -1
  102. package/dist/scaffold/project-detection.d.ts.map +1 -1
  103. package/dist/scaffold/project-detection.js +2 -1
  104. package/dist/scaffold/project-detection.js.map +1 -1
  105. package/dist/selfimprove/draft-pr.d.ts +14 -1
  106. package/dist/selfimprove/draft-pr.d.ts.map +1 -1
  107. package/dist/selfimprove/draft-pr.js.map +1 -1
  108. package/dist/selfimprove/fix-gen.d.ts +0 -21
  109. package/dist/selfimprove/fix-gen.d.ts.map +1 -1
  110. package/dist/selfimprove/fix-gen.js +276 -76
  111. package/dist/selfimprove/fix-gen.js.map +3 -3
  112. package/dist/selfimprove/run-loop.d.ts.map +1 -1
  113. package/dist/selfimprove/run-loop.js +50 -19
  114. package/dist/selfimprove/run-loop.js.map +1 -1
  115. package/dist/selfimprove/scenarios/dhq-replay-find/golden.json +114 -4
  116. package/dist/selfimprove/scenarios/dhq-replay-find/scenario.json +3 -3
  117. package/dist/sentient/dream-cycle.d.ts +20 -19
  118. package/dist/sentient/dream-cycle.d.ts.map +1 -1
  119. package/dist/sentient/dream-cycle.js +322 -11
  120. package/dist/sentient/dream-cycle.js.map +1 -1
  121. package/dist/sentient/dream-watermark.d.ts +134 -0
  122. package/dist/sentient/dream-watermark.d.ts.map +1 -0
  123. package/dist/sentient/dream-watermark.js +156 -0
  124. package/dist/sentient/dream-watermark.js.map +1 -0
  125. package/dist/sentient/state.d.ts +17 -0
  126. package/dist/sentient/state.d.ts.map +1 -1
  127. package/dist/sentient/state.js +22 -3
  128. package/dist/sentient/state.js.map +1 -1
  129. package/dist/sentient/tick.d.ts.map +1 -1
  130. package/dist/sentient/tick.js +79 -2
  131. package/dist/sentient/tick.js.map +1 -1
  132. package/dist/sessions/index.d.ts.map +1 -1
  133. package/dist/sessions/index.js +5 -1
  134. package/dist/sessions/index.js.map +1 -1
  135. package/dist/sessions/snapshot.d.ts.map +1 -1
  136. package/dist/sessions/snapshot.js +6 -3
  137. package/dist/sessions/snapshot.js.map +1 -1
  138. package/dist/store/checkpoint-git-dir.d.ts +95 -0
  139. package/dist/store/checkpoint-git-dir.d.ts.map +1 -0
  140. package/dist/store/checkpoint-git-dir.js +111 -0
  141. package/dist/store/checkpoint-git-dir.js.map +1 -0
  142. package/dist/store/git-checkpoint.d.ts.map +1 -1
  143. package/dist/store/git-checkpoint.js +6 -5
  144. package/dist/store/git-checkpoint.js.map +1 -1
  145. package/dist/system/health.d.ts.map +1 -1
  146. package/dist/system/health.js +2 -1
  147. package/dist/system/health.js.map +1 -1
  148. package/dist/tasks/add.d.ts.map +1 -1
  149. package/dist/tasks/add.js +40 -4
  150. package/dist/tasks/add.js.map +1 -1
  151. package/dist/tasks/collection.d.ts +51 -0
  152. package/dist/tasks/collection.d.ts.map +1 -0
  153. package/dist/tasks/collection.js +81 -0
  154. package/dist/tasks/collection.js.map +1 -0
  155. package/dist/tasks/complete.d.ts.map +1 -1
  156. package/dist/tasks/complete.js +8 -3
  157. package/dist/tasks/complete.js.map +1 -1
  158. package/dist/tasks/evidence.d.ts.map +1 -1
  159. package/dist/tasks/evidence.js +17 -0
  160. package/dist/tasks/evidence.js.map +1 -1
  161. package/dist/tasks/find.d.ts +50 -0
  162. package/dist/tasks/find.d.ts.map +1 -1
  163. package/dist/tasks/find.js +63 -2
  164. package/dist/tasks/find.js.map +1 -1
  165. package/dist/tasks/heavy-tool-env.d.ts +112 -0
  166. package/dist/tasks/heavy-tool-env.d.ts.map +1 -0
  167. package/dist/tasks/heavy-tool-env.js +133 -0
  168. package/dist/tasks/heavy-tool-env.js.map +1 -0
  169. package/dist/tasks/index.d.ts +2 -0
  170. package/dist/tasks/index.d.ts.map +1 -1
  171. package/dist/tasks/index.js +1 -0
  172. package/dist/tasks/index.js.map +1 -1
  173. package/dist/tasks/tool-cache.d.ts.map +1 -1
  174. package/dist/tasks/tool-cache.js +9 -3
  175. package/dist/tasks/tool-cache.js.map +1 -1
  176. package/dist/tasks/tool-resolver.d.ts +1 -1
  177. package/dist/tasks/tool-resolver.d.ts.map +1 -1
  178. package/dist/tasks/tool-resolver.js +83 -0
  179. package/dist/tasks/tool-resolver.js.map +1 -1
  180. package/dist/tasks/tool-semaphore.d.ts +82 -12
  181. package/dist/tasks/tool-semaphore.d.ts.map +1 -1
  182. package/dist/tasks/tool-semaphore.js +79 -17
  183. package/dist/tasks/tool-semaphore.js.map +1 -1
  184. package/package.json +13 -13
  185. package/templates/CLEO-INJECTION.md +55 -22
  186. package/templates/workflows/release-prepare.yml.tmpl +73 -16
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Runtime detection of a reachable local inference server (T12082).
3
+ *
4
+ * ## Why CLEO should find this by itself
5
+ *
6
+ * The auxiliary fallback chain exists so that "context compression, brain dream
7
+ * cycles, hygiene scans" do not fail silently when the primary provider is
8
+ * unavailable. Its default chain is `anthropic → openrouter → groq` — all
9
+ * cloud. When every cloud credential is unusable, the chain is exhausted and
10
+ * the capability simply stops.
11
+ *
12
+ * That is exactly the state this machine was in on 2026-08-06: the anthropic
13
+ * credential was consent-gated, the openai one returned 401 — and an Ollama
14
+ * server was running the whole time with `qwen2.5-coder:3b` loaded. The dream
15
+ * cycle reported "no usable LLM client" while free local inference sat one
16
+ * HTTP request away. Nothing was broken except that nobody looked.
17
+ *
18
+ * A background consolidation pass is precisely the workload that should degrade
19
+ * to a small local model rather than stop: it is not latency-critical, it is
20
+ * not user-facing, and a shallow synthesis is worth more than none. So the
21
+ * chain now ends with a local tier that is included ONLY when a server actually
22
+ * answers.
23
+ *
24
+ * ## Why a probe rather than a config flag
25
+ *
26
+ * A config flag would need the operator to know their own machine's state and
27
+ * keep it current. A 300 ms probe against loopback is cheap, is cached for the
28
+ * process, and is correct by construction — it reports what is true now.
29
+ *
30
+ * @task T12082
31
+ */
32
+ /** Loopback endpoints checked, in order. Ollama first — it is the common case. */
33
+ const LOCAL_ENDPOINTS = [
34
+ {
35
+ name: 'ollama',
36
+ tagsUrl: 'http://127.0.0.1:11434/api/tags',
37
+ baseUrl: 'http://127.0.0.1:11434',
38
+ openAiBaseUrl: 'http://127.0.0.1:11434/v1',
39
+ },
40
+ {
41
+ name: 'lm-studio',
42
+ tagsUrl: 'http://127.0.0.1:1234/v1/models',
43
+ baseUrl: 'http://127.0.0.1:1234',
44
+ openAiBaseUrl: 'http://127.0.0.1:1234/v1',
45
+ },
46
+ ];
47
+ /** Probe timeout. Loopback either answers immediately or is not there. */
48
+ const PROBE_TIMEOUT_MS = 300;
49
+ /** Process-lifetime cache — `undefined` = not yet probed, `null` = none found. */
50
+ let cached;
51
+ /**
52
+ * Extract model ids from either an Ollama `/api/tags` or an OpenAI `/v1/models`
53
+ * payload, whichever shape came back.
54
+ *
55
+ * @param body - parsed JSON response.
56
+ * @returns model ids, possibly empty.
57
+ */
58
+ function extractModels(body) {
59
+ if (body === null || typeof body !== 'object')
60
+ return [];
61
+ const rec = body;
62
+ // Ollama: { models: [{ name, model, … }] }
63
+ if (Array.isArray(rec.models)) {
64
+ return rec.models
65
+ .map((m) => m !== null && typeof m === 'object'
66
+ ? (m.name ?? m.model)
67
+ : undefined)
68
+ .filter((n) => typeof n === 'string');
69
+ }
70
+ // OpenAI-compatible: { data: [{ id }] }
71
+ if (Array.isArray(rec.data)) {
72
+ return rec.data
73
+ .map((m) => (m !== null && typeof m === 'object' ? m.id : undefined))
74
+ .filter((n) => typeof n === 'string');
75
+ }
76
+ return [];
77
+ }
78
+ /**
79
+ * Detect a reachable local inference server with at least one model loaded.
80
+ *
81
+ * Cached for the process lifetime: a background tick may consult this many
82
+ * times and the answer does not change mid-run in practice.
83
+ *
84
+ * Never throws — a probe failure means "not available", which is a normal
85
+ * result rather than an error.
86
+ *
87
+ * @param opts - `force` re-probes, ignoring the cache (tests).
88
+ * @returns the detected server, or `null` when none answers.
89
+ *
90
+ * @example
91
+ * ```ts
92
+ * const local = await detectLocalInference();
93
+ * if (local) console.log(`${local.name} has ${local.models.length} model(s)`);
94
+ * ```
95
+ *
96
+ * @task T12082
97
+ */
98
+ export async function detectLocalInference(opts = {}) {
99
+ // Operator opt-out. Two audiences: someone on a shared or metered box who
100
+ // does not want background work routed to a daemon they run for other
101
+ // purposes, and the test suite — which must produce the same result whether
102
+ // or not the developer happens to have Ollama running.
103
+ if (process.env['CLEO_DISABLE_LOCAL_INFERENCE']?.trim())
104
+ return null;
105
+ if (!opts.force && cached !== undefined)
106
+ return cached;
107
+ for (const endpoint of LOCAL_ENDPOINTS) {
108
+ try {
109
+ const response = await fetch(endpoint.tagsUrl, {
110
+ signal: AbortSignal.timeout(PROBE_TIMEOUT_MS),
111
+ });
112
+ if (!response.ok)
113
+ continue;
114
+ const models = extractModels(await response.json());
115
+ // A server with no models loaded cannot serve a completion, so it is not
116
+ // a usable fallback — treat it as absent rather than advertising it.
117
+ if (models.length === 0)
118
+ continue;
119
+ cached = {
120
+ name: endpoint.name,
121
+ baseUrl: endpoint.baseUrl,
122
+ openAiBaseUrl: endpoint.openAiBaseUrl,
123
+ models,
124
+ };
125
+ return cached;
126
+ }
127
+ catch {
128
+ // Unreachable / timed out / non-JSON — try the next endpoint.
129
+ }
130
+ }
131
+ cached = null;
132
+ return cached;
133
+ }
134
+ /**
135
+ * List the models an Ollama daemon actually has pulled.
136
+ *
137
+ * The cross-provider selector picks an Ollama model from a RAM heuristic
138
+ * alone, which answers "what could this machine run", not "what is installed".
139
+ * Those differ constantly — this machine has 62 GB of RAM and therefore scored
140
+ * into the `gemma4:e4b` tier while holding only `qwen2.5-coder:3b` and
141
+ * `qwen2:0.5b`, so the resolved model would have 404'd at the daemon. The fit
142
+ * heuristic is still right; it just has to be intersected with reality.
143
+ *
144
+ * Not cached — the installed set changes whenever the operator pulls a model,
145
+ * and the call is one loopback request.
146
+ *
147
+ * Never throws.
148
+ *
149
+ * @param baseUrl - Ollama root URL, e.g. `http://localhost:11434` (no `/v1`).
150
+ * @returns installed model ids in daemon order, empty when unreachable.
151
+ *
152
+ * @task T12082
153
+ */
154
+ export async function listOllamaModels(baseUrl) {
155
+ try {
156
+ const url = `${baseUrl.replace(/\/+$/, '')}/api/tags`;
157
+ const response = await fetch(url, { signal: AbortSignal.timeout(PROBE_TIMEOUT_MS) });
158
+ if (!response.ok)
159
+ return [];
160
+ return extractModels(await response.json());
161
+ }
162
+ catch {
163
+ return [];
164
+ }
165
+ }
166
+ /**
167
+ * Clear the probe cache. Tests only.
168
+ *
169
+ * @task T12082
170
+ */
171
+ export function _resetLocalInferenceCacheForTests() {
172
+ cached = undefined;
173
+ }
174
+ //# sourceMappingURL=local-inference-probe.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"local-inference-probe.js","sourceRoot":"","sources":["../../src/llm/local-inference-probe.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,kFAAkF;AAClF,MAAM,eAAe,GAA6B;IAChD;QACE,IAAI,EAAE,QAAQ;QACd,OAAO,EAAE,iCAAiC;QAC1C,OAAO,EAAE,wBAAwB;QACjC,aAAa,EAAE,2BAA2B;KAC3C;IACD;QACE,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,iCAAiC;QAC1C,OAAO,EAAE,uBAAuB;QAChC,aAAa,EAAE,0BAA0B;KAC1C;CACF,CAAC;AAkCF,0EAA0E;AAC1E,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B,kFAAkF;AAClF,IAAI,MAAyC,CAAC;AAE9C;;;;;;GAMG;AACH,SAAS,aAAa,CAAC,IAAa;IAClC,IAAI,IAAI,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,EAAE,CAAC;IACzD,MAAM,GAAG,GAAG,IAA4C,CAAC;IAEzD,2CAA2C;IAC3C,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QAC9B,OAAO,GAAG,CAAC,MAAM;aACd,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CACT,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ;YACjC,CAAC,CAAC,CAAE,CAAwB,CAAC,IAAI,IAAK,CAAyB,CAAC,KAAK,CAAC;YACtE,CAAC,CAAC,SAAS,CACd;aACA,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC;IACvD,CAAC;IACD,wCAAwC;IACxC,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5B,OAAO,GAAG,CAAC,IAAI;aACZ,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAE,CAAsB,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;aAC1F,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAA4B,EAAE;IAE9B,0EAA0E;IAC1E,sEAAsE;IACtE,4EAA4E;IAC5E,uDAAuD;IACvD,IAAI,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,EAAE,IAAI,EAAE;QAAE,OAAO,IAAI,CAAC;IAErE,IAAI,CAAC,IAAI,CAAC,KAAK,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IAEvD,KAAK,MAAM,QAAQ,IAAI,eAAe,EAAE,CAAC;QACvC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,QAAQ,CAAC,OAAO,EAAE;gBAC7C,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,gBAAgB,CAAC;aAC9C,CAAC,CAAC;YACH,IAAI,CAAC,QAAQ,CAAC,EAAE;gBAAE,SAAS;YAC3B,MAAM,MAAM,GAAG,aAAa,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;YACpD,yEAAyE;YACzE,qEAAqE;YACrE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAS;YAClC,MAAM,GAAG;gBACP,IAAI,EAAE,QAAQ,CAAC,IAAI;gBACnB,OAAO,EAAE,QAAQ,CAAC,OAAO;gBACzB,aAAa,EAAE,QAAQ,CAAC,aAAa;gBACrC,MAAM;aACP,CAAC;YACF,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,MAAM,CAAC;YACP,8DAA8D;QAChE,CAAC;IACH,CAAC;IAED,MAAM,GAAG,IAAI,CAAC;IACd,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,OAAe;IACpD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,WAAW,CAAC;QACtD,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAC;QACrF,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO,EAAE,CAAC;QAC5B,OAAO,aAAa,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iCAAiC;IAC/C,MAAM,GAAG,SAAS,CAAC;AACrB,CAAC"}
@@ -10043,6 +10043,27 @@ var init_operations_registry = __esm({
10043
10043
  type: "boolean",
10044
10044
  required: false,
10045
10045
  description: "Commit plan file before dispatching"
10046
+ },
10047
+ // T12089: the plan SCOPE. The workflow regenerates the plan when no
10048
+ // committed plan is supplied, and `cleo release plan` requires
10049
+ // `--saga | --epic | --tasks` — so without one of these the dispatched
10050
+ // run dies at "Prepare bump-PR" after a fully green preflight.
10051
+ //
10052
+ // A param absent from this registry is STRIPPED before the domain handler
10053
+ // sees it, silently. `--tasks` was accepted by the CLI, forwarded by the
10054
+ // handler and by `releaseOpen`, and still arrived empty at the workflow
10055
+ // because this declaration was the fourth hop in the chain.
10056
+ {
10057
+ name: "epic",
10058
+ type: "string",
10059
+ required: false,
10060
+ description: "Epic task ID forwarded as the plan scope (e.g. T9999)"
10061
+ },
10062
+ {
10063
+ name: "tasks",
10064
+ type: "string",
10065
+ required: false,
10066
+ description: "Comma-separated task IDs forwarded as the plan scope for a task-scoped release"
10046
10067
  }
10047
10068
  ]
10048
10069
  },
@@ -15734,6 +15755,13 @@ var init_caamp_markers = __esm({
15734
15755
  }
15735
15756
  });
15736
15757
 
15758
+ // packages/contracts/src/collection-keys.ts
15759
+ var init_collection_keys = __esm({
15760
+ "packages/contracts/src/collection-keys.ts"() {
15761
+ "use strict";
15762
+ }
15763
+ });
15764
+
15737
15765
  // packages/contracts/src/index.ts
15738
15766
  var init_src = __esm({
15739
15767
  "packages/contracts/src/index.ts"() {
@@ -15802,6 +15830,7 @@ var init_src = __esm({
15802
15830
  init_service_provider();
15803
15831
  init_workgraph();
15804
15832
  init_caamp_markers();
15833
+ init_collection_keys();
15805
15834
  }
15806
15835
  });
15807
15836
 
@@ -24483,7 +24512,9 @@ function computeClassBudget(cls, sample, opts = {}) {
24483
24512
  return Math.max(1, cpus - 2);
24484
24513
  case "test-run":
24485
24514
  case "scoped-build": {
24486
- const base = Math.max(1, Math.floor(cpus / 4));
24515
+ const estRamBytes = (opts.testRunEstRamMb ?? 24576) * MB;
24516
+ const byMem = Math.floor((availBytes - headroomBytes) / estRamBytes);
24517
+ const base = clamp(1, byMem, Math.max(1, Math.floor(cpus / 4)));
24487
24518
  if (some > floor) return 1;
24488
24519
  if (some > hold) return Math.max(1, Math.floor(base / 2));
24489
24520
  return base;
@@ -24976,6 +25007,11 @@ var init_registry = __esm({
24976
25007
  * });
24977
25008
  * ```
24978
25009
  */
25010
+ // T12071: keyed on the EVENT so the payload shape is checked against
25011
+ // `HookEventPayloadMap`. The previous `<T extends HookPayload>` form inferred
25012
+ // T from the argument, relating the event and the payload not at all — which
25013
+ // is how `PostToolUse` came to be dispatched with `newStatus` instead of
25014
+ // `status` and filled 29% of BRAIN with "status: undefined".
24979
25015
  async dispatch(event, projectRoot, payload) {
24980
25016
  if (!this.config.enabled) return;
24981
25017
  const resolvedEvent = this.resolveEvent(event);
@@ -28257,8 +28293,8 @@ function reuseValid(key, store) {
28257
28293
  return row;
28258
28294
  }
28259
28295
  async function bindAgainst(key, store, establish) {
28260
- const cached2 = reuseValid(key, store);
28261
- if (cached2) return cached2;
28296
+ const cached3 = reuseValid(key, store);
28297
+ if (cached3) return cached3;
28262
28298
  const pending = _inflight.get(key);
28263
28299
  if (pending) {
28264
28300
  const row = await pending;
@@ -36067,9 +36103,9 @@ var init_chat_completions = __esm({
36067
36103
  outputTokens: response.usage?.completion_tokens ?? 0
36068
36104
  };
36069
36105
  const promptDetails = response.usage?.["prompt_tokens_details"];
36070
- const cached2 = promptDetails?.["cached_tokens"];
36071
- if (typeof cached2 === "number" && cached2 > 0) {
36072
- usage.cachedTokens = cached2;
36106
+ const cached3 = promptDetails?.["cached_tokens"];
36107
+ if (typeof cached3 === "number" && cached3 > 0) {
36108
+ usage.cachedTokens = cached3;
36073
36109
  }
36074
36110
  return {
36075
36111
  id: response.id ?? `chat-${Date.now().toString(36)}`,
@@ -36311,9 +36347,9 @@ function normalizeUsage(usage) {
36311
36347
  const outputTokens = usage["output_tokens"] ?? 0;
36312
36348
  const normalized = { inputTokens, outputTokens };
36313
36349
  const details = usage["input_tokens_details"];
36314
- const cached2 = details?.["cached_tokens"];
36315
- if (typeof cached2 === "number" && cached2 > 0) {
36316
- normalized.cachedTokens = cached2;
36350
+ const cached3 = details?.["cached_tokens"];
36351
+ if (typeof cached3 === "number" && cached3 > 0) {
36352
+ normalized.cachedTokens = cached3;
36317
36353
  }
36318
36354
  return normalized;
36319
36355
  }
@@ -65486,6 +65522,76 @@ var init_model_runner = __esm({
65486
65522
  }
65487
65523
  });
65488
65524
 
65525
+ // packages/core/src/llm/local-inference-probe.ts
65526
+ function extractModels(body) {
65527
+ if (body === null || typeof body !== "object") return [];
65528
+ const rec = body;
65529
+ if (Array.isArray(rec.models)) {
65530
+ return rec.models.map(
65531
+ (m) => m !== null && typeof m === "object" ? m.name ?? m.model : void 0
65532
+ ).filter((n) => typeof n === "string");
65533
+ }
65534
+ if (Array.isArray(rec.data)) {
65535
+ return rec.data.map((m) => m !== null && typeof m === "object" ? m.id : void 0).filter((n) => typeof n === "string");
65536
+ }
65537
+ return [];
65538
+ }
65539
+ async function detectLocalInference(opts = {}) {
65540
+ if (process.env["CLEO_DISABLE_LOCAL_INFERENCE"]?.trim()) return null;
65541
+ if (!opts.force && cached2 !== void 0) return cached2;
65542
+ for (const endpoint of LOCAL_ENDPOINTS) {
65543
+ try {
65544
+ const response = await fetch(endpoint.tagsUrl, {
65545
+ signal: AbortSignal.timeout(PROBE_TIMEOUT_MS)
65546
+ });
65547
+ if (!response.ok) continue;
65548
+ const models = extractModels(await response.json());
65549
+ if (models.length === 0) continue;
65550
+ cached2 = {
65551
+ name: endpoint.name,
65552
+ baseUrl: endpoint.baseUrl,
65553
+ openAiBaseUrl: endpoint.openAiBaseUrl,
65554
+ models
65555
+ };
65556
+ return cached2;
65557
+ } catch {
65558
+ }
65559
+ }
65560
+ cached2 = null;
65561
+ return cached2;
65562
+ }
65563
+ async function listOllamaModels(baseUrl) {
65564
+ try {
65565
+ const url2 = `${baseUrl.replace(/\/+$/, "")}/api/tags`;
65566
+ const response = await fetch(url2, { signal: AbortSignal.timeout(PROBE_TIMEOUT_MS) });
65567
+ if (!response.ok) return [];
65568
+ return extractModels(await response.json());
65569
+ } catch {
65570
+ return [];
65571
+ }
65572
+ }
65573
+ var LOCAL_ENDPOINTS, PROBE_TIMEOUT_MS, cached2;
65574
+ var init_local_inference_probe = __esm({
65575
+ "packages/core/src/llm/local-inference-probe.ts"() {
65576
+ "use strict";
65577
+ LOCAL_ENDPOINTS = [
65578
+ {
65579
+ name: "ollama",
65580
+ tagsUrl: "http://127.0.0.1:11434/api/tags",
65581
+ baseUrl: "http://127.0.0.1:11434",
65582
+ openAiBaseUrl: "http://127.0.0.1:11434/v1"
65583
+ },
65584
+ {
65585
+ name: "lm-studio",
65586
+ tagsUrl: "http://127.0.0.1:1234/v1/models",
65587
+ baseUrl: "http://127.0.0.1:1234",
65588
+ openAiBaseUrl: "http://127.0.0.1:1234/v1"
65589
+ }
65590
+ ];
65591
+ PROBE_TIMEOUT_MS = 300;
65592
+ }
65593
+ });
65594
+
65489
65595
  // packages/core/src/llm/catalog-model-resolver.ts
65490
65596
  var catalog_model_resolver_exports = {};
65491
65597
  __export(catalog_model_resolver_exports, {
@@ -65570,9 +65676,9 @@ var init_catalog_model_resolver = __esm({
65570
65676
  // packages/core/src/llm/cross-provider-selector.ts
65571
65677
  import { totalmem as totalmem2 } from "node:os";
65572
65678
  async function probeOllamaAlive(baseUrl) {
65573
- const cached2 = _ollamaProbeCache.get(baseUrl);
65574
- if (cached2 && Date.now() - cached2.checkedAt < OLLAMA_PROBE_TTL_MS) {
65575
- return cached2.alive;
65679
+ const cached3 = _ollamaProbeCache.get(baseUrl);
65680
+ if (cached3 && Date.now() - cached3.checkedAt < OLLAMA_PROBE_TTL_MS) {
65681
+ return cached3.alive;
65576
65682
  }
65577
65683
  let alive = false;
65578
65684
  try {
@@ -65614,6 +65720,16 @@ function isProvisioned(provider) {
65614
65720
  const cred = resolveCredentials(provider);
65615
65721
  return (cred.apiKey?.trim().length ?? 0) > 0;
65616
65722
  }
65723
+ function reconcileOllamaModel(preferred, installed) {
65724
+ if (installed.length === 0) return preferred;
65725
+ if (installed.includes(preferred)) return preferred;
65726
+ const family = preferred.split(":")[0];
65727
+ if (family !== void 0 && family !== "") {
65728
+ const sameFamily = installed.find((m) => m.split(":")[0] === family);
65729
+ if (sameFamily !== void 0) return sameFamily;
65730
+ }
65731
+ return installed[0] ?? preferred;
65732
+ }
65617
65733
  function scoreProvider(provider, providerTier, taskTier, ollamaAlive, costInputPerMillion) {
65618
65734
  let score = TIER_BASE[providerTier];
65619
65735
  if (taskTier === "frontier" && providerTier === "frontier") {
@@ -65646,6 +65762,7 @@ function roleTierFor(role) {
65646
65762
  }
65647
65763
  async function selectBestProvisioned(role, opts) {
65648
65764
  const taskTier = roleTierFor(role);
65765
+ const excluded = new Set(opts.exclude ?? []);
65649
65766
  for (const id of BUILTIN_PROVIDER_IDS) {
65650
65767
  const creds = await listCredentials(id);
65651
65768
  const hasExpiredOAuth = creds.some(
@@ -65670,10 +65787,23 @@ async function selectBestProvisioned(role, opts) {
65670
65787
  }
65671
65788
  const provisionedIds = [];
65672
65789
  for (const id of BUILTIN_PROVIDER_IDS) {
65790
+ if (excluded.has(id)) continue;
65673
65791
  if (isProvisioned(id)) {
65674
65792
  provisionedIds.push(id);
65675
65793
  }
65676
65794
  }
65795
+ let localModels = [];
65796
+ if (!excluded.has("ollama") && !provisionedIds.includes("ollama")) {
65797
+ const local = await detectLocalInference();
65798
+ if (local !== null && local.name === "ollama") {
65799
+ provisionedIds.push("ollama");
65800
+ localModels = local.models;
65801
+ logger4.debug(
65802
+ { baseUrl: local.baseUrl, models: local.models },
65803
+ "cross-provider-selector: unlisted ollama daemon is live \u2014 admitting as provisioned"
65804
+ );
65805
+ }
65806
+ }
65677
65807
  if (provisionedIds.length === 0) {
65678
65808
  logger4.warn(
65679
65809
  {
@@ -65690,11 +65820,12 @@ async function selectBestProvisioned(role, opts) {
65690
65820
  const { resolveProviderDefaultModel: resolveProviderDefaultModel2, catalogKeyForProvider: catalogKeyForProvider2 } = await Promise.resolve().then(() => (init_catalog_model_resolver(), catalog_model_resolver_exports));
65691
65821
  const { pickCredentialForProvider: pickCredentialForProvider2 } = await Promise.resolve().then(() => (init_credentials_store(), credentials_store_exports));
65692
65822
  const ollamaProvisioned = provisionedIds.includes("ollama");
65693
- let ollamaAlive = false;
65694
- if (ollamaProvisioned) {
65823
+ let ollamaAlive = localModels.length > 0;
65824
+ if (ollamaProvisioned && !ollamaAlive) {
65695
65825
  const profile = await getProviderProfile2("ollama");
65696
65826
  const ollamaBase = profile?.baseUrl ?? "http://localhost:11434";
65697
65827
  ollamaAlive = await probeOllamaAlive(ollamaBase);
65828
+ if (ollamaAlive) localModels = await listOllamaModels(ollamaBase);
65698
65829
  }
65699
65830
  const scored = [];
65700
65831
  for (const id of provisionedIds) {
@@ -65724,6 +65855,7 @@ async function selectBestProvisioned(role, opts) {
65724
65855
  } else {
65725
65856
  model = ollamaDefaultModelForTier(winner.tier, ramBytes);
65726
65857
  }
65858
+ model = reconcileOllamaModel(model, localModels);
65727
65859
  const catalogKey = catalogKeyForProvider2(winner.id);
65728
65860
  const catalogModel = resolveProviderDefaultModel2(catalogKey);
65729
65861
  if (!catalogModel?.startsWith("gemma4")) {
@@ -65767,6 +65899,7 @@ var init_cross_provider_selector = __esm({
65767
65899
  init_credential_pool();
65768
65900
  init_credentials2();
65769
65901
  init_credentials_store();
65902
+ init_local_inference_probe();
65770
65903
  logger4 = getLogger("llm-cross-provider-selector");
65771
65904
  BUILTIN_PROVIDER_IDS = [
65772
65905
  "anthropic",
@@ -65883,6 +66016,7 @@ __export(role_resolver_exports, {
65883
66016
  HYGIENE_FALLBACK_MODEL: () => HYGIENE_FALLBACK_MODEL,
65884
66017
  IMPLICIT_FALLBACK_MODEL: () => IMPLICIT_FALLBACK_MODEL,
65885
66018
  IMPLICIT_FALLBACK_PROVIDER: () => IMPLICIT_FALLBACK_PROVIDER,
66019
+ LOCAL_PLACEHOLDER_TOKEN: () => LOCAL_PLACEHOLDER_TOKEN,
65886
66020
  resolveAnthropicForRole: () => resolveAnthropicForRole,
65887
66021
  resolveLLMForRole: () => resolveLLMForRole
65888
66022
  });
@@ -65986,6 +66120,16 @@ async function selectProviderModel(llm, role, systemKey, profileOverride, projec
65986
66120
  source: "implicit-fallback"
65987
66121
  };
65988
66122
  }
66123
+ async function applyProviderExclusions(selected, role, projectRoot, exclude) {
66124
+ if (exclude === void 0 || exclude.length === 0) return selected;
66125
+ if (!exclude.includes(selected.provider)) return selected;
66126
+ try {
66127
+ const substitute = await selectBestProvisioned(role, { projectRoot, exclude });
66128
+ if (substitute !== null) return substitute;
66129
+ } catch {
66130
+ }
66131
+ return selected;
66132
+ }
65989
66133
  async function resolveCredentialForRole(provider, credentialLabel, projectRoot) {
65990
66134
  try {
65991
66135
  await new CredentialPool(provider).refreshExpiredOAuth();
@@ -66000,7 +66144,9 @@ async function resolveCredentialForRole(provider, credentialLabel, projectRoot)
66000
66144
  provider,
66001
66145
  apiKey: stored.accessToken,
66002
66146
  source: "cred-file",
66003
- authType: wireAuthType
66147
+ authType: wireAuthType,
66148
+ // T12081: preserve the stored endpoint (Ollama/LM Studio/vLLM/…).
66149
+ baseUrl: stored.baseUrl ?? null
66004
66150
  },
66005
66151
  usedLabel: stored.label
66006
66152
  };
@@ -66016,7 +66162,9 @@ async function resolveCredentialForRole(provider, credentialLabel, projectRoot)
66016
66162
  provider,
66017
66163
  apiKey: picked.accessToken,
66018
66164
  source: "cred-file",
66019
- authType: wireAuthType
66165
+ authType: wireAuthType,
66166
+ // T12081: preserve the stored endpoint (Ollama/LM Studio/vLLM/…).
66167
+ baseUrl: picked.baseUrl ?? null
66020
66168
  },
66021
66169
  usedLabel: picked.label
66022
66170
  };
@@ -66026,6 +66174,23 @@ async function resolveCredentialForRole(provider, credentialLabel, projectRoot)
66026
66174
  if (cred.apiKey) {
66027
66175
  return { credential: cred, usedLabel: void 0 };
66028
66176
  }
66177
+ if (provider === "ollama") {
66178
+ const local = await detectLocalInference();
66179
+ if (local !== null && local.name === "ollama") {
66180
+ return {
66181
+ credential: {
66182
+ provider,
66183
+ apiKey: LOCAL_PLACEHOLDER_TOKEN,
66184
+ source: "local-daemon",
66185
+ authType: "api_key",
66186
+ // ROOT url, not the `/v1` form: `deriveApiWire('ollama')` selects the
66187
+ // NATIVE transport, which appends `/api/chat` itself.
66188
+ baseUrl: local.baseUrl
66189
+ },
66190
+ usedLabel: void 0
66191
+ };
66192
+ }
66193
+ }
66029
66194
  return { credential: null, usedLabel: void 0 };
66030
66195
  }
66031
66196
  async function resolveLLMForRole(role, opts) {
@@ -66038,13 +66203,19 @@ async function resolveLLMForRole(role, opts) {
66038
66203
  config2 = void 0;
66039
66204
  }
66040
66205
  const llmBlock = readLlmBlock(config2);
66041
- const { provider, model, credentialLabel, source } = await selectProviderModel(
66206
+ const selected = await selectProviderModel(
66042
66207
  llmBlock,
66043
66208
  role,
66044
66209
  opts?.systemKey,
66045
66210
  opts?.profileOverride,
66046
66211
  projectRoot
66047
66212
  );
66213
+ const { provider, model, credentialLabel, source } = await applyProviderExclusions(
66214
+ selected,
66215
+ role,
66216
+ projectRoot,
66217
+ opts?.excludeProviders
66218
+ );
66048
66219
  const { credential, usedLabel } = await resolveCredentialForRole(
66049
66220
  provider,
66050
66221
  credentialLabel,
@@ -66070,7 +66241,12 @@ async function resolveLLMForRole(role, opts) {
66070
66241
  credentialMetadata = {
66071
66242
  provider: credential.provider,
66072
66243
  source: credential.source,
66073
- authType: wireAuthType
66244
+ authType: wireAuthType,
66245
+ // T12081: carry the credential's OWN endpoint. Without it a consumer
66246
+ // building a transport cannot tell that this credential targets a local
66247
+ // or proxied host, and falls back to the provider's public API — which
66248
+ // rejected every Ollama / LM Studio / vLLM / OpenRouter credential 401.
66249
+ baseUrl: credential.baseUrl ?? null
66074
66250
  };
66075
66251
  sealedCredential = makeSealedCredential({
66076
66252
  provider: credential.provider,
@@ -66112,7 +66288,7 @@ async function resolveAnthropicForRole(role, opts) {
66112
66288
  model: llm.model
66113
66289
  };
66114
66290
  }
66115
- var IMPLICIT_FALLBACK_PROVIDER, HYGIENE_FALLBACK_MODEL;
66291
+ var IMPLICIT_FALLBACK_PROVIDER, LOCAL_PLACEHOLDER_TOKEN, HYGIENE_FALLBACK_MODEL;
66116
66292
  var init_role_resolver = __esm({
66117
66293
  "packages/core/src/llm/role-resolver.ts"() {
66118
66294
  "use strict";
@@ -66123,10 +66299,12 @@ var init_role_resolver = __esm({
66123
66299
  init_credentials_store();
66124
66300
  init_cross_provider_selector();
66125
66301
  init_fallback_model();
66302
+ init_local_inference_probe();
66126
66303
  init_sealed_credential();
66127
66304
  init_system_of_use_registry();
66128
66305
  init_anthropic_client_factory();
66129
66306
  IMPLICIT_FALLBACK_PROVIDER = "anthropic";
66307
+ LOCAL_PLACEHOLDER_TOKEN = "local";
66130
66308
  HYGIENE_FALLBACK_MODEL = "claude-sonnet-4-6";
66131
66309
  }
66132
66310
  });
@@ -66985,8 +67163,8 @@ var _engineRegistry = /* @__PURE__ */ new Map([
66985
67163
  registerContextEngine("llm-summarization", _engineRegistry.get("compression"));
66986
67164
  var _executorCache = /* @__PURE__ */ new Map();
66987
67165
  async function getLlmExecutor(role) {
66988
- const cached2 = _executorCache.get(role);
66989
- if (cached2 !== void 0) return cached2;
67166
+ const cached3 = _executorCache.get(role);
67167
+ if (cached3 !== void 0) return cached3;
66990
67168
  const contextEngine = _engineRegistry.get(role);
66991
67169
  const factory11 = new DefaultLlmExecutorFactory({ contextEngine });
66992
67170
  const executor = await factory11.createForRole(role);