all-hands-cli 0.1.6 → 0.1.7
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/.allhands/flows/COMPOUNDING.md +3 -3
- package/.allhands/flows/EMERGENT_PLANNING.md +1 -1
- package/.allhands/flows/INITIATIVE_STEERING.md +0 -1
- package/.allhands/flows/PROMPT_TASK_EXECUTION.md +0 -1
- package/.allhands/flows/SPEC_PLANNING.md +1 -2
- package/.allhands/flows/harness/WRITING_HARNESS_FLOWS.md +1 -1
- package/.allhands/flows/harness/WRITING_HARNESS_KNOWLEDGE.md +1 -1
- package/.allhands/flows/harness/WRITING_HARNESS_ORCHESTRATION.md +1 -1
- package/.allhands/flows/harness/WRITING_HARNESS_SKILLS.md +1 -1
- package/.allhands/flows/harness/WRITING_HARNESS_TOOLS.md +1 -1
- package/.allhands/flows/harness/WRITING_HARNESS_VALIDATION_TOOLING.md +1 -1
- package/.allhands/flows/shared/CODEBASE_UNDERSTANDING.md +2 -3
- package/.allhands/flows/shared/CREATE_VALIDATION_TOOLING_SPEC.md +1 -1
- package/.allhands/flows/shared/PLAN_DEEPENING.md +2 -3
- package/.allhands/flows/shared/PROMPT_TASKS_CURATION.md +3 -5
- package/.allhands/flows/shared/WRITING_HARNESS_FLOWS.md +1 -1
- package/.allhands/flows/shared/jury/BEST_PRACTICES_REVIEW.md +4 -4
- package/.allhands/harness/src/cli.ts +4 -0
- package/.allhands/harness/src/commands/knowledge.ts +8 -5
- package/.allhands/harness/src/commands/skills.ts +299 -16
- package/.allhands/harness/src/commands/solutions.ts +227 -111
- package/.allhands/harness/src/commands/spawn.ts +6 -13
- package/.allhands/harness/src/hooks/shared.ts +1 -0
- package/.allhands/harness/src/lib/opencode/index.ts +65 -0
- package/.allhands/harness/src/lib/opencode/prompts/skills-aggregator.md +77 -0
- package/.allhands/harness/src/lib/opencode/prompts/solutions-aggregator.md +97 -0
- package/.allhands/harness/src/lib/opencode/runner.ts +98 -5
- package/.allhands/settings.json +2 -1
- package/.allhands/skills/harness-maintenance/SKILL.md +1 -1
- package/.allhands/skills/harness-maintenance/references/harness-skills.md +1 -1
- package/.allhands/skills/harness-maintenance/references/knowledge-compounding.md +5 -10
- package/.allhands/skills/harness-maintenance/references/writing-flows.md +1 -1
- package/CLAUDE.md +1 -1
- package/docs/flows/compounding.md +2 -2
- package/docs/flows/plan-deepening-and-research.md +2 -2
- package/docs/flows/validation-and-skills-integration.md +14 -8
- package/docs/flows/wip/wip-flows.md +1 -1
- package/docs/harness/cli/search-commands.md +9 -19
- package/docs/memories.md +1 -1
- package/package.json +1 -1
- package/specs/workflow-domain-configuration.spec.md +2 -2
- package/.allhands/flows/shared/SKILL_EXTRACTION.md +0 -84
- package/.allhands/harness/src/commands/memories.ts +0 -302
|
@@ -3,19 +3,30 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Grep-based search for documented solutions in docs/solutions/.
|
|
5
5
|
* Uses frontmatter fields (tags, module, component, symptoms) for precise matching.
|
|
6
|
+
* Search includes AI aggregation with memory context from docs/memories.md.
|
|
6
7
|
*
|
|
7
8
|
* Usage:
|
|
8
|
-
* ah solutions search <query>
|
|
9
|
-
* ah solutions search <query> --
|
|
10
|
-
* ah solutions
|
|
11
|
-
* ah solutions list <category> List solutions in a category
|
|
9
|
+
* ah solutions search <query> Search solutions with AI aggregation + memory context
|
|
10
|
+
* ah solutions search <query> --no-aggregate Skip aggregation, return raw matches
|
|
11
|
+
* ah solutions search <query> --full Include full content of matches (no-aggregate mode)
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { Command } from 'commander';
|
|
15
15
|
import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
16
|
-
import {
|
|
16
|
+
import { dirname, join } from 'path';
|
|
17
|
+
import { fileURLToPath } from 'url';
|
|
17
18
|
import { parse } from 'yaml';
|
|
18
19
|
import { tracedAction } from '../lib/base-command.js';
|
|
20
|
+
import { AgentRunner, withDebugInfo, type SolutionSearchOutput } from '../lib/opencode/index.js';
|
|
21
|
+
|
|
22
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
23
|
+
const __dirname = dirname(__filename);
|
|
24
|
+
|
|
25
|
+
const AGGREGATOR_PROMPT_PATH = join(__dirname, '../lib/opencode/prompts/solutions-aggregator.md');
|
|
26
|
+
|
|
27
|
+
const getAggregatorPrompt = (): string => {
|
|
28
|
+
return readFileSync(AGGREGATOR_PROMPT_PATH, 'utf-8');
|
|
29
|
+
};
|
|
19
30
|
|
|
20
31
|
const getProjectRoot = (): string => {
|
|
21
32
|
return process.env.PROJECT_ROOT || process.cwd();
|
|
@@ -25,6 +36,10 @@ const getSolutionsDir = (): string => {
|
|
|
25
36
|
return join(getProjectRoot(), 'docs', 'solutions');
|
|
26
37
|
};
|
|
27
38
|
|
|
39
|
+
const getMemoriesPath = (): string => {
|
|
40
|
+
return join(getProjectRoot(), 'docs', 'memories.md');
|
|
41
|
+
};
|
|
42
|
+
|
|
28
43
|
interface SolutionFrontmatter {
|
|
29
44
|
title: string;
|
|
30
45
|
date: string;
|
|
@@ -45,6 +60,13 @@ interface SolutionMatch {
|
|
|
45
60
|
matchedFields: string[];
|
|
46
61
|
}
|
|
47
62
|
|
|
63
|
+
interface MemoryEntry {
|
|
64
|
+
name: string;
|
|
65
|
+
domain: string;
|
|
66
|
+
source: string;
|
|
67
|
+
description: string;
|
|
68
|
+
}
|
|
69
|
+
|
|
48
70
|
/**
|
|
49
71
|
* Extract keywords from a search query.
|
|
50
72
|
* Handles quoted phrases and splits on whitespace.
|
|
@@ -155,20 +177,6 @@ function findSolutionFiles(dir: string): string[] {
|
|
|
155
177
|
return files;
|
|
156
178
|
}
|
|
157
179
|
|
|
158
|
-
/**
|
|
159
|
-
* Get all category directories.
|
|
160
|
-
*/
|
|
161
|
-
function getCategories(dir: string): string[] {
|
|
162
|
-
if (!existsSync(dir)) return [];
|
|
163
|
-
|
|
164
|
-
return readdirSync(dir)
|
|
165
|
-
.filter(entry => {
|
|
166
|
-
const fullPath = join(dir, entry);
|
|
167
|
-
return statSync(fullPath).isDirectory();
|
|
168
|
-
})
|
|
169
|
-
.sort();
|
|
170
|
-
}
|
|
171
|
-
|
|
172
180
|
/**
|
|
173
181
|
* Search for solutions matching the query.
|
|
174
182
|
*/
|
|
@@ -221,22 +229,107 @@ function getSolutionContent(filePath: string): string | null {
|
|
|
221
229
|
}
|
|
222
230
|
}
|
|
223
231
|
|
|
232
|
+
/**
|
|
233
|
+
* Parse all memory entries from docs/memories.md.
|
|
234
|
+
*/
|
|
235
|
+
function parseMemories(): MemoryEntry[] {
|
|
236
|
+
const memoriesPath = getMemoriesPath();
|
|
237
|
+
if (!existsSync(memoriesPath)) return [];
|
|
238
|
+
|
|
239
|
+
const content = readFileSync(memoriesPath, 'utf-8');
|
|
240
|
+
const lines = content.split('\n');
|
|
241
|
+
const entries: MemoryEntry[] = [];
|
|
242
|
+
|
|
243
|
+
for (const line of lines) {
|
|
244
|
+
const trimmed = line.trim();
|
|
245
|
+
|
|
246
|
+
// Skip non-table lines, header rows, and separator rows
|
|
247
|
+
if (!trimmed.startsWith('|') || trimmed.includes('---') || /\|\s*Name\s*\|/i.test(trimmed)) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Parse table row
|
|
252
|
+
const cells = trimmed.split('|').map(c => c.trim()).filter(c => c.length > 0);
|
|
253
|
+
if (cells.length >= 4) {
|
|
254
|
+
entries.push({
|
|
255
|
+
name: cells[0],
|
|
256
|
+
domain: cells[1],
|
|
257
|
+
source: cells[2],
|
|
258
|
+
description: cells[3],
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return entries;
|
|
264
|
+
}
|
|
265
|
+
|
|
224
266
|
export function register(program: Command): void {
|
|
225
267
|
const solutionsCmd = program
|
|
226
268
|
.command('solutions')
|
|
227
|
-
.description('Search and browse documented solutions');
|
|
269
|
+
.description('Search and browse documented solutions (includes memory context)');
|
|
228
270
|
|
|
229
271
|
// Search command
|
|
230
272
|
solutionsCmd
|
|
231
273
|
.command('search <query>')
|
|
232
|
-
.description('Search solutions by keywords
|
|
233
|
-
.option('--full', 'Include full content of matched solutions')
|
|
274
|
+
.description('Search solutions by keywords with AI aggregation and memory context')
|
|
275
|
+
.option('--full', 'Include full content of matched solutions (non-aggregated mode)')
|
|
234
276
|
.option('--limit <n>', 'Maximum number of results', '10')
|
|
235
|
-
.
|
|
236
|
-
|
|
277
|
+
.option('--no-aggregate', 'Skip aggregation, return raw matches')
|
|
278
|
+
.option('--debug', 'Include agent debug metadata (model, timing, fallback) in output')
|
|
279
|
+
.action(tracedAction('solutions search', async (query: string, _opts: Record<string, unknown>, command: Command) => {
|
|
280
|
+
const opts = command.opts();
|
|
281
|
+
const limit = parseInt(opts.limit as string || '10', 10);
|
|
282
|
+
const noAggregate = opts.aggregate === false;
|
|
283
|
+
const debug = !!opts.debug;
|
|
284
|
+
const full = !!opts.full;
|
|
285
|
+
|
|
237
286
|
const matches = searchSolutions(query, limit);
|
|
238
287
|
|
|
239
288
|
if (matches.length === 0) {
|
|
289
|
+
// Still check memories even when no solution matches
|
|
290
|
+
if (!noAggregate) {
|
|
291
|
+
const memories = parseMemories();
|
|
292
|
+
if (memories.length > 0) {
|
|
293
|
+
// Build aggregation input with only memories
|
|
294
|
+
const userMessage = JSON.stringify({
|
|
295
|
+
query,
|
|
296
|
+
solutions: [],
|
|
297
|
+
memories,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
const projectRoot = getProjectRoot();
|
|
301
|
+
const runner = new AgentRunner(projectRoot);
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
const agentResult = await runner.run<SolutionSearchOutput>(
|
|
305
|
+
{
|
|
306
|
+
name: 'solutions-aggregator',
|
|
307
|
+
systemPrompt: getAggregatorPrompt(),
|
|
308
|
+
timeoutMs: 60000,
|
|
309
|
+
steps: 5,
|
|
310
|
+
},
|
|
311
|
+
userMessage,
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
if (agentResult.success && agentResult.data) {
|
|
315
|
+
console.log(JSON.stringify(withDebugInfo({
|
|
316
|
+
success: true,
|
|
317
|
+
query,
|
|
318
|
+
aggregated: true,
|
|
319
|
+
guidance: agentResult.data.guidance,
|
|
320
|
+
relevant_solutions: agentResult.data.relevant_solutions,
|
|
321
|
+
memory_insights: agentResult.data.memory_insights,
|
|
322
|
+
design_notes: agentResult.data.design_notes,
|
|
323
|
+
source_matches: 0,
|
|
324
|
+
}, agentResult, debug), null, 2));
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
} catch {
|
|
328
|
+
// Fall through to no-results output
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
240
333
|
console.log(JSON.stringify({
|
|
241
334
|
success: true,
|
|
242
335
|
query,
|
|
@@ -247,107 +340,130 @@ export function register(program: Command): void {
|
|
|
247
340
|
return;
|
|
248
341
|
}
|
|
249
342
|
|
|
250
|
-
//
|
|
251
|
-
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
343
|
+
// Return raw matches if aggregation disabled
|
|
344
|
+
if (noAggregate) {
|
|
345
|
+
const results = matches.map(match => {
|
|
346
|
+
const result: Record<string, unknown> = {
|
|
347
|
+
path: match.path,
|
|
348
|
+
title: match.frontmatter.title,
|
|
349
|
+
score: match.score,
|
|
350
|
+
matched_fields: match.matchedFields,
|
|
351
|
+
severity: match.frontmatter.severity,
|
|
352
|
+
problem_type: match.frontmatter.problem_type,
|
|
353
|
+
component: match.frontmatter.component,
|
|
354
|
+
tags: match.frontmatter.tags,
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
if (full) {
|
|
358
|
+
result.content = getSolutionContent(match.path);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
return result;
|
|
362
|
+
});
|
|
262
363
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
364
|
+
console.log(JSON.stringify({
|
|
365
|
+
success: true,
|
|
366
|
+
query,
|
|
367
|
+
keywords: extractKeywords(query),
|
|
368
|
+
result_count: results.length,
|
|
369
|
+
results,
|
|
370
|
+
aggregated: false,
|
|
371
|
+
}, null, 2));
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
266
374
|
|
|
267
|
-
|
|
375
|
+
// Build aggregation input
|
|
376
|
+
const solutionsInput = matches.map(m => {
|
|
377
|
+
const content = getSolutionContent(m.path);
|
|
378
|
+
return {
|
|
379
|
+
title: m.frontmatter.title,
|
|
380
|
+
path: m.path,
|
|
381
|
+
severity: m.frontmatter.severity,
|
|
382
|
+
problem_type: m.frontmatter.problem_type,
|
|
383
|
+
component: m.frontmatter.component,
|
|
384
|
+
tags: m.frontmatter.tags,
|
|
385
|
+
content: content || '',
|
|
386
|
+
};
|
|
268
387
|
});
|
|
269
388
|
|
|
270
|
-
|
|
271
|
-
success: true,
|
|
272
|
-
query,
|
|
273
|
-
keywords: extractKeywords(query),
|
|
274
|
-
result_count: results.length,
|
|
275
|
-
results,
|
|
276
|
-
}, null, 2));
|
|
277
|
-
}));
|
|
278
|
-
|
|
279
|
-
// List command
|
|
280
|
-
solutionsCmd
|
|
281
|
-
.command('list [category]')
|
|
282
|
-
.description('List solution categories or solutions in a category')
|
|
283
|
-
.action(tracedAction('solutions list', async (category?: string) => {
|
|
284
|
-
const solutionsDir = getSolutionsDir();
|
|
389
|
+
const memories = parseMemories();
|
|
285
390
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
391
|
+
const userMessage = JSON.stringify({
|
|
392
|
+
query,
|
|
393
|
+
solutions: solutionsInput,
|
|
394
|
+
memories,
|
|
395
|
+
});
|
|
289
396
|
|
|
290
|
-
|
|
291
|
-
|
|
397
|
+
const projectRoot = getProjectRoot();
|
|
398
|
+
const runner = new AgentRunner(projectRoot);
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
const agentResult = await runner.run<SolutionSearchOutput>(
|
|
402
|
+
{
|
|
403
|
+
name: 'solutions-aggregator',
|
|
404
|
+
systemPrompt: getAggregatorPrompt(),
|
|
405
|
+
timeoutMs: 60000,
|
|
406
|
+
steps: 5,
|
|
407
|
+
},
|
|
408
|
+
userMessage,
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
if (!agentResult.success) {
|
|
412
|
+
// Graceful degradation: return raw matches on aggregation failure
|
|
413
|
+
const results = matches.map(match => ({
|
|
414
|
+
path: match.path,
|
|
415
|
+
title: match.frontmatter.title,
|
|
416
|
+
score: match.score,
|
|
417
|
+
matched_fields: match.matchedFields,
|
|
418
|
+
severity: match.frontmatter.severity,
|
|
419
|
+
problem_type: match.frontmatter.problem_type,
|
|
420
|
+
component: match.frontmatter.component,
|
|
421
|
+
tags: match.frontmatter.tags,
|
|
422
|
+
}));
|
|
423
|
+
|
|
424
|
+
console.log(JSON.stringify(withDebugInfo({
|
|
292
425
|
success: true,
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
426
|
+
query,
|
|
427
|
+
results,
|
|
428
|
+
count: results.length,
|
|
429
|
+
aggregated: false,
|
|
430
|
+
aggregation_error: agentResult.error,
|
|
431
|
+
}, agentResult, debug), null, 2));
|
|
296
432
|
return;
|
|
297
433
|
}
|
|
298
434
|
|
|
299
|
-
|
|
300
|
-
const categoryCounts = categories.map(cat => {
|
|
301
|
-
const catDir = join(solutionsDir, cat);
|
|
302
|
-
const files = findSolutionFiles(catDir);
|
|
303
|
-
return { category: cat, count: files.length };
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
console.log(JSON.stringify({
|
|
435
|
+
console.log(JSON.stringify(withDebugInfo({
|
|
307
436
|
success: true,
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
437
|
+
query,
|
|
438
|
+
aggregated: true,
|
|
439
|
+
guidance: agentResult.data!.guidance,
|
|
440
|
+
relevant_solutions: agentResult.data!.relevant_solutions,
|
|
441
|
+
memory_insights: agentResult.data!.memory_insights,
|
|
442
|
+
design_notes: agentResult.data!.design_notes,
|
|
443
|
+
source_matches: matches.length,
|
|
444
|
+
}, agentResult, debug), null, 2));
|
|
445
|
+
} catch (error) {
|
|
446
|
+
// Graceful degradation on any error
|
|
447
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
448
|
+
const results = matches.map(match => ({
|
|
449
|
+
path: match.path,
|
|
450
|
+
title: match.frontmatter.title,
|
|
451
|
+
score: match.score,
|
|
452
|
+
matched_fields: match.matchedFields,
|
|
453
|
+
severity: match.frontmatter.severity,
|
|
454
|
+
problem_type: match.frontmatter.problem_type,
|
|
455
|
+
component: match.frontmatter.component,
|
|
456
|
+
tags: match.frontmatter.tags,
|
|
457
|
+
}));
|
|
315
458
|
|
|
316
|
-
if (!existsSync(categoryDir)) {
|
|
317
|
-
const available = getCategories(solutionsDir);
|
|
318
459
|
console.log(JSON.stringify({
|
|
319
|
-
success:
|
|
320
|
-
|
|
321
|
-
|
|
460
|
+
success: true,
|
|
461
|
+
query,
|
|
462
|
+
results,
|
|
463
|
+
count: results.length,
|
|
464
|
+
aggregated: false,
|
|
465
|
+
aggregation_error: message,
|
|
322
466
|
}, null, 2));
|
|
323
|
-
process.exit(1);
|
|
324
467
|
}
|
|
325
|
-
|
|
326
|
-
const files = findSolutionFiles(categoryDir);
|
|
327
|
-
const solutions = files.map(file => {
|
|
328
|
-
const fm = parseFrontmatter(file);
|
|
329
|
-
const relativePath = file.replace(getProjectRoot() + '/', '');
|
|
330
|
-
return {
|
|
331
|
-
path: relativePath,
|
|
332
|
-
title: fm?.title || basename(file, '.md'),
|
|
333
|
-
date: fm?.date,
|
|
334
|
-
severity: fm?.severity,
|
|
335
|
-
tags: fm?.tags || [],
|
|
336
|
-
};
|
|
337
|
-
});
|
|
338
|
-
|
|
339
|
-
// Sort by date descending
|
|
340
|
-
solutions.sort((a, b) => {
|
|
341
|
-
if (!a.date) return 1;
|
|
342
|
-
if (!b.date) return -1;
|
|
343
|
-
return b.date.localeCompare(a.date);
|
|
344
|
-
});
|
|
345
|
-
|
|
346
|
-
console.log(JSON.stringify({
|
|
347
|
-
success: true,
|
|
348
|
-
category,
|
|
349
|
-
solution_count: solutions.length,
|
|
350
|
-
solutions,
|
|
351
|
-
}, null, 2));
|
|
352
468
|
}));
|
|
353
469
|
}
|
|
@@ -9,7 +9,7 @@ import { Command } from "commander";
|
|
|
9
9
|
import { readFileSync } from "fs";
|
|
10
10
|
import { dirname, join } from "path";
|
|
11
11
|
import { fileURLToPath } from "url";
|
|
12
|
-
import { AgentRunner } from "../lib/opencode/index.js";
|
|
12
|
+
import { AgentRunner, withDebugInfo } from "../lib/opencode/index.js";
|
|
13
13
|
import { BaseCommand, type CommandResult } from "../lib/base-command.js";
|
|
14
14
|
import { loadProjectSettings } from "../hooks/shared.js";
|
|
15
15
|
|
|
@@ -60,11 +60,13 @@ class CodesearchCommand extends BaseCommand {
|
|
|
60
60
|
cmd
|
|
61
61
|
.argument("<query>", "Code search query (natural language or pattern)")
|
|
62
62
|
.option("--budget <n>", "Soft tool budget hint for the agent", String(DEFAULT_TOOL_BUDGET))
|
|
63
|
-
.option("--steps <n>", "Hard step limit for agent iterations", String(DEFAULT_STEPS_LIMIT))
|
|
63
|
+
.option("--steps <n>", "Hard step limit for agent iterations", String(DEFAULT_STEPS_LIMIT))
|
|
64
|
+
.option("--debug", "Include agent debug metadata (model, timing, fallback) in output");
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
async execute(args: Record<string, unknown>): Promise<CommandResult> {
|
|
67
68
|
const query = args.query as string;
|
|
69
|
+
const debug = !!args.debug;
|
|
68
70
|
const settings = loadProjectSettings();
|
|
69
71
|
const toolBudget = parseInt(
|
|
70
72
|
(args.budget as string) ??
|
|
@@ -98,13 +100,6 @@ Respond with JSON matching the required schema.`;
|
|
|
98
100
|
systemPrompt: getCodesearchPrompt(),
|
|
99
101
|
timeoutMs: DEFAULT_TIMEOUT_MS,
|
|
100
102
|
steps: stepsLimit,
|
|
101
|
-
// MCP servers can be configured via environment or passed explicitly
|
|
102
|
-
// mcp: {
|
|
103
|
-
// "ast-grep": {
|
|
104
|
-
// type: "local",
|
|
105
|
-
// command: ["uvx", "--from", "ast-grep-mcp", "ast-grep-mcp"],
|
|
106
|
-
// },
|
|
107
|
-
// },
|
|
108
103
|
},
|
|
109
104
|
userMessage
|
|
110
105
|
);
|
|
@@ -115,16 +110,14 @@ Respond with JSON matching the required schema.`;
|
|
|
115
110
|
|
|
116
111
|
const data = result.data!;
|
|
117
112
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
return this.success({
|
|
113
|
+
return this.success(withDebugInfo({
|
|
121
114
|
query,
|
|
122
115
|
result_count: data.results.length,
|
|
123
116
|
results: data.results,
|
|
124
117
|
warnings: data.warnings,
|
|
125
118
|
dev_notes: data.dev_notes,
|
|
126
119
|
metadata: result.metadata,
|
|
127
|
-
});
|
|
120
|
+
}, result, debug));
|
|
128
121
|
} catch (error) {
|
|
129
122
|
const message = error instanceof Error ? error.message : String(error);
|
|
130
123
|
return this.error("spawn_error", message);
|
|
@@ -36,6 +36,8 @@ export interface AgentResult<T = unknown> {
|
|
|
36
36
|
model: string;
|
|
37
37
|
tokens_used?: number;
|
|
38
38
|
duration_ms: number;
|
|
39
|
+
fallback?: boolean;
|
|
40
|
+
primary_error?: string;
|
|
39
41
|
};
|
|
40
42
|
}
|
|
41
43
|
|
|
@@ -56,6 +58,38 @@ export interface AggregatorInput {
|
|
|
56
58
|
minimized_results: SearchResult[];
|
|
57
59
|
}
|
|
58
60
|
|
|
61
|
+
// Skills aggregator output
|
|
62
|
+
export interface SkillSearchOutput {
|
|
63
|
+
guidance: string;
|
|
64
|
+
relevant_skills: Array<{
|
|
65
|
+
name: string;
|
|
66
|
+
file: string;
|
|
67
|
+
relevance: string;
|
|
68
|
+
key_excerpts: string[];
|
|
69
|
+
references: string[];
|
|
70
|
+
}>;
|
|
71
|
+
design_notes?: string[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Solutions aggregator output
|
|
75
|
+
export interface SolutionSearchOutput {
|
|
76
|
+
guidance: string;
|
|
77
|
+
relevant_solutions: Array<{
|
|
78
|
+
title: string;
|
|
79
|
+
file: string;
|
|
80
|
+
relevance: string;
|
|
81
|
+
key_excerpts: string[];
|
|
82
|
+
related_memories: string[];
|
|
83
|
+
}>;
|
|
84
|
+
memory_insights: Array<{
|
|
85
|
+
name: string;
|
|
86
|
+
domain: string;
|
|
87
|
+
source: string;
|
|
88
|
+
relevance: string;
|
|
89
|
+
}>;
|
|
90
|
+
design_notes?: string[];
|
|
91
|
+
}
|
|
92
|
+
|
|
59
93
|
// Knowledge aggregator output
|
|
60
94
|
export interface AggregatorOutput {
|
|
61
95
|
insight: string;
|
|
@@ -68,3 +102,34 @@ export interface AggregatorOutput {
|
|
|
68
102
|
}
|
|
69
103
|
|
|
70
104
|
export { AgentRunner } from "./runner.js";
|
|
105
|
+
|
|
106
|
+
// Debug metadata for agent results (included when --debug flag is passed)
|
|
107
|
+
export interface AgentDebugInfo {
|
|
108
|
+
model_used: string;
|
|
109
|
+
time_taken_ms: number;
|
|
110
|
+
fallback_used: boolean;
|
|
111
|
+
primary_error?: string;
|
|
112
|
+
tokens_used?: number;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Conditionally enrich a payload with agent debug metadata.
|
|
117
|
+
* Use with --debug flag on commands that run opencode agents.
|
|
118
|
+
*/
|
|
119
|
+
export function withDebugInfo<T extends Record<string, unknown>>(
|
|
120
|
+
payload: T,
|
|
121
|
+
result: AgentResult,
|
|
122
|
+
debug: boolean,
|
|
123
|
+
): T & { _debug?: AgentDebugInfo } {
|
|
124
|
+
if (!debug || !result.metadata) return payload;
|
|
125
|
+
return {
|
|
126
|
+
...payload,
|
|
127
|
+
_debug: {
|
|
128
|
+
model_used: result.metadata.model,
|
|
129
|
+
time_taken_ms: result.metadata.duration_ms,
|
|
130
|
+
fallback_used: result.metadata.fallback ?? false,
|
|
131
|
+
primary_error: result.metadata.primary_error,
|
|
132
|
+
tokens_used: result.metadata.tokens_used,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Skills Aggregator
|
|
2
|
+
|
|
3
|
+
You synthesize domain expertise from skill files into task-relevant guidance. The caller needs actionable knowledge for their implementation task - not a skill catalog.
|
|
4
|
+
|
|
5
|
+
## Core Principle
|
|
6
|
+
|
|
7
|
+
Extract what matters for the task. Every piece of guidance must be grounded in skill content:
|
|
8
|
+
- BAD: "Follow best practices for component design..."
|
|
9
|
+
- GOOD: "The building-expo-ui skill specifies using `<Link.Preview>` for context menus and `contentInsetAdjustmentBehavior="automatic"` over SafeAreaView - see `.allhands/skills/building-expo-ui/SKILL.md`"
|
|
10
|
+
|
|
11
|
+
## Input Format
|
|
12
|
+
|
|
13
|
+
You receive JSON with:
|
|
14
|
+
1. `query`: The user's task description or question
|
|
15
|
+
2. `skills`: Array of matched skills, each containing:
|
|
16
|
+
- `name`: Skill identifier
|
|
17
|
+
- `description`: What the skill covers
|
|
18
|
+
- `globs`: File patterns this skill applies to
|
|
19
|
+
- `file`: Path to SKILL.md
|
|
20
|
+
- `content`: Full SKILL.md body (without frontmatter)
|
|
21
|
+
- `reference_files`: Paths to reference docs within the skill directory
|
|
22
|
+
|
|
23
|
+
## Expansion Protocol
|
|
24
|
+
|
|
25
|
+
Need content from a reference file? Output:
|
|
26
|
+
```
|
|
27
|
+
EXPAND: <reference_file_path>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
You'll receive the content. Max 3 expansions. Only expand if the reference file path suggests direct relevance to the query.
|
|
31
|
+
|
|
32
|
+
## Output Format
|
|
33
|
+
|
|
34
|
+
Return ONLY valid JSON:
|
|
35
|
+
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"guidance": "Task-relevant synthesis: what patterns to follow, what to avoid, key conventions. Ground every statement in skill content. 3-6 sentences max.",
|
|
39
|
+
"relevant_skills": [
|
|
40
|
+
{
|
|
41
|
+
"name": "skill-name",
|
|
42
|
+
"file": ".allhands/skills/skill-name/SKILL.md",
|
|
43
|
+
"relevance": "Why this skill matters for the query",
|
|
44
|
+
"key_excerpts": ["Specific actionable instructions extracted from the skill"],
|
|
45
|
+
"references": [".allhands/skills/skill-name/references/relevant-doc.md"]
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"design_notes": ["Architectural decisions or constraints from skills that affect the task"]
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Field Guidelines
|
|
53
|
+
|
|
54
|
+
**guidance**:
|
|
55
|
+
- Synthesize across all matched skills into coherent task guidance
|
|
56
|
+
- Include specific patterns, conventions, and constraints
|
|
57
|
+
- Mention file paths and skill names for attribution
|
|
58
|
+
- If skills encode best practices, state them as "best practice: X"
|
|
59
|
+
|
|
60
|
+
**relevant_skills** (ranked by relevance to query):
|
|
61
|
+
- `name`: Skill identifier
|
|
62
|
+
- `file`: Path to SKILL.md
|
|
63
|
+
- `relevance`: One sentence - why does this skill matter for the task?
|
|
64
|
+
- `key_excerpts`: 1-4 specific, actionable instructions extracted verbatim or closely paraphrased from the skill
|
|
65
|
+
- `references`: Paths that contain deeper information for the caller. May include the SKILL.md itself (from `file`) alongside reference docs (from `reference_files`). Only include paths that are relevant to the query.
|
|
66
|
+
|
|
67
|
+
**design_notes** (optional, max 3):
|
|
68
|
+
- Only include if skills explicitly discuss design rationale or constraints
|
|
69
|
+
- Format: "[Constraint]: [Detail]" e.g. "First principles required: MUST cite first principles by name when adding features"
|
|
70
|
+
|
|
71
|
+
## Anti-patterns
|
|
72
|
+
|
|
73
|
+
- Generic advice not grounded in skill content
|
|
74
|
+
- Copying entire skill files instead of extracting task-relevant parts
|
|
75
|
+
- Including skills that aren't relevant to the query
|
|
76
|
+
- Restating the query as guidance
|
|
77
|
+
- Listing every reference file (only include relevant ones)
|