@soederpop/luca 0.1.3 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/release.yaml +167 -0
- package/README.md +3 -0
- package/assistants/codingAssistant/ABOUT.md +3 -0
- package/assistants/codingAssistant/CORE.md +22 -17
- package/assistants/codingAssistant/hooks.ts +19 -2
- package/assistants/codingAssistant/tools.ts +1 -106
- package/assistants/inkbot/ABOUT.md +5 -0
- package/assistants/inkbot/CORE.md +2 -0
- package/bun.lock +20 -4
- package/commands/release.ts +75 -181
- package/docs/ideas/assistant-factory-pattern.md +142 -0
- package/package.json +3 -2
- package/src/agi/container.server.ts +10 -0
- package/src/agi/features/agent-memory.ts +694 -0
- package/src/agi/features/assistant.ts +1 -1
- package/src/agi/features/assistants-manager.ts +25 -0
- package/src/agi/features/browser-use.ts +30 -0
- package/src/agi/features/coding-tools.ts +175 -0
- package/src/agi/features/file-tools.ts +33 -26
- package/src/agi/features/skills-library.ts +28 -11
- package/src/bootstrap/generated.ts +1 -1
- package/src/cli/build-info.ts +2 -2
- package/src/clients/voicebox/index.ts +300 -0
- package/src/introspection/generated.agi.ts +1997 -789
- package/src/introspection/generated.node.ts +788 -736
- package/src/introspection/generated.web.ts +1 -1
- package/src/node/features/content-db.ts +54 -27
- package/src/node/features/process-manager.ts +50 -17
- package/src/python/generated.ts +1 -1
- package/src/scaffolds/generated.ts +1 -1
- package/test/assistant.test.ts +14 -5
- package/test-integration/memory.test.ts +204 -0
|
@@ -27,6 +27,10 @@ export interface AssistantEntry {
|
|
|
27
27
|
hasHooks: boolean
|
|
28
28
|
/** Whether a voice.yaml configuration file exists. */
|
|
29
29
|
hasVoice: boolean
|
|
30
|
+
/** Contents of ABOUT.md if present, undefined otherwise. */
|
|
31
|
+
about?: string
|
|
32
|
+
/** Frontmatter metadata parsed from CORE.md. */
|
|
33
|
+
meta?: Record<string, any>
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
export const AssistantsManagerEventsSchema = FeatureEventsSchema.extend({
|
|
@@ -191,6 +195,25 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
|
|
|
191
195
|
|
|
192
196
|
// Don't overwrite earlier entries (home takes precedence for same name)
|
|
193
197
|
if (!discovered[entry]) {
|
|
198
|
+
const hasAbout = fs.exists(`${folder}/ABOUT.md`)
|
|
199
|
+
let about: string | undefined
|
|
200
|
+
let meta: Record<string, any> | undefined
|
|
201
|
+
|
|
202
|
+
if (hasAbout) {
|
|
203
|
+
about = fs.readFileSync(`${folder}/ABOUT.md`, 'utf8')
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
const coreContent = fs.readFileSync(`${folder}/CORE.md`, 'utf8')
|
|
208
|
+
const fmMatch = coreContent.match(/^---\r?\n([\s\S]*?)\r?\n---/)
|
|
209
|
+
if (fmMatch) {
|
|
210
|
+
const yaml = this.container.feature('yaml')
|
|
211
|
+
meta = yaml.parse(fmMatch[1])
|
|
212
|
+
}
|
|
213
|
+
} catch {
|
|
214
|
+
// CORE.md exists but couldn't be parsed — skip meta
|
|
215
|
+
}
|
|
216
|
+
|
|
194
217
|
discovered[entry] = {
|
|
195
218
|
name: entry,
|
|
196
219
|
folder,
|
|
@@ -198,6 +221,8 @@ export class AssistantsManager extends Feature<AssistantsManagerState, Assistant
|
|
|
198
221
|
hasTools: fs.exists(`${folder}/tools.ts`),
|
|
199
222
|
hasHooks: fs.exists(`${folder}/hooks.ts`),
|
|
200
223
|
hasVoice: fs.exists(`${folder}/voice.yaml`),
|
|
224
|
+
...(about != null && { about }),
|
|
225
|
+
...(meta != null && { meta }),
|
|
201
226
|
}
|
|
202
227
|
}
|
|
203
228
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
import { FeatureStateSchema, FeatureOptionsSchema, FeatureEventsSchema } from '../../schemas/base.js'
|
|
3
3
|
import { Feature } from '../feature.js'
|
|
4
|
+
import type { Helper } from '../../helper.js'
|
|
4
5
|
|
|
5
6
|
declare module '@soederpop/luca/feature' {
|
|
6
7
|
interface AvailableFeatures {
|
|
@@ -223,6 +224,35 @@ export class BrowserUse extends Feature<BrowserUseState, BrowserUseOptions> {
|
|
|
223
224
|
|
|
224
225
|
static { Feature.register(this, 'browserUse') }
|
|
225
226
|
|
|
227
|
+
/**
|
|
228
|
+
* When an assistant uses browserUse, inject system prompt guidance
|
|
229
|
+
* about the browser interaction loop.
|
|
230
|
+
*/
|
|
231
|
+
override setupToolsConsumer(consumer: Helper) {
|
|
232
|
+
if (typeof (consumer as any).addSystemPromptExtension === 'function') {
|
|
233
|
+
(consumer as any).addSystemPromptExtension('browserUse', [
|
|
234
|
+
'## Browser Automation',
|
|
235
|
+
'',
|
|
236
|
+
'**The core loop:** `browserOpen` → `browserGetState` → interact → `browserGetState` again.',
|
|
237
|
+
'',
|
|
238
|
+
'`browserGetState` is your eyes. It returns all interactive elements with index numbers. You MUST call it:',
|
|
239
|
+
'- After every `browserOpen` or navigation',
|
|
240
|
+
'- After any action that changes the page (click, submit, scroll)',
|
|
241
|
+
'- Before any interaction — to get fresh element indices',
|
|
242
|
+
'',
|
|
243
|
+
'Element indices change whenever the page updates. Never reuse indices from a previous `browserGetState` call after the page has changed.',
|
|
244
|
+
'',
|
|
245
|
+
'**Interacting with elements:** Use `browserInput` (click + type) for form fields. Use `browserClick` for buttons and links. Use `browserSelect` for dropdowns. All require an element index from `browserGetState`.',
|
|
246
|
+
'',
|
|
247
|
+
'**When things load asynchronously:** Use `browserWaitForSelector` or `browserWaitForText` after actions that trigger page updates (form submissions, AJAX). Then call `browserGetState` to see the updated page.',
|
|
248
|
+
'',
|
|
249
|
+
'**Debugging:** If an interaction doesn\'t work, take a `browserScreenshot` to see the actual page state. Check `browserGetState` to see what elements are available.',
|
|
250
|
+
'',
|
|
251
|
+
'**Cleanup:** Call `browserClose` when you\'re done to free resources.',
|
|
252
|
+
].join('\n'))
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
226
256
|
override async afterInitialize() {
|
|
227
257
|
if (this.options.session) this.state.set('session', this.options.session)
|
|
228
258
|
if (this.options.headed) this.state.set('headed', true)
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { z } from 'zod'
|
|
2
|
+
import { FeatureStateSchema, FeatureOptionsSchema } from '../../schemas/base.js'
|
|
3
|
+
import { Feature } from '../feature.js'
|
|
4
|
+
import type { Helper } from '../../helper.js'
|
|
5
|
+
import type { ChildProcess } from '../../node/features/proc.js'
|
|
6
|
+
|
|
7
|
+
declare module '@soederpop/luca/feature' {
|
|
8
|
+
interface AvailableFeatures {
|
|
9
|
+
codingTools: typeof CodingTools
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const CodingToolsStateSchema = FeatureStateSchema.extend({})
|
|
14
|
+
export const CodingToolsOptionsSchema = FeatureOptionsSchema.extend({})
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Shell primitives for AI coding assistants: rg, ls, cat, sed, awk.
|
|
18
|
+
*
|
|
19
|
+
* Wraps standard Unix tools into the assistant tool surface with
|
|
20
|
+
* LLM-optimized descriptions and system prompt guidance. These are
|
|
21
|
+
* the raw, flexible tools for reading, searching, and exploring code.
|
|
22
|
+
*
|
|
23
|
+
* Compose with other features (fileTools, processManager, skillsLibrary)
|
|
24
|
+
* in assistant hooks for a complete coding tool surface.
|
|
25
|
+
*
|
|
26
|
+
* Usage:
|
|
27
|
+
* ```typescript
|
|
28
|
+
* assistant.use(container.feature('codingTools'))
|
|
29
|
+
* ```
|
|
30
|
+
*
|
|
31
|
+
* @extends Feature
|
|
32
|
+
*/
|
|
33
|
+
export class CodingTools extends Feature {
|
|
34
|
+
static override shortcut = 'features.codingTools' as const
|
|
35
|
+
static override stateSchema = CodingToolsStateSchema
|
|
36
|
+
static override optionsSchema = CodingToolsOptionsSchema
|
|
37
|
+
|
|
38
|
+
static { Feature.register(this, 'codingTools') }
|
|
39
|
+
|
|
40
|
+
static override tools: Record<string, { schema: z.ZodType; description?: string }> = {
|
|
41
|
+
rg: {
|
|
42
|
+
description: 'ripgrep — fast content search across files. The fastest way to find where something is defined, referenced, or used. Supports regex, file type filtering, context lines, and everything ripgrep supports.',
|
|
43
|
+
schema: z.object({
|
|
44
|
+
args: z.string().describe(
|
|
45
|
+
'Arguments to pass to rg, exactly as you would on the command line. ' +
|
|
46
|
+
'Examples: "TODO --type ts", "-n "function handleAuth" src/", ' +
|
|
47
|
+
'"import.*lodash -g "*.ts" --count", "-C 3 "class User" src/models/"'
|
|
48
|
+
),
|
|
49
|
+
cwd: z.string().optional().describe('Working directory. Defaults to project root.'),
|
|
50
|
+
}).describe('ripgrep — fast content search across files. Supports regex, file type filtering, context lines. Use this as your primary search tool for finding code.'),
|
|
51
|
+
},
|
|
52
|
+
ls: {
|
|
53
|
+
description: 'List files and directories. Use to orient yourself in the project structure, check what exists in a directory, or verify paths before operating on them.',
|
|
54
|
+
schema: z.object({
|
|
55
|
+
args: z.string().optional().describe(
|
|
56
|
+
'Arguments to pass to ls. Examples: "-la src/", "-R --color=never commands/", "-1 *.ts". ' +
|
|
57
|
+
'Defaults to listing the project root.'
|
|
58
|
+
),
|
|
59
|
+
cwd: z.string().optional().describe('Working directory. Defaults to project root.'),
|
|
60
|
+
}).describe('List files and directories. Use to orient yourself, check directory contents, or verify paths.'),
|
|
61
|
+
},
|
|
62
|
+
cat: {
|
|
63
|
+
description: 'Read file contents. Use for reading entire files or specific line ranges. For large files, prefer reading specific ranges with sed or use rg to find the relevant section first.',
|
|
64
|
+
schema: z.object({
|
|
65
|
+
args: z.string().describe(
|
|
66
|
+
'Arguments to pass to cat. Typically just a file path. ' +
|
|
67
|
+
'Examples: "src/index.ts", "-n src/index.ts" (with line numbers). ' +
|
|
68
|
+
'For line ranges, use sed instead: sed -n "10,20p" file.ts'
|
|
69
|
+
),
|
|
70
|
+
cwd: z.string().optional().describe('Working directory. Defaults to project root.'),
|
|
71
|
+
}).describe('Read file contents. Best for reading entire files or viewing file content with line numbers.'),
|
|
72
|
+
},
|
|
73
|
+
sed: {
|
|
74
|
+
description: 'Stream editor for extracting or transforming text. Use for reading specific line ranges from files, or performing find-and-replace operations.',
|
|
75
|
+
schema: z.object({
|
|
76
|
+
args: z.string().describe(
|
|
77
|
+
'Arguments to pass to sed. Examples: ' +
|
|
78
|
+
'"-n \\"10,30p\\" src/index.ts" (print lines 10-30), ' +
|
|
79
|
+
'"-n \\"1,5p\\" package.json" (first 5 lines), ' +
|
|
80
|
+
'"s/oldName/newName/g src/config.ts" (find-and-replace)'
|
|
81
|
+
),
|
|
82
|
+
cwd: z.string().optional().describe('Working directory. Defaults to project root.'),
|
|
83
|
+
}).describe('Stream editor for extracting line ranges or transforming text in files.'),
|
|
84
|
+
},
|
|
85
|
+
awk: {
|
|
86
|
+
description: 'Pattern scanning and text processing. Use for extracting specific fields from structured output, summarizing data, or complex text transformations.',
|
|
87
|
+
schema: z.object({
|
|
88
|
+
args: z.string().describe(
|
|
89
|
+
'Arguments to pass to awk. Examples: ' +
|
|
90
|
+
'"\'{print $1}\' file.txt" (first column), ' +
|
|
91
|
+
'"-F: \'{print $1, $3}\' /etc/passwd" (colon-delimited fields), ' +
|
|
92
|
+
'"\'/pattern/ {print}\' file.txt" (lines matching pattern)'
|
|
93
|
+
),
|
|
94
|
+
cwd: z.string().optional().describe('Working directory. Defaults to project root.'),
|
|
95
|
+
}).describe('Pattern scanning and text processing. Extract fields, summarize data, or perform complex text transformations.'),
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private get proc(): ChildProcess {
|
|
100
|
+
return this.container.feature('proc') as unknown as ChildProcess
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// -------------------------------------------------------------------------
|
|
104
|
+
// Shell tool implementations
|
|
105
|
+
// -------------------------------------------------------------------------
|
|
106
|
+
|
|
107
|
+
private async _exec(command: string, args: string, cwd?: string): Promise<string> {
|
|
108
|
+
const fullCommand = args ? `${command} ${args}` : command
|
|
109
|
+
const result = await this.proc.execAndCapture(fullCommand, {
|
|
110
|
+
cwd: cwd ?? this.container.cwd,
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
if (result.exitCode !== 0) {
|
|
114
|
+
const parts: string[] = []
|
|
115
|
+
if (result.stdout?.trim()) parts.push(result.stdout.trim())
|
|
116
|
+
if (result.stderr?.trim()) parts.push(`[stderr] ${result.stderr.trim()}`)
|
|
117
|
+
parts.push(`[exit code: ${result.exitCode}]`)
|
|
118
|
+
return parts.join('\n')
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return result.stdout || '(no output)'
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async rg(args: { args: string; cwd?: string }): Promise<string> {
|
|
125
|
+
return this._exec('rg', args.args, args.cwd)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async ls(args: { args?: string; cwd?: string }): Promise<string> {
|
|
129
|
+
return this._exec('ls', args.args || '', args.cwd)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async cat(args: { args: string; cwd?: string }): Promise<string> {
|
|
133
|
+
return this._exec('cat', args.args, args.cwd)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async sed(args: { args: string; cwd?: string }): Promise<string> {
|
|
137
|
+
return this._exec('sed', args.args, args.cwd)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async awk(args: { args: string; cwd?: string }): Promise<string> {
|
|
141
|
+
return this._exec('awk', args.args, args.cwd)
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
override setupToolsConsumer(consumer: Helper) {
|
|
145
|
+
if (typeof (consumer as any).addSystemPromptExtension === 'function') {
|
|
146
|
+
(consumer as any).addSystemPromptExtension('codingTools', SYSTEM_PROMPT_EXTENSION)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ─── System Prompt Extension ──────────────────────────────────────────────────
|
|
152
|
+
|
|
153
|
+
const SYSTEM_PROMPT_EXTENSION = [
|
|
154
|
+
'## Shell Tools',
|
|
155
|
+
'',
|
|
156
|
+
'You have direct access to standard Unix tools. These are your primary read/search/explore tools:',
|
|
157
|
+
'',
|
|
158
|
+
'**`rg` (ripgrep) — your most important tool.** Use it before guessing where anything is.',
|
|
159
|
+
'- `rg -n "pattern" --type ts` — search TypeScript files with line numbers',
|
|
160
|
+
'- `rg -C 3 "pattern"` — show 3 lines of context around matches',
|
|
161
|
+
'- `rg -l "pattern"` — list only filenames that match',
|
|
162
|
+
'- `rg "TODO|FIXME" --type ts --count` — count matches per file',
|
|
163
|
+
'',
|
|
164
|
+
'**`cat` — read files.** Use `cat -n` for line numbers. For large files, use `sed -n "10,30p"` to read a range.',
|
|
165
|
+
'',
|
|
166
|
+
'**`ls` — orient yourself.** `ls -la src/` for details, `ls -R` for recursive listing.',
|
|
167
|
+
'',
|
|
168
|
+
'**`sed` — extract line ranges.** `sed -n "50,80p" file.ts` reads lines 50-80.',
|
|
169
|
+
'',
|
|
170
|
+
'**`awk` — structured text processing.** Extract columns, summarize, transform.',
|
|
171
|
+
'',
|
|
172
|
+
'**Workflow:** `rg` to find → `cat -n` to read → `editFile` to change → `runCommand` to verify.',
|
|
173
|
+
].join('\n')
|
|
174
|
+
|
|
175
|
+
export default CodingTools
|
|
@@ -48,20 +48,20 @@ export class FileTools extends Feature {
|
|
|
48
48
|
}).describe('Read the contents of a file. Returns the text content. Use offset/limit to read portions of large files.'),
|
|
49
49
|
},
|
|
50
50
|
writeFile: {
|
|
51
|
-
description: 'Create a new file or overwrite an existing file
|
|
51
|
+
description: 'Create a new file or completely overwrite an existing file. WARNING: this replaces the entire file — use editFile instead for modifying existing files. Use writeFile only for creating new files or intentional full rewrites.',
|
|
52
52
|
schema: z.object({
|
|
53
|
-
path: z.string().describe('File path relative to the project root'),
|
|
54
|
-
content: z.string().describe('The
|
|
55
|
-
}).describe('Create a new file or overwrite an existing file
|
|
53
|
+
path: z.string().describe('File path relative to the project root. Parent directories are created automatically.'),
|
|
54
|
+
content: z.string().describe('The complete file content. This replaces everything in the file — there is no merge or append.'),
|
|
55
|
+
}).describe('Create a new file or completely overwrite an existing file. WARNING: this replaces the entire file — use editFile instead for modifying existing files. Use writeFile only for creating new files or intentional full rewrites.'),
|
|
56
56
|
},
|
|
57
57
|
editFile: {
|
|
58
|
-
description: 'Make a surgical edit to a file by replacing an exact string match. The
|
|
58
|
+
description: 'Make a surgical edit to a file by replacing an exact string match. The preferred way to modify existing files — always use this over writeFile for changes to existing code.',
|
|
59
59
|
schema: z.object({
|
|
60
60
|
path: z.string().describe('File path relative to the project root'),
|
|
61
|
-
oldString: z.string().describe('The
|
|
62
|
-
newString: z.string().describe('The replacement text'),
|
|
63
|
-
replaceAll: z.boolean().optional().describe('Replace all occurrences instead of requiring uniqueness
|
|
64
|
-
}).describe('Make a surgical edit to a file by replacing an exact string match. The
|
|
61
|
+
oldString: z.string().describe('The EXACT text to find, copied verbatim from the file — including whitespace and indentation. Must appear exactly once in the file (unless replaceAll is true). If the match fails, read the file again and copy the exact text.'),
|
|
62
|
+
newString: z.string().describe('The replacement text. Preserve the same indentation style as the surrounding code.'),
|
|
63
|
+
replaceAll: z.boolean().optional().describe('Replace all occurrences instead of requiring uniqueness. Use for renaming a variable across a file. Default: false.'),
|
|
64
|
+
}).describe('Make a surgical edit to a file by replacing an exact string match. The preferred way to modify existing files — always use this over writeFile for changes to existing code.'),
|
|
65
65
|
},
|
|
66
66
|
listDirectory: {
|
|
67
67
|
description: 'List files and directories at a path. Returns arrays of file and directory names.',
|
|
@@ -73,23 +73,23 @@ export class FileTools extends Feature {
|
|
|
73
73
|
}).describe('List files and directories at a path. Returns arrays of file and directory names.'),
|
|
74
74
|
},
|
|
75
75
|
searchFiles: {
|
|
76
|
-
description: 'Search file contents for a pattern
|
|
76
|
+
description: 'Search inside file contents for a pattern (like grep/ripgrep). Returns matching lines with file path and line number. Use this to find where code is defined, where a function is called, or any text pattern across the codebase.',
|
|
77
77
|
schema: z.object({
|
|
78
|
-
pattern: z.string().describe('Search pattern
|
|
79
|
-
path: z.string().optional().describe('Directory to search in (defaults to project root)'),
|
|
80
|
-
include: z.string().optional().describe('Glob
|
|
81
|
-
exclude: z.string().optional().describe('Glob
|
|
82
|
-
ignoreCase: z.boolean().optional().describe('Case insensitive search'),
|
|
83
|
-
maxResults: z.number().optional().describe('Maximum
|
|
84
|
-
}).describe('Search file contents for a pattern
|
|
78
|
+
pattern: z.string().describe('Search pattern — supports regex. Examples: "function handleAuth", "TODO|FIXME", "import.*from.*react"'),
|
|
79
|
+
path: z.string().optional().describe('Directory to search in (defaults to project root). Narrow this to avoid searching node_modules or irrelevant directories.'),
|
|
80
|
+
include: z.string().optional().describe('Glob to filter which files to search (e.g. "*.ts", "*.tsx"). Use this to scope to specific file types.'),
|
|
81
|
+
exclude: z.string().optional().describe('Glob to exclude files (e.g. "node_modules", "dist"). node_modules is excluded by default.'),
|
|
82
|
+
ignoreCase: z.boolean().optional().describe('Case insensitive search. Default: false.'),
|
|
83
|
+
maxResults: z.number().optional().describe('Maximum results to return. Default: 50. Use a smaller number for broad patterns.'),
|
|
84
|
+
}).describe('Search inside file contents for a pattern (like grep/ripgrep). Returns matching lines with file path and line number. Use this to find where code is defined, where a function is called, or any text pattern across the codebase.'),
|
|
85
85
|
},
|
|
86
86
|
findFiles: {
|
|
87
|
-
description: 'Find files by name
|
|
87
|
+
description: 'Find files by name or glob pattern. Returns file paths, not contents. Use this to locate files ("where is the config?"), not to search inside them (use searchFiles for that).',
|
|
88
88
|
schema: z.object({
|
|
89
|
-
pattern: z.string().describe('Glob pattern to match
|
|
89
|
+
pattern: z.string().describe('Glob pattern to match file names. Examples: "**/*.test.ts", "src/**/*.tsx", "**/config.*"'),
|
|
90
90
|
path: z.string().optional().describe('Directory to search from (defaults to project root)'),
|
|
91
|
-
exclude: z.string().optional().describe('Glob pattern to exclude'),
|
|
92
|
-
}).describe('Find files by name
|
|
91
|
+
exclude: z.string().optional().describe('Glob pattern to exclude. node_modules and .git are excluded by default.'),
|
|
92
|
+
}).describe('Find files by name or glob pattern. Returns file paths, not contents. Use this to locate files ("where is the config?"), not to search inside them (use searchFiles for that).'),
|
|
93
93
|
},
|
|
94
94
|
fileInfo: {
|
|
95
95
|
description: 'Get information about a file or directory: whether it exists, its type (file/directory), size, and modification time.',
|
|
@@ -273,11 +273,18 @@ export class FileTools extends Feature {
|
|
|
273
273
|
if (typeof (consumer as any).addSystemPromptExtension === 'function') {
|
|
274
274
|
(consumer as any).addSystemPromptExtension('fileTools', [
|
|
275
275
|
'## File Tools',
|
|
276
|
-
'
|
|
277
|
-
'
|
|
278
|
-
'
|
|
279
|
-
'
|
|
280
|
-
'
|
|
276
|
+
'',
|
|
277
|
+
'All paths are relative to the project root unless they start with /.',
|
|
278
|
+
'',
|
|
279
|
+
'**Before modifying code:** Always read the file first. Use `searchFiles` to find where something is defined before making changes. Use `listDirectory` to verify paths exist before assuming.',
|
|
280
|
+
'',
|
|
281
|
+
'**Editing files:** Prefer `editFile` over `writeFile` for existing files — it makes surgical replacements. The `oldString` must appear exactly once in the file (unless using `replaceAll`). If your match isn\'t unique, include more surrounding context to make it unique. Never guess at file contents — read first, then edit.',
|
|
282
|
+
'',
|
|
283
|
+
'**Finding things:** Use `findFiles` to locate files by name or glob pattern (e.g. "**/*.test.ts"). Use `searchFiles` to find specific content inside files (grep). These are different tools for different questions: "where is the file?" vs "where is this code?".',
|
|
284
|
+
'',
|
|
285
|
+
'**Large files:** Use `readFile` with `offset` and `limit` to read portions. Don\'t load a 5000-line file when you only need lines 100-150.',
|
|
286
|
+
'',
|
|
287
|
+
'**`writeFile` is destructive** — it overwrites the entire file. Only use it for creating new files or when you intend a complete rewrite.',
|
|
281
288
|
].join('\n'))
|
|
282
289
|
}
|
|
283
290
|
}
|
|
@@ -76,19 +76,19 @@ export class SkillsLibrary extends Feature<SkillsLibraryState, SkillsLibraryOpti
|
|
|
76
76
|
static override tools: Record<string, { schema: z.ZodType; handler?: Function }> = {
|
|
77
77
|
searchAvailableSkills: {
|
|
78
78
|
schema: z.object({
|
|
79
|
-
query: z.string().optional().describe('
|
|
80
|
-
}).describe('
|
|
79
|
+
query: z.string().optional().describe('A keyword or phrase to filter skills by name or description. Omit to list all available skills.'),
|
|
80
|
+
}).describe('Discover what skills are available. Call this first when you need specialized knowledge — skills are curated guides and reference material for specific domains (frameworks, tools, patterns). Returns skill names and descriptions so you can decide which to load.'),
|
|
81
81
|
},
|
|
82
82
|
loadSkill: {
|
|
83
83
|
schema: z.object({
|
|
84
|
-
skillName: z.string().describe('The name
|
|
85
|
-
}).describe('Load a skill
|
|
84
|
+
skillName: z.string().describe('The exact skill name as returned by searchAvailableSkills'),
|
|
85
|
+
}).describe('Load a skill\'s full reference content (SKILL.md). This gives you detailed guidance, examples, and best practices for that domain. Load a skill before attempting work in an unfamiliar area — the content is curated to prevent common mistakes.'),
|
|
86
86
|
},
|
|
87
87
|
askSkillBasedQuestion: {
|
|
88
88
|
schema: z.object({
|
|
89
|
-
skillName: z.string().describe('The
|
|
90
|
-
question: z.string().describe('
|
|
91
|
-
}).describe('Ask a question about a
|
|
89
|
+
skillName: z.string().describe('The exact skill name to query'),
|
|
90
|
+
question: z.string().describe('A specific question about the skill\'s domain. Be precise — "how do I add a new feature to the container?" is better than "tell me about features".'),
|
|
91
|
+
}).describe('Ask a focused question about a skill\'s domain using AI-assisted document reading. Use this when you need a specific answer from a skill rather than reading the whole thing. More efficient than loadSkill for targeted lookups.'),
|
|
92
92
|
},
|
|
93
93
|
}
|
|
94
94
|
|
|
@@ -107,8 +107,23 @@ export class SkillsLibrary extends Feature<SkillsLibraryState, SkillsLibraryOpti
|
|
|
107
107
|
if (!(assistant instanceof Assistant)) {
|
|
108
108
|
throw new Error('Skills library tools require an Assistant instance (including subclasses).')
|
|
109
109
|
}
|
|
110
|
-
|
|
110
|
+
|
|
111
111
|
const a : Assistant = assistant as Assistant
|
|
112
|
+
|
|
113
|
+
a.addSystemPromptExtension('skillsLibrary', [
|
|
114
|
+
'## Skills Library',
|
|
115
|
+
'',
|
|
116
|
+
'You have access to a library of curated skills — domain-specific reference guides with examples, patterns, and best practices.',
|
|
117
|
+
'',
|
|
118
|
+
'**When to use skills:**',
|
|
119
|
+
'- When working in an unfamiliar domain or framework — load the skill before writing code',
|
|
120
|
+
'- When the user asks about a topic that might have a matching skill — search first',
|
|
121
|
+
'- When you see "Required Skills" in a message — load those skills immediately with `loadSkill` before answering',
|
|
122
|
+
'',
|
|
123
|
+
'**Workflow:** `searchAvailableSkills` → find relevant skill → `loadSkill` to get the full guide → follow its patterns. Use `askSkillBasedQuestion` for targeted lookups when you don\'t need the whole guide.',
|
|
124
|
+
'',
|
|
125
|
+
'**Skills are authoritative.** When a loaded skill contradicts your general knowledge, follow the skill — it reflects project-specific conventions and decisions.',
|
|
126
|
+
].join('\n'))
|
|
112
127
|
|
|
113
128
|
const { container } = a
|
|
114
129
|
|
|
@@ -451,10 +466,12 @@ Return only the skill names that are directly relevant. Return an empty array if
|
|
|
451
466
|
|
|
452
467
|
const fork = assistant.conversation.fork()
|
|
453
468
|
const result = await fork.ask(prompt, { schema: responseSchema }) as unknown as { skills: string[] }
|
|
454
|
-
|
|
455
|
-
console.log('Got a result', result)
|
|
456
469
|
|
|
457
|
-
|
|
470
|
+
const found = result.skills.filter(name => this.find(name) !== undefined)
|
|
471
|
+
|
|
472
|
+
this.emit('foundSkills', found, assistant, userQuery)
|
|
473
|
+
|
|
474
|
+
return found
|
|
458
475
|
}
|
|
459
476
|
}
|
|
460
477
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Auto-generated bootstrap content
|
|
2
|
-
// Generated at: 2026-04-
|
|
2
|
+
// Generated at: 2026-04-05T06:58:07.990Z
|
|
3
3
|
// Source: docs/bootstrap/*.md, docs/bootstrap/templates/*, docs/examples/*.md, docs/tutorials/*.md
|
|
4
4
|
//
|
|
5
5
|
// Do not edit manually. Run: luca build-bootstrap
|
package/src/cli/build-info.ts
CHANGED