@kernel.chat/kbot 2.8.0 → 2.9.1

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 (48) hide show
  1. package/dist/agent-protocol.d.ts +97 -0
  2. package/dist/agent-protocol.d.ts.map +1 -0
  3. package/dist/agent-protocol.js +618 -0
  4. package/dist/agent-protocol.js.map +1 -0
  5. package/dist/agent.d.ts +2 -0
  6. package/dist/agent.d.ts.map +1 -1
  7. package/dist/agent.js +1 -1
  8. package/dist/agent.js.map +1 -1
  9. package/dist/auth.d.ts.map +1 -1
  10. package/dist/auth.js +17 -12
  11. package/dist/auth.js.map +1 -1
  12. package/dist/cli.js +166 -2
  13. package/dist/cli.js.map +1 -1
  14. package/dist/cloud-sync.d.ts.map +1 -1
  15. package/dist/cloud-sync.js +15 -4
  16. package/dist/cloud-sync.js.map +1 -1
  17. package/dist/confidence.d.ts +102 -0
  18. package/dist/confidence.d.ts.map +1 -0
  19. package/dist/confidence.js +696 -0
  20. package/dist/confidence.js.map +1 -0
  21. package/dist/ide/acp-server.js +5 -4
  22. package/dist/ide/acp-server.js.map +1 -1
  23. package/dist/ide/lsp-bridge.d.ts.map +1 -1
  24. package/dist/ide/lsp-bridge.js +11 -5
  25. package/dist/ide/lsp-bridge.js.map +1 -1
  26. package/dist/intentionality.d.ts +139 -0
  27. package/dist/intentionality.d.ts.map +1 -0
  28. package/dist/intentionality.js +1092 -0
  29. package/dist/intentionality.js.map +1 -0
  30. package/dist/planner.d.ts.map +1 -1
  31. package/dist/planner.js +3 -2
  32. package/dist/planner.js.map +1 -1
  33. package/dist/reasoning.d.ts +100 -0
  34. package/dist/reasoning.d.ts.map +1 -0
  35. package/dist/reasoning.js +1292 -0
  36. package/dist/reasoning.js.map +1 -0
  37. package/dist/streaming.d.ts.map +1 -1
  38. package/dist/streaming.js +18 -2
  39. package/dist/streaming.js.map +1 -1
  40. package/dist/temporal.d.ts +133 -0
  41. package/dist/temporal.d.ts.map +1 -0
  42. package/dist/temporal.js +778 -0
  43. package/dist/temporal.js.map +1 -0
  44. package/dist/tools/index.d.ts.map +1 -1
  45. package/dist/tools/index.js +30 -10
  46. package/dist/tools/index.js.map +1 -1
  47. package/dist/ui.js +1 -1
  48. package/package.json +2 -2
@@ -0,0 +1,1292 @@
1
+ // K:BOT Advanced Reasoning Engine — Abductive, Counterfactual, Meta-Planning
2
+ //
3
+ // Three reasoning systems that operate without LLM calls:
4
+ //
5
+ // 1. ABDUCTIVE REASONING — Inference to best explanation.
6
+ // Given an error/symptom, generate ranked hypotheses about root cause,
7
+ // then refine via test-and-eliminate loop.
8
+ //
9
+ // 2. COUNTERFACTUAL THINKING — "What if we did X instead?"
10
+ // Analyze alternative approaches without executing them, compare
11
+ // tradeoffs, and recommend whether to pivot.
12
+ //
13
+ // 3. META-PLANNING — Planning about how to plan.
14
+ // Choose the right planning strategy based on task characteristics,
15
+ // adapt strategies mid-execution when things go wrong.
16
+ //
17
+ // All reasoning is pure heuristic — zero API calls, zero external deps.
18
+ import { randomBytes } from 'node:crypto';
19
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
20
+ import { homedir } from 'node:os';
21
+ import { join } from 'node:path';
22
+ import { registerTool } from './tools/index.js';
23
+ // ── Helpers ──
24
+ function shortId() {
25
+ return randomBytes(4).toString('hex');
26
+ }
27
+ function kbotDir() {
28
+ const dir = join(homedir(), '.kbot');
29
+ if (!existsSync(dir))
30
+ mkdirSync(dir, { recursive: true });
31
+ return dir;
32
+ }
33
+ function loadJson(path, fallback) {
34
+ try {
35
+ if (existsSync(path))
36
+ return JSON.parse(readFileSync(path, 'utf-8'));
37
+ }
38
+ catch { /* corrupt file — return fallback */ }
39
+ return fallback;
40
+ }
41
+ function saveJson(path, data) {
42
+ writeFileSync(path, JSON.stringify(data, null, 2), 'utf-8');
43
+ }
44
+ const TYPESCRIPT_CAUSE_MAP = [
45
+ {
46
+ pattern: /TS2322.*Type '(.+)' is not assignable to type '(.+)'/i,
47
+ causes: [
48
+ { explanation: 'Type mismatch — the value type does not match the expected type. Check the assignment or function argument.', likelihood: 0.85, testable: true, testAction: 'Read the file at the error line and compare the actual type with the expected type' },
49
+ { explanation: 'Stale type definitions — a dependency was updated but its types are outdated. The @types package may need updating.', likelihood: 0.3, testable: true, testAction: 'Check the dependency version and its @types package version with npm ls' },
50
+ { explanation: 'Generic type inference failure — TypeScript cannot infer the correct generic. An explicit type annotation may be needed.', likelihood: 0.25, testable: true, testAction: 'Add explicit generic type parameters and re-run tsc' },
51
+ ],
52
+ },
53
+ {
54
+ pattern: /TS2339.*Property '(.+)' does not exist on type/i,
55
+ causes: [
56
+ { explanation: 'Missing property on the type — the object does not have this field. Check for typos or extend the interface.', likelihood: 0.8, testable: true, testAction: 'Read the type definition and check if the property name is correct or needs to be added' },
57
+ { explanation: 'Incorrect type narrowing — the variable is typed as a union and needs a type guard before accessing the property.', likelihood: 0.45, testable: true, testAction: 'Check if the variable is a union type and add a type guard (if/in check) before the access' },
58
+ { explanation: 'Missing import — the correct type is defined but not imported, and a different type with the same name is being used.', likelihood: 0.2, testable: true, testAction: 'Search for the type definition in the codebase and verify the import path' },
59
+ ],
60
+ },
61
+ {
62
+ pattern: /TS2307.*Cannot find module '(.+)'/i,
63
+ causes: [
64
+ { explanation: 'Missing dependency — the package is not installed. Run npm install.', likelihood: 0.6, testable: true, testAction: 'Check package.json for the dependency and run npm install if missing' },
65
+ { explanation: 'Incorrect import path — the relative path is wrong or missing the file extension.', likelihood: 0.5, testable: true, testAction: 'Verify the file exists at the import path, check for .js extension requirement (ESM)' },
66
+ { explanation: 'Missing type declarations — the package exists but has no types. Install @types/package or declare the module.', likelihood: 0.35, testable: true, testAction: 'Check if @types/<package> exists on npm and install it' },
67
+ { explanation: 'Path alias misconfigured — tsconfig.json paths mapping is incorrect or missing.', likelihood: 0.25, testable: true, testAction: 'Read tsconfig.json and verify the paths/baseUrl configuration' },
68
+ ],
69
+ },
70
+ {
71
+ pattern: /TS2345.*Argument of type '(.+)' is not assignable to parameter/i,
72
+ causes: [
73
+ { explanation: 'Function argument type mismatch — the value passed does not match the parameter type.', likelihood: 0.8, testable: true, testAction: 'Read the function signature and compare with the argument being passed' },
74
+ { explanation: 'Overload resolution failure — the function has multiple overloads and none match the provided arguments.', likelihood: 0.3, testable: true, testAction: 'Check all function overloads and find the correct signature to use' },
75
+ ],
76
+ },
77
+ {
78
+ pattern: /TS18046.*'(.+)' is of type 'unknown'/i,
79
+ causes: [
80
+ { explanation: 'Untyped catch clause — error in catch block is unknown by default in strict mode. Add a type assertion or type guard.', likelihood: 0.7, testable: true, testAction: 'Add `if (err instanceof Error)` guard or `as Error` assertion in the catch block' },
81
+ { explanation: 'Untyped JSON parse — JSON.parse returns unknown. Add a type assertion or validation.', likelihood: 0.4, testable: true, testAction: 'Add a type assertion after JSON.parse or use a validation library like zod' },
82
+ ],
83
+ },
84
+ ];
85
+ const RUNTIME_CAUSE_MAP = [
86
+ {
87
+ pattern: /TypeError:.*Cannot read propert(y|ies) of (null|undefined)/i,
88
+ causes: [
89
+ { explanation: 'Null reference — a variable is null/undefined when a property access was attempted. Check the data flow.', likelihood: 0.85, testable: true, testAction: 'Add console.log before the error line to trace which variable is null/undefined' },
90
+ { explanation: 'Async timing issue — the data has not loaded yet when the code runs. Check for missing await or race condition.', likelihood: 0.45, testable: true, testAction: 'Check if the variable is populated asynchronously and ensure proper await/then handling' },
91
+ { explanation: 'Incorrect API response shape — the API returned a different structure than expected.', likelihood: 0.3, testable: true, testAction: 'Log the raw API response and compare with the expected type' },
92
+ ],
93
+ },
94
+ {
95
+ pattern: /ReferenceError:.*is not defined/i,
96
+ causes: [
97
+ { explanation: 'Missing import or declaration — the variable/function is used but never imported or declared.', likelihood: 0.75, testable: true, testAction: 'Search the codebase for the definition and add the correct import' },
98
+ { explanation: 'Scope issue — the variable is declared inside a block but accessed outside of it.', likelihood: 0.35, testable: true, testAction: 'Check the variable declaration scope and move it if necessary' },
99
+ { explanation: 'Circular dependency — two modules import each other, causing one to be undefined at import time.', likelihood: 0.2, testable: true, testAction: 'Check the import chain for circular references using a dependency graph' },
100
+ ],
101
+ },
102
+ {
103
+ pattern: /SyntaxError:.*Unexpected token/i,
104
+ causes: [
105
+ { explanation: 'JSON parse error — attempted to parse invalid JSON (HTML error page, empty response, etc.).', likelihood: 0.6, testable: true, testAction: 'Log the raw string being parsed before the JSON.parse call' },
106
+ { explanation: 'ESM/CJS mismatch — importing a CommonJS module with ESM syntax or vice versa.', likelihood: 0.4, testable: true, testAction: 'Check the module type in package.json and the file extension (.mjs vs .cjs vs .js)' },
107
+ { explanation: 'Corrupted file — the source file has invalid syntax from a bad edit or encoding issue.', likelihood: 0.15, testable: true, testAction: 'Run the file through a linter or syntax checker' },
108
+ ],
109
+ },
110
+ {
111
+ pattern: /Error:.*ENOENT.*no such file or directory/i,
112
+ causes: [
113
+ { explanation: 'File path is wrong — the path references a file that does not exist. Check for typos.', likelihood: 0.7, testable: true, testAction: 'List the directory contents to see what files actually exist' },
114
+ { explanation: 'Working directory is different than expected — the relative path resolves to the wrong location.', likelihood: 0.45, testable: true, testAction: 'Log process.cwd() and verify it matches expectations' },
115
+ { explanation: 'File was deleted or moved — a previous operation removed the file.', likelihood: 0.2, testable: true, testAction: 'Check git status or recent file operations for the missing file' },
116
+ ],
117
+ },
118
+ {
119
+ pattern: /Error:.*EACCES.*permission denied/i,
120
+ causes: [
121
+ { explanation: 'File permission issue — the process does not have read/write access to the path.', likelihood: 0.75, testable: true, testAction: 'Check file permissions with ls -la and fix with chmod if needed' },
122
+ { explanation: 'File is locked by another process — another application has an exclusive lock.', likelihood: 0.3, testable: true, testAction: 'Check for processes using the file with lsof or fuser' },
123
+ ],
124
+ },
125
+ {
126
+ pattern: /ERR_MODULE_NOT_FOUND|MODULE_NOT_FOUND/i,
127
+ causes: [
128
+ { explanation: 'Missing dependency — the npm package is not installed.', likelihood: 0.6, testable: true, testAction: 'Run npm install and verify the package is in node_modules' },
129
+ { explanation: 'ESM import missing file extension — Node.js ESM requires explicit .js extensions in imports.', likelihood: 0.55, testable: true, testAction: 'Check the import statement and add .js extension if using ESM' },
130
+ { explanation: 'Incorrect exports field — package.json exports map does not include the requested subpath.', likelihood: 0.25, testable: true, testAction: 'Check the package.json exports field of the target package' },
131
+ ],
132
+ },
133
+ {
134
+ pattern: /UnhandledPromiseRejection|unhandled promise/i,
135
+ causes: [
136
+ { explanation: 'Missing await — an async function call is not awaited, and its rejection is unhandled.', likelihood: 0.7, testable: true, testAction: 'Search for async function calls without await in the error context' },
137
+ { explanation: 'Missing .catch() — a Promise chain lacks error handling.', likelihood: 0.5, testable: true, testAction: 'Add .catch() to the Promise chain or wrap in try/catch with await' },
138
+ { explanation: 'Error in event handler — an async event callback throws and nothing catches it.', likelihood: 0.25, testable: true, testAction: 'Wrap the event handler body in try/catch' },
139
+ ],
140
+ },
141
+ {
142
+ pattern: /ECONNREFUSED|ECONNRESET|ETIMEDOUT/i,
143
+ causes: [
144
+ { explanation: 'Service not running — the target server/service is not started or has crashed.', likelihood: 0.65, testable: true, testAction: 'Check if the target service is running on the expected port' },
145
+ { explanation: 'Wrong port or host — the connection URL is misconfigured.', likelihood: 0.4, testable: true, testAction: 'Verify the connection URL/port in config or environment variables' },
146
+ { explanation: 'Firewall or network issue — a firewall rule or network configuration is blocking the connection.', likelihood: 0.2, testable: true, testAction: 'Try curl or ping to the target host to verify network connectivity' },
147
+ ],
148
+ },
149
+ {
150
+ pattern: /env|ENV|environment variable/i,
151
+ causes: [
152
+ { explanation: 'Missing environment variable — a required env var is not set.', likelihood: 0.7, testable: true, testAction: 'Check .env file and process.env for the required variable' },
153
+ { explanation: 'Wrong .env file loaded — the application is loading a different .env file than expected.', likelihood: 0.3, testable: true, testAction: 'Verify which .env file is being loaded (check dotenv config path)' },
154
+ ],
155
+ },
156
+ ];
157
+ const BUILD_CAUSE_MAP = [
158
+ {
159
+ pattern: /Cannot resolve|Module not found|Failed to resolve/i,
160
+ causes: [
161
+ { explanation: 'Missing dependency — a package referenced in the code is not installed.', likelihood: 0.6, testable: true, testAction: 'Run npm install and check package.json for the dependency' },
162
+ { explanation: 'Incorrect import path — a relative or alias path does not resolve to a real file.', likelihood: 0.55, testable: true, testAction: 'Verify the import path and check vite/webpack alias configuration' },
163
+ { explanation: 'Build config error — the bundler resolve configuration is misconfigured.', likelihood: 0.25, testable: true, testAction: 'Check vite.config.ts or webpack.config.js resolve section' },
164
+ ],
165
+ },
166
+ {
167
+ pattern: /out of memory|heap|allocation failed/i,
168
+ causes: [
169
+ { explanation: 'Node.js heap limit — the build process needs more memory. Increase --max-old-space-size.', likelihood: 0.7, testable: true, testAction: 'Set NODE_OPTIONS=--max-old-space-size=4096 and retry the build' },
170
+ { explanation: 'Circular dependency causing infinite expansion — two modules create an import cycle that bloats the bundle.', likelihood: 0.35, testable: true, testAction: 'Use a circular dependency detector (madge) to find cycles' },
171
+ ],
172
+ },
173
+ {
174
+ pattern: /chunk|bundle|size|limit/i,
175
+ causes: [
176
+ { explanation: 'Bundle too large — a large dependency is being included. Use code splitting or dynamic imports.', likelihood: 0.6, testable: true, testAction: 'Run bundle analyzer (vite-bundle-visualizer) to identify large chunks' },
177
+ { explanation: 'Tree-shaking not working — a dependency uses CommonJS and cannot be tree-shaken.', likelihood: 0.35, testable: true, testAction: 'Check if the dependency provides an ESM build' },
178
+ ],
179
+ },
180
+ ];
181
+ const GIT_CAUSE_MAP = [
182
+ {
183
+ pattern: /CONFLICT|merge conflict|Automatic merge failed/i,
184
+ causes: [
185
+ { explanation: 'Merge conflict — two branches modified the same lines. Manual resolution required.', likelihood: 0.9, testable: true, testAction: 'Run git diff to see conflicting files and resolve the conflict markers' },
186
+ { explanation: 'Rebase conflict — interactive rebase encountered overlapping changes.', likelihood: 0.3, testable: true, testAction: 'Run git status to see which files need resolution, then git rebase --continue' },
187
+ ],
188
+ },
189
+ {
190
+ pattern: /HEAD detached|detached HEAD/i,
191
+ causes: [
192
+ { explanation: 'Checked out a commit instead of a branch — HEAD is not on any branch.', likelihood: 0.8, testable: true, testAction: 'Run git branch to see available branches and git checkout <branch> to reattach' },
193
+ { explanation: 'Rebase left HEAD detached — a rebase was interrupted or completed without re-attaching.', likelihood: 0.3, testable: true, testAction: 'Run git reflog to find the branch and git checkout <branch>' },
194
+ ],
195
+ },
196
+ {
197
+ pattern: /Permission denied|access denied|authentication failed/i,
198
+ causes: [
199
+ { explanation: 'SSH key not configured or expired — the SSH key for the remote is missing or invalid.', likelihood: 0.55, testable: true, testAction: 'Run ssh -T git@github.com to test SSH authentication' },
200
+ { explanation: 'Token expired or missing — the personal access token or OAuth token needs refreshing.', likelihood: 0.5, testable: true, testAction: 'Check git config credential helper and re-authenticate' },
201
+ { explanation: 'Repository permissions — you do not have push access to this repository.', likelihood: 0.3, testable: true, testAction: 'Check repository settings on GitHub to verify your access level' },
202
+ ],
203
+ },
204
+ {
205
+ pattern: /rejected.*non-fast-forward|Updates were rejected/i,
206
+ causes: [
207
+ { explanation: 'Remote has new commits — someone pushed changes you do not have locally. Pull first.', likelihood: 0.8, testable: true, testAction: 'Run git pull --rebase to integrate remote changes before pushing' },
208
+ { explanation: 'Branch protection — the branch has protection rules that reject direct pushes.', likelihood: 0.3, testable: true, testAction: 'Check branch protection rules on GitHub and create a PR instead' },
209
+ ],
210
+ },
211
+ ];
212
+ const ALL_CAUSE_MAPS = [
213
+ ...TYPESCRIPT_CAUSE_MAP,
214
+ ...RUNTIME_CAUSE_MAP,
215
+ ...BUILD_CAUSE_MAP,
216
+ ...GIT_CAUSE_MAP,
217
+ ];
218
+ // ── Abductive state ──
219
+ let currentSession = null;
220
+ /**
221
+ * Generate ranked hypotheses for an observed error or unexpected behavior.
222
+ * Uses heuristic pattern matching against pre-built cause maps.
223
+ */
224
+ export function generateHypotheses(observation, context) {
225
+ const hypotheses = [];
226
+ const combined = `${observation}\n${context}`;
227
+ // Match against all cause maps
228
+ for (const causePattern of ALL_CAUSE_MAPS) {
229
+ if (causePattern.pattern.test(combined)) {
230
+ for (const cause of causePattern.causes) {
231
+ // Avoid duplicate explanations
232
+ if (hypotheses.some(h => h.explanation === cause.explanation))
233
+ continue;
234
+ hypotheses.push({
235
+ id: shortId(),
236
+ explanation: cause.explanation,
237
+ evidence: extractEvidence(observation, context, cause.explanation),
238
+ contradictions: [],
239
+ likelihood: cause.likelihood,
240
+ testable: cause.testable,
241
+ testAction: cause.testAction,
242
+ });
243
+ }
244
+ }
245
+ }
246
+ // If no pattern matched, generate generic hypotheses from keywords
247
+ if (hypotheses.length === 0) {
248
+ hypotheses.push(...generateGenericHypotheses(observation, context));
249
+ }
250
+ // Sort by likelihood descending
251
+ hypotheses.sort((a, b) => b.likelihood - a.likelihood);
252
+ const result = {
253
+ observation,
254
+ hypotheses,
255
+ recommended: hypotheses.length > 0 ? hypotheses[0].id : '',
256
+ };
257
+ currentSession = result;
258
+ return result;
259
+ }
260
+ /**
261
+ * Update hypothesis likelihoods after testing one.
262
+ * If the test confirmed the hypothesis, boost it and reduce others.
263
+ * If the test contradicted it, reduce it and boost alternatives.
264
+ */
265
+ export function testHypothesis(id, result) {
266
+ if (!currentSession)
267
+ return;
268
+ const hypothesis = currentSession.hypotheses.find(h => h.id === id);
269
+ if (!hypothesis)
270
+ return;
271
+ const lowerResult = result.toLowerCase();
272
+ const isConfirming = lowerResult.includes('confirmed') ||
273
+ lowerResult.includes('found') ||
274
+ lowerResult.includes('yes') ||
275
+ lowerResult.includes('correct') ||
276
+ lowerResult.includes('match') ||
277
+ lowerResult.includes('success');
278
+ const isContracting = lowerResult.includes('no') ||
279
+ lowerResult.includes('not found') ||
280
+ lowerResult.includes('wrong') ||
281
+ lowerResult.includes('incorrect') ||
282
+ lowerResult.includes('failed') ||
283
+ lowerResult.includes('contradicted');
284
+ if (isConfirming) {
285
+ // Boost this hypothesis, reduce others proportionally
286
+ hypothesis.likelihood = Math.min(1.0, hypothesis.likelihood + 0.25);
287
+ hypothesis.evidence.push(result);
288
+ for (const other of currentSession.hypotheses) {
289
+ if (other.id !== id) {
290
+ other.likelihood = Math.max(0.01, other.likelihood * 0.7);
291
+ }
292
+ }
293
+ }
294
+ else if (isContracting) {
295
+ // Reduce this hypothesis, slightly boost others
296
+ hypothesis.likelihood = Math.max(0.01, hypothesis.likelihood * 0.3);
297
+ hypothesis.contradictions.push(result);
298
+ for (const other of currentSession.hypotheses) {
299
+ if (other.id !== id) {
300
+ other.likelihood = Math.min(1.0, other.likelihood * 1.15);
301
+ }
302
+ }
303
+ }
304
+ else {
305
+ // Ambiguous result — add as evidence but only mildly adjust
306
+ hypothesis.evidence.push(result);
307
+ hypothesis.likelihood = Math.min(1.0, hypothesis.likelihood * 1.05);
308
+ }
309
+ // Re-sort and update recommended
310
+ currentSession.hypotheses.sort((a, b) => b.likelihood - a.likelihood);
311
+ currentSession.recommended = currentSession.hypotheses[0]?.id ?? '';
312
+ }
313
+ /**
314
+ * Eliminate a hypothesis with a reason. Sets its likelihood to near-zero.
315
+ */
316
+ export function eliminateHypothesis(id, reason) {
317
+ if (!currentSession)
318
+ return;
319
+ const hypothesis = currentSession.hypotheses.find(h => h.id === id);
320
+ if (!hypothesis)
321
+ return;
322
+ hypothesis.likelihood = 0.0;
323
+ hypothesis.contradictions.push(`ELIMINATED: ${reason}`);
324
+ // Re-sort and update recommended (skip eliminated)
325
+ currentSession.hypotheses.sort((a, b) => b.likelihood - a.likelihood);
326
+ const best = currentSession.hypotheses.find(h => h.likelihood > 0);
327
+ currentSession.recommended = best?.id ?? '';
328
+ }
329
+ /**
330
+ * Get the current best explanation after testing.
331
+ */
332
+ export function getBestExplanation() {
333
+ if (!currentSession || currentSession.hypotheses.length === 0)
334
+ return null;
335
+ return currentSession.hypotheses.reduce((best, h) => h.likelihood > best.likelihood ? h : best);
336
+ }
337
+ // ── Abductive helpers ──
338
+ function extractEvidence(observation, context, _explanation) {
339
+ const evidence = [];
340
+ // Extract file paths mentioned
341
+ const filePaths = observation.match(/(?:\/[\w./-]+|[\w./-]+\.\w{1,5})/g);
342
+ if (filePaths) {
343
+ evidence.push(`Files mentioned: ${[...new Set(filePaths)].join(', ')}`);
344
+ }
345
+ // Extract line numbers
346
+ const lineNums = observation.match(/line\s+(\d+)/gi);
347
+ if (lineNums) {
348
+ evidence.push(`Line references: ${lineNums.join(', ')}`);
349
+ }
350
+ // Extract error codes
351
+ const errorCodes = observation.match(/(?:TS|E)\d{4,}/g);
352
+ if (errorCodes) {
353
+ evidence.push(`Error codes: ${[...new Set(errorCodes)].join(', ')}`);
354
+ }
355
+ // Include truncated context as evidence
356
+ if (context.length > 0) {
357
+ const contextSnippet = context.length > 200 ? context.slice(0, 200) + '...' : context;
358
+ evidence.push(`Context: ${contextSnippet}`);
359
+ }
360
+ return evidence;
361
+ }
362
+ function generateGenericHypotheses(observation, context) {
363
+ const hypotheses = [];
364
+ const combined = `${observation} ${context}`.toLowerCase();
365
+ // Generic: configuration issue
366
+ if (combined.includes('config') || combined.includes('setting') || combined.includes('.json') || combined.includes('.toml')) {
367
+ hypotheses.push({
368
+ id: shortId(),
369
+ explanation: 'Configuration issue — a config file has an incorrect or missing value.',
370
+ evidence: extractEvidence(observation, context, ''),
371
+ contradictions: [],
372
+ likelihood: 0.5,
373
+ testable: true,
374
+ testAction: 'Read the relevant config file and verify all required fields are present and correct',
375
+ });
376
+ }
377
+ // Generic: version mismatch
378
+ if (combined.includes('version') || combined.includes('upgrade') || combined.includes('deprecat')) {
379
+ hypotheses.push({
380
+ id: shortId(),
381
+ explanation: 'Version mismatch — a dependency or tool version is incompatible.',
382
+ evidence: extractEvidence(observation, context, ''),
383
+ contradictions: [],
384
+ likelihood: 0.45,
385
+ testable: true,
386
+ testAction: 'Check the version of the relevant dependency or tool and compare with requirements',
387
+ });
388
+ }
389
+ // Generic: state corruption
390
+ if (combined.includes('cache') || combined.includes('corrupt') || combined.includes('stale') || combined.includes('invalid state')) {
391
+ hypotheses.push({
392
+ id: shortId(),
393
+ explanation: 'Stale cache or corrupted state — cached data is outdated or corrupted.',
394
+ evidence: extractEvidence(observation, context, ''),
395
+ contradictions: [],
396
+ likelihood: 0.4,
397
+ testable: true,
398
+ testAction: 'Clear relevant caches (node_modules, .cache, build output) and retry',
399
+ });
400
+ }
401
+ // Fallback: always include a generic "unknown cause" hypothesis
402
+ if (hypotheses.length === 0) {
403
+ hypotheses.push({
404
+ id: shortId(),
405
+ explanation: 'Unknown cause — the error does not match any known pattern. Gather more information.',
406
+ evidence: extractEvidence(observation, context, ''),
407
+ contradictions: [],
408
+ likelihood: 0.3,
409
+ testable: true,
410
+ testAction: 'Read the source file at the error location and examine the surrounding code',
411
+ });
412
+ }
413
+ return hypotheses;
414
+ }
415
+ const APPROACH_SIGNALS = [
416
+ { keywords: ['rewrite', 'rebuild', 'from scratch', 'replace entirely'], riskLevel: 0.8, effortLevel: 0.9, reversibility: 0.3, complexity: 0.8 },
417
+ { keywords: ['refactor', 'restructure', 'reorganize'], riskLevel: 0.5, effortLevel: 0.7, reversibility: 0.6, complexity: 0.6 },
418
+ { keywords: ['patch', 'fix', 'hotfix', 'quick fix', 'band-aid'], riskLevel: 0.3, effortLevel: 0.2, reversibility: 0.9, complexity: 0.2 },
419
+ { keywords: ['migrate', 'upgrade', 'move to', 'switch to'], riskLevel: 0.6, effortLevel: 0.7, reversibility: 0.4, complexity: 0.7 },
420
+ { keywords: ['add', 'extend', 'enhance', 'augment'], riskLevel: 0.3, effortLevel: 0.5, reversibility: 0.7, complexity: 0.4 },
421
+ { keywords: ['remove', 'delete', 'drop', 'deprecate'], riskLevel: 0.4, effortLevel: 0.3, reversibility: 0.5, complexity: 0.2 },
422
+ { keywords: ['test', 'verify', 'validate', 'check'], riskLevel: 0.1, effortLevel: 0.3, reversibility: 1.0, complexity: 0.2 },
423
+ { keywords: ['configure', 'config', 'setting', 'toggle'], riskLevel: 0.2, effortLevel: 0.2, reversibility: 0.9, complexity: 0.1 },
424
+ { keywords: ['workaround', 'bypass', 'skip', 'ignore'], riskLevel: 0.5, effortLevel: 0.15, reversibility: 0.8, complexity: 0.15 },
425
+ { keywords: ['new file', 'create', 'scaffold', 'generate'], riskLevel: 0.2, effortLevel: 0.5, reversibility: 0.8, complexity: 0.4 },
426
+ ];
427
+ function analyzeApproach(description) {
428
+ const lower = description.toLowerCase();
429
+ let totalWeight = 0;
430
+ let weightedRisk = 0;
431
+ let weightedEffort = 0;
432
+ let weightedReversibility = 0;
433
+ let weightedComplexity = 0;
434
+ for (const signal of APPROACH_SIGNALS) {
435
+ const matchCount = signal.keywords.filter(kw => lower.includes(kw)).length;
436
+ if (matchCount > 0) {
437
+ const weight = matchCount;
438
+ totalWeight += weight;
439
+ weightedRisk += signal.riskLevel * weight;
440
+ weightedEffort += signal.effortLevel * weight;
441
+ weightedReversibility += signal.reversibility * weight;
442
+ weightedComplexity += signal.complexity * weight;
443
+ }
444
+ }
445
+ if (totalWeight === 0) {
446
+ return { risk: 0.5, effort: 0.5, reversibility: 0.5, complexity: 0.5 };
447
+ }
448
+ return {
449
+ risk: weightedRisk / totalWeight,
450
+ effort: weightedEffort / totalWeight,
451
+ reversibility: weightedReversibility / totalWeight,
452
+ complexity: weightedComplexity / totalWeight,
453
+ };
454
+ }
455
+ /**
456
+ * Analyze an alternative approach without executing it.
457
+ * Compares risk, effort, and reversibility of current vs alternative.
458
+ */
459
+ export function exploreCounterfactual(currentApproach, alternative, context) {
460
+ const currentAnalysis = analyzeApproach(currentApproach);
461
+ const altAnalysis = analyzeApproach(alternative);
462
+ const benefits = [];
463
+ const risks = [];
464
+ // Compare risk
465
+ if (altAnalysis.risk < currentAnalysis.risk) {
466
+ benefits.push(`Lower risk (${(altAnalysis.risk * 100).toFixed(0)}% vs ${(currentAnalysis.risk * 100).toFixed(0)}%)`);
467
+ }
468
+ else if (altAnalysis.risk > currentAnalysis.risk) {
469
+ risks.push(`Higher risk (${(altAnalysis.risk * 100).toFixed(0)}% vs ${(currentAnalysis.risk * 100).toFixed(0)}%)`);
470
+ }
471
+ // Compare effort
472
+ if (altAnalysis.effort < currentAnalysis.effort) {
473
+ benefits.push(`Less effort (${(altAnalysis.effort * 100).toFixed(0)}% vs ${(currentAnalysis.effort * 100).toFixed(0)}%)`);
474
+ }
475
+ else if (altAnalysis.effort > currentAnalysis.effort) {
476
+ risks.push(`More effort (${(altAnalysis.effort * 100).toFixed(0)}% vs ${(currentAnalysis.effort * 100).toFixed(0)}%)`);
477
+ }
478
+ // Compare reversibility
479
+ if (altAnalysis.reversibility > currentAnalysis.reversibility) {
480
+ benefits.push(`More reversible (${(altAnalysis.reversibility * 100).toFixed(0)}% vs ${(currentAnalysis.reversibility * 100).toFixed(0)}%)`);
481
+ }
482
+ else if (altAnalysis.reversibility < currentAnalysis.reversibility) {
483
+ risks.push(`Less reversible (${(altAnalysis.reversibility * 100).toFixed(0)}% vs ${(currentAnalysis.reversibility * 100).toFixed(0)}%)`);
484
+ }
485
+ // Compare complexity
486
+ if (altAnalysis.complexity < currentAnalysis.complexity) {
487
+ benefits.push(`Simpler approach (${(altAnalysis.complexity * 100).toFixed(0)}% vs ${(currentAnalysis.complexity * 100).toFixed(0)}%)`);
488
+ }
489
+ else if (altAnalysis.complexity > currentAnalysis.complexity) {
490
+ risks.push(`More complex (${(altAnalysis.complexity * 100).toFixed(0)}% vs ${(currentAnalysis.complexity * 100).toFixed(0)}%)`);
491
+ }
492
+ // Context-based signals
493
+ const lowerContext = context.toLowerCase();
494
+ if (lowerContext.includes('deadline') || lowerContext.includes('urgent') || lowerContext.includes('asap')) {
495
+ if (altAnalysis.effort > currentAnalysis.effort) {
496
+ risks.push('Time pressure makes higher-effort alternatives risky');
497
+ }
498
+ else {
499
+ benefits.push('Faster approach aligns with time constraints');
500
+ }
501
+ }
502
+ if (lowerContext.includes('production') || lowerContext.includes('live') || lowerContext.includes('deployed')) {
503
+ if (altAnalysis.risk > 0.5) {
504
+ risks.push('High-risk changes in production environment');
505
+ }
506
+ if (altAnalysis.reversibility > 0.7) {
507
+ benefits.push('Easily reversible — safe for production');
508
+ }
509
+ }
510
+ // Determine effort comparison
511
+ const effortDiff = altAnalysis.effort - currentAnalysis.effort;
512
+ const effort = effortDiff < -0.1 ? 'less' : effortDiff > 0.1 ? 'more' : 'same';
513
+ // Make recommendation
514
+ const benefitScore = benefits.length;
515
+ const riskScore = risks.length;
516
+ let recommendation;
517
+ let reasoning;
518
+ if (benefitScore > riskScore + 1) {
519
+ recommendation = 'switch';
520
+ reasoning = `The alternative approach has ${benefitScore} benefits vs ${riskScore} risks. The net advantage is clear.`;
521
+ }
522
+ else if (riskScore > benefitScore + 1) {
523
+ recommendation = 'stay';
524
+ reasoning = `The alternative approach has ${riskScore} risks vs ${benefitScore} benefits. Staying with the current approach is safer.`;
525
+ }
526
+ else {
527
+ recommendation = 'defer';
528
+ reasoning = `The tradeoffs are roughly balanced (${benefitScore} benefits, ${riskScore} risks). Consider gathering more information before deciding.`;
529
+ }
530
+ return {
531
+ id: shortId(),
532
+ scenario: `What if we used "${alternative}" instead of "${currentApproach}"?`,
533
+ currentPath: currentApproach,
534
+ alternativePath: alternative,
535
+ tradeoffs: { benefits, risks, effort },
536
+ recommendation,
537
+ reasoning,
538
+ };
539
+ }
540
+ /**
541
+ * Compare multiple alternative approaches.
542
+ */
543
+ export function compareApproaches(approaches) {
544
+ if (approaches.length < 2)
545
+ return [];
546
+ const results = [];
547
+ const baseline = approaches[0];
548
+ for (let i = 1; i < approaches.length; i++) {
549
+ results.push(exploreCounterfactual(baseline, approaches[i], ''));
550
+ }
551
+ return results;
552
+ }
553
+ /**
554
+ * Given current progress and obstacles, determine if we should pivot to a different approach.
555
+ */
556
+ export function shouldPivot(currentProgress, obstacles) {
557
+ const lowerProgress = currentProgress.toLowerCase();
558
+ const allObstacles = obstacles.join(' ').toLowerCase();
559
+ // Heuristic 1: Blocked 2+ times on same issue
560
+ const obstacleCounts = new Map();
561
+ for (const obstacle of obstacles) {
562
+ // Normalize obstacle to a rough category
563
+ const key = categorizeObstacle(obstacle);
564
+ obstacleCounts.set(key, (obstacleCounts.get(key) ?? 0) + 1);
565
+ }
566
+ const repeatedBlocks = [...obstacleCounts.entries()].filter(([_, count]) => count >= 2);
567
+ if (repeatedBlocks.length > 0) {
568
+ const [category] = repeatedBlocks[0];
569
+ return {
570
+ pivot: true,
571
+ to: suggestPivotTarget(category),
572
+ reason: `Blocked ${repeatedBlocks[0][1]} times on ${category}. Repeated failures on the same issue suggest the current approach has a fundamental problem.`,
573
+ };
574
+ }
575
+ // Heuristic 2: Cost exceeds 2x estimate (inferred from obstacle mentions)
576
+ if (allObstacles.includes('over budget') || allObstacles.includes('too expensive') || allObstacles.includes('cost') || allObstacles.includes('2x')) {
577
+ return {
578
+ pivot: true,
579
+ to: 'a simpler approach with fewer steps',
580
+ reason: 'Cost has exceeded estimates. A simpler approach would reduce total expense.',
581
+ };
582
+ }
583
+ // Heuristic 3: If >50% done, stay unless critical
584
+ if (lowerProgress.includes('50%') || lowerProgress.includes('half') || lowerProgress.includes('halfway') ||
585
+ lowerProgress.includes('most done') || lowerProgress.includes('nearly') || lowerProgress.includes('almost')) {
586
+ const hasCritical = allObstacles.includes('critical') || allObstacles.includes('blocker') ||
587
+ allObstacles.includes('impossible') || allObstacles.includes('cannot');
588
+ if (!hasCritical) {
589
+ return {
590
+ pivot: false,
591
+ reason: 'Significant progress has been made. The obstacles are not critical, so staying the course is more efficient than starting over.',
592
+ };
593
+ }
594
+ }
595
+ // Heuristic 4: Multiple diverse obstacles suggest fundamental issue
596
+ if (obstacles.length >= 3) {
597
+ const categories = new Set(obstacles.map(categorizeObstacle));
598
+ if (categories.size >= 3) {
599
+ return {
600
+ pivot: true,
601
+ to: 'a research-first approach to understand the problem space before attempting implementation',
602
+ reason: `${obstacles.length} obstacles across ${categories.size} different categories suggest insufficient understanding of the problem. Research first.`,
603
+ };
604
+ }
605
+ }
606
+ // Default: don't pivot
607
+ return {
608
+ pivot: false,
609
+ reason: 'Current obstacles do not indicate a fundamental problem with the approach. Continue with the current plan.',
610
+ };
611
+ }
612
+ function categorizeObstacle(obstacle) {
613
+ const lower = obstacle.toLowerCase();
614
+ if (lower.includes('type') || lower.includes('typescript') || lower.includes('ts'))
615
+ return 'type-system';
616
+ if (lower.includes('import') || lower.includes('module') || lower.includes('require'))
617
+ return 'module-resolution';
618
+ if (lower.includes('permission') || lower.includes('access') || lower.includes('auth'))
619
+ return 'permissions';
620
+ if (lower.includes('network') || lower.includes('timeout') || lower.includes('connection'))
621
+ return 'network';
622
+ if (lower.includes('config') || lower.includes('setting') || lower.includes('env'))
623
+ return 'configuration';
624
+ if (lower.includes('test') || lower.includes('assert') || lower.includes('expect'))
625
+ return 'testing';
626
+ if (lower.includes('build') || lower.includes('compile') || lower.includes('bundle'))
627
+ return 'build';
628
+ if (lower.includes('dependency') || lower.includes('package') || lower.includes('npm'))
629
+ return 'dependencies';
630
+ if (lower.includes('git') || lower.includes('merge') || lower.includes('conflict'))
631
+ return 'version-control';
632
+ return 'general';
633
+ }
634
+ function suggestPivotTarget(obstacleCategory) {
635
+ const pivotSuggestions = {
636
+ 'type-system': 'using explicit type assertions or a simpler type structure to bypass the type system complexity',
637
+ 'module-resolution': 'restructuring imports or using a different module format (CJS vs ESM)',
638
+ 'permissions': 'running with elevated permissions or changing the file ownership approach',
639
+ 'network': 'using a local fallback or cached data instead of relying on network calls',
640
+ 'configuration': 'simplifying the configuration or using sensible defaults instead of complex config',
641
+ 'testing': 'writing the implementation first with manual testing, then adding automated tests',
642
+ 'build': 'simplifying the build pipeline or using a different bundler',
643
+ 'dependencies': 'reducing external dependencies or vendoring the problematic package',
644
+ 'version-control': 'creating a clean branch from main and cherry-picking only the needed changes',
645
+ 'general': 'a different approach that avoids the recurring obstacle',
646
+ };
647
+ return pivotSuggestions[obstacleCategory] ?? pivotSuggestions['general'];
648
+ }
649
+ // ── Pre-built strategies ──
650
+ const STRATEGIES = [
651
+ {
652
+ name: 'divide-and-conquer',
653
+ description: 'Break the task into independent subtasks that can be worked on in parallel or sequence.',
654
+ when: 'Complex tasks with multiple independent components.',
655
+ steps: [
656
+ 'Identify all major components of the task',
657
+ 'Find dependencies between components',
658
+ 'Group independent components for parallel work',
659
+ 'Execute each group, verify before moving to dependent groups',
660
+ 'Integration test after all components are complete',
661
+ ],
662
+ },
663
+ {
664
+ name: 'incremental',
665
+ description: 'Make small changes one at a time, verifying after each step.',
666
+ when: 'High-risk changes or unfamiliar codebases where mistakes are costly.',
667
+ steps: [
668
+ 'Identify the smallest meaningful change',
669
+ 'Implement that single change',
670
+ 'Verify it works (type-check, test, manual check)',
671
+ 'Commit/checkpoint the working state',
672
+ 'Repeat with the next smallest change',
673
+ ],
674
+ },
675
+ {
676
+ name: 'prototype-first',
677
+ description: 'Build a quick and dirty version first, then refine into production quality.',
678
+ when: 'Time pressure, or when the design is unclear and needs exploration.',
679
+ steps: [
680
+ 'Build the simplest version that demonstrates the concept',
681
+ 'Test with real data to validate the approach',
682
+ 'Identify gaps and edge cases from the prototype',
683
+ 'Refactor into production-quality code',
684
+ 'Add error handling, types, and tests',
685
+ ],
686
+ },
687
+ {
688
+ name: 'research-first',
689
+ description: 'Gather information and understand the problem space before taking any action.',
690
+ when: 'High uncertainty — unfamiliar APIs, libraries, or problem domains.',
691
+ steps: [
692
+ 'Read relevant documentation and source code',
693
+ 'Find examples of similar implementations',
694
+ 'Identify potential approaches and their tradeoffs',
695
+ 'Choose the best approach based on evidence',
696
+ 'Implement with confidence from research',
697
+ ],
698
+ },
699
+ {
700
+ name: 'test-driven',
701
+ description: 'Write tests first that define the desired behavior, then implement to pass them.',
702
+ when: 'Well-defined requirements, existing test infrastructure, or when correctness is critical.',
703
+ steps: [
704
+ 'Understand the requirements and edge cases',
705
+ 'Write failing tests that capture the expected behavior',
706
+ 'Implement the minimum code to pass each test',
707
+ 'Refactor while keeping tests green',
708
+ 'Add integration tests if needed',
709
+ ],
710
+ },
711
+ {
712
+ name: 'surgical',
713
+ description: 'Make the absolute minimum change needed. Precision over completeness.',
714
+ when: 'Bug fixes, hotfixes, or changes to fragile/critical code.',
715
+ steps: [
716
+ 'Identify the exact location of the issue',
717
+ 'Understand the surrounding code and its constraints',
718
+ 'Make the smallest possible change to fix the issue',
719
+ 'Verify nothing else was affected',
720
+ 'Document why this minimal approach was chosen',
721
+ ],
722
+ },
723
+ ];
724
+ function scoreStrategies(task, context) {
725
+ const combined = `${task} ${context}`.toLowerCase();
726
+ const scores = STRATEGIES.map(s => ({ strategy: s.name, score: 0, reasons: [] }));
727
+ function boost(name, amount, reason) {
728
+ const entry = scores.find(s => s.strategy === name);
729
+ if (entry) {
730
+ entry.score += amount;
731
+ entry.reasons.push(reason);
732
+ }
733
+ }
734
+ // ── Complexity signals → divide-and-conquer
735
+ if (combined.includes('multiple') || combined.includes('several') || combined.includes('many')) {
736
+ boost('divide-and-conquer', 3, 'Multiple components detected');
737
+ }
738
+ if (combined.includes('parallel') || combined.includes('independent')) {
739
+ boost('divide-and-conquer', 2, 'Independent subtasks mentioned');
740
+ }
741
+ if ((combined.match(/\band\b/g) ?? []).length >= 3) {
742
+ boost('divide-and-conquer', 2, 'Task description lists multiple goals');
743
+ }
744
+ // ── Risk signals → incremental
745
+ if (combined.includes('careful') || combined.includes('cautious') || combined.includes('fragile')) {
746
+ boost('incremental', 3, 'Risk-averse signals detected');
747
+ }
748
+ if (combined.includes('production') || combined.includes('live') || combined.includes('deployed')) {
749
+ boost('incremental', 2, 'Production environment — minimize risk');
750
+ }
751
+ if (combined.includes('unfamiliar') || combined.includes('new codebase') || combined.includes('legacy')) {
752
+ boost('incremental', 2, 'Unfamiliar code — verify each step');
753
+ }
754
+ // ── Time pressure → prototype-first
755
+ if (combined.includes('quick') || combined.includes('fast') || combined.includes('urgent') || combined.includes('asap')) {
756
+ boost('prototype-first', 3, 'Time pressure detected');
757
+ }
758
+ if (combined.includes('prototype') || combined.includes('poc') || combined.includes('proof of concept')) {
759
+ boost('prototype-first', 4, 'Explicitly requested prototype');
760
+ }
761
+ if (combined.includes('explore') || combined.includes('experiment') || combined.includes('try')) {
762
+ boost('prototype-first', 2, 'Exploratory intent detected');
763
+ }
764
+ // ── Uncertainty signals → research-first
765
+ if (combined.includes('how to') || combined.includes('not sure') || combined.includes('don\'t know')) {
766
+ boost('research-first', 3, 'Uncertainty expressed');
767
+ }
768
+ if (combined.includes('api') || combined.includes('library') || combined.includes('documentation')) {
769
+ boost('research-first', 2, 'External API/library involved');
770
+ }
771
+ if (combined.includes('new') || combined.includes('unfamiliar') || combined.includes('first time')) {
772
+ boost('research-first', 2, 'New territory — research first');
773
+ }
774
+ // ── Test signals → test-driven
775
+ if (combined.includes('test') || combined.includes('spec') || combined.includes('assert')) {
776
+ boost('test-driven', 3, 'Testing explicitly mentioned');
777
+ }
778
+ if (combined.includes('correct') || combined.includes('reliable') || combined.includes('robust')) {
779
+ boost('test-driven', 2, 'Correctness is a priority');
780
+ }
781
+ if (combined.includes('regression') || combined.includes('broke') || combined.includes('broken again')) {
782
+ boost('test-driven', 3, 'Regression concern — tests prevent recurrence');
783
+ }
784
+ // ── Precision signals → surgical
785
+ if (combined.includes('bug') || combined.includes('fix') || combined.includes('hotfix') || combined.includes('patch')) {
786
+ boost('surgical', 3, 'Bug fix — minimal change preferred');
787
+ }
788
+ if (combined.includes('one line') || combined.includes('small change') || combined.includes('tiny')) {
789
+ boost('surgical', 3, 'Small scope — surgical precision');
790
+ }
791
+ if (combined.includes('critical') || combined.includes('breaking') || combined.includes('blocker')) {
792
+ boost('surgical', 2, 'Critical issue — precise fix needed');
793
+ }
794
+ // Sort by score descending
795
+ scores.sort((a, b) => b.score - a.score);
796
+ return scores;
797
+ }
798
+ /**
799
+ * Choose the best planning strategy for a task.
800
+ */
801
+ export function selectStrategy(task, context) {
802
+ const scores = scoreStrategies(task, context);
803
+ const best = scores[0];
804
+ const fallback = scores[1];
805
+ // Check if the top strategy is a clear winner (>= 2 points ahead of runner-up)
806
+ const isClean = best.score - (fallback?.score ?? 0) >= 2;
807
+ const adaptations = [];
808
+ // Adapt the chosen strategy based on context
809
+ const combined = `${task} ${context}`.toLowerCase();
810
+ if (combined.includes('deadline') && best.strategy !== 'prototype-first') {
811
+ adaptations.push('Time constraint detected — consider shortening verification steps');
812
+ }
813
+ if (combined.includes('production') && best.strategy === 'prototype-first') {
814
+ adaptations.push('Production environment — add extra verification before deploying prototype');
815
+ }
816
+ if ((combined.includes('large') || combined.includes('big') || combined.includes('massive')) && best.strategy === 'surgical') {
817
+ adaptations.push('Large scope detected — may need to expand from surgical to incremental if more changes surface');
818
+ }
819
+ return {
820
+ chosenStrategy: best.strategy,
821
+ reasoning: isClean
822
+ ? `Chose "${best.strategy}" (score: ${best.score}). ${best.reasons.join('. ')}.`
823
+ : `Chose "${best.strategy}" (score: ${best.score}) over "${fallback?.strategy}" (score: ${fallback?.score ?? 0}). The signals are close — monitor and adapt. ${best.reasons.join('. ')}.`,
824
+ adaptations,
825
+ fallbackStrategy: fallback?.strategy ?? 'incremental',
826
+ };
827
+ }
828
+ /**
829
+ * Evaluate whether the current planning strategy is working.
830
+ */
831
+ export function evaluateStrategy(strategy, progress) {
832
+ const lower = progress.toLowerCase();
833
+ const strategyDef = STRATEGIES.find(s => s.name === strategy);
834
+ // Positive signals
835
+ const positiveSignals = [
836
+ 'completed', 'done', 'success', 'working', 'passed', 'verified', 'green',
837
+ 'progress', 'advancing', 'making headway',
838
+ ];
839
+ const positiveCount = positiveSignals.filter(s => lower.includes(s)).length;
840
+ // Negative signals
841
+ const negativeSignals = [
842
+ 'failed', 'error', 'blocked', 'stuck', 'broken', 'regression', 'timeout',
843
+ 'impossible', 'cannot', 'doesn\'t work', 'not working',
844
+ ];
845
+ const negativeCount = negativeSignals.filter(s => lower.includes(s)).length;
846
+ if (negativeCount > positiveCount + 1) {
847
+ // Strategy is not working — suggest switching
848
+ let suggestion;
849
+ switch (strategy) {
850
+ case 'divide-and-conquer':
851
+ suggestion = 'Components may be more coupled than expected. Try "incremental" to make one small change at a time.';
852
+ break;
853
+ case 'incremental':
854
+ suggestion = 'Incremental changes keep failing. Try "research-first" to better understand the problem before more changes.';
855
+ break;
856
+ case 'prototype-first':
857
+ suggestion = 'Prototype is hitting too many issues. Try "research-first" or "incremental" for a more careful approach.';
858
+ break;
859
+ case 'research-first':
860
+ suggestion = 'Research is not leading to actionable steps. Try "prototype-first" to learn by doing.';
861
+ break;
862
+ case 'test-driven':
863
+ suggestion = 'Tests are hard to write for this domain. Try "prototype-first" to discover the right interface, then add tests.';
864
+ break;
865
+ case 'surgical':
866
+ suggestion = 'The fix is more involved than expected. Try "incremental" to make broader changes safely.';
867
+ break;
868
+ default:
869
+ suggestion = 'Consider switching to "incremental" for a safe, step-by-step approach.';
870
+ }
871
+ return { working: false, suggestion };
872
+ }
873
+ if (positiveCount > negativeCount) {
874
+ return { working: true };
875
+ }
876
+ // Neutral — keep going but note the uncertainty
877
+ return {
878
+ working: true,
879
+ suggestion: strategyDef
880
+ ? `Progress is unclear. Continue with "${strategy}" but be ready to switch to "${strategy === 'incremental' ? 'research-first' : 'incremental'}" if obstacles persist.`
881
+ : undefined,
882
+ };
883
+ }
884
+ /**
885
+ * Adapt the current strategy when encountering a problem.
886
+ */
887
+ export function adaptStrategy(currentStrategy, problem) {
888
+ // Use the problem description to find a better strategy
889
+ const result = selectStrategy(problem, `currently using ${currentStrategy} but encountering issues`);
890
+ // If we'd pick the same strategy, suggest the fallback instead
891
+ if (result.chosenStrategy === currentStrategy) {
892
+ const strategies = scoreStrategies(problem, '');
893
+ const alternative = strategies.find(s => s.strategy !== currentStrategy);
894
+ if (alternative) {
895
+ return {
896
+ chosenStrategy: alternative.strategy,
897
+ reasoning: `Current strategy "${currentStrategy}" is not working for this problem. Switching to "${alternative.strategy}". ${alternative.reasons.join('. ')}.`,
898
+ adaptations: [`Adapted from "${currentStrategy}" due to: ${problem}`],
899
+ fallbackStrategy: currentStrategy, // can always go back
900
+ };
901
+ }
902
+ }
903
+ result.adaptations.push(`Adapted from "${currentStrategy}" due to: ${problem}`);
904
+ return result;
905
+ }
906
+ const STRATEGY_FILE = join(kbotDir(), 'strategies.json');
907
+ /**
908
+ * Record which strategy worked for a task type for future reference.
909
+ */
910
+ export function recordStrategyOutcome(taskType, strategy, outcome) {
911
+ const history = loadJson(STRATEGY_FILE, []);
912
+ history.push({
913
+ taskType,
914
+ strategy,
915
+ outcome,
916
+ timestamp: new Date().toISOString(),
917
+ });
918
+ // Keep last 200 entries
919
+ const trimmed = history.slice(-200);
920
+ saveJson(STRATEGY_FILE, trimmed);
921
+ }
922
+ /**
923
+ * Get the historically best strategy for a given task type.
924
+ */
925
+ export function getHistoricalBestStrategy(taskType) {
926
+ const history = loadJson(STRATEGY_FILE, []);
927
+ const relevant = history.filter(h => h.taskType === taskType);
928
+ if (relevant.length === 0)
929
+ return null;
930
+ // Count successes per strategy
931
+ const counts = new Map();
932
+ for (const entry of relevant) {
933
+ const existing = counts.get(entry.strategy) ?? { success: 0, total: 0 };
934
+ existing.total++;
935
+ if (entry.outcome === 'success')
936
+ existing.success++;
937
+ counts.set(entry.strategy, existing);
938
+ }
939
+ // Find highest success rate (minimum 2 uses)
940
+ let bestStrategy = null;
941
+ let bestRate = 0;
942
+ for (const [strategy, data] of counts) {
943
+ if (data.total >= 2) {
944
+ const rate = data.success / data.total;
945
+ if (rate > bestRate) {
946
+ bestRate = rate;
947
+ bestStrategy = strategy;
948
+ }
949
+ }
950
+ }
951
+ return bestStrategy;
952
+ }
953
+ // ════════════════════════════════════════════════════════════════════════════
954
+ // TOOL REGISTRATION
955
+ // ════════════════════════════════════════════════════════════════════════════
956
+ export function registerReasoningTools() {
957
+ // ── Tool 1: hypothesize ──
958
+ registerTool({
959
+ name: 'hypothesize',
960
+ description: 'Generate ranked hypotheses for an error or unexpected behavior using abductive reasoning. ' +
961
+ 'Given an observation (error message, unexpected output, etc.) and optional context, ' +
962
+ 'returns a ranked list of possible explanations with evidence, test actions, and likelihoods. ' +
963
+ 'Use this when debugging to systematically explore possible root causes.',
964
+ parameters: {
965
+ observation: {
966
+ type: 'string',
967
+ description: 'The error message, unexpected behavior, or symptom to diagnose',
968
+ required: true,
969
+ },
970
+ context: {
971
+ type: 'string',
972
+ description: 'Additional context: file paths, recent changes, stack trace, etc.',
973
+ },
974
+ action: {
975
+ type: 'string',
976
+ description: 'Action to take: "generate" (default), "test" (update after testing), "eliminate" (rule out), "best" (get current best)',
977
+ },
978
+ hypothesis_id: {
979
+ type: 'string',
980
+ description: 'Hypothesis ID (required for "test" and "eliminate" actions)',
981
+ },
982
+ result: {
983
+ type: 'string',
984
+ description: 'Test result or elimination reason (required for "test" and "eliminate" actions)',
985
+ },
986
+ },
987
+ tier: 'free',
988
+ async execute(args) {
989
+ const action = String(args.action ?? 'generate');
990
+ const observation = String(args.observation ?? '');
991
+ const context = String(args.context ?? '');
992
+ switch (action) {
993
+ case 'generate': {
994
+ if (!observation)
995
+ return 'Error: observation is required for generating hypotheses';
996
+ const result = generateHypotheses(observation, context);
997
+ return formatAbductiveResult(result);
998
+ }
999
+ case 'test': {
1000
+ const id = String(args.hypothesis_id ?? '');
1001
+ const testResult = String(args.result ?? '');
1002
+ if (!id || !testResult)
1003
+ return 'Error: hypothesis_id and result are required for testing';
1004
+ testHypothesis(id, testResult);
1005
+ if (!currentSession)
1006
+ return 'Error: no active hypothesis session. Generate hypotheses first.';
1007
+ return formatAbductiveResult(currentSession);
1008
+ }
1009
+ case 'eliminate': {
1010
+ const id = String(args.hypothesis_id ?? '');
1011
+ const reason = String(args.result ?? '');
1012
+ if (!id || !reason)
1013
+ return 'Error: hypothesis_id and result (reason) are required for elimination';
1014
+ eliminateHypothesis(id, reason);
1015
+ if (!currentSession)
1016
+ return 'Error: no active hypothesis session. Generate hypotheses first.';
1017
+ return formatAbductiveResult(currentSession);
1018
+ }
1019
+ case 'best': {
1020
+ const best = getBestExplanation();
1021
+ if (!best)
1022
+ return 'No hypotheses available. Generate hypotheses first.';
1023
+ return `Best explanation (${(best.likelihood * 100).toFixed(0)}% likelihood):\n` +
1024
+ ` ${best.explanation}\n` +
1025
+ ` Evidence: ${best.evidence.join('; ')}\n` +
1026
+ ` Test: ${best.testAction}`;
1027
+ }
1028
+ default:
1029
+ return `Unknown action: ${action}. Use "generate", "test", "eliminate", or "best".`;
1030
+ }
1031
+ },
1032
+ });
1033
+ // ── Tool 2: counterfactual ──
1034
+ registerTool({
1035
+ name: 'counterfactual',
1036
+ description: 'Explore alternative approaches without executing them using counterfactual thinking. ' +
1037
+ 'Compare the current approach against alternatives to understand tradeoffs in risk, effort, ' +
1038
+ 'reversibility, and complexity. Also determines whether to pivot based on current obstacles. ' +
1039
+ 'Use this when stuck or before committing to a potentially costly approach.',
1040
+ parameters: {
1041
+ current_approach: {
1042
+ type: 'string',
1043
+ description: 'Description of the current approach being taken',
1044
+ required: true,
1045
+ },
1046
+ alternative: {
1047
+ type: 'string',
1048
+ description: 'Description of the alternative approach to evaluate',
1049
+ },
1050
+ alternatives: {
1051
+ type: 'string',
1052
+ description: 'Comma-separated list of alternatives to compare (used with action "compare")',
1053
+ },
1054
+ context: {
1055
+ type: 'string',
1056
+ description: 'Additional context: deadlines, environment, constraints',
1057
+ },
1058
+ action: {
1059
+ type: 'string',
1060
+ description: 'Action: "explore" (default), "compare" (multiple alternatives), "pivot" (should we switch?)',
1061
+ },
1062
+ obstacles: {
1063
+ type: 'string',
1064
+ description: 'Comma-separated list of obstacles encountered (used with action "pivot")',
1065
+ },
1066
+ progress: {
1067
+ type: 'string',
1068
+ description: 'Description of current progress (used with action "pivot")',
1069
+ },
1070
+ },
1071
+ tier: 'free',
1072
+ async execute(args) {
1073
+ const action = String(args.action ?? 'explore');
1074
+ const currentApproach = String(args.current_approach ?? '');
1075
+ const context = String(args.context ?? '');
1076
+ switch (action) {
1077
+ case 'explore': {
1078
+ const alternative = String(args.alternative ?? '');
1079
+ if (!currentApproach || !alternative)
1080
+ return 'Error: current_approach and alternative are required';
1081
+ const result = exploreCounterfactual(currentApproach, alternative, context);
1082
+ return formatCounterfactual(result);
1083
+ }
1084
+ case 'compare': {
1085
+ const alts = String(args.alternatives ?? '');
1086
+ if (!currentApproach || !alts)
1087
+ return 'Error: current_approach and alternatives are required';
1088
+ const approaches = [currentApproach, ...alts.split(',').map(s => s.trim()).filter(Boolean)];
1089
+ const results = compareApproaches(approaches);
1090
+ if (results.length === 0)
1091
+ return 'Error: need at least 2 approaches to compare';
1092
+ return results.map((r, i) => `--- Alternative ${i + 1} ---\n${formatCounterfactual(r)}`).join('\n\n');
1093
+ }
1094
+ case 'pivot': {
1095
+ const progress = String(args.progress ?? currentApproach);
1096
+ const obstaclesStr = String(args.obstacles ?? '');
1097
+ const obstacles = obstaclesStr.split(',').map(s => s.trim()).filter(Boolean);
1098
+ if (obstacles.length === 0)
1099
+ return 'Error: obstacles are required for pivot analysis';
1100
+ const result = shouldPivot(progress, obstacles);
1101
+ return `Pivot decision: ${result.pivot ? 'YES — PIVOT' : 'NO — STAY THE COURSE'}\n` +
1102
+ (result.to ? `Suggested pivot: ${result.to}\n` : '') +
1103
+ `Reasoning: ${result.reason}`;
1104
+ }
1105
+ default:
1106
+ return `Unknown action: ${action}. Use "explore", "compare", or "pivot".`;
1107
+ }
1108
+ },
1109
+ });
1110
+ // ── Tool 3: meta_plan ──
1111
+ registerTool({
1112
+ name: 'meta_plan',
1113
+ description: 'Choose or adapt the planning strategy for a task using meta-planning. ' +
1114
+ 'Analyzes the task characteristics (complexity, risk, uncertainty, time pressure) ' +
1115
+ 'to recommend the best planning approach: divide-and-conquer, incremental, prototype-first, ' +
1116
+ 'research-first, test-driven, or surgical. Can also evaluate and adapt strategies mid-execution. ' +
1117
+ 'Use this at the start of complex tasks or when the current approach is failing.',
1118
+ parameters: {
1119
+ task: {
1120
+ type: 'string',
1121
+ description: 'Description of the task to plan for',
1122
+ required: true,
1123
+ },
1124
+ context: {
1125
+ type: 'string',
1126
+ description: 'Additional context: codebase state, constraints, past attempts',
1127
+ },
1128
+ action: {
1129
+ type: 'string',
1130
+ description: 'Action: "select" (default), "evaluate" (is strategy working?), "adapt" (switch strategy), "history" (past results), "record" (save outcome)',
1131
+ },
1132
+ current_strategy: {
1133
+ type: 'string',
1134
+ description: 'Name of the current strategy (required for "evaluate" and "adapt")',
1135
+ },
1136
+ progress: {
1137
+ type: 'string',
1138
+ description: 'Description of progress so far (required for "evaluate")',
1139
+ },
1140
+ problem: {
1141
+ type: 'string',
1142
+ description: 'Description of the problem encountered (required for "adapt")',
1143
+ },
1144
+ task_type: {
1145
+ type: 'string',
1146
+ description: 'Category of the task (used for "history" and "record")',
1147
+ },
1148
+ outcome: {
1149
+ type: 'string',
1150
+ description: 'Outcome of the strategy: "success", "failure", or "partial" (used for "record")',
1151
+ },
1152
+ },
1153
+ tier: 'free',
1154
+ async execute(args) {
1155
+ const action = String(args.action ?? 'select');
1156
+ const task = String(args.task ?? '');
1157
+ const context = String(args.context ?? '');
1158
+ switch (action) {
1159
+ case 'select': {
1160
+ if (!task)
1161
+ return 'Error: task description is required';
1162
+ // Check historical data first
1163
+ const taskType = String(args.task_type ?? '');
1164
+ if (taskType) {
1165
+ const historicalBest = getHistoricalBestStrategy(taskType);
1166
+ if (historicalBest) {
1167
+ const result = selectStrategy(task, context);
1168
+ return `Historical recommendation for "${taskType}": ${historicalBest}\n\n` +
1169
+ `Current analysis:\n${formatMetaPlanResult(result)}`;
1170
+ }
1171
+ }
1172
+ const result = selectStrategy(task, context);
1173
+ return formatMetaPlanResult(result);
1174
+ }
1175
+ case 'evaluate': {
1176
+ const strategy = String(args.current_strategy ?? '');
1177
+ const progress = String(args.progress ?? '');
1178
+ if (!strategy || !progress)
1179
+ return 'Error: current_strategy and progress are required';
1180
+ const result = evaluateStrategy(strategy, progress);
1181
+ return `Strategy "${strategy}" evaluation: ${result.working ? 'WORKING' : 'NOT WORKING'}` +
1182
+ (result.suggestion ? `\nSuggestion: ${result.suggestion}` : '');
1183
+ }
1184
+ case 'adapt': {
1185
+ const strategy = String(args.current_strategy ?? '');
1186
+ const problem = String(args.problem ?? '');
1187
+ if (!strategy || !problem)
1188
+ return 'Error: current_strategy and problem are required';
1189
+ const result = adaptStrategy(strategy, problem);
1190
+ return `Adapting from "${strategy}":\n${formatMetaPlanResult(result)}`;
1191
+ }
1192
+ case 'history': {
1193
+ const taskType = String(args.task_type ?? '');
1194
+ if (!taskType)
1195
+ return 'Error: task_type is required for history lookup';
1196
+ const best = getHistoricalBestStrategy(taskType);
1197
+ return best
1198
+ ? `Best historical strategy for "${taskType}": ${best}`
1199
+ : `No historical data for "${taskType}" yet (need at least 2 recorded outcomes).`;
1200
+ }
1201
+ case 'record': {
1202
+ const taskType = String(args.task_type ?? '');
1203
+ const strategy = String(args.current_strategy ?? '');
1204
+ const outcome = String(args.outcome ?? '');
1205
+ if (!taskType || !strategy || !outcome)
1206
+ return 'Error: task_type, current_strategy, and outcome are required';
1207
+ if (!['success', 'failure', 'partial'].includes(outcome))
1208
+ return 'Error: outcome must be "success", "failure", or "partial"';
1209
+ recordStrategyOutcome(taskType, strategy, outcome);
1210
+ return `Recorded: strategy "${strategy}" for task type "${taskType}" → ${outcome}`;
1211
+ }
1212
+ default:
1213
+ return `Unknown action: ${action}. Use "select", "evaluate", "adapt", "history", or "record".`;
1214
+ }
1215
+ },
1216
+ });
1217
+ }
1218
+ // ════════════════════════════════════════════════════════════════════════════
1219
+ // FORMATTING HELPERS
1220
+ // ════════════════════════════════════════════════════════════════════════════
1221
+ function formatAbductiveResult(result) {
1222
+ const lines = [
1223
+ `Observation: ${result.observation}`,
1224
+ `Hypotheses (${result.hypotheses.length}):`,
1225
+ '',
1226
+ ];
1227
+ for (const h of result.hypotheses) {
1228
+ const marker = h.id === result.recommended ? ' ◀ RECOMMENDED' : '';
1229
+ const eliminated = h.likelihood === 0 ? ' [ELIMINATED]' : '';
1230
+ lines.push(` [${h.id}] ${(h.likelihood * 100).toFixed(0)}% — ${h.explanation}${marker}${eliminated}`);
1231
+ if (h.evidence.length > 0) {
1232
+ lines.push(` Evidence: ${h.evidence.slice(0, 3).join('; ')}`);
1233
+ }
1234
+ if (h.contradictions.length > 0) {
1235
+ lines.push(` Contradictions: ${h.contradictions.join('; ')}`);
1236
+ }
1237
+ if (h.testable) {
1238
+ lines.push(` Test: ${h.testAction}`);
1239
+ }
1240
+ lines.push('');
1241
+ }
1242
+ return lines.join('\n');
1243
+ }
1244
+ function formatCounterfactual(cf) {
1245
+ const lines = [
1246
+ `Scenario: ${cf.scenario}`,
1247
+ `Current: ${cf.currentPath}`,
1248
+ `Alternative: ${cf.alternativePath}`,
1249
+ '',
1250
+ 'Tradeoffs:',
1251
+ ` Effort: ${cf.tradeoffs.effort}`,
1252
+ ];
1253
+ if (cf.tradeoffs.benefits.length > 0) {
1254
+ lines.push(` Benefits:`);
1255
+ for (const b of cf.tradeoffs.benefits)
1256
+ lines.push(` + ${b}`);
1257
+ }
1258
+ if (cf.tradeoffs.risks.length > 0) {
1259
+ lines.push(` Risks:`);
1260
+ for (const r of cf.tradeoffs.risks)
1261
+ lines.push(` - ${r}`);
1262
+ }
1263
+ lines.push('');
1264
+ lines.push(`Recommendation: ${cf.recommendation.toUpperCase()}`);
1265
+ lines.push(`Reasoning: ${cf.reasoning}`);
1266
+ return lines.join('\n');
1267
+ }
1268
+ function formatMetaPlanResult(result) {
1269
+ const strategy = STRATEGIES.find(s => s.name === result.chosenStrategy);
1270
+ const lines = [
1271
+ `Strategy: ${result.chosenStrategy}`,
1272
+ `Reasoning: ${result.reasoning}`,
1273
+ `Fallback: ${result.fallbackStrategy}`,
1274
+ ];
1275
+ if (strategy) {
1276
+ lines.push('');
1277
+ lines.push(`Description: ${strategy.description}`);
1278
+ lines.push(`Best when: ${strategy.when}`);
1279
+ lines.push('Steps:');
1280
+ for (const [i, step] of strategy.steps.entries()) {
1281
+ lines.push(` ${i + 1}. ${step}`);
1282
+ }
1283
+ }
1284
+ if (result.adaptations.length > 0) {
1285
+ lines.push('');
1286
+ lines.push('Adaptations:');
1287
+ for (const a of result.adaptations)
1288
+ lines.push(` * ${a}`);
1289
+ }
1290
+ return lines.join('\n');
1291
+ }
1292
+ //# sourceMappingURL=reasoning.js.map