@cleocode/core 2026.6.14 → 2026.6.17

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 (152) hide show
  1. package/dist/check/pr-gate.d.ts +6 -2
  2. package/dist/check/pr-gate.d.ts.map +1 -1
  3. package/dist/check/pr-gate.js +14 -32
  4. package/dist/check/pr-gate.js.map +1 -1
  5. package/dist/config/config-repair.d.ts +70 -0
  6. package/dist/config/config-repair.d.ts.map +1 -0
  7. package/dist/config/config-repair.js +248 -0
  8. package/dist/config/config-repair.js.map +1 -0
  9. package/dist/config/registry.d.ts.map +1 -1
  10. package/dist/config/registry.js +4 -4
  11. package/dist/config/registry.js.map +1 -1
  12. package/dist/config.d.ts.map +1 -1
  13. package/dist/config.js +11 -10
  14. package/dist/config.js.map +1 -1
  15. package/dist/docs/export-document.js +58 -16
  16. package/dist/docs/export-document.js.map +2 -2
  17. package/dist/gc/cleanup.d.ts +23 -8
  18. package/dist/gc/cleanup.d.ts.map +1 -1
  19. package/dist/gc/cleanup.js +287 -22
  20. package/dist/gc/cleanup.js.map +1 -1
  21. package/dist/gc/janitor.d.ts +149 -0
  22. package/dist/gc/janitor.d.ts.map +1 -0
  23. package/dist/gc/janitor.js +760 -0
  24. package/dist/gc/janitor.js.map +1 -0
  25. package/dist/internal.d.ts +1 -0
  26. package/dist/internal.d.ts.map +1 -1
  27. package/dist/internal.js +3 -0
  28. package/dist/internal.js.map +1 -1
  29. package/dist/llm/cli-ops.d.ts +31 -4
  30. package/dist/llm/cli-ops.d.ts.map +1 -1
  31. package/dist/llm/cli-ops.js +110 -26
  32. package/dist/llm/cli-ops.js.map +1 -1
  33. package/dist/llm/credential-pool.d.ts +62 -0
  34. package/dist/llm/credential-pool.d.ts.map +1 -1
  35. package/dist/llm/credential-pool.js +161 -0
  36. package/dist/llm/credential-pool.js.map +1 -1
  37. package/dist/llm/cross-provider-selector.d.ts +254 -0
  38. package/dist/llm/cross-provider-selector.d.ts.map +1 -0
  39. package/dist/llm/cross-provider-selector.js +635 -0
  40. package/dist/llm/cross-provider-selector.js.map +1 -0
  41. package/dist/llm/index.d.ts +2 -0
  42. package/dist/llm/index.d.ts.map +1 -1
  43. package/dist/llm/index.js +1 -0
  44. package/dist/llm/index.js.map +1 -1
  45. package/dist/llm/local-model-fit.d.ts +260 -0
  46. package/dist/llm/local-model-fit.d.ts.map +1 -0
  47. package/dist/llm/local-model-fit.js +563 -0
  48. package/dist/llm/local-model-fit.js.map +1 -0
  49. package/dist/llm/plugin-facade.js +883 -231
  50. package/dist/llm/plugin-facade.js.map +3 -3
  51. package/dist/llm/provider-registry/builtin/ollama.d.ts +18 -0
  52. package/dist/llm/provider-registry/builtin/ollama.d.ts.map +1 -1
  53. package/dist/llm/provider-registry/builtin/ollama.js +20 -2
  54. package/dist/llm/provider-registry/builtin/ollama.js.map +1 -1
  55. package/dist/llm/role-resolver.d.ts.map +1 -1
  56. package/dist/llm/role-resolver.js +45 -10
  57. package/dist/llm/role-resolver.js.map +1 -1
  58. package/dist/llm/transports/codex-responses.d.ts +111 -88
  59. package/dist/llm/transports/codex-responses.d.ts.map +1 -1
  60. package/dist/llm/transports/codex-responses.js +456 -258
  61. package/dist/llm/transports/codex-responses.js.map +1 -1
  62. package/dist/release/index.d.ts +2 -2
  63. package/dist/release/index.d.ts.map +1 -1
  64. package/dist/release/index.js +1 -1
  65. package/dist/release/index.js.map +1 -1
  66. package/dist/release/reconcile.d.ts +97 -0
  67. package/dist/release/reconcile.d.ts.map +1 -1
  68. package/dist/release/reconcile.js +412 -5
  69. package/dist/release/reconcile.js.map +1 -1
  70. package/dist/resources/backend.d.ts +155 -0
  71. package/dist/resources/backend.d.ts.map +1 -0
  72. package/dist/resources/backend.js +23 -0
  73. package/dist/resources/backend.js.map +1 -0
  74. package/dist/resources/linux-backend.d.ts +167 -0
  75. package/dist/resources/linux-backend.d.ts.map +1 -0
  76. package/dist/resources/linux-backend.js +259 -0
  77. package/dist/resources/linux-backend.js.map +1 -0
  78. package/dist/resources/monitor.d.ts +230 -0
  79. package/dist/resources/monitor.d.ts.map +1 -0
  80. package/dist/resources/monitor.js +255 -0
  81. package/dist/resources/monitor.js.map +1 -0
  82. package/dist/resources/spawn-wrapper.d.ts +265 -0
  83. package/dist/resources/spawn-wrapper.d.ts.map +1 -0
  84. package/dist/resources/spawn-wrapper.js +304 -0
  85. package/dist/resources/spawn-wrapper.js.map +1 -0
  86. package/dist/selfimprove/budget.d.ts +11 -2
  87. package/dist/selfimprove/budget.d.ts.map +1 -1
  88. package/dist/selfimprove/budget.js +11 -2
  89. package/dist/selfimprove/budget.js.map +1 -1
  90. package/dist/selfimprove/fix-gen-context.d.ts +172 -0
  91. package/dist/selfimprove/fix-gen-context.d.ts.map +1 -0
  92. package/dist/selfimprove/fix-gen-context.js +218 -0
  93. package/dist/selfimprove/fix-gen-context.js.map +1 -0
  94. package/dist/selfimprove/fix-gen.d.ts +285 -0
  95. package/dist/selfimprove/fix-gen.d.ts.map +1 -0
  96. package/dist/selfimprove/fix-gen.js +346 -0
  97. package/dist/selfimprove/fix-gen.js.map +1 -0
  98. package/dist/selfimprove/op-source-map.d.ts +92 -0
  99. package/dist/selfimprove/op-source-map.d.ts.map +1 -0
  100. package/dist/selfimprove/op-source-map.js +187 -0
  101. package/dist/selfimprove/op-source-map.js.map +1 -0
  102. package/dist/selfimprove/probe-helper.d.ts +61 -0
  103. package/dist/selfimprove/probe-helper.d.ts.map +1 -0
  104. package/dist/selfimprove/probe-helper.js +64 -0
  105. package/dist/selfimprove/probe-helper.js.map +1 -0
  106. package/dist/selfimprove/run-loop.d.ts +44 -4
  107. package/dist/selfimprove/run-loop.d.ts.map +1 -1
  108. package/dist/selfimprove/run-loop.js +73 -6
  109. package/dist/selfimprove/run-loop.js.map +1 -1
  110. package/dist/selfimprove/scenarios/seeded-code-regression/golden.json +18 -0
  111. package/dist/selfimprove/scenarios/seeded-code-regression/scenario.json +12 -0
  112. package/dist/sentient/cross-project-hygiene.d.ts +11 -2
  113. package/dist/sentient/cross-project-hygiene.d.ts.map +1 -1
  114. package/dist/sentient/cross-project-hygiene.js +73 -10
  115. package/dist/sentient/cross-project-hygiene.js.map +1 -1
  116. package/dist/sentient/daemon.d.ts.map +1 -1
  117. package/dist/sentient/daemon.js +7 -2
  118. package/dist/sentient/daemon.js.map +1 -1
  119. package/dist/sentient/worktree-dispatch.d.ts.map +1 -1
  120. package/dist/sentient/worktree-dispatch.js +8 -1
  121. package/dist/sentient/worktree-dispatch.js.map +1 -1
  122. package/dist/setup/index.d.ts +1 -0
  123. package/dist/setup/index.d.ts.map +1 -1
  124. package/dist/setup/index.js +1 -0
  125. package/dist/setup/index.js.map +1 -1
  126. package/dist/setup/sections/models-roles.d.ts +32 -1
  127. package/dist/setup/sections/models-roles.d.ts.map +1 -1
  128. package/dist/setup/sections/models-roles.js +118 -19
  129. package/dist/setup/sections/models-roles.js.map +1 -1
  130. package/dist/setup/whoami-summary.d.ts +34 -0
  131. package/dist/setup/whoami-summary.d.ts.map +1 -0
  132. package/dist/setup/whoami-summary.js +106 -0
  133. package/dist/setup/whoami-summary.js.map +1 -0
  134. package/dist/spawn/branch-lock.d.ts.map +1 -1
  135. package/dist/spawn/branch-lock.js +7 -1
  136. package/dist/spawn/branch-lock.js.map +1 -1
  137. package/dist/store/attachment-repair.d.ts +110 -0
  138. package/dist/store/attachment-repair.d.ts.map +1 -0
  139. package/dist/store/attachment-repair.js +326 -0
  140. package/dist/store/attachment-repair.js.map +1 -0
  141. package/dist/store/attachment-store.d.ts.map +1 -1
  142. package/dist/store/attachment-store.js +24 -7
  143. package/dist/store/attachment-store.js.map +1 -1
  144. package/dist/tasks/evidence.d.ts +34 -2
  145. package/dist/tasks/evidence.d.ts.map +1 -1
  146. package/dist/tasks/evidence.js +293 -35
  147. package/dist/tasks/evidence.js.map +1 -1
  148. package/dist/verification/verify-tools.d.ts.map +1 -1
  149. package/dist/verification/verify-tools.js +5 -6
  150. package/dist/verification/verify-tools.js.map +1 -1
  151. package/package.json +22 -12
  152. package/templates/workflows/release-prepare.yml.tmpl +48 -2
@@ -0,0 +1,346 @@
1
+ /**
2
+ * Fix-generation stage for the self-improvement loop (T11889 · T11975).
3
+ *
4
+ * This is the missing autonomous link the v1 loop explicitly stubbed out
5
+ * ("NO autonomous fix-gen in v1", see {@link "./run-loop.js"}): given a detected
6
+ * regression (an open `selfimprove_dhq` + its envelope-diff) plus minimal repo
7
+ * context, it asks the LLM to PROPOSE a unified-diff patch and writes that patch
8
+ * to the path the draft-PR egress ({@link "./draft-pr.js".openDraftPr}) already
9
+ * `existsSync`-checks: `<cwd>/selfimprove-<scenario>.patch`. Once the patch is on
10
+ * disk the existing egress guard opens ONE DRAFT PR — closing the
11
+ * DHQ → fix → draft-PR pipeline end-to-end.
12
+ *
13
+ * ## Hard safety posture (unchanged from v1 — P5 spec §B.7)
14
+ *
15
+ * - **LLM ONLY via the E9 chokepoint.** The real generator
16
+ * ({@link createLlmFixGenerator}) resolves its model strictly through
17
+ * {@link "../llm/system-resolver.js".resolveLLMForSystem} → {@link ModelRunner}
18
+ * (Gate-13). It constructs NO raw provider client, reads NO `*_API_KEY` env,
19
+ * and hardcodes no model id — the resolver/registry is the SSoT. The
20
+ * plaintext token is materialized only at the wire boundary (E10).
21
+ * - **Draft-PR-ONLY, never auto-merge.** This module produces a PATCH FILE only;
22
+ * the egress that consumes it always opens a `--draft` PR against a feature
23
+ * branch (never `main`). This module performs NO git/gh action.
24
+ * - **Graceful degrade, never a rogue mutation.** Every failure (no credential,
25
+ * LLM error, empty / non-unified-diff output) returns a typed
26
+ * {@link FixGenResult} of kind `'skipped'` and writes NO patch file — so the
27
+ * existing egress guard (`existsSync` on the patch path) simply skips the PR.
28
+ * This function NEVER throws.
29
+ * - **Injectable seam.** The LLM dependency is the {@link FixGenerator} port;
30
+ * unit tests inject a deterministic fake that returns a canned patch, so NO
31
+ * real LLM is reached in tests.
32
+ *
33
+ * CORE-first: this engine lives in `core` and owns the `FixGenerator` port TYPE.
34
+ * Import-time side-effect-free.
35
+ *
36
+ * @module @cleocode/core/selfimprove/fix-gen
37
+ * @epic T11889
38
+ * @task T11975
39
+ */
40
+ import { writeFileSync } from 'node:fs';
41
+ import { resolve as resolvePath } from 'node:path';
42
+ import { cwd as processCwd } from 'node:process';
43
+ import { redact } from '@cleocode/utils';
44
+ import { loadFileContext, renderFileContextSection, } from './fix-gen-context.js';
45
+ /**
46
+ * Maximum byte length of a model-reply excerpt stored in DHQ evidence or logs.
47
+ * Replies beyond this threshold are truncated with a marker so logs stay bounded
48
+ * while preserving enough context for diagnosis.
49
+ */
50
+ const REPLY_EXCERPT_MAX_BYTES = 3072;
51
+ /**
52
+ * The E9 system-of-use label the fix-generator resolves its model under. Maps to
53
+ * the `judgement` role (see `SYSTEM_ROLE_MAP`) — the closest standing role for
54
+ * "reason about a regression and propose a code change".
55
+ */
56
+ const FIXGEN_SYSTEM = 'task-executor';
57
+ /** A leading marker every unified-diff hunk header / file header carries. */
58
+ const UNIFIED_DIFF_MARKERS = ['diff --git ', '--- ', '+++ ', '@@ '];
59
+ /**
60
+ * Compute the patch path the draft-PR egress expects for a scenario:
61
+ * `<cwd>/selfimprove-<scenario>.patch`. The scenario is sanitized to a safe file
62
+ * stem so a hostile scenario name cannot escape the cwd.
63
+ *
64
+ * @param scenario - The scenario name.
65
+ * @param cwd - The working directory to resolve against (defaults to `process.cwd`).
66
+ * @returns The absolute patch path.
67
+ */
68
+ export function fixPatchPath(scenario, cwd) {
69
+ const safeScenario = scenario.replace(/[^a-z0-9-]/gi, '-');
70
+ return resolvePath(cwd ?? processCwd(), `selfimprove-${safeScenario}.patch`);
71
+ }
72
+ /**
73
+ * Is `text` a plausible unified diff?
74
+ *
75
+ * A real `git apply`-able patch always carries at least one of the canonical
76
+ * unified-diff markers ({@link UNIFIED_DIFF_MARKERS}). An LLM that returns prose,
77
+ * an apology, or an empty string fails this guard, so the loop degrades to "no
78
+ * patch" rather than writing junk the egress would then fail to `git apply`.
79
+ *
80
+ * This is a cheap structural sniff, NOT a full parse — the authoritative
81
+ * applicability check is `git apply` inside the egress (which fails the PR cut on
82
+ * a bad patch, never `main`).
83
+ *
84
+ * @param text - Candidate patch text.
85
+ * @returns `true` when the text looks like a unified diff.
86
+ */
87
+ export function looksLikeUnifiedDiff(text) {
88
+ if (text.trim().length === 0)
89
+ return false;
90
+ return UNIFIED_DIFF_MARKERS.some((marker) => text.includes(marker));
91
+ }
92
+ /**
93
+ * Build the system + user prompt the real generator sends to the LLM. Pure
94
+ * (no IO) so it is unit-testable in isolation when an explicit `fileContext` is
95
+ * passed; the default path calls {@link loadFileContext} to resolve the relevant
96
+ * source files for each regressing op-coordinate (IO-bearing path used in
97
+ * production).
98
+ *
99
+ * The prompt is deliberately strict: "respond with ONLY a unified diff" — anything
100
+ * else fails {@link looksLikeUnifiedDiff} downstream and degrades to "no patch".
101
+ * When file context is available it is embedded between the regression description
102
+ * and the "produce the diff" instruction so the model can locate the responsible
103
+ * code. When context is absent (unmapped op or budget exhausted) the model is
104
+ * explicitly told it may respond `NO_PATCH` — this keeps the prompt honest.
105
+ *
106
+ * @param request - The fix-generation request.
107
+ * @param fileContext - Optional pre-loaded file context (injected for pure unit
108
+ * tests). When absent, {@link loadFileContext} is called with the request's
109
+ * `repoContext.projectRoot` and the op-coordinates from `regressions`.
110
+ * @returns The `{ system, user }` prompt pair.
111
+ *
112
+ * @task T11988
113
+ */
114
+ export function buildFixGenPrompt(request, fileContext) {
115
+ const system = 'You are an autonomous software-maintenance agent. You are given a detected ' +
116
+ 'regression in a TypeScript monorepo (a divergence between a replayed scenario ' +
117
+ 'and its golden expected output). Produce a MINIMAL fix as a single unified diff ' +
118
+ '(`git diff` format, with `diff --git`, `---`, `+++`, and `@@` hunk headers). ' +
119
+ 'Respond with ONLY the unified diff and NOTHING else — no prose, no code fences, ' +
120
+ 'no explanation. If you cannot propose a safe fix, respond with the single token ' +
121
+ 'NO_PATCH.';
122
+ const lines = request.regressions.map((r) => `- op ${r.opCoord} (#${r.opIndex}) at path \`${r.path}\`: ` +
123
+ `actual=${JSON.stringify(r.actual)} expected=${JSON.stringify(r.expected)}`);
124
+ // Resolve file context: use the pre-loaded context (pure test path) or load it
125
+ // now from disk (production path). Either way we render the same section string.
126
+ const ctx = fileContext ??
127
+ loadFileContext({
128
+ projectRoot: request.repoContext.projectRoot,
129
+ opCoords: request.regressions.map((r) => r.opCoord),
130
+ ...request.repoContext.contextBudget,
131
+ });
132
+ const fileSection = renderFileContextSection(ctx);
133
+ const user = `Regression ${request.dhqId} in scenario \`${request.scenario}\` ` +
134
+ `(${request.regressions.length} diverging path(s)).\n` +
135
+ (request.repoContext.summary ? `Scenario summary: ${request.repoContext.summary}\n` : '') +
136
+ `Project root: ${request.repoContext.projectRoot}\n\n` +
137
+ `Diverging paths:\n${lines.join('\n')}\n` +
138
+ (fileSection
139
+ ? `\n${fileSection}\n`
140
+ : '\n[No source file context available — respond NO_PATCH if you cannot locate the fix.]\n') +
141
+ '\nProduce the unified diff that resolves these divergences.';
142
+ return { system, user };
143
+ }
144
+ /**
145
+ * Produce a credential-redacted, byte-bounded excerpt of a raw model reply for
146
+ * safe logging and DHQ evidence attachment.
147
+ *
148
+ * The excerpt is at most {@link REPLY_EXCERPT_MAX_BYTES} UTF-8 bytes. When the
149
+ * reply is longer a `…[truncated <N> bytes]` marker replaces the tail so the
150
+ * caller can distinguish a genuinely-short reply from a truncated one. The
151
+ * returned string is already passed through {@link redact} so no downstream
152
+ * scrubbing is needed.
153
+ *
154
+ * @param reply - The raw model reply text.
155
+ * @returns A scrubbed, bounded excerpt string.
156
+ */
157
+ export function truncateReply(reply) {
158
+ const scrubbed = redact(reply);
159
+ const buf = Buffer.from(scrubbed, 'utf8');
160
+ if (buf.byteLength <= REPLY_EXCERPT_MAX_BYTES)
161
+ return scrubbed;
162
+ const truncated = buf.subarray(0, REPLY_EXCERPT_MAX_BYTES).toString('utf8');
163
+ const overflow = buf.byteLength - REPLY_EXCERPT_MAX_BYTES;
164
+ return `${truncated}…[truncated ${overflow} bytes]`;
165
+ }
166
+ /**
167
+ * Lazily-resolved module logger (import-time side-effect-free).
168
+ */
169
+ let cachedLogger;
170
+ async function getModuleLogger() {
171
+ if (cachedLogger === undefined) {
172
+ const { getLogger } = await import('../logger.js');
173
+ cachedLogger = getLogger('selfimprove-fix-gen');
174
+ }
175
+ return cachedLogger;
176
+ }
177
+ /**
178
+ * Run the fix-generation stage: ask the injected {@link FixGenerator} for a patch,
179
+ * validate it as a unified diff, and write it to the egress-expected patch path.
180
+ *
181
+ * This function NEVER throws and NEVER mutates anything but the single patch file.
182
+ * On ANY degrade path — generator returns `'none'`, the generator throws, or the
183
+ * returned text is not a unified diff — it writes NO file and returns
184
+ * `{ kind: 'skipped', reason }`, so the downstream `existsSync` egress guard
185
+ * simply skips the PR. The caller records the `reason` on the DHQ evidence.
186
+ *
187
+ * @param opts - See {@link GenerateFixPatchOptions}.
188
+ * @returns A {@link FixGenResult}.
189
+ *
190
+ * @example
191
+ * ```ts
192
+ * const res = await generateFixPatch({
193
+ * request: { dhqId: 'DHQ-abcd1234', scenario, questionHash, regressions, repoContext },
194
+ * generator: createLlmFixGenerator(),
195
+ * cwd: projectRoot,
196
+ * });
197
+ * if (res.kind === 'written') { // openDraftPr will now find selfimprove-<scenario>.patch }
198
+ * ```
199
+ */
200
+ export async function generateFixPatch(opts) {
201
+ const logger = opts.logger ?? (await getModuleLogger());
202
+ const { request, generator } = opts;
203
+ const patchPath = fixPatchPath(request.scenario, opts.cwd);
204
+ let output;
205
+ try {
206
+ output = await generator.propose(request);
207
+ }
208
+ catch (err) {
209
+ const reason = `fixgen-threw:${err instanceof Error ? err.message : String(err)}`;
210
+ logger.warn({ dhqId: request.dhqId, scenario: request.scenario, reason }, 'fix-gen degraded');
211
+ return { kind: 'skipped', reason };
212
+ }
213
+ if (output.kind === 'none') {
214
+ // For model-declined the generator already pre-sanitized the rawReply field;
215
+ // log it at debug so the reply is visible without noise on green runs.
216
+ const replyExcerpt = output.rawReply;
217
+ if (replyExcerpt !== undefined) {
218
+ logger.debug({ dhqId: request.dhqId, scenario: request.scenario, reason: output.reason, replyExcerpt }, 'fix-gen model declined — raw reply excerpt (redacted, truncated) attached to DHQ evidence');
219
+ }
220
+ else {
221
+ logger.info({ dhqId: request.dhqId, scenario: request.scenario, reason: output.reason }, 'fix-gen produced no patch (graceful skip — no PR)');
222
+ }
223
+ return {
224
+ kind: 'skipped',
225
+ reason: `fixgen-none:${output.reason}`,
226
+ ...(replyExcerpt !== undefined ? { replyExcerpt } : {}),
227
+ };
228
+ }
229
+ if (!looksLikeUnifiedDiff(output.diff)) {
230
+ // The model returned something but it is not a unified diff. Log the reply
231
+ // excerpt at warn (actionable — the model may need a prompt tweak) and
232
+ // attach it to the DHQ evidence row for the operator.
233
+ const replyExcerpt = truncateReply(output.diff);
234
+ logger.warn({ dhqId: request.dhqId, scenario: request.scenario, replyExcerpt }, 'fix-gen output is not a unified diff (graceful skip — no PR); raw reply attached to DHQ evidence');
235
+ return { kind: 'skipped', reason: 'fixgen-not-a-diff', replyExcerpt };
236
+ }
237
+ // Normalize a trailing newline so `git apply` accepts the hunk.
238
+ const diff = output.diff.endsWith('\n') ? output.diff : `${output.diff}\n`;
239
+ writeFileSync(patchPath, diff, 'utf8');
240
+ const bytes = Buffer.byteLength(diff, 'utf8');
241
+ logger.info({ dhqId: request.dhqId, scenario: request.scenario, patchPath, bytes, model: output.model }, 'fix-gen wrote candidate patch — egress will open a DRAFT PR');
242
+ return { kind: 'written', patchPath, bytes, model: output.model };
243
+ }
244
+ /**
245
+ * Construct the REAL fix generator — the one that drives the E9 LLM chokepoint.
246
+ *
247
+ * Resolution funnels through {@link resolveLLMForSystem}(`'task-executor'`) →
248
+ * {@link ModelRunner.build} → `session.send` (ONE non-streaming completion).
249
+ * It constructs NO raw provider client, reads NO API-key env, and hardcodes no
250
+ * model id (Gate-13). The plaintext credential is materialized ONLY at the
251
+ * `ModelRunner` wire boundary (E10) via the sealed handle. When no credential is
252
+ * reachable it returns `{ kind: 'none' }` — the loop degrades to "no patch", no
253
+ * PR. This function constructs nothing at call time (lazy dynamic imports keep the
254
+ * module import-time side-effect-free + the E9 deps off the hot import path).
255
+ *
256
+ * @param opts - Optional project root threaded into the resolver.
257
+ * @returns A {@link FixGenerator} backed by the E9 chokepoint.
258
+ *
259
+ * @example
260
+ * ```ts
261
+ * const gen = createLlmFixGenerator({ projectRoot });
262
+ * const out = await gen.propose(request); // resolves model via E9, never raw client
263
+ * ```
264
+ */
265
+ export function createLlmFixGenerator(opts = {}) {
266
+ return {
267
+ async propose(request) {
268
+ const logger = await getModuleLogger();
269
+ // Lazy imports: keep E9 deps off the module import path + side-effect-free.
270
+ const { resolveLLMForSystem } = await import('../llm/system-resolver.js');
271
+ const { ModelRunner } = await import('../llm/model-runner.js');
272
+ // 1. E9 resolution chokepoint. Never throws (resolver contract).
273
+ const projectRoot = opts.projectRoot ?? request.repoContext.projectRoot;
274
+ const resolved = await resolveLLMForSystem(FIXGEN_SYSTEM, { projectRoot });
275
+ if (!resolved.sealedCredential && resolved.authType !== 'aws_sdk') {
276
+ return { kind: 'none', reason: 'no-credential-resolved' };
277
+ }
278
+ // 2. Materialize the plaintext ONLY at the wire boundary (E10), build a
279
+ // clean descriptor, and construct the transport inside ModelRunner.
280
+ const apiKey = resolved.sealedCredential
281
+ ? (await resolved.sealedCredential.fetch()).value
282
+ : null;
283
+ const built = await ModelRunner.build({
284
+ provider: resolved.provider,
285
+ model: resolved.model,
286
+ credential: resolved.credential
287
+ ? {
288
+ provider: resolved.credential.provider,
289
+ apiKey,
290
+ source: resolved.credential.source,
291
+ authType: resolved.credential.authType,
292
+ }
293
+ : null,
294
+ source: resolved.source,
295
+ ...(resolved.credentialLabel !== undefined
296
+ ? { credentialLabel: resolved.credentialLabel }
297
+ : {}),
298
+ apiMode: resolved.apiMode,
299
+ baseUrl: resolved.baseUrl,
300
+ authType: resolved.authType,
301
+ ...(resolved.capabilities !== undefined ? { capabilities: resolved.capabilities } : {}),
302
+ });
303
+ // 3. ONE non-streaming completion. The system prompt is threaded onto the
304
+ // request via `systemSuffix` (ConcreteSession maps it to
305
+ // `TransportRequest.system`); the user turn carries the regression detail.
306
+ const { system, user } = buildFixGenPrompt(request);
307
+ try {
308
+ const response = await built.session.send([{ role: 'user', content: user }], {
309
+ systemSuffix: system,
310
+ });
311
+ const text = (response.content ?? '').trim();
312
+ if (text.length === 0 || text === 'NO_PATCH') {
313
+ // Attach a redacted, bounded excerpt so the caller can log it onto
314
+ // the DHQ evidence row without having to re-run. The empty-reply case
315
+ // carries no useful excerpt; only the non-empty case (e.g. prose like
316
+ // "I cannot fix this") is worth capturing.
317
+ const rawReply = text.length > 0 ? truncateReply(text) : undefined;
318
+ return {
319
+ kind: 'none',
320
+ reason: 'model-declined',
321
+ ...(rawReply !== undefined ? { rawReply } : {}),
322
+ };
323
+ }
324
+ return { kind: 'patch', diff: stripCodeFences(text), model: response.model };
325
+ }
326
+ catch (err) {
327
+ logger.warn({ system: FIXGEN_SYSTEM, err: err instanceof Error ? err.message : String(err) }, 'fix-gen LLM call failed — degrading to no-patch');
328
+ return { kind: 'none', reason: 'llm-call-failed' };
329
+ }
330
+ },
331
+ };
332
+ }
333
+ /**
334
+ * Strip a single surrounding markdown code fence (```diff … ```), if present, so a
335
+ * model that wraps its diff still yields an applyable patch. A bare diff passes
336
+ * through untouched.
337
+ *
338
+ * @param text - Candidate patch text (already trimmed).
339
+ * @returns The fence-stripped text.
340
+ */
341
+ export function stripCodeFences(text) {
342
+ const fence = /^```[a-z]*\n([\s\S]*?)\n```$/i;
343
+ const m = text.match(fence);
344
+ return m?.[1] !== undefined ? m[1].trim() : text;
345
+ }
346
+ //# sourceMappingURL=fix-gen.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fix-gen.js","sourceRoot":"","sources":["../../src/selfimprove/fix-gen.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACxC,OAAO,EAAE,OAAO,IAAI,WAAW,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,GAAG,IAAI,UAAU,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAGzC,OAAO,EAGL,eAAe,EACf,wBAAwB,GACzB,MAAM,sBAAsB,CAAC;AAE9B;;;;GAIG;AACH,MAAM,uBAAuB,GAAG,IAAI,CAAC;AAErC;;;;GAIG;AACH,MAAM,aAAa,GAAG,eAAwB,CAAC;AAE/C,6EAA6E;AAC7E,MAAM,oBAAoB,GAAG,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAU,CAAC;AAmI7E;;;;;;;;GAQG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,GAAY;IACzD,MAAM,YAAY,GAAG,QAAQ,CAAC,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC,CAAC;IAC3D,OAAO,WAAW,CAAC,GAAG,IAAI,UAAU,EAAE,EAAE,eAAe,YAAY,QAAQ,CAAC,CAAC;AAC/E,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC3C,OAAO,oBAAoB,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;AACtE,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,OAAsB,EACtB,WAA+B;IAE/B,MAAM,MAAM,GACV,6EAA6E;QAC7E,gFAAgF;QAChF,kFAAkF;QAClF,+EAA+E;QAC/E,kFAAkF;QAClF,kFAAkF;QAClF,WAAW,CAAC;IAEd,MAAM,KAAK,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,CACnC,CAAC,CAAC,EAAE,EAAE,CACJ,QAAQ,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,OAAO,eAAe,CAAC,CAAC,IAAI,MAAM;QAC3D,UAAU,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,aAAa,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAC9E,CAAC;IAEF,+EAA+E;IAC/E,iFAAiF;IACjF,MAAM,GAAG,GACP,WAAW;QACX,eAAe,CAAC;YACd,WAAW,EAAE,OAAO,CAAC,WAAW,CAAC,WAAW;YAC5C,QAAQ,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;YACnD,GAAG,OAAO,CAAC,WAAW,CAAC,aAAa;SACrC,CAAC,CAAC;IACL,MAAM,WAAW,GAAG,wBAAwB,CAAC,GAAG,CAAC,CAAC;IAElD,MAAM,IAAI,GACR,cAAc,OAAO,CAAC,KAAK,kBAAkB,OAAO,CAAC,QAAQ,KAAK;QAClE,IAAI,OAAO,CAAC,WAAW,CAAC,MAAM,wBAAwB;QACtD,CAAC,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,qBAAqB,OAAO,CAAC,WAAW,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QACzF,iBAAiB,OAAO,CAAC,WAAW,CAAC,WAAW,MAAM;QACtD,qBAAqB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;QACzC,CAAC,WAAW;YACV,CAAC,CAAC,KAAK,WAAW,IAAI;YACtB,CAAC,CAAC,yFAAyF,CAAC;QAC9F,6DAA6D,CAAC;IAEhE,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;AAC1B,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IAC/B,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAC1C,IAAI,GAAG,CAAC,UAAU,IAAI,uBAAuB;QAAE,OAAO,QAAQ,CAAC;IAC/D,MAAM,SAAS,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,EAAE,uBAAuB,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC5E,MAAM,QAAQ,GAAG,GAAG,CAAC,UAAU,GAAG,uBAAuB,CAAC;IAC1D,OAAO,GAAG,SAAS,eAAe,QAAQ,SAAS,CAAC;AACtD,CAAC;AAED;;GAEG;AACH,IAAI,YAAgC,CAAC;AACrC,KAAK,UAAU,eAAe;IAC5B,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC/B,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,cAAc,CAAC,CAAC;QACnD,YAAY,GAAG,SAAS,CAAC,qBAAqB,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,YAAY,CAAC;AACtB,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAA6B;IAClE,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,MAAM,eAAe,EAAE,CAAC,CAAC;IACxD,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IACpC,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC;IAE3D,IAAI,MAAoB,CAAC;IACzB,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5C,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,MAAM,GAAG,gBAAgB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QAClF,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,EAAE,kBAAkB,CAAC,CAAC;QAC9F,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IACrC,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QAC3B,6EAA6E;QAC7E,uEAAuE;QACvE,MAAM,YAAY,GAAG,MAAM,CAAC,QAAQ,CAAC;QACrC,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;YAC/B,MAAM,CAAC,KAAK,CACV,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,EAAE,EACzF,2FAA2F,CAC5F,CAAC;QACJ,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CACT,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,EAC3E,mDAAmD,CACpD,CAAC;QACJ,CAAC;QACD,OAAO;YACL,IAAI,EAAE,SAAS;YACf,MAAM,EAAE,eAAe,MAAM,CAAC,MAAM,EAAE;YACtC,GAAG,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxD,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,oBAAoB,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,2EAA2E;QAC3E,uEAAuE;QACvE,sDAAsD;QACtD,MAAM,YAAY,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAChD,MAAM,CAAC,IAAI,CACT,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,YAAY,EAAE,EAClE,kGAAkG,CACnG,CAAC;QACF,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,mBAAmB,EAAE,YAAY,EAAE,CAAC;IACxE,CAAC;IAED,gEAAgE;IAChE,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,IAAI,IAAI,CAAC;IAC3E,aAAa,CAAC,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC9C,MAAM,CAAC,IAAI,CACT,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,EAC3F,6DAA6D,CAC9D,CAAC;IACF,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC;AACpE,CAAC;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,qBAAqB,CAAC,OAAiC,EAAE;IACvE,OAAO;QACL,KAAK,CAAC,OAAO,CAAC,OAAsB;YAClC,MAAM,MAAM,GAAG,MAAM,eAAe,EAAE,CAAC;YACvC,4EAA4E;YAC5E,MAAM,EAAE,mBAAmB,EAAE,GAAG,MAAM,MAAM,CAAC,2BAA2B,CAAC,CAAC;YAC1E,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC;YAE/D,iEAAiE;YACjE,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC,WAAW,CAAC;YACxE,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,aAAa,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC;YAC3E,IAAI,CAAC,QAAQ,CAAC,gBAAgB,IAAI,QAAQ,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAClE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,wBAAwB,EAAE,CAAC;YAC5D,CAAC;YAED,wEAAwE;YACxE,uEAAuE;YACvE,MAAM,MAAM,GAAG,QAAQ,CAAC,gBAAgB;gBACtC,CAAC,CAAC,CAAC,MAAM,QAAQ,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK;gBACjD,CAAC,CAAC,IAAI,CAAC;YACT,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,KAAK,CAAC;gBACpC,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gBAC3B,KAAK,EAAE,QAAQ,CAAC,KAAK;gBACrB,UAAU,EAAE,QAAQ,CAAC,UAAU;oBAC7B,CAAC,CAAC;wBACE,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,QAAQ;wBACtC,MAAM;wBACN,MAAM,EAAE,QAAQ,CAAC,UAAU,CAAC,MAAM;wBAClC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,QAAQ;qBACvC;oBACH,CAAC,CAAC,IAAI;gBACR,MAAM,EAAE,QAAQ,CAAC,MAAM;gBACvB,GAAG,CAAC,QAAQ,CAAC,eAAe,KAAK,SAAS;oBACxC,CAAC,CAAC,EAAE,eAAe,EAAE,QAAQ,CAAC,eAAe,EAAE;oBAC/C,CAAC,CAAC,EAAE,CAAC;gBACP,OAAO,EAAE,QAAQ,CAAC,OAAO;gBACzB,OAAO,EAAE,QAAQ,CAAC,OAAO;gBACzB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gBAC3B,GAAG,CAAC,QAAQ,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,QAAQ,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACxF,CAAC,CAAC;YAEH,0EAA0E;YAC1E,4DAA4D;YAC5D,8EAA8E;YAC9E,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;YACpD,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE;oBAC3E,YAAY,EAAE,MAAM;iBACrB,CAAC,CAAC;gBACH,MAAM,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;gBAC7C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,KAAK,UAAU,EAAE,CAAC;oBAC7C,mEAAmE;oBACnE,sEAAsE;oBACtE,sEAAsE;oBACtE,2CAA2C;oBAC3C,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;oBACnE,OAAO;wBACL,IAAI,EAAE,MAAM;wBACZ,MAAM,EAAE,gBAAgB;wBACxB,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;qBAChD,CAAC;gBACJ,CAAC;gBACD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,eAAe,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC;YAC/E,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,CAAC,IAAI,CACT,EAAE,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAChF,iDAAiD,CAClD,CAAC;gBACF,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,CAAC;YACrD,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,KAAK,GAAG,+BAA+B,CAAC;IAC9C,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC5B,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACnD,CAAC"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Static op-coordinate → source-file map for the self-improvement fix-gen stage.
3
+ *
4
+ * Given a regression's `opCoord` (the `domain.operation` string produced by
5
+ * {@link "../selfimprove/envelope-diff.js".DiffEntry.opCoord}, e.g. `tasks.show`),
6
+ * this module resolves the **repo-relative** paths of the CLI handler and the
7
+ * core module(s) most likely to contain the regression. The file list is fed into
8
+ * the fix-gen prompt builder ({@link "./fix-gen-context.js"}) so the LLM receives
9
+ * bounded, targeted code context rather than zero context or an unbounded repo
10
+ * dump.
11
+ *
12
+ * ## Design: static map, not a registry query
13
+ *
14
+ * The ops catalog (the `OPERATIONS` array in `@cleocode/contracts`) encodes
15
+ * `{ gateway, domain, operation }` but does NOT track source-file provenance.
16
+ * Runtime nexus queries (`cleo nexus query`) would work but introduce an
17
+ * in-process dependency on the nexus subsystem (an expensive side-effectful
18
+ * import in the fix-gen hot path). A **static map keyed by `domain.operation`**
19
+ * is the cost-free alternative: it is derived once (by reading the repo's known
20
+ * handler layout), is small enough to inline, and carries zero import-time cost.
21
+ *
22
+ * The map covers the common `tasks` domain operations exercised by the canonical
23
+ * scenarios. The entry for an unregistered `opCoord` returns an empty file list
24
+ * (`{ handlerFiles: [], coreFiles: [] }`) — the fix-gen stage degrades gracefully
25
+ * to "no context" (the model still receives the regression diff and may emit
26
+ * `NO_PATCH`, which is the honest outcome when context is absent).
27
+ *
28
+ * ## File path convention
29
+ *
30
+ * Every path is **repo-relative** (no leading `/`). The file content loader
31
+ * ({@link "./fix-gen-context.js"}) resolves them against `repoContext.projectRoot`
32
+ * before reading.
33
+ *
34
+ * Import-time side-effect-free: no IO, no dynamic imports.
35
+ *
36
+ * @module @cleocode/core/selfimprove/op-source-map
37
+ * @epic T11889
38
+ * @task T11988
39
+ */
40
+ /**
41
+ * Resolved source files for one op-coordinate.
42
+ *
43
+ * - `handlerFiles`: the CLI dispatch handler(s) in `packages/cleo/src/dispatch/domains/`
44
+ * - `coreFiles`: the core-logic module(s) in `packages/core/src/` (the file most
45
+ * likely to contain the regression for this operation)
46
+ *
47
+ * Both lists are repo-relative. Either may be empty when not applicable.
48
+ */
49
+ export interface OpSourceEntry {
50
+ /** CLI dispatch handler files (repo-relative). */
51
+ readonly handlerFiles: readonly string[];
52
+ /** Core module files that implement the operation logic (repo-relative). */
53
+ readonly coreFiles: readonly string[];
54
+ }
55
+ /**
56
+ * Resolve the handler + core source files for a given op-coordinate.
57
+ *
58
+ * Returns the {@link OpSourceEntry} from the static map when the `opCoord` is
59
+ * registered, or {@link EMPTY_ENTRY} when it is not. NEVER throws. The returned
60
+ * lists contain repo-relative paths; callers resolve them against the project root
61
+ * before reading.
62
+ *
63
+ * @param opCoord - The op coordinate to resolve (e.g. `'tasks.show'`).
64
+ * @returns The resolved {@link OpSourceEntry} (may be empty for unknown ops).
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * const { handlerFiles, coreFiles } = resolveOpSourceFiles('tasks.show');
69
+ * // handlerFiles: ['packages/cleo/src/dispatch/domains/tasks.ts']
70
+ * // coreFiles: ['packages/core/src/tasks/show.ts']
71
+ * ```
72
+ */
73
+ export declare function resolveOpSourceFiles(opCoord: string): OpSourceEntry;
74
+ /**
75
+ * Collect the unique set of source files for a list of op-coordinates.
76
+ *
77
+ * Deduplicates across entries so a scenario with two ops from the same handler
78
+ * does not include the handler twice. Returns the merged `{ handlerFiles, coreFiles }`
79
+ * with order preserved (first-seen wins for dedup).
80
+ *
81
+ * @param opCoords - The op-coordinate strings to collect files for.
82
+ * @returns The merged, deduplicated {@link OpSourceEntry}.
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * const entry = collectOpSourceFiles(['tasks.find', 'tasks.show']);
87
+ * // entry.handlerFiles: ['packages/cleo/src/dispatch/domains/tasks.ts']
88
+ * // entry.coreFiles: ['packages/core/src/tasks/find.ts', 'packages/core/src/tasks/show.ts']
89
+ * ```
90
+ */
91
+ export declare function collectOpSourceFiles(opCoords: readonly string[]): OpSourceEntry;
92
+ //# sourceMappingURL=op-source-map.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"op-source-map.d.ts","sourceRoot":"","sources":["../../src/selfimprove/op-source-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AAEH;;;;;;;;GAQG;AACH,MAAM,WAAW,aAAa;IAC5B,kDAAkD;IAClD,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,4EAA4E;IAC5E,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AA2FD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CAEnE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,aAAa,CAuB/E"}
@@ -0,0 +1,187 @@
1
+ /**
2
+ * Static op-coordinate → source-file map for the self-improvement fix-gen stage.
3
+ *
4
+ * Given a regression's `opCoord` (the `domain.operation` string produced by
5
+ * {@link "../selfimprove/envelope-diff.js".DiffEntry.opCoord}, e.g. `tasks.show`),
6
+ * this module resolves the **repo-relative** paths of the CLI handler and the
7
+ * core module(s) most likely to contain the regression. The file list is fed into
8
+ * the fix-gen prompt builder ({@link "./fix-gen-context.js"}) so the LLM receives
9
+ * bounded, targeted code context rather than zero context or an unbounded repo
10
+ * dump.
11
+ *
12
+ * ## Design: static map, not a registry query
13
+ *
14
+ * The ops catalog (the `OPERATIONS` array in `@cleocode/contracts`) encodes
15
+ * `{ gateway, domain, operation }` but does NOT track source-file provenance.
16
+ * Runtime nexus queries (`cleo nexus query`) would work but introduce an
17
+ * in-process dependency on the nexus subsystem (an expensive side-effectful
18
+ * import in the fix-gen hot path). A **static map keyed by `domain.operation`**
19
+ * is the cost-free alternative: it is derived once (by reading the repo's known
20
+ * handler layout), is small enough to inline, and carries zero import-time cost.
21
+ *
22
+ * The map covers the common `tasks` domain operations exercised by the canonical
23
+ * scenarios. The entry for an unregistered `opCoord` returns an empty file list
24
+ * (`{ handlerFiles: [], coreFiles: [] }`) — the fix-gen stage degrades gracefully
25
+ * to "no context" (the model still receives the regression diff and may emit
26
+ * `NO_PATCH`, which is the honest outcome when context is absent).
27
+ *
28
+ * ## File path convention
29
+ *
30
+ * Every path is **repo-relative** (no leading `/`). The file content loader
31
+ * ({@link "./fix-gen-context.js"}) resolves them against `repoContext.projectRoot`
32
+ * before reading.
33
+ *
34
+ * Import-time side-effect-free: no IO, no dynamic imports.
35
+ *
36
+ * @module @cleocode/core/selfimprove/op-source-map
37
+ * @epic T11889
38
+ * @task T11988
39
+ */
40
+ /**
41
+ * The static `opCoord → source files` map.
42
+ *
43
+ * Keys are `domain.operation` strings (lowercase, e.g. `'tasks.show'`). Values
44
+ * are {@link OpSourceEntry} objects with repo-relative paths.
45
+ *
46
+ * Coverage: the `tasks` domain operations exercised by the built-in scenarios
47
+ * (T11988: `seeded-code-regression`) plus the pre-existing `dhq-replay-find`
48
+ * scenario (`tasks.find`, `tasks.show`).
49
+ *
50
+ * @internal
51
+ */
52
+ const OP_SOURCE_MAP = {
53
+ 'tasks.show': {
54
+ handlerFiles: ['packages/cleo/src/dispatch/domains/tasks.ts'],
55
+ coreFiles: ['packages/core/src/tasks/show.ts'],
56
+ },
57
+ 'tasks.find': {
58
+ handlerFiles: ['packages/cleo/src/dispatch/domains/tasks.ts'],
59
+ coreFiles: ['packages/core/src/tasks/find.ts'],
60
+ },
61
+ 'tasks.list': {
62
+ handlerFiles: ['packages/cleo/src/dispatch/domains/tasks.ts'],
63
+ coreFiles: ['packages/core/src/tasks/list.ts'],
64
+ },
65
+ 'tasks.add': {
66
+ handlerFiles: ['packages/cleo/src/dispatch/domains/tasks.ts'],
67
+ coreFiles: ['packages/core/src/tasks/add.ts'],
68
+ },
69
+ 'tasks.update': {
70
+ handlerFiles: ['packages/cleo/src/dispatch/domains/tasks.ts'],
71
+ coreFiles: ['packages/core/src/tasks/update.ts'],
72
+ },
73
+ 'tasks.complete': {
74
+ handlerFiles: ['packages/cleo/src/dispatch/domains/tasks.ts'],
75
+ coreFiles: ['packages/core/src/tasks/complete.ts'],
76
+ },
77
+ 'tasks.delete': {
78
+ handlerFiles: ['packages/cleo/src/dispatch/domains/tasks.ts'],
79
+ coreFiles: ['packages/core/src/tasks/delete.ts'],
80
+ },
81
+ 'tasks.next': {
82
+ handlerFiles: ['packages/cleo/src/dispatch/domains/tasks.ts'],
83
+ coreFiles: ['packages/core/src/tasks/next.ts'],
84
+ },
85
+ 'tasks.current': {
86
+ handlerFiles: ['packages/cleo/src/dispatch/domains/tasks.ts'],
87
+ coreFiles: ['packages/core/src/tasks/current.ts'],
88
+ },
89
+ 'tasks.tree': {
90
+ handlerFiles: ['packages/cleo/src/dispatch/domains/tasks.ts'],
91
+ coreFiles: ['packages/core/src/tasks/generic-tree.ts'],
92
+ },
93
+ 'tasks.analyze': {
94
+ handlerFiles: ['packages/cleo/src/dispatch/domains/tasks.ts'],
95
+ coreFiles: ['packages/core/src/tasks/analyze.ts'],
96
+ },
97
+ 'memory.find': {
98
+ handlerFiles: ['packages/cleo/src/dispatch/domains/memory.ts'],
99
+ coreFiles: [],
100
+ },
101
+ 'memory.fetch': {
102
+ handlerFiles: ['packages/cleo/src/dispatch/domains/memory.ts'],
103
+ coreFiles: [],
104
+ },
105
+ 'selfimprove.run': {
106
+ handlerFiles: ['packages/cleo/src/dispatch/domains/selfimprove.ts'],
107
+ coreFiles: ['packages/core/src/selfimprove/run-loop.ts'],
108
+ },
109
+ /**
110
+ * `selfimprove.probe` — the seeded-code-regression scenario target (T11988).
111
+ * The probe-helper is intentionally broken (`probeVersion()` returns `2`
112
+ * instead of `1`); the fix is a single-line change in this file.
113
+ */
114
+ 'selfimprove.probe': {
115
+ handlerFiles: ['packages/cleo/src/dispatch/domains/selfimprove.ts'],
116
+ coreFiles: ['packages/core/src/selfimprove/probe-helper.ts'],
117
+ },
118
+ };
119
+ /**
120
+ * An empty source entry returned for unknown or unmapped op-coordinates.
121
+ *
122
+ * The fix-gen stage treats an empty file list as "no context available" and
123
+ * degrades the prompt to the regression-only view. The model may still produce
124
+ * a patch (or `NO_PATCH`) without file context.
125
+ */
126
+ const EMPTY_ENTRY = { handlerFiles: [], coreFiles: [] };
127
+ /**
128
+ * Resolve the handler + core source files for a given op-coordinate.
129
+ *
130
+ * Returns the {@link OpSourceEntry} from the static map when the `opCoord` is
131
+ * registered, or {@link EMPTY_ENTRY} when it is not. NEVER throws. The returned
132
+ * lists contain repo-relative paths; callers resolve them against the project root
133
+ * before reading.
134
+ *
135
+ * @param opCoord - The op coordinate to resolve (e.g. `'tasks.show'`).
136
+ * @returns The resolved {@link OpSourceEntry} (may be empty for unknown ops).
137
+ *
138
+ * @example
139
+ * ```ts
140
+ * const { handlerFiles, coreFiles } = resolveOpSourceFiles('tasks.show');
141
+ * // handlerFiles: ['packages/cleo/src/dispatch/domains/tasks.ts']
142
+ * // coreFiles: ['packages/core/src/tasks/show.ts']
143
+ * ```
144
+ */
145
+ export function resolveOpSourceFiles(opCoord) {
146
+ return OP_SOURCE_MAP[opCoord] ?? EMPTY_ENTRY;
147
+ }
148
+ /**
149
+ * Collect the unique set of source files for a list of op-coordinates.
150
+ *
151
+ * Deduplicates across entries so a scenario with two ops from the same handler
152
+ * does not include the handler twice. Returns the merged `{ handlerFiles, coreFiles }`
153
+ * with order preserved (first-seen wins for dedup).
154
+ *
155
+ * @param opCoords - The op-coordinate strings to collect files for.
156
+ * @returns The merged, deduplicated {@link OpSourceEntry}.
157
+ *
158
+ * @example
159
+ * ```ts
160
+ * const entry = collectOpSourceFiles(['tasks.find', 'tasks.show']);
161
+ * // entry.handlerFiles: ['packages/cleo/src/dispatch/domains/tasks.ts']
162
+ * // entry.coreFiles: ['packages/core/src/tasks/find.ts', 'packages/core/src/tasks/show.ts']
163
+ * ```
164
+ */
165
+ export function collectOpSourceFiles(opCoords) {
166
+ const seenHandler = new Set();
167
+ const seenCore = new Set();
168
+ const handlerFiles = [];
169
+ const coreFiles = [];
170
+ for (const coord of opCoords) {
171
+ const entry = resolveOpSourceFiles(coord);
172
+ for (const f of entry.handlerFiles) {
173
+ if (!seenHandler.has(f)) {
174
+ seenHandler.add(f);
175
+ handlerFiles.push(f);
176
+ }
177
+ }
178
+ for (const f of entry.coreFiles) {
179
+ if (!seenCore.has(f)) {
180
+ seenCore.add(f);
181
+ coreFiles.push(f);
182
+ }
183
+ }
184
+ }
185
+ return { handlerFiles, coreFiles };
186
+ }
187
+ //# sourceMappingURL=op-source-map.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"op-source-map.js","sourceRoot":"","sources":["../../src/selfimprove/op-source-map.ts"],"names":[],"mappings":"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"}