@manishbht/helpcode 0.2.2

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 (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +186 -0
  3. package/dist/bin/helpcode.d.ts +5 -0
  4. package/dist/bin/helpcode.js +10 -0
  5. package/dist/bin/helpcode.js.map +1 -0
  6. package/dist/src/commands/apply.d.ts +15 -0
  7. package/dist/src/commands/apply.js +195 -0
  8. package/dist/src/commands/apply.js.map +1 -0
  9. package/dist/src/commands/ask.d.ts +12 -0
  10. package/dist/src/commands/ask.js +93 -0
  11. package/dist/src/commands/ask.js.map +1 -0
  12. package/dist/src/commands/init.d.ts +13 -0
  13. package/dist/src/commands/init.js +91 -0
  14. package/dist/src/commands/init.js.map +1 -0
  15. package/dist/src/commands/reset.d.ts +8 -0
  16. package/dist/src/commands/reset.js +19 -0
  17. package/dist/src/commands/reset.js.map +1 -0
  18. package/dist/src/commands/run.d.ts +15 -0
  19. package/dist/src/commands/run.js +109 -0
  20. package/dist/src/commands/run.js.map +1 -0
  21. package/dist/src/commands/status.d.ts +4 -0
  22. package/dist/src/commands/status.js +53 -0
  23. package/dist/src/commands/status.js.map +1 -0
  24. package/dist/src/core/llmSelector.d.ts +65 -0
  25. package/dist/src/core/llmSelector.js +134 -0
  26. package/dist/src/core/llmSelector.js.map +1 -0
  27. package/dist/src/core/ollama.d.ts +43 -0
  28. package/dist/src/core/ollama.js +128 -0
  29. package/dist/src/core/ollama.js.map +1 -0
  30. package/dist/src/core/parser.d.ts +78 -0
  31. package/dist/src/core/parser.js +273 -0
  32. package/dist/src/core/parser.js.map +1 -0
  33. package/dist/src/core/patcher.d.ts +31 -0
  34. package/dist/src/core/patcher.js +128 -0
  35. package/dist/src/core/patcher.js.map +1 -0
  36. package/dist/src/core/project.d.ts +26 -0
  37. package/dist/src/core/project.js +199 -0
  38. package/dist/src/core/project.js.map +1 -0
  39. package/dist/src/core/prompt.d.ts +19 -0
  40. package/dist/src/core/prompt.js +121 -0
  41. package/dist/src/core/prompt.js.map +1 -0
  42. package/dist/src/core/selector.d.ts +46 -0
  43. package/dist/src/core/selector.js +193 -0
  44. package/dist/src/core/selector.js.map +1 -0
  45. package/dist/src/core/state.d.ts +11 -0
  46. package/dist/src/core/state.js +63 -0
  47. package/dist/src/core/state.js.map +1 -0
  48. package/dist/src/core/tools.d.ts +32 -0
  49. package/dist/src/core/tools.js +67 -0
  50. package/dist/src/core/tools.js.map +1 -0
  51. package/dist/src/core/triage.d.ts +37 -0
  52. package/dist/src/core/triage.js +69 -0
  53. package/dist/src/core/triage.js.map +1 -0
  54. package/dist/src/index.d.ts +12 -0
  55. package/dist/src/index.js +120 -0
  56. package/dist/src/index.js.map +1 -0
  57. package/dist/src/lib/compress.d.ts +14 -0
  58. package/dist/src/lib/compress.js +35 -0
  59. package/dist/src/lib/compress.js.map +1 -0
  60. package/dist/src/lib/errors.d.ts +25 -0
  61. package/dist/src/lib/errors.js +32 -0
  62. package/dist/src/lib/errors.js.map +1 -0
  63. package/dist/src/lib/git.d.ts +7 -0
  64. package/dist/src/lib/git.js +45 -0
  65. package/dist/src/lib/git.js.map +1 -0
  66. package/dist/src/lib/runclass.d.ts +24 -0
  67. package/dist/src/lib/runclass.js +66 -0
  68. package/dist/src/lib/runclass.js.map +1 -0
  69. package/dist/src/lib/ui.d.ts +41 -0
  70. package/dist/src/lib/ui.js +92 -0
  71. package/dist/src/lib/ui.js.map +1 -0
  72. package/dist/src/types.d.ts +70 -0
  73. package/dist/src/types.js +7 -0
  74. package/dist/src/types.js.map +1 -0
  75. package/package.json +53 -0
@@ -0,0 +1,91 @@
1
+ /**
2
+ * `helpcode init` — detect the project, write .helpcode/project.json,
3
+ * make sure .helpcode/ is gitignored.
4
+ */
5
+ import * as fs from 'fs';
6
+ import * as path from 'path';
7
+ import { buildDetectedConfig, projectExists, saveProjectConfig } from '../core/project.js';
8
+ import { isOllamaReachable, listModels } from '../core/ollama.js';
9
+ import { c, log } from '../lib/ui.js';
10
+ /** Coder-tuned models preferred for file-selection reasoning, best first. */
11
+ const PREFERRED_MODELS = [
12
+ 'qwen2.5-coder', 'deepseek-coder-v2', 'codestral', 'llama3.1', 'mistral',
13
+ ];
14
+ export async function handleInit(opts = {}) {
15
+ const cwd = process.cwd();
16
+ if (projectExists(cwd) && !opts.force) {
17
+ log.warn('.helpcode/project.json already exists.');
18
+ log.dim('Pass --force to regenerate.');
19
+ return 1;
20
+ }
21
+ const config = buildDetectedConfig(cwd);
22
+ // Detect Ollama. If reachable with a model, enable LLM selection and pick
23
+ // the best available coder model as the default.
24
+ let ollamaNote = '(not detected — LLM file selection off; install Ollama to enable)';
25
+ if (config.ollama && !opts.skipOllamaDetection) {
26
+ const host = config.ollama.host;
27
+ const reachable = await isOllamaReachable(host, { timeoutMs: 1000 });
28
+ if (reachable) {
29
+ try {
30
+ const models = await listModels(host, { timeoutMs: 2000 });
31
+ const chosen = pickModel(models);
32
+ if (chosen) {
33
+ config.ollama.enabled = true;
34
+ config.ollama.model = chosen;
35
+ ollamaNote = `enabled, model: ${chosen}`;
36
+ }
37
+ else if (models.length > 0) {
38
+ config.ollama.enabled = true;
39
+ config.ollama.model = models[0];
40
+ ollamaNote = `enabled, model: ${models[0]} (no coder model found — consider pulling qwen2.5-coder)`;
41
+ }
42
+ else {
43
+ ollamaNote = 'reachable but no models pulled — run `ollama pull qwen2.5-coder`';
44
+ }
45
+ }
46
+ catch {
47
+ ollamaNote = 'reachable but model list failed — left disabled';
48
+ }
49
+ }
50
+ }
51
+ saveProjectConfig(config, cwd);
52
+ log.ok('Initialised .helpcode/project.json');
53
+ console.log();
54
+ console.log('Detected:');
55
+ console.log(` ${c.dim('language: ')}${config.language}`);
56
+ console.log(` ${c.dim('framework: ')}${config.framework ?? '(none)'}`);
57
+ console.log(` ${c.dim('source dirs: ')}${config.sourceDirs.join(', ')}`);
58
+ console.log(` ${c.dim('test cmd: ')}${config.testCommand ?? '(none detected)'}`);
59
+ console.log(` ${c.dim('ollama: ')}${ollamaNote}`);
60
+ console.log();
61
+ ensureGitignore(cwd);
62
+ log.dim('Edit .helpcode/project.json to override any of the above.');
63
+ log.dim('Next: `helpcode ask "your task"`');
64
+ return 0;
65
+ }
66
+ /** Pick the best preferred model present, matching on prefix. */
67
+ function pickModel(available) {
68
+ for (const pref of PREFERRED_MODELS) {
69
+ const match = available.find(m => m.startsWith(pref));
70
+ if (match)
71
+ return match;
72
+ }
73
+ return null;
74
+ }
75
+ function ensureGitignore(cwd) {
76
+ const gitignorePath = path.join(cwd, '.gitignore');
77
+ const line = '.helpcode/';
78
+ let existing = '';
79
+ if (fs.existsSync(gitignorePath)) {
80
+ existing = fs.readFileSync(gitignorePath, 'utf-8');
81
+ if (existing.split(/\r?\n/).some(l => l.trim() === line)) {
82
+ return; // already there
83
+ }
84
+ }
85
+ const updated = existing.endsWith('\n') || existing.length === 0
86
+ ? existing + line + '\n'
87
+ : existing + '\n' + line + '\n';
88
+ fs.writeFileSync(gitignorePath, updated, 'utf-8');
89
+ log.ok('Added .helpcode/ to .gitignore');
90
+ }
91
+ //# sourceMappingURL=init.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../../src/commands/init.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `helpcode reset` — clear the current task and history.
3
+ * Does NOT touch project config or any source files.
4
+ */
5
+ export interface ResetOptions {
6
+ yes?: boolean;
7
+ }
8
+ export declare function handleReset(opts?: ResetOptions): Promise<number>;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `helpcode reset` — clear the current task and history.
3
+ * Does NOT touch project config or any source files.
4
+ */
5
+ import { resetState } from '../core/state.js';
6
+ import { confirm, log } from '../lib/ui.js';
7
+ export async function handleReset(opts = {}) {
8
+ if (!opts.yes) {
9
+ const ok = await confirm('Clear current task and history? (project config is untouched)');
10
+ if (!ok) {
11
+ log.dim('Cancelled.');
12
+ return 0;
13
+ }
14
+ }
15
+ resetState();
16
+ log.ok('State cleared. Project config and source files are untouched.');
17
+ return 0;
18
+ }
19
+ //# sourceMappingURL=reset.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reset.js","sourceRoot":"","sources":["../../../src/commands/reset.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAM5C,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAAqB,EAAE;IACvD,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;QACd,MAAM,EAAE,GAAG,MAAM,OAAO,CAAC,+DAA+D,CAAC,CAAC;QAC1F,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YACtB,OAAO,CAAC,CAAC;QACX,CAAC;IACH,CAAC;IACD,UAAU,EAAE,CAAC;IACb,GAAG,CAAC,EAAE,CAAC,+DAA+D,CAAC,CAAC;IACxE,OAAO,CAAC,CAAC;AACX,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * `helpcode run <command>` — run a shell command and capture the output
3
+ * in compact form. The result is saved into state so the next `ask` can
4
+ * include it for Claude.
5
+ *
6
+ * Side effect (v0.1.3): if the command matches the project's configured
7
+ * test command AND it succeeds AND the current task is in a `failed` state,
8
+ * the task is auto-resolved. This fixes the state-drift problem where a
9
+ * task stays `failed` forever after the user fixes the issue outside
10
+ * helpcode and re-runs the tests manually.
11
+ */
12
+ export interface RunOptions {
13
+ timeout?: number;
14
+ }
15
+ export declare function handleRun(command: string, opts?: RunOptions): Promise<number>;
@@ -0,0 +1,109 @@
1
+ /**
2
+ * `helpcode run <command>` — run a shell command and capture the output
3
+ * in compact form. The result is saved into state so the next `ask` can
4
+ * include it for Claude.
5
+ *
6
+ * Side effect (v0.1.3): if the command matches the project's configured
7
+ * test command AND it succeeds AND the current task is in a `failed` state,
8
+ * the task is auto-resolved. This fixes the state-drift problem where a
9
+ * task stays `failed` forever after the user fixes the issue outside
10
+ * helpcode and re-runs the tests manually.
11
+ */
12
+ import { runShellCommand } from '../core/tools.js';
13
+ import { truncateLines, extractTraceback } from '../lib/compress.js';
14
+ import { loadState, saveState } from '../core/state.js';
15
+ import { projectExists, loadProjectConfig } from '../core/project.js';
16
+ import { triageOutput, shouldTriage } from '../core/triage.js';
17
+ import { c, log } from '../lib/ui.js';
18
+ const MAX_OUTPUT_LINES = 40;
19
+ export async function handleRun(command, opts = {}) {
20
+ if (!command || !command.trim()) {
21
+ log.err('Usage: helpcode run "<command>"');
22
+ return 1;
23
+ }
24
+ const result = await runShellCommand(command, { timeoutSecs: opts.timeout ?? 60 });
25
+ // Render compact report
26
+ const parts = [];
27
+ parts.push(`$ ${command}`);
28
+ parts.push(`Exit: ${result.exitCode} Time: ${(result.durationMs / 1000).toFixed(2)}s` +
29
+ (result.timedOut ? c.yellow(' (TIMEOUT)') : ''));
30
+ if (result.stdout.trim()) {
31
+ parts.push('');
32
+ parts.push('--- stdout ---');
33
+ parts.push(truncateLines(result.stdout.trimEnd(), MAX_OUTPUT_LINES, 'stdout lines'));
34
+ }
35
+ if (result.stderr.trim()) {
36
+ parts.push('');
37
+ parts.push('--- stderr ---');
38
+ parts.push(extractTraceback(result.stderr));
39
+ }
40
+ if (!result.stdout.trim() && !result.stderr.trim()) {
41
+ parts.push('(no output)');
42
+ }
43
+ const report = parts.join('\n');
44
+ console.log(report);
45
+ // Save to state so `ask` can include it. When Ollama is enabled and the
46
+ // raw output is long, run it through local-LLM triage so the NEXT brief
47
+ // carries just the key failure rather than the whole wall of output. The
48
+ // console above still shows the full compact report to the user.
49
+ const state = loadState();
50
+ if (state.currentTask) {
51
+ let savedOutput = report;
52
+ const cfg = loadConfigSafe();
53
+ const rawForTriage = [result.stdout, result.stderr].filter(Boolean).join('\n').trim();
54
+ if (cfg?.ollama && shouldTriage(rawForTriage, cfg.ollama)) {
55
+ const triaged = await triageOutput(rawForTriage, cfg.ollama);
56
+ if (triaged.triaged) {
57
+ savedOutput = [
58
+ `$ ${command}`,
59
+ `Exit: ${result.exitCode} Time: ${(result.durationMs / 1000).toFixed(2)}s`,
60
+ '',
61
+ '--- key failure (summarised by local model) ---',
62
+ triaged.text,
63
+ ].join('\n');
64
+ log.dim('(summarised long output with local model for the next brief)');
65
+ }
66
+ }
67
+ state.currentTask.lastTestOutput = savedOutput;
68
+ // Auto-resolve: if this run matches the project's test command, it
69
+ // succeeded, and the task was failed, clear the failed state.
70
+ if (result.exitCode === 0 &&
71
+ state.currentTask.status === 'failed' &&
72
+ commandMatchesTestCommand(command)) {
73
+ state.currentTask.status = 'resolved';
74
+ console.log();
75
+ log.ok('Task marked resolved — the project test command now passes.');
76
+ }
77
+ saveState(state);
78
+ }
79
+ return result.exitCode;
80
+ }
81
+ /** Load project config without throwing if absent. */
82
+ function loadConfigSafe() {
83
+ if (!projectExists())
84
+ return null;
85
+ try {
86
+ return loadProjectConfig();
87
+ }
88
+ catch {
89
+ return null;
90
+ }
91
+ }
92
+ /** Does the run command match the project's configured test command? */
93
+ function commandMatchesTestCommand(command) {
94
+ if (!projectExists())
95
+ return false;
96
+ try {
97
+ const cfg = loadProjectConfig();
98
+ if (!cfg.testCommand)
99
+ return false;
100
+ return normalise(command) === normalise(cfg.testCommand);
101
+ }
102
+ catch {
103
+ return false;
104
+ }
105
+ }
106
+ function normalise(cmd) {
107
+ return cmd.trim().replace(/\s+/g, ' ');
108
+ }
109
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../../src/commands/run.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ /**
2
+ * `helpcode status` — show what the agent thinks the current state is.
3
+ */
4
+ export declare function handleStatus(): Promise<number>;
@@ -0,0 +1,53 @@
1
+ /**
2
+ * `helpcode status` — show what the agent thinks the current state is.
3
+ */
4
+ import { loadState } from '../core/state.js';
5
+ import { loadProjectConfig } from '../core/project.js';
6
+ import { c, log } from '../lib/ui.js';
7
+ export async function handleStatus() {
8
+ let projectLine = '(not initialised — run `helpcode init`)';
9
+ try {
10
+ const cfg = loadProjectConfig();
11
+ projectLine = `${cfg.language}${cfg.framework ? ' / ' + cfg.framework : ''}`;
12
+ }
13
+ catch {
14
+ // not initialised
15
+ }
16
+ const state = loadState();
17
+ console.log(`${c.bold('project:')} ${projectLine}`);
18
+ if (!state.currentTask) {
19
+ console.log(`${c.bold('task:')} ${c.dim('(none)')}`);
20
+ console.log();
21
+ log.dim('Run `helpcode ask "your task"` to start.');
22
+ return 0;
23
+ }
24
+ const t = state.currentTask;
25
+ console.log(`${c.bold('task:')} ${t.description}`);
26
+ console.log(`${c.dim(' id:')} ${t.id}`);
27
+ console.log(`${c.dim(' status:')} ${formatStatus(t.status)}`);
28
+ console.log(`${c.dim(' iters:')} ${t.iterations}`);
29
+ console.log(`${c.dim(' created:')} ${t.createdAt}`);
30
+ if (t.lastDiffsApplied.length > 0) {
31
+ console.log();
32
+ console.log(c.bold('last applied:'));
33
+ for (const d of t.lastDiffsApplied) {
34
+ const mark = d.ok ? c.green('✓') : c.red('✗');
35
+ const tag = d.created ? c.dim(' (new)') : '';
36
+ console.log(` ${mark} ${d.filepath}${tag}`);
37
+ }
38
+ }
39
+ if (state.history.length > 0) {
40
+ console.log();
41
+ console.log(c.dim(`history: ${state.history.length} resolved task(s)`));
42
+ }
43
+ return 0;
44
+ }
45
+ function formatStatus(s) {
46
+ switch (s) {
47
+ case 'awaiting_paste': return c.yellow(s);
48
+ case 'resolved': return c.green(s);
49
+ case 'failed': return c.red(s);
50
+ default: return s;
51
+ }
52
+ }
53
+ //# sourceMappingURL=status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"status.js","sourceRoot":"","sources":["../../../src/commands/status.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * LLM-based file selection (v0.2).
3
+ *
4
+ * Given a task description and a set of candidate files, ask a local model
5
+ * (via Ollama) which files are most relevant and why. Unlike the keyword
6
+ * heuristic in selector.ts, the model can reason about indirection: a "login
7
+ * bug" task can surface session.py and the user model, not just files that
8
+ * literally contain the word "login".
9
+ *
10
+ * The CRITICAL guard is hallucination rejection: the model sometimes invents
11
+ * filenames. Every returned path is validated against the real candidate set;
12
+ * invented paths are dropped. If nothing valid survives, the caller falls
13
+ * back to the heuristic selector.
14
+ *
15
+ * This module is pure logic over its inputs (prompt building + parsing). The
16
+ * actual model call and the strategy/fallback wiring live in selector.ts so
17
+ * this stays easily unit-testable without a live Ollama.
18
+ */
19
+ import { OllamaError } from './ollama.js';
20
+ export interface Candidate {
21
+ /** Path relative to the project root, as shown to the model. */
22
+ path: string;
23
+ /** A compact signature: declarations or first lines, to give the model context. */
24
+ signature: string;
25
+ }
26
+ export interface Selection {
27
+ path: string;
28
+ reason: string;
29
+ }
30
+ /**
31
+ * Build a compact signature for a file: lines that look like declarations
32
+ * (def/class/function/export/const/interface/type/struct/fn/func), capped.
33
+ * Falls back to the first few non-blank lines if no declarations are found.
34
+ * Language-agnostic by design — works reasonably for Python, JS/TS, Go, Rust.
35
+ */
36
+ export declare function buildSignature(filepath: string): string;
37
+ /**
38
+ * Build the selection prompt. Strict, parseable format — same philosophy as
39
+ * the Claude response protocol: lean on the model's format-following.
40
+ */
41
+ export declare function buildSelectionPrompt(task: string, candidates: Candidate[], count: number): string;
42
+ /**
43
+ * Parse the model's response into validated selections.
44
+ *
45
+ * Tolerant of real-world model messiness: prose preambles, list markers
46
+ * (`- `, `1. `), backtick-wrapped paths, and missing reasons. Strict on the
47
+ * one thing that matters: the path must exist in the candidate set.
48
+ */
49
+ export declare function parseSelectionResponse(response: string, candidates: Candidate[]): Selection[];
50
+ /**
51
+ * Full LLM selection: build candidates' signatures, prompt the model, parse
52
+ * and validate. Returns validated selections, or an empty array if the model
53
+ * produced nothing usable (caller falls back to heuristic).
54
+ *
55
+ * Throws OllamaError on transport failures so the caller can distinguish
56
+ * "model ran but found nothing" (empty array) from "couldn't reach model"
57
+ * (throw) — both lead to fallback, but the caller may log them differently.
58
+ */
59
+ export declare function llmSelectFiles(task: string, candidatePaths: string[], projectRoot: string, opts: {
60
+ host: string;
61
+ model: string;
62
+ count: number;
63
+ timeoutMs?: number;
64
+ }): Promise<Selection[]>;
65
+ export { OllamaError };
@@ -0,0 +1,134 @@
1
+ /**
2
+ * LLM-based file selection (v0.2).
3
+ *
4
+ * Given a task description and a set of candidate files, ask a local model
5
+ * (via Ollama) which files are most relevant and why. Unlike the keyword
6
+ * heuristic in selector.ts, the model can reason about indirection: a "login
7
+ * bug" task can surface session.py and the user model, not just files that
8
+ * literally contain the word "login".
9
+ *
10
+ * The CRITICAL guard is hallucination rejection: the model sometimes invents
11
+ * filenames. Every returned path is validated against the real candidate set;
12
+ * invented paths are dropped. If nothing valid survives, the caller falls
13
+ * back to the heuristic selector.
14
+ *
15
+ * This module is pure logic over its inputs (prompt building + parsing). The
16
+ * actual model call and the strategy/fallback wiring live in selector.ts so
17
+ * this stays easily unit-testable without a live Ollama.
18
+ */
19
+ import * as fs from 'fs';
20
+ import * as path from 'path';
21
+ import { generate, OllamaError } from './ollama.js';
22
+ const MAX_SIGNATURE_DECLS = 8; // declarations to include per file
23
+ const MAX_SIGNATURE_FALLBACK_LINES = 6; // if no declarations found
24
+ /**
25
+ * Build a compact signature for a file: lines that look like declarations
26
+ * (def/class/function/export/const/interface/type/struct/fn/func), capped.
27
+ * Falls back to the first few non-blank lines if no declarations are found.
28
+ * Language-agnostic by design — works reasonably for Python, JS/TS, Go, Rust.
29
+ */
30
+ export function buildSignature(filepath) {
31
+ let text;
32
+ try {
33
+ text = fs.readFileSync(filepath, 'utf-8');
34
+ }
35
+ catch {
36
+ return '(unreadable)';
37
+ }
38
+ const lines = text.split(/\r?\n/);
39
+ const declRe = /^\s*(?:export\s+)?(?:async\s+)?(?:def|class|function|const|let|interface|type|struct|fn|func|public|private|protected)\b/;
40
+ const decls = [];
41
+ for (const line of lines) {
42
+ if (declRe.test(line)) {
43
+ // Trim to the signature part — drop bodies/braces for compactness
44
+ const trimmed = line.trim().replace(/\s*[{:].*$/, '').slice(0, 80);
45
+ if (trimmed)
46
+ decls.push(trimmed);
47
+ if (decls.length >= MAX_SIGNATURE_DECLS)
48
+ break;
49
+ }
50
+ }
51
+ if (decls.length > 0) {
52
+ return decls.join('; ');
53
+ }
54
+ // Fallback: first few non-blank, non-comment lines
55
+ const firstLines = lines
56
+ .map(l => l.trim())
57
+ .filter(l => l.length > 0 && !l.startsWith('#') && !l.startsWith('//'))
58
+ .slice(0, MAX_SIGNATURE_FALLBACK_LINES);
59
+ return firstLines.join(' / ').slice(0, 200) || '(empty)';
60
+ }
61
+ /**
62
+ * Build the selection prompt. Strict, parseable format — same philosophy as
63
+ * the Claude response protocol: lean on the model's format-following.
64
+ */
65
+ export function buildSelectionPrompt(task, candidates, count) {
66
+ const lines = [];
67
+ lines.push(`TASK: ${task}`);
68
+ lines.push('');
69
+ lines.push('CANDIDATE FILES:');
70
+ for (const c of candidates) {
71
+ lines.push(`- ${c.path}: ${c.signature}`);
72
+ }
73
+ lines.push('');
74
+ lines.push(`Return the ${count} files most relevant to the task, most relevant first.`);
75
+ lines.push('Format each line exactly as:');
76
+ lines.push(' PATH | one-line reason');
77
+ lines.push('Only use paths from the candidate list above. No prose, no other text.');
78
+ return lines.join('\n');
79
+ }
80
+ /**
81
+ * Parse the model's response into validated selections.
82
+ *
83
+ * Tolerant of real-world model messiness: prose preambles, list markers
84
+ * (`- `, `1. `), backtick-wrapped paths, and missing reasons. Strict on the
85
+ * one thing that matters: the path must exist in the candidate set.
86
+ */
87
+ export function parseSelectionResponse(response, candidates) {
88
+ const validPaths = new Set(candidates.map(c => c.path));
89
+ const seen = new Set();
90
+ const out = [];
91
+ for (const rawLine of response.split(/\r?\n/)) {
92
+ let line = rawLine.trim();
93
+ if (!line)
94
+ continue;
95
+ // Strip leading list markers: "- ", "* ", "1. ", "2) "
96
+ line = line.replace(/^(?:[-*]\s+|\d+[.)]\s+)/, '');
97
+ // Split on the first pipe into path-part and reason-part
98
+ const pipeIdx = line.indexOf('|');
99
+ let pathPart = pipeIdx >= 0 ? line.slice(0, pipeIdx) : line;
100
+ const reason = pipeIdx >= 0 ? line.slice(pipeIdx + 1).trim() : '';
101
+ // Clean the path: strip backticks, surrounding quotes, whitespace
102
+ pathPart = pathPart.trim().replace(/^[`'"]+|[`'"]+$/g, '').trim();
103
+ if (!validPaths.has(pathPart))
104
+ continue; // hallucination guard
105
+ if (seen.has(pathPart))
106
+ continue; // dedupe
107
+ seen.add(pathPart);
108
+ out.push({ path: pathPart, reason });
109
+ }
110
+ return out;
111
+ }
112
+ /**
113
+ * Full LLM selection: build candidates' signatures, prompt the model, parse
114
+ * and validate. Returns validated selections, or an empty array if the model
115
+ * produced nothing usable (caller falls back to heuristic).
116
+ *
117
+ * Throws OllamaError on transport failures so the caller can distinguish
118
+ * "model ran but found nothing" (empty array) from "couldn't reach model"
119
+ * (throw) — both lead to fallback, but the caller may log them differently.
120
+ */
121
+ export async function llmSelectFiles(task, candidatePaths, projectRoot, opts) {
122
+ const candidates = candidatePaths.map(p => ({
123
+ path: path.relative(projectRoot, p),
124
+ signature: buildSignature(p),
125
+ }));
126
+ const prompt = buildSelectionPrompt(task, candidates, opts.count);
127
+ const response = await generate(opts.host, opts.model, prompt, {
128
+ timeoutMs: opts.timeoutMs,
129
+ });
130
+ return parseSelectionResponse(response, candidates);
131
+ }
132
+ // Re-export for callers that want to detect transport errors specifically.
133
+ export { OllamaError };
134
+ //# sourceMappingURL=llmSelector.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"llmSelector.js","sourceRoot":"","sources":["../../../src/core/llmSelector.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Minimal client for a local Ollama server (default http://localhost:11434).
3
+ *
4
+ * Design:
5
+ * - Zero runtime dependencies: plain fetch, no SDK.
6
+ * - fetch is injectable so tests never touch a live Ollama and CI passes
7
+ * with no Ollama installed.
8
+ * - Every failure becomes an OllamaError; callers (the selector) catch it
9
+ * and fall back to the heuristic. The LLM path can NEVER break `ask`.
10
+ *
11
+ * This is the v0.2 foundation. It does file-selection reasoning today; the
12
+ * same client will serve later local-LLM tasks (response rescue, triage).
13
+ */
14
+ export declare class OllamaError extends Error {
15
+ constructor(message: string);
16
+ }
17
+ /** Injectable fetch type — matches the global fetch signature we use. */
18
+ type FetchImpl = (url: string, init?: RequestInit) => Promise<Response>;
19
+ export interface OllamaCallOptions {
20
+ /** Injectable fetch for testing. Defaults to global fetch. */
21
+ fetchImpl?: FetchImpl;
22
+ /** Timeout in milliseconds. */
23
+ timeoutMs?: number;
24
+ }
25
+ /**
26
+ * Quick liveness probe. Returns true if Ollama answers /api/tags with 200.
27
+ * Never throws — a down server simply returns false.
28
+ */
29
+ export declare function isOllamaReachable(host: string, opts?: OllamaCallOptions): Promise<boolean>;
30
+ /**
31
+ * List the models pulled into Ollama (via GET /api/tags).
32
+ * Throws OllamaError if the server is unreachable or the response is bad.
33
+ */
34
+ export declare function listModels(host: string, opts?: OllamaCallOptions): Promise<string[]>;
35
+ /**
36
+ * Generate a completion via POST /api/chat (non-streaming).
37
+ * Returns the assistant message content.
38
+ *
39
+ * Throws OllamaError on timeout, connection failure, non-2xx status,
40
+ * or unparseable response.
41
+ */
42
+ export declare function generate(host: string, model: string, prompt: string, opts?: OllamaCallOptions): Promise<string>;
43
+ export {};