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.
Files changed (43) hide show
  1. package/.allhands/flows/COMPOUNDING.md +3 -3
  2. package/.allhands/flows/EMERGENT_PLANNING.md +1 -1
  3. package/.allhands/flows/INITIATIVE_STEERING.md +0 -1
  4. package/.allhands/flows/PROMPT_TASK_EXECUTION.md +0 -1
  5. package/.allhands/flows/SPEC_PLANNING.md +1 -2
  6. package/.allhands/flows/harness/WRITING_HARNESS_FLOWS.md +1 -1
  7. package/.allhands/flows/harness/WRITING_HARNESS_KNOWLEDGE.md +1 -1
  8. package/.allhands/flows/harness/WRITING_HARNESS_ORCHESTRATION.md +1 -1
  9. package/.allhands/flows/harness/WRITING_HARNESS_SKILLS.md +1 -1
  10. package/.allhands/flows/harness/WRITING_HARNESS_TOOLS.md +1 -1
  11. package/.allhands/flows/harness/WRITING_HARNESS_VALIDATION_TOOLING.md +1 -1
  12. package/.allhands/flows/shared/CODEBASE_UNDERSTANDING.md +2 -3
  13. package/.allhands/flows/shared/CREATE_VALIDATION_TOOLING_SPEC.md +1 -1
  14. package/.allhands/flows/shared/PLAN_DEEPENING.md +2 -3
  15. package/.allhands/flows/shared/PROMPT_TASKS_CURATION.md +3 -5
  16. package/.allhands/flows/shared/WRITING_HARNESS_FLOWS.md +1 -1
  17. package/.allhands/flows/shared/jury/BEST_PRACTICES_REVIEW.md +4 -4
  18. package/.allhands/harness/src/cli.ts +4 -0
  19. package/.allhands/harness/src/commands/knowledge.ts +8 -5
  20. package/.allhands/harness/src/commands/skills.ts +299 -16
  21. package/.allhands/harness/src/commands/solutions.ts +227 -111
  22. package/.allhands/harness/src/commands/spawn.ts +6 -13
  23. package/.allhands/harness/src/hooks/shared.ts +1 -0
  24. package/.allhands/harness/src/lib/opencode/index.ts +65 -0
  25. package/.allhands/harness/src/lib/opencode/prompts/skills-aggregator.md +77 -0
  26. package/.allhands/harness/src/lib/opencode/prompts/solutions-aggregator.md +97 -0
  27. package/.allhands/harness/src/lib/opencode/runner.ts +98 -5
  28. package/.allhands/settings.json +2 -1
  29. package/.allhands/skills/harness-maintenance/SKILL.md +1 -1
  30. package/.allhands/skills/harness-maintenance/references/harness-skills.md +1 -1
  31. package/.allhands/skills/harness-maintenance/references/knowledge-compounding.md +5 -10
  32. package/.allhands/skills/harness-maintenance/references/writing-flows.md +1 -1
  33. package/CLAUDE.md +1 -1
  34. package/docs/flows/compounding.md +2 -2
  35. package/docs/flows/plan-deepening-and-research.md +2 -2
  36. package/docs/flows/validation-and-skills-integration.md +14 -8
  37. package/docs/flows/wip/wip-flows.md +1 -1
  38. package/docs/harness/cli/search-commands.md +9 -19
  39. package/docs/memories.md +1 -1
  40. package/package.json +1 -1
  41. package/specs/workflow-domain-configuration.spec.md +2 -2
  42. package/.allhands/flows/shared/SKILL_EXTRACTION.md +0 -84
  43. 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> Search solutions by keywords
9
- * ah solutions search <query> --full Include full content of matches
10
- * ah solutions list List all solution categories
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 { basename, join } from 'path';
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 (searches title, tags, component, symptoms)')
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
- .action(tracedAction('solutions search', async (query: string, options: { full?: boolean; limit?: string }) => {
236
- const limit = parseInt(options.limit || '10', 10);
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
- // Format output
251
- const results = matches.map(match => {
252
- const result: Record<string, unknown> = {
253
- path: match.path,
254
- title: match.frontmatter.title,
255
- score: match.score,
256
- matched_fields: match.matchedFields,
257
- severity: match.frontmatter.severity,
258
- problem_type: match.frontmatter.problem_type,
259
- component: match.frontmatter.component,
260
- tags: match.frontmatter.tags,
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
- if (options.full) {
264
- result.content = getSolutionContent(match.path);
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
- return result;
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
- console.log(JSON.stringify({
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
- if (!category) {
287
- // List all categories
288
- const categories = getCategories(solutionsDir);
391
+ const userMessage = JSON.stringify({
392
+ query,
393
+ solutions: solutionsInput,
394
+ memories,
395
+ });
289
396
 
290
- if (categories.length === 0) {
291
- console.log(JSON.stringify({
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
- message: 'No solution categories found. Create docs/solutions/<category>/ directories.',
294
- categories: [],
295
- }, null, 2));
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
- // Count solutions in each category
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
- categories: categoryCounts,
309
- }, null, 2));
310
- return;
311
- }
312
-
313
- // List solutions in specific category
314
- const categoryDir = join(solutionsDir, category);
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: false,
320
- error: `Category not found: ${category}`,
321
- available_categories: available,
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
- // Warnings are included in the response data
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);
@@ -538,6 +538,7 @@ export interface OracleSettings {
538
538
  /** OpenCode SDK agent execution settings */
539
539
  export interface OpencodeSdkSettings {
540
540
  model?: string;
541
+ fallbackModel?: string;
541
542
  codesearchToolBudget?: number;
542
543
  }
543
544
 
@@ -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)