promptscout 1.3.2 → 1.5.0

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 (40) hide show
  1. package/README.md +165 -55
  2. package/dist/commands/templates.d.ts +3 -0
  3. package/dist/commands/templates.js +62 -0
  4. package/dist/commands/templates.js.map +1 -0
  5. package/dist/constants.d.ts +1 -1
  6. package/dist/constants.js +1 -2
  7. package/dist/constants.js.map +1 -1
  8. package/dist/core/orchestrator.d.ts +3 -1
  9. package/dist/core/orchestrator.js +7 -3
  10. package/dist/core/orchestrator.js.map +1 -1
  11. package/dist/core/rewriter.d.ts +4 -1
  12. package/dist/core/rewriter.js +22 -12
  13. package/dist/core/rewriter.js.map +1 -1
  14. package/dist/core/template-service.d.ts +14 -0
  15. package/dist/core/template-service.js +57 -0
  16. package/dist/core/template-service.js.map +1 -0
  17. package/dist/index.js +7 -1
  18. package/dist/index.js.map +1 -1
  19. package/dist/llm/prompts/tool-calling.d.ts +1 -1
  20. package/dist/llm/prompts/tool-calling.js +86 -21
  21. package/dist/llm/prompts/tool-calling.js.map +1 -1
  22. package/dist/storage/database.js +12 -0
  23. package/dist/storage/database.js.map +1 -1
  24. package/dist/storage/schema.d.ts +83 -0
  25. package/dist/storage/schema.js +6 -0
  26. package/dist/storage/schema.js.map +1 -1
  27. package/dist/storage/template-repo.d.ts +15 -0
  28. package/dist/storage/template-repo.js +36 -0
  29. package/dist/storage/template-repo.js.map +1 -0
  30. package/dist/tools/implementations.d.ts +4 -5
  31. package/dist/tools/implementations.js +32 -23
  32. package/dist/tools/implementations.js.map +1 -1
  33. package/dist/tools/index.d.ts +2 -20
  34. package/dist/tools/index.js +6 -106
  35. package/dist/tools/index.js.map +1 -1
  36. package/dist/tools/search-utils.d.ts +2 -4
  37. package/dist/tools/search-utils.js +61 -43
  38. package/dist/tools/search-utils.js.map +1 -1
  39. package/dist/types.d.ts +6 -0
  40. package/package.json +1 -2
package/README.md CHANGED
@@ -4,11 +4,14 @@ A CLI tool that enriches your coding agent prompts with codebase context using a
4
4
 
5
5
  **Designed as a Claude Code plugin.** It hooks into your prompt submission flow and adds codebase context before Claude sees it.
6
6
 
7
+ <img width="1715" height="1230" alt="CleanShot 2026-02-13 at 02 49 39" src="https://github.com/user-attachments/assets/d37eba7d-8697-4a2a-b231-b4217b90692e" />
8
+
9
+
7
10
  ## Motivation
8
11
 
9
12
  When you ask a coding agent like Claude Code, Cursor, or Copilot to work on your codebase, the agent spends time and tokens discovering which files matter. It greps, reads, explores, all on your dime.
10
13
 
11
- promptscout does that discovery locally, for free. A small local LLM reads your prompt, searches your codebase with `grep` and `git`, and appends the results to your original prompt. The paid agent gets a prompt that already contains the relevant file paths, code snippets, and commit history. It can skip straight to the actual work.
14
+ promptscout does that discovery locally, for free. A small local LLM reads your prompt, searches your codebase with `ripgrep` and `git`, and appends the results to your original prompt. The paid agent gets a prompt that already contains the relevant file paths, code snippets, and commit history. It can skip straight to the actual work.
12
15
 
13
16
  Your original prompt is never modified. promptscout only appends context.
14
17
 
@@ -17,16 +20,22 @@ Your original prompt is never modified. promptscout only appends context.
17
20
  ```mermaid
18
21
  flowchart LR
19
22
  A["Raw Prompt"] --> B["Local LLM<br/>(Qwen 3 4B)"]
20
- B -->|"tool calls"| C["Execute Tools"]
21
- C --> D["grep / git"]
22
- D --> E["Original Prompt<br/>+ Discovered Context"]
23
+ B -->|"tool calls"| C["ripgrep / git"]
24
+ C --> D["Original Prompt<br/>+ Discovered Context"]
25
+
26
+ subgraph B_ctx[" "]
27
+ direction TB
28
+ T["Project Tree<br/>(git ls-files)"]
29
+ end
30
+ T -.->|"context"| B
23
31
  ```
24
32
 
25
33
  1. You run `promptscout "check the auth module, there might be a token refresh bug"`
26
- 2. The local LLM reads your prompt and picks which tools to call (e.g. `file_finder("auth")`, `section_finder("refresh")`, `git_history("token")`)
27
- 3. Each tool runs against your codebase using `grep` and `git`
28
- 4. The output is your original prompt unchanged, followed by the discovered context
29
- 5. The result is copied to your clipboard, ready to paste into your coding agent
34
+ 2. The local LLM sees your prompt along with the project file tree (`git ls-files`) and decides which search tools to call
35
+ 3. The LLM outputs tool calls directly as JSON (e.g. `file_finder("auth")`, `section_finder("token")`, `git_history("refresh")`)
36
+ 4. Each tool runs against your codebase using `ripgrep` and `git`
37
+ 5. The output is your original prompt unchanged, followed by the discovered context
38
+ 6. The result is copied to your clipboard, ready to paste into your coding agent
30
39
 
31
40
  ## Installation
32
41
 
@@ -34,6 +43,8 @@ flowchart LR
34
43
 
35
44
  - Node.js >= 20
36
45
  - C++ compiler (Xcode Command Line Tools on macOS, `build-essential` on Linux)
46
+ - [ripgrep](https://github.com/BurntSushi/ripgrep) (`rg`) for fast codebase search
47
+ - `git` (for project tree generation and commit history search)
37
48
  - ~3GB disk space for the model
38
49
 
39
50
  ### Install
@@ -94,13 +105,13 @@ If `promptscout` is not installed or fails for any reason, the plugin falls back
94
105
  promptscout uses `Qwen 3 4B` (`Q4_K_M` quantization) running locally via [node-llama-cpp](https://github.com/withcatai/node-llama-cpp). The model uses GPU acceleration automatically when available (Metal on macOS, CUDA on Linux).
95
106
 
96
107
  - Size: ~2.5GB (`GGUF Q4_K_M`)
97
- - Context: 4096 tokens
108
+ - Context: 8192 tokens
98
109
  - Latency: ~2s per prompt (Metal, Apple Silicon)
99
- - Purpose: Decides which search tools to call based on your prompt. Does not rewrite your text.
110
+ - Purpose: Decides which search tools to call based on your prompt and the project file tree. Does not rewrite your text.
100
111
 
101
112
  ## Tools
102
113
 
103
- promptscout has 5 built-in tools that the LLM can invoke:
114
+ promptscout has 5 built-in search tools. The LLM picks which ones to call and with what keywords:
104
115
 
105
116
  | Tool | What it does |
106
117
  |---|---|
@@ -110,7 +121,7 @@ promptscout has 5 built-in tools that the LLM can invoke:
110
121
  | `import_tracer` | Finds import/require/include statements referencing a module. |
111
122
  | `git_history` | Finds recent commits that added or removed code matching a keyword. |
112
123
 
113
- All `grep`-based tools respect `.gitignore` and skip binary files.
124
+ All search tools use `ripgrep`, which respects `.gitignore` and skips binary files automatically.
114
125
 
115
126
  ## Usage
116
127
 
@@ -170,6 +181,70 @@ promptscout history show 42
170
181
  promptscout history clear
171
182
  ```
172
183
 
184
+ ## Templates
185
+
186
+ promptscout supports per-directory prompt templates. A template wraps your enriched prompt with custom instructions using the `<@prompt>` placeholder.
187
+
188
+ ### Example template
189
+
190
+ ```
191
+ You are an expert developer working on a React Native project.
192
+ Always prefer functional components and hooks.
193
+
194
+ <@prompt>
195
+
196
+ Follow the project's ESLint rules and use TypeScript strictly.
197
+ ```
198
+
199
+ When you run `promptscout "fix the navigation bug"`, the output becomes:
200
+
201
+ ```
202
+ You are an expert developer working on a React Native project.
203
+ Always prefer functional components and hooks.
204
+
205
+ fix the navigation bug
206
+
207
+ Context from codebase:
208
+ <file_finder query="navigation">
209
+ ...
210
+ </file_finder>
211
+
212
+ Follow the project's ESLint rules and use TypeScript strictly.
213
+ ```
214
+
215
+ ### Managing templates
216
+
217
+ ```bash
218
+ # Create or edit a template for the current directory (opens $EDITOR)
219
+ promptscout template edit
220
+
221
+ # Create or edit a template for a specific directory
222
+ promptscout template edit -d /path/to/project
223
+
224
+ # View the template for a directory
225
+ promptscout template show -d /path/to/project
226
+
227
+ # Delete a template
228
+ promptscout template delete -d /path/to/project
229
+
230
+ # List all templates
231
+ promptscout template list
232
+ ```
233
+
234
+ Templates are stored in the promptscout database (`~/.promptscout/promptscout.db`), not in your project files.
235
+
236
+ ### Directory resolution
237
+
238
+ Templates use nearest-ancestor matching. If you set a template for `/projects/monorepo`, it also applies when running from `/projects/monorepo/packages/api` or any subdirectory, unless that subdirectory has its own template.
239
+
240
+ ### Claude Code plugin
241
+
242
+ When the Claude Code plugin detects a template was used, it shows an indicator in the status message:
243
+
244
+ ```
245
+ promptscout: (template used) enriched context (+5 files) (+3 sections)
246
+ ```
247
+
173
248
  ## Examples
174
249
 
175
250
  ### Swift project (macOS audio capture tool)
@@ -188,26 +263,35 @@ Context from codebase:
188
263
  <file_finder query="audio">
189
264
  Sources/Core/AudioCaptureSession.swift
190
265
  Sources/Core/AudioTapManager.swift
191
- entitlements.plist
266
+ Sources/Info.plist
267
+ Sources/main.swift
268
+ Sources/CLI/ArgumentParser.swift
269
+ Sources/CLI/ExitCodes.swift
192
270
  README.md
193
- Sources/Core/InputDeviceQuery.swift
271
+ entitlements.plist
194
272
  Sources/IO/RingBuffer.swift
195
- Sources/IO/WAVWriter.swift
196
- Sources/CLI/ExitCodes.swift
273
+ Package.swift
197
274
  </file_finder>
198
275
 
199
- <git_history query="audio">
200
- d41aece Initial commit: audiograb - macOS system audio capture CLI
201
- Package.swift
202
- README.md
203
- Sources/CLI/ArgumentParser.swift
204
- Sources/Core/AudioCaptureSession.swift
205
- Sources/Core/AudioTapManager.swift
206
- Sources/IO/RingBuffer.swift
207
- Sources/main.swift
208
- d800ac1 Add microphone recording support via --source mic flag
209
- Sources/Info.plist
210
- </git_history>
276
+ <definition_finder query="format">
277
+ Sources/main.swift:49: let tapFormat = tapManager.tapFormat else {
278
+ Sources/main.swift:53: let outputSampleRate = UInt32(tapFormat.mSampleRate)
279
+ Sources/main.swift:55: let sourceChannels = Int(tapFormat.mChannelsPerFrame)
280
+ Sources/Core/InputDeviceQuery.swift:41: var formatAddress = AudioObjectPropertyAddress(
281
+ Sources/Core/InputDeviceQuery.swift:46: var format = AudioStreamBasicDescription()
282
+ Sources/Core/AudioTapManager.swift:23: case .formatQueryFailed(let status):
283
+ Sources/Core/AudioTapManager.swift:34: private(set) var tapFormat: AudioStreamBasicDescription?
284
+ Sources/Core/AudioTapManager.swift:103: private func queryTapFormat(tapID: AudioObjectID) throws -> AudioStreamBasicDescription {
285
+ Sources/Core/AudioTapManager.swift:110: var format = AudioStreamBasicDescription()
286
+ Sources/Core/AudioTapManager.swift:112: let status = AudioObjectGetPropertyData(tapID, &address, 0, nil, &size, &format)
287
+ </definition_finder>
288
+
289
+ <import_tracer query="audio">
290
+ Sources/IO/RingBuffer.swift:39: /// Write bytes into the ring buffer. Called from the real-time audio thread.
291
+ README.md:13:Download the latest build from the releases page.
292
+ README.md:77:Record from the default microphone instead of system audio.
293
+ README.md:149:The included entitlements.plist declares com.apple.security.device.audio-input.
294
+ </import_tracer>
211
295
  ```
212
296
 
213
297
  ### TypeScript project (task management CLI)
@@ -223,25 +307,48 @@ I want to add task filtering by status and tags. check how tasks are stored and
223
307
  Context from codebase:
224
308
 
225
309
  <file_finder query="task">
310
+ src/services/task.ts
311
+ src/commands/task.ts
312
+ src/commands/subtask.ts
226
313
  tests/commands/subtask.test.ts
227
314
  tests/commands/task.test.ts
228
- src/commands/task.ts
229
- src/services/task.ts
230
- tests/integration/workflows.test.ts
231
- tests/commands/comment.test.ts
232
- tests/commands/history.test.ts
315
+ package.json
316
+ src/services/epic.ts
317
+ README.md
318
+ CLAUDE.md
319
+ src/services/history.ts
233
320
  </file_finder>
234
321
 
235
- <definition_finder query="Task">
236
- tests/integration/workflows.test.ts:10:interface Task {
322
+ <section_finder query="status">
323
+ src/services/task.ts:9: TaskStatus,
324
+ src/services/task.ts:73: status?: TaskStatus;
325
+ src/services/task.ts:81: if (options?.status) {
326
+ src/services/task.ts:82: conditions.push(eq(tasks.status, options.status));
327
+ src/services/list.ts:56: if (options?.statuses && options.statuses.length > 0) {
328
+ src/services/list.ts:57: const placeholders = options.statuses.map(() => "?").join(", ");
329
+ src/services/list.ts:58: conditions.push(`status IN (${placeholders})`);
330
+ </section_finder>
331
+
332
+ <section_finder query="tag">
333
+ src/services/task.ts:56: tags: input.tags ?? null,
334
+ src/services/task.ts:140: if (input.tags !== undefined) updates.tags = input.tags;
335
+ src/commands/task.ts:29: .option("--tags <tags>", "Comma-separated tags")
336
+ src/commands/task.ts:91: .option("--tags <tags>", "New tags (comma-separated)")
337
+ </section_finder>
338
+
339
+ <definition_finder query="task">
340
+ src/services/task.ts:16:export function createTask(input: CreateTaskInput): Task {
341
+ src/services/task.ts:66:export function getTask(id: string): Task | undefined {
237
342
  src/services/task.ts:72:export function listTasks(options?: {
343
+ src/services/task.ts:116:export function updateTask(id: string, input: UpdateTaskInput): Task {
238
344
  </definition_finder>
239
345
 
240
- <section_finder query="listTasks">
241
- src/commands/task.ts:5: listTasks,
242
- src/commands/task.ts:58: const tasks = listTasks({
243
- src/services/task.ts:72:export function listTasks(options?: {
244
- </section_finder>
346
+ <import_tracer query="task">
347
+ src/services/task.ts:3:import { tasks, projects, epics } from "../db/schema";
348
+ src/services/comment.ts:3:import { comments, tasks } from "../db/schema";
349
+ src/services/dependency.ts:3:import { dependencies, tasks } from "../db/schema";
350
+ src/commands/task.ts:8:} from "../services/task";
351
+ </import_tracer>
245
352
  ```
246
353
 
247
354
  ### React/TypeScript project (terminal ebook downloader)
@@ -258,35 +365,38 @@ and find the related components
258
365
  Context from codebase:
259
366
 
260
367
  <file_finder query="search">
261
- src/tui/layouts/search/search-input/SearchWarning.tsx
262
368
  src/tui/layouts/search/search-input/SearchInput.tsx
369
+ src/tui/layouts/search/search-input/SearchWarning.tsx
263
370
  src/tui/layouts/search/index.tsx
264
371
  src/tui/layouts/search/search-input/index.tsx
265
- README.md
266
- package.json
372
+ src/options.ts
373
+ src/labels.ts
374
+ src/constants.ts
375
+ src/utils.ts
376
+ src/settings.ts
267
377
  CLAUDE.md
268
- src/tui/index.tsx
269
378
  </file_finder>
270
379
 
271
- <section_finder query="search">
272
- src/tui/components/ResultListInfo.tsx:6: const searchValue = useBoundStore((state) => state.searchValue);
273
- CLAUDE.md:30:- `libgen-downloader -s "query"` - Direct search with TUI
274
- CLAUDE.md:43: - `Adapter.ts` - Abstract base for different search sources
275
- CLAUDE.md:54:- `app.ts` - UI state, layouts, loading indicators, search state
276
- CLAUDE.md:58:- `cache.ts` - Search result caching mechanism
277
- </section_finder>
380
+ <definition_finder query="search">
381
+ src/api/data/document.ts:3:export async function getDocument(searchURL: string): Promise<Document> {
382
+ src/constants.ts:1:export const SEARCH_MIN_CHAR = 3;
383
+ src/settings.ts:11:export const SEARCH_PAGE_SIZE = 25;
384
+ src/tui/store/events.ts:31: const searchURL = get().mirrorAdapter?.getSearchURL(query, pageNumber, SEARCH_PAGE_SIZE);
385
+ src/tui/store/events.ts:68: const entries = await store.search(store.searchValue, store.currentPage);
386
+ src/tui/layouts/search/index.tsx:8:const Search: React.FC = () => {
387
+ </definition_finder>
278
388
  ```
279
389
 
280
- ### Feedback detection
390
+ ### No-context detection
281
391
 
282
- When the prompt is feedback or an observation (not asking to change code), promptscout returns it unchanged with no context appended:
392
+ When the prompt has no technical keywords (pure acknowledgment), promptscout returns it unchanged:
283
393
 
284
394
  ```
285
- $ promptscout "that didn't solve the issue, it is still rotated 90 degrees clockwise"
395
+ $ promptscout "thanks, that works perfectly"
286
396
  ```
287
397
 
288
398
  ```
289
- that didn't solve the issue, it is still rotated 90 degrees clockwise
399
+ thanks, that works perfectly
290
400
  ```
291
401
 
292
402
  ## License
@@ -0,0 +1,3 @@
1
+ import type { Command } from "commander";
2
+ import type { TemplateService } from "../core/template-service.js";
3
+ export declare function registerTemplateCommand(program: Command, templateService: TemplateService): void;
@@ -0,0 +1,62 @@
1
+ export function registerTemplateCommand(program, templateService) {
2
+ const tmpl = program
3
+ .command("template")
4
+ .description("Manage per-directory prompt templates");
5
+ // promptscout template edit [-d <dir>]
6
+ tmpl
7
+ .command("edit")
8
+ .option("-d, --dir <dir>", "Target directory", process.cwd())
9
+ .action((opts) => {
10
+ const dir = opts.dir;
11
+ const saved = templateService.edit(dir);
12
+ if (saved) {
13
+ console.log(`Template saved for ${dir}`);
14
+ }
15
+ else {
16
+ console.log("Template not saved (empty content).");
17
+ }
18
+ });
19
+ // promptscout template show [-d <dir>]
20
+ tmpl
21
+ .command("show")
22
+ .option("-d, --dir <dir>", "Target directory", process.cwd())
23
+ .action((opts) => {
24
+ const dir = opts.dir;
25
+ const content = templateService.get(dir);
26
+ if (content) {
27
+ console.log(content);
28
+ }
29
+ else {
30
+ console.log(`No template found for ${dir}`);
31
+ }
32
+ });
33
+ // promptscout template delete [-d <dir>]
34
+ tmpl
35
+ .command("delete")
36
+ .option("-d, --dir <dir>", "Target directory", process.cwd())
37
+ .action((opts) => {
38
+ const dir = opts.dir;
39
+ const deleted = templateService.delete(dir);
40
+ if (deleted) {
41
+ console.log(`Template deleted for ${dir}`);
42
+ }
43
+ else {
44
+ console.log(`No template found for ${dir}`);
45
+ }
46
+ });
47
+ // promptscout template list
48
+ tmpl
49
+ .command("list")
50
+ .action(() => {
51
+ const entries = templateService.list();
52
+ if (entries.length === 0) {
53
+ console.log("No templates defined.");
54
+ return;
55
+ }
56
+ for (const entry of entries) {
57
+ const preview = entry.content.split("\n")[0]?.slice(0, 60) ?? "";
58
+ console.log(` ${entry.directory} ${preview}...`);
59
+ }
60
+ });
61
+ }
62
+ //# sourceMappingURL=templates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/commands/templates.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,uBAAuB,CACrC,OAAgB,EAChB,eAAgC;IAEhC,MAAM,IAAI,GAAG,OAAO;SACjB,OAAO,CAAC,UAAU,CAAC;SACnB,WAAW,CAAC,uCAAuC,CAAC,CAAC;IAExD,uCAAuC;IACvC,IAAI;SACD,OAAO,CAAC,MAAM,CAAC;SACf,MAAM,CAAC,iBAAiB,EAAE,kBAAkB,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC;SAC5D,MAAM,CAAC,CAAC,IAA6B,EAAE,EAAE;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAa,CAAC;QAC/B,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,KAAK,EAAE,CAAC;YACV,OAAO,CAAC,GAAG,CAAC,sBAAsB,GAAG,EAAE,CAAC,CAAC;QAC3C,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,qCAAqC,CAAC,CAAC;QACrD,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,uCAAuC;IACvC,IAAI;SACD,OAAO,CAAC,MAAM,CAAC;SACf,MAAM,CAAC,iBAAiB,EAAE,kBAAkB,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC;SAC5D,MAAM,CAAC,CAAC,IAA6B,EAAE,EAAE;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAa,CAAC;QAC/B,MAAM,OAAO,GAAG,eAAe,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACzC,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACvB,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,yBAAyB,GAAG,EAAE,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,yCAAyC;IACzC,IAAI;SACD,OAAO,CAAC,QAAQ,CAAC;SACjB,MAAM,CAAC,iBAAiB,EAAE,kBAAkB,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC;SAC5D,MAAM,CAAC,CAAC,IAA6B,EAAE,EAAE;QACxC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAa,CAAC;QAC/B,MAAM,OAAO,GAAG,eAAe,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC5C,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,GAAG,CAAC,wBAAwB,GAAG,EAAE,CAAC,CAAC;QAC7C,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,yBAAyB,GAAG,EAAE,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC,CAAC,CAAC;IAEL,4BAA4B;IAC5B,IAAI;SACD,OAAO,CAAC,MAAM,CAAC;SACf,MAAM,CAAC,GAAG,EAAE;QACX,MAAM,OAAO,GAAG,eAAe,CAAC,IAAI,EAAE,CAAC;QACvC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;YACrC,OAAO;QACT,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;YAC5B,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;YACjE,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,SAAS,KAAK,OAAO,KAAK,CAAC,CAAC;QACrD,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -4,7 +4,7 @@ export declare const MODEL_DIR: string;
4
4
  export declare const MODEL_FILE_NAME = "Qwen3-4B-Q4_K_M.gguf";
5
5
  export declare const MODEL_HF_URI: string;
6
6
  export declare const MODEL_DOWNLOAD_URI = "hf:Qwen/Qwen3-4B-GGUF:Q4_K_M";
7
- export declare const LLM_CONTEXT_SIZE = 4096;
7
+ export declare const LLM_CONTEXT_SIZE = 8192;
8
8
  export declare const MODEL_HF_URI_KEY = "model_hf_uri";
9
9
  export declare const MODEL_CONTEXT_SIZE_KEY = "model_context_size";
10
10
  export declare const GPU_LAYERS: "auto";
package/dist/constants.js CHANGED
@@ -7,8 +7,7 @@ export const MODEL_DIR = join(DATA_DIR, "models");
7
7
  export const MODEL_FILE_NAME = "Qwen3-4B-Q4_K_M.gguf";
8
8
  export const MODEL_HF_URI = join(MODEL_DIR, MODEL_FILE_NAME);
9
9
  export const MODEL_DOWNLOAD_URI = "hf:Qwen/Qwen3-4B-GGUF:Q4_K_M";
10
- // 3.5K
11
- export const LLM_CONTEXT_SIZE = 4096;
10
+ export const LLM_CONTEXT_SIZE = 8192;
12
11
  export const MODEL_HF_URI_KEY = "model_hf_uri";
13
12
  export const MODEL_CONTEXT_SIZE_KEY = "model_context_size";
14
13
  // "auto" lets node-llama-cpp offload as many layers as fit in GPU/Metal memory
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;AAEvB,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;AACnD,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC;AACxD,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAElD,MAAM,CAAC,MAAM,eAAe,GAAG,sBAAsB,CAAC;AACtD,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;AAC7D,MAAM,CAAC,MAAM,kBAAkB,GAAG,8BAA8B,CAAC;AAEjE,OAAO;AACP,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAErC,MAAM,CAAC,MAAM,gBAAgB,GAAG,cAAc,CAAC;AAC/C,MAAM,CAAC,MAAM,sBAAsB,GAAG,oBAAoB,CAAC;AAC3D,+EAA+E;AAC/E,MAAM,CAAC,MAAM,UAAU,GAAG,MAAe,CAAC;AAE1C,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,CAAC;AACzC,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAC"}
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;AAEvB,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;AACnD,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,EAAE,gBAAgB,CAAC,CAAC;AACxD,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AAElD,MAAM,CAAC,MAAM,eAAe,GAAG,sBAAsB,CAAC;AACtD,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;AAC7D,MAAM,CAAC,MAAM,kBAAkB,GAAG,8BAA8B,CAAC;AAEjE,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAErC,MAAM,CAAC,MAAM,gBAAgB,GAAG,cAAc,CAAC;AAC/C,MAAM,CAAC,MAAM,sBAAsB,GAAG,oBAAoB,CAAC;AAC3D,+EAA+E;AAC/E,MAAM,CAAC,MAAM,UAAU,GAAG,MAAe,CAAC;AAE1C,MAAM,CAAC,MAAM,sBAAsB,GAAG,EAAE,CAAC;AACzC,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAC"}
@@ -1,10 +1,12 @@
1
1
  import type { ProcessOptions } from "../types.js";
2
2
  import type { HistoryRepo } from "../storage/history-repo.js";
3
3
  import type { Rewriter } from "./rewriter.js";
4
+ import type { TemplateService } from "./template-service.js";
4
5
  export declare class Orchestrator {
5
6
  private historyRepo;
6
7
  private rewriter;
7
- constructor(historyRepo: HistoryRepo, rewriter: Rewriter);
8
+ private templateService;
9
+ constructor(historyRepo: HistoryRepo, rewriter: Rewriter, templateService: TemplateService);
8
10
  processPrompt(options: ProcessOptions): Promise<{
9
11
  improved: string;
10
12
  }>;
@@ -4,21 +4,25 @@ import ora from "ora";
4
4
  export class Orchestrator {
5
5
  historyRepo;
6
6
  rewriter;
7
- constructor(historyRepo, rewriter) {
7
+ templateService;
8
+ constructor(historyRepo, rewriter, templateService) {
8
9
  this.historyRepo = historyRepo;
9
10
  this.rewriter = rewriter;
11
+ this.templateService = templateService;
10
12
  }
11
13
  async processPrompt(options) {
12
14
  const { rawPrompt, dryRun, outputFile, jsonOutput, noClipboard, projectDir, } = options;
15
+ const searchDir = projectDir ?? process.cwd();
16
+ const template = this.templateService.resolve(searchDir);
13
17
  const spinner = jsonOutput ? null : ora().start();
14
18
  const onStatus = spinner
15
19
  ? (message) => { spinner.text = message; }
16
20
  : undefined;
17
- const improved = await this.rewriter.rewrite(rawPrompt, projectDir, onStatus);
21
+ const { text: improved, templateUsed } = await this.rewriter.rewrite(rawPrompt, projectDir, onStatus, template);
18
22
  spinner?.stop();
19
23
  console.log("");
20
24
  if (jsonOutput) {
21
- console.log(JSON.stringify({ improved }));
25
+ console.log(JSON.stringify({ improved, templateUsed }));
22
26
  }
23
27
  else {
24
28
  console.log(improved);
@@ -1 +1 @@
1
- {"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../../src/core/orchestrator.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,GAAG,MAAM,KAAK,CAAC;AAEtB,MAAM,OAAO,YAAY;IAEb;IACA;IAFV,YACU,WAAwB,EACxB,QAAkB;QADlB,gBAAW,GAAX,WAAW,CAAa;QACxB,aAAQ,GAAR,QAAQ,CAAU;IACzB,CAAC;IAEJ,KAAK,CAAC,aAAa,CAAC,OAAuB;QAGzC,MAAM,EACJ,SAAS,EACT,MAAM,EACN,UAAU,EACV,UAAU,EACV,WAAW,EACX,UAAU,GACX,GAAG,OAAO,CAAC;QAEZ,MAAM,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC;QAClD,MAAM,QAAQ,GAAG,OAAO;YACtB,CAAC,CAAC,CAAC,OAAe,EAAE,EAAE,GAAG,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC;YAClD,CAAC,CAAC,SAAS,CAAC;QAEd,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;QAE9E,OAAO,EAAE,IAAI,EAAE,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAEhB,IAAI,UAAU,EAAE,CAAC;YACf,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;QAC5C,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACxB,CAAC;QAED,IAAI,MAAM,EAAE,CAAC;YACX,OAAO,EAAE,QAAQ,EAAE,CAAC;QACtB,CAAC;QAED,IAAI,UAAU,EAAE,CAAC;YACf,eAAe,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QACxC,CAAC;QAED,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;YAC/C,IAAI,MAAM,EAAE,CAAC;gBACX,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;YACxC,CAAC;QACH,CAAC;QAED,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;YACtB,SAAS,EAAE,UAAU,IAAI,OAAO,CAAC,GAAG,EAAE;YACtC,SAAS,EAAE,SAAS;YACpB,eAAe,EAAE,QAAQ;YACzB,YAAY,EAAE,QAAQ;YACtB,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE;SACxC,CAAC,CAAC;QAEH,OAAO,EAAE,QAAQ,EAAE,CAAC;IACtB,CAAC;CACF"}
1
+ {"version":3,"file":"orchestrator.js","sourceRoot":"","sources":["../../src/core/orchestrator.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,GAAG,MAAM,KAAK,CAAC;AAEtB,MAAM,OAAO,YAAY;IAEb;IACA;IACA;IAHV,YACU,WAAwB,EACxB,QAAkB,EAClB,eAAgC;QAFhC,gBAAW,GAAX,WAAW,CAAa;QACxB,aAAQ,GAAR,QAAQ,CAAU;QAClB,oBAAe,GAAf,eAAe,CAAiB;IACvC,CAAC;IAEJ,KAAK,CAAC,aAAa,CAAC,OAAuB;QAGzC,MAAM,EACJ,SAAS,EACT,MAAM,EACN,UAAU,EACV,UAAU,EACV,WAAW,EACX,UAAU,GACX,GAAG,OAAO,CAAC;QAEZ,MAAM,SAAS,GAAG,UAAU,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QAC9C,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;QAEzD,MAAM,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC;QAClD,MAAM,QAAQ,GAAG,OAAO;YACtB,CAAC,CAAC,CAAC,OAAe,EAAE,EAAE,GAAG,OAAO,CAAC,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC;YAClD,CAAC,CAAC,SAAS,CAAC;QAEd,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAEhH,OAAO,EAAE,IAAI,EAAE,CAAC;QAChB,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAEhB,IAAI,UAAU,EAAE,CAAC;YACf,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;QAC1D,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACxB,CAAC;QAED,IAAI,MAAM,EAAE,CAAC;YACX,OAAO,EAAE,QAAQ,EAAE,CAAC;QACtB,CAAC;QAED,IAAI,UAAU,EAAE,CAAC;YACf,eAAe,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;QACxC,CAAC;QAED,IAAI,CAAC,WAAW,EAAE,CAAC;YACjB,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;YAC/C,IAAI,MAAM,EAAE,CAAC;gBACX,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;YACxC,CAAC;QACH,CAAC;QAED,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC;YACtB,SAAS,EAAE,UAAU,IAAI,OAAO,CAAC,GAAG,EAAE;YACtC,SAAS,EAAE,SAAS;YACpB,eAAe,EAAE,QAAQ;YACzB,YAAY,EAAE,QAAQ;YACtB,UAAU,EAAE,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE;SACxC,CAAC,CAAC;QAEH,OAAO,EAAE,QAAQ,EAAE,CAAC;IACtB,CAAC;CACF"}
@@ -3,5 +3,8 @@ export declare class Rewriter {
3
3
  private configRepo;
4
4
  constructor(configRepo: ConfigRepo);
5
5
  getModelUri(): string;
6
- rewrite(rawPrompt: string, projectDir?: string, onStatus?: (message: string) => void): Promise<string>;
6
+ rewrite(rawPrompt: string, projectDir?: string, onStatus?: (message: string) => void, template?: string): Promise<{
7
+ text: string;
8
+ templateUsed: boolean;
9
+ }>;
7
10
  }
@@ -1,8 +1,8 @@
1
1
  import { generate } from "../llm/inference.js";
2
2
  import { buildToolCallingPrompt, parseToolCalls, } from "../llm/prompts/tool-calling.js";
3
- import { TOOL_DEFINITIONS, executeToolCall, loadIgnoreFilter, } from "../tools/index.js";
3
+ import { executeToolCall, buildProjectTree, } from "../tools/index.js";
4
4
  const TOOL_CALLING_PARAMS = {
5
- temperature: 0.6,
5
+ temperature: 0.3,
6
6
  topP: 0.8,
7
7
  topK: 20,
8
8
  minP: 0.0,
@@ -19,7 +19,7 @@ function isEmptyResult(result) {
19
19
  return NO_RESULT_PREFIXES.some((prefix) => result.startsWith(prefix));
20
20
  }
21
21
  function formatToolResult(call, result) {
22
- const param = call.arguments.url ?? call.arguments.query;
22
+ const param = call.arguments.query;
23
23
  return `<${call.name} query="${param}">\n${result}\n</${call.name}>`;
24
24
  }
25
25
  export class Rewriter {
@@ -30,32 +30,42 @@ export class Rewriter {
30
30
  getModelUri() {
31
31
  return this.configRepo.getModelHfUri();
32
32
  }
33
- async rewrite(rawPrompt, projectDir, onStatus) {
33
+ async rewrite(rawPrompt, projectDir, onStatus, template) {
34
34
  const hfUri = this.configRepo.getModelHfUri();
35
35
  const contextSize = this.configRepo.getModelContextSize();
36
36
  const searchDir = projectDir ?? process.cwd();
37
37
  onStatus?.("Evaluating");
38
- const systemPrompt = buildToolCallingPrompt(TOOL_DEFINITIONS);
38
+ const tree = buildProjectTree(searchDir);
39
+ const systemPrompt = buildToolCallingPrompt(tree);
39
40
  const raw = await generate(systemPrompt, rawPrompt, hfUri, contextSize, TOOL_CALLING_PARAMS);
40
41
  const calls = parseToolCalls(raw);
41
- if (calls.length === 0)
42
- return rawPrompt;
43
- const ig = loadIgnoreFilter(searchDir);
42
+ if (calls.length === 0) {
43
+ if (template)
44
+ return { text: template.replace("<@prompt>", rawPrompt), templateUsed: true };
45
+ return { text: rawPrompt, templateUsed: false };
46
+ }
44
47
  onStatus?.(`Running ${calls.length} tool calls`);
45
48
  const settled = await Promise.all(calls.map(async (call) => {
46
- const result = await executeToolCall(call, searchDir, ig);
49
+ const result = await executeToolCall(call, searchDir);
47
50
  if (!result || isEmptyResult(result))
48
51
  return null;
49
52
  return formatToolResult(call, result);
50
53
  }));
51
54
  const results = settled.filter((r) => r !== null);
52
- if (results.length === 0)
53
- return rawPrompt;
55
+ if (results.length === 0) {
56
+ if (template)
57
+ return { text: template.replace("<@prompt>", rawPrompt), templateUsed: true };
58
+ return { text: rawPrompt, templateUsed: false };
59
+ }
54
60
  const outputs = [];
55
61
  outputs.push(rawPrompt);
56
62
  outputs.push("Context from codebase:");
57
63
  outputs.push(results.join("\n\n"));
58
- return outputs.join("\n\n");
64
+ const assembled = outputs.join("\n\n");
65
+ if (template) {
66
+ return { text: template.replace("<@prompt>", assembled), templateUsed: true };
67
+ }
68
+ return { text: assembled, templateUsed: false };
59
69
  }
60
70
  }
61
71
  //# sourceMappingURL=rewriter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"rewriter.js","sourceRoot":"","sources":["../../src/core/rewriter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rewriter.js","sourceRoot":"","sources":["../../src/core/rewriter.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EACL,sBAAsB,EACtB,cAAc,GACf,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAEL,eAAe,EACf,gBAAgB,GACjB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,mBAAmB,GAAoB;IAC3C,WAAW,EAAE,GAAG;IAChB,IAAI,EAAE,GAAG;IACT,IAAI,EAAE,EAAE;IACR,IAAI,EAAE,GAAG;IACT,aAAa,EAAE;QACb,UAAU,EAAE,EAAE;QACd,OAAO,EAAE,GAAG;QACZ,gBAAgB,EAAE,GAAG;QACrB,eAAe,EAAE,GAAG;QACpB,eAAe,EAAE,KAAK;KACvB;CACF,CAAC;AAEF,MAAM,kBAAkB,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,cAAc,CAAC,CAAC;AAE7D,SAAS,aAAa,CAAC,MAAc;IACnC,OAAO,kBAAkB,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;AACxE,CAAC;AAED,SAAS,gBAAgB,CAAC,IAAc,EAAE,MAAc;IACtD,MAAM,KAAK,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;IACnC,OAAO,IAAI,IAAI,CAAC,IAAI,WAAW,KAAK,OAAO,MAAM,OAAO,IAAI,CAAC,IAAI,GAAG,CAAC;AACvE,CAAC;AAED,MAAM,OAAO,QAAQ;IACC;IAApB,YAAoB,UAAsB;QAAtB,eAAU,GAAV,UAAU,CAAY;IAAI,CAAC;IAE/C,WAAW;QACT,OAAO,IAAI,CAAC,UAAU,CAAC,aAAa,EAAE,CAAC;IACzC,CAAC;IAED,KAAK,CAAC,OAAO,CACX,SAAiB,EACjB,UAAmB,EACnB,QAAoC,EACpC,QAAiB;QAEjB,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,aAAa,EAAE,CAAC;QAC9C,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,CAAC,mBAAmB,EAAE,CAAC;QAC1D,MAAM,SAAS,GAAG,UAAU,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;QAE9C,QAAQ,EAAE,CAAC,YAAY,CAAC,CAAC;QAEzB,MAAM,IAAI,GAAG,gBAAgB,CAAC,SAAS,CAAC,CAAC;QACzC,MAAM,YAAY,GAAG,sBAAsB,CAAC,IAAI,CAAC,CAAC;QAClD,MAAM,GAAG,GAAG,MAAM,QAAQ,CACxB,YAAY,EACZ,SAAS,EACT,KAAK,EACL,WAAW,EACX,mBAAmB,CACpB,CAAC;QAEF,MAAM,KAAK,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;QAClC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,IAAI,QAAQ;gBAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,SAAS,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;YAC5F,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;QAClD,CAAC;QAED,QAAQ,EAAE,CAAC,WAAW,KAAK,CAAC,MAAM,aAAa,CAAC,CAAC;QACjD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;YACvB,MAAM,MAAM,GAAG,MAAM,eAAe,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;YACtD,IAAI,CAAC,MAAM,IAAI,aAAa,CAAC,MAAM,CAAC;gBAAE,OAAO,IAAI,CAAC;YAClD,OAAO,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACxC,CAAC,CAAC,CACH,CAAC;QACF,MAAM,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;QAE/D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,IAAI,QAAQ;gBAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,SAAS,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;YAC5F,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;QAClD,CAAC;QAED,MAAM,OAAO,GAAG,EAAE,CAAC;QACnB,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACxB,OAAO,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;QACvC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QAEnC,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAEvC,IAAI,QAAQ,EAAE,CAAC;YACb,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,WAAW,EAAE,SAAS,CAAC,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;QAChF,CAAC;QAED,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,KAAK,EAAE,CAAC;IAClD,CAAC;CACF"}
@@ -0,0 +1,14 @@
1
+ import type { TemplateRepo } from "../storage/template-repo.js";
2
+ export declare class TemplateService {
3
+ private templateRepo;
4
+ constructor(templateRepo: TemplateRepo);
5
+ edit(directory: string): boolean;
6
+ delete(directory: string): boolean;
7
+ get(directory: string): string | undefined;
8
+ list(): {
9
+ directory: string;
10
+ content: string;
11
+ }[];
12
+ resolve(directory: string): string | undefined;
13
+ apply(template: string, content: string): string;
14
+ }
@@ -0,0 +1,57 @@
1
+ import { resolve, dirname, join } from "node:path";
2
+ import { writeFileSync, readFileSync, unlinkSync } from "node:fs";
3
+ import { tmpdir } from "node:os";
4
+ import { execFileSync } from "node:child_process";
5
+ const PLACEHOLDER = "<@prompt>";
6
+ export class TemplateService {
7
+ templateRepo;
8
+ constructor(templateRepo) {
9
+ this.templateRepo = templateRepo;
10
+ }
11
+ edit(directory) {
12
+ const absDir = resolve(directory);
13
+ const existing = this.templateRepo.get(absDir);
14
+ const editor = process.env.EDITOR || process.env.VISUAL || "vi";
15
+ const tmpFile = join(tmpdir(), `promptscout-template-${Date.now()}.txt`);
16
+ writeFileSync(tmpFile, existing?.content ?? `\n${PLACEHOLDER}\n\n`, "utf-8");
17
+ try {
18
+ execFileSync(editor, [tmpFile], { stdio: "inherit" });
19
+ }
20
+ catch {
21
+ unlinkSync(tmpFile);
22
+ return false;
23
+ }
24
+ const content = readFileSync(tmpFile, "utf-8");
25
+ unlinkSync(tmpFile);
26
+ if (!content.trim())
27
+ return false;
28
+ this.templateRepo.upsert(absDir, content);
29
+ return true;
30
+ }
31
+ delete(directory) {
32
+ return this.templateRepo.delete(resolve(directory));
33
+ }
34
+ get(directory) {
35
+ return this.templateRepo.get(resolve(directory))?.content;
36
+ }
37
+ list() {
38
+ return this.templateRepo.list();
39
+ }
40
+ resolve(directory) {
41
+ let current = resolve(directory);
42
+ const root = resolve("/");
43
+ while (true) {
44
+ const entry = this.templateRepo.get(current);
45
+ if (entry)
46
+ return entry.content;
47
+ if (current === root)
48
+ break;
49
+ current = dirname(current);
50
+ }
51
+ return undefined;
52
+ }
53
+ apply(template, content) {
54
+ return template.replace(PLACEHOLDER, content);
55
+ }
56
+ }
57
+ //# sourceMappingURL=template-service.js.map