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.
- package/README.md +165 -55
- package/dist/commands/templates.d.ts +3 -0
- package/dist/commands/templates.js +62 -0
- package/dist/commands/templates.js.map +1 -0
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -2
- package/dist/constants.js.map +1 -1
- package/dist/core/orchestrator.d.ts +3 -1
- package/dist/core/orchestrator.js +7 -3
- package/dist/core/orchestrator.js.map +1 -1
- package/dist/core/rewriter.d.ts +4 -1
- package/dist/core/rewriter.js +22 -12
- package/dist/core/rewriter.js.map +1 -1
- package/dist/core/template-service.d.ts +14 -0
- package/dist/core/template-service.js +57 -0
- package/dist/core/template-service.js.map +1 -0
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/llm/prompts/tool-calling.d.ts +1 -1
- package/dist/llm/prompts/tool-calling.js +86 -21
- package/dist/llm/prompts/tool-calling.js.map +1 -1
- package/dist/storage/database.js +12 -0
- package/dist/storage/database.js.map +1 -1
- package/dist/storage/schema.d.ts +83 -0
- package/dist/storage/schema.js +6 -0
- package/dist/storage/schema.js.map +1 -1
- package/dist/storage/template-repo.d.ts +15 -0
- package/dist/storage/template-repo.js +36 -0
- package/dist/storage/template-repo.js.map +1 -0
- package/dist/tools/implementations.d.ts +4 -5
- package/dist/tools/implementations.js +32 -23
- package/dist/tools/implementations.js.map +1 -1
- package/dist/tools/index.d.ts +2 -20
- package/dist/tools/index.js +6 -106
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/search-utils.d.ts +2 -4
- package/dist/tools/search-utils.js +61 -43
- package/dist/tools/search-utils.js.map +1 -1
- package/dist/types.d.ts +6 -0
- 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 `
|
|
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["
|
|
21
|
-
C --> D["
|
|
22
|
-
|
|
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
|
|
27
|
-
3.
|
|
28
|
-
4.
|
|
29
|
-
5. The
|
|
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:
|
|
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
|
|
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
|
|
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
|
-
|
|
266
|
+
Sources/Info.plist
|
|
267
|
+
Sources/main.swift
|
|
268
|
+
Sources/CLI/ArgumentParser.swift
|
|
269
|
+
Sources/CLI/ExitCodes.swift
|
|
192
270
|
README.md
|
|
193
|
-
|
|
271
|
+
entitlements.plist
|
|
194
272
|
Sources/IO/RingBuffer.swift
|
|
195
|
-
|
|
196
|
-
Sources/CLI/ExitCodes.swift
|
|
273
|
+
Package.swift
|
|
197
274
|
</file_finder>
|
|
198
275
|
|
|
199
|
-
<
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
</
|
|
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
|
-
|
|
229
|
-
src/services/
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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
|
-
<
|
|
236
|
-
|
|
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
|
-
<
|
|
241
|
-
src/
|
|
242
|
-
src/
|
|
243
|
-
src/services/
|
|
244
|
-
|
|
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
|
-
|
|
266
|
-
|
|
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
|
-
<
|
|
272
|
-
src/
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
###
|
|
390
|
+
### No-context detection
|
|
281
391
|
|
|
282
|
-
When the prompt
|
|
392
|
+
When the prompt has no technical keywords (pure acknowledgment), promptscout returns it unchanged:
|
|
283
393
|
|
|
284
394
|
```
|
|
285
|
-
$ promptscout "
|
|
395
|
+
$ promptscout "thanks, that works perfectly"
|
|
286
396
|
```
|
|
287
397
|
|
|
288
398
|
```
|
|
289
|
-
|
|
399
|
+
thanks, that works perfectly
|
|
290
400
|
```
|
|
291
401
|
|
|
292
402
|
## License
|
|
@@ -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"}
|
package/dist/constants.d.ts
CHANGED
|
@@ -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 =
|
|
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
|
-
|
|
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
|
package/dist/constants.js.map
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
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":"
|
|
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"}
|
package/dist/core/rewriter.d.ts
CHANGED
|
@@ -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<
|
|
6
|
+
rewrite(rawPrompt: string, projectDir?: string, onStatus?: (message: string) => void, template?: string): Promise<{
|
|
7
|
+
text: string;
|
|
8
|
+
templateUsed: boolean;
|
|
9
|
+
}>;
|
|
7
10
|
}
|
package/dist/core/rewriter.js
CHANGED
|
@@ -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 {
|
|
3
|
+
import { executeToolCall, buildProjectTree, } from "../tools/index.js";
|
|
4
4
|
const TOOL_CALLING_PARAMS = {
|
|
5
|
-
temperature: 0.
|
|
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.
|
|
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
|
|
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
|
-
|
|
43
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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":"AAEA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EACL,sBAAsB,EACtB,cAAc,GACf,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAEL,
|
|
1
|
+
{"version":3,"file":"rewriter.js","sourceRoot":"","sources":["../../src/core/rewriter.ts"],"names":[],"mappings":"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"}
|
|
@@ -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
|