all-hands-cli 0.1.6 → 0.1.8

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 (46) 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/bin/sync-cli.js +18 -1
  35. package/docs/flows/compounding.md +2 -2
  36. package/docs/flows/plan-deepening-and-research.md +2 -2
  37. package/docs/flows/validation-and-skills-integration.md +14 -8
  38. package/docs/flows/wip/wip-flows.md +1 -1
  39. package/docs/harness/cli/search-commands.md +9 -19
  40. package/docs/memories.md +1 -1
  41. package/package.json +1 -1
  42. package/specs/workflow-domain-configuration.spec.md +2 -2
  43. package/src/commands/push.ts +21 -2
  44. package/src/lib/git.ts +19 -0
  45. package/.allhands/flows/shared/SKILL_EXTRACTION.md +0 -84
  46. package/.allhands/harness/src/commands/memories.ts +0 -302
@@ -1,84 +0,0 @@
1
- <goal>
2
- Find and extract domain expertise from skills to embed in prompt instructions. Per **Knowledge Compounding**, skills are "how to do it right" - expertise that compounds across prompts.
3
- </goal>
4
-
5
- <inputs>
6
- - Files/domains involved in the implementation task
7
- - Nature of the changes (UI, native code, deployment, etc.)
8
- </inputs>
9
-
10
- <outputs>
11
- - Extracted knowledge distilled for prompt embedding
12
- - Sources consulted (skill file paths)
13
- </outputs>
14
-
15
- <constraints>
16
- - MUST run `ah skills list` to discover available skills
17
- - MUST match skills via both glob patterns AND description inference
18
- - MUST extract task-relevant knowledge, not copy entire skill files
19
- - MUST list sources consulted in output
20
- </constraints>
21
-
22
- ## Step 1: Discover Available Skills
23
-
24
- - Run `ah skills list`
25
- - Returns JSON with: `name`, `description`, `globs`, `file` path
26
-
27
- ## Step 2: Identify Relevant Skills
28
-
29
- Match skills using two approaches:
30
-
31
- **Glob pattern matching** (programmatic):
32
- - Compare files you're touching against each skill's `globs`
33
- - Skills with matching patterns are likely relevant
34
-
35
- **Description inference** (semantic):
36
- - Read skill descriptions
37
- - Match against task nature (UI, deployment, native modules, etc.)
38
-
39
- Select all skills that apply to implementation scope.
40
-
41
- ## Step 3: Read Skill Documentation
42
-
43
- For each relevant skill, read the full file:
44
- - Read `.allhands/skills/<skill-name>/SKILL.md`
45
-
46
- Extract:
47
- - **Key patterns**: Code patterns, library preferences, common pitfalls
48
- - **Best practices**: Guidelines specific to this domain
49
- - **References**: Sub-documents within the skill folder
50
-
51
- ## Step 4: Extract Knowledge for Prompt
52
-
53
- Synthesize skill content into actionable prompt guidance:
54
- - Distill key instructions
55
- - Include specific examples where relevant
56
- - Reference sources
57
- - Avoid duplication - extract what's task-relevant
58
-
59
- ## Step 5: Output with Sources
60
-
61
- Provide:
62
-
63
- ```
64
- ## Skill-Derived Guidance
65
-
66
- ### From building-expo-ui:
67
- - Use `<Link.Preview>` for context menus
68
- - Prefer `contentInsetAdjustmentBehavior="automatic"` over SafeAreaView
69
-
70
- ### From react-native-best-practices:
71
- - Profile with React DevTools before optimizing
72
- - Use FlashList for lists with >50 items
73
-
74
- ## Sources Consulted
75
- - .allhands/skills/building-expo-ui/SKILL.md
76
- - .allhands/skills/react-native-best-practices/SKILL.md
77
- ```
78
-
79
- ## For Prompt Curation
80
-
81
- When used via PROMPT_TASKS_CURATION:
82
- - Add skill file paths to prompt's `skills` frontmatter
83
- - Embed extracted guidance in prompt's Tasks section
84
- - Makes domain expertise explicit and immediately available to executors
@@ -1,302 +0,0 @@
1
- /**
2
- * Memories Command (Agent-Facing)
3
- *
4
- * Keyword-based search for memories in docs/memories.md.
5
- * Parses markdown tables and searches across name, domain, source, description fields.
6
- *
7
- * Usage:
8
- * ah memories search <query> Search memories by keywords
9
- * ah memories search <query> --domain planning Filter by domain
10
- * ah memories search <query> --source user-steering Filter by source
11
- * ah memories list List memory sections with counts
12
- */
13
-
14
- import { Command } from 'commander';
15
- import { existsSync, readFileSync } from 'fs';
16
- import { join } from 'path';
17
- import { tracedAction } from '../lib/base-command.js';
18
-
19
- const getProjectRoot = (): string => {
20
- return process.env.PROJECT_ROOT || process.cwd();
21
- };
22
-
23
- const getMemoriesPath = (): string => {
24
- return join(getProjectRoot(), 'docs', 'memories.md');
25
- };
26
-
27
- interface MemoryEntry {
28
- name: string;
29
- domain: string;
30
- source: string;
31
- description: string;
32
- specSection: string;
33
- }
34
-
35
- interface MemoryMatch extends MemoryEntry {
36
- score: number;
37
- matchedFields: string[];
38
- }
39
-
40
- interface MemorySection {
41
- name: string;
42
- count: number;
43
- }
44
-
45
- /**
46
- * Extract keywords from a search query.
47
- * Handles quoted phrases and splits on whitespace.
48
- */
49
- function extractKeywords(query: string): string[] {
50
- const keywords: string[] = [];
51
-
52
- // Extract quoted phrases first
53
- const quotedRegex = /"([^"]+)"/g;
54
- let match;
55
- while ((match = quotedRegex.exec(query)) !== null) {
56
- keywords.push(match[1].toLowerCase());
57
- }
58
-
59
- // Remove quoted phrases and split remaining on whitespace
60
- const remaining = query.replace(quotedRegex, '').trim();
61
- if (remaining) {
62
- keywords.push(...remaining.toLowerCase().split(/\s+/).filter(k => k.length > 0));
63
- }
64
-
65
- return keywords;
66
- }
67
-
68
- /**
69
- * Parse markdown tables from memories.md into structured entries.
70
- * Expects tables with columns: Name, Domain, Source, Description
71
- * grouped under ## section headers.
72
- */
73
- function parseMemories(filePath: string): MemoryEntry[] {
74
- if (!existsSync(filePath)) return [];
75
-
76
- const content = readFileSync(filePath, 'utf-8');
77
- const lines = content.split('\n');
78
- const entries: MemoryEntry[] = [];
79
- let currentSection = 'unknown';
80
-
81
- for (let i = 0; i < lines.length; i++) {
82
- const line = lines[i].trim();
83
-
84
- // Track section headers
85
- if (line.startsWith('## ')) {
86
- currentSection = line.replace('## ', '').trim();
87
- continue;
88
- }
89
-
90
- // Skip non-table lines, header rows, and separator rows
91
- if (!line.startsWith('|') || line.includes('---') || /\|\s*Name\s*\|/i.test(line)) {
92
- continue;
93
- }
94
-
95
- // Parse table row
96
- const cells = line.split('|').map(c => c.trim()).filter(c => c.length > 0);
97
- if (cells.length >= 4) {
98
- entries.push({
99
- name: cells[0],
100
- domain: cells[1],
101
- source: cells[2],
102
- description: cells[3],
103
- specSection: currentSection,
104
- });
105
- }
106
- }
107
-
108
- return entries;
109
- }
110
-
111
- /**
112
- * Get section names and entry counts from memories file.
113
- */
114
- function getMemorySections(filePath: string): MemorySection[] {
115
- if (!existsSync(filePath)) return [];
116
-
117
- const content = readFileSync(filePath, 'utf-8');
118
- const lines = content.split('\n');
119
- const sections: MemorySection[] = [];
120
- let currentSection = '';
121
- let currentCount = 0;
122
-
123
- for (const line of lines) {
124
- const trimmed = line.trim();
125
-
126
- if (trimmed.startsWith('## ')) {
127
- if (currentSection) {
128
- sections.push({ name: currentSection, count: currentCount });
129
- }
130
- currentSection = trimmed.replace('## ', '').trim();
131
- currentCount = 0;
132
- continue;
133
- }
134
-
135
- // Count data rows (not headers or separators)
136
- if (currentSection && trimmed.startsWith('|') && !trimmed.includes('---') && !/\|\s*Name\s*\|/i.test(trimmed)) {
137
- const cells = trimmed.split('|').map(c => c.trim()).filter(c => c.length > 0);
138
- if (cells.length >= 4) {
139
- currentCount++;
140
- }
141
- }
142
- }
143
-
144
- // Push final section
145
- if (currentSection) {
146
- sections.push({ name: currentSection, count: currentCount });
147
- }
148
-
149
- return sections;
150
- }
151
-
152
- /**
153
- * Score how well a memory matches the search keywords.
154
- */
155
- function scoreMemory(entry: MemoryEntry, keywords: string[]): { score: number; matchedFields: string[] } {
156
- let score = 0;
157
- const matchedFields: string[] = [];
158
-
159
- for (const keyword of keywords) {
160
- // Name match (high weight)
161
- if (entry.name.toLowerCase().includes(keyword)) {
162
- score += 3;
163
- if (!matchedFields.includes('name')) matchedFields.push('name');
164
- }
165
-
166
- // Description match (medium weight)
167
- if (entry.description.toLowerCase().includes(keyword)) {
168
- score += 2;
169
- if (!matchedFields.includes('description')) matchedFields.push('description');
170
- }
171
-
172
- // Domain match (medium weight)
173
- if (entry.domain.toLowerCase().includes(keyword)) {
174
- score += 2;
175
- if (!matchedFields.includes('domain')) matchedFields.push('domain');
176
- }
177
-
178
- // Source match (low weight)
179
- if (entry.source.toLowerCase().includes(keyword)) {
180
- score += 1;
181
- if (!matchedFields.includes('source')) matchedFields.push('source');
182
- }
183
- }
184
-
185
- return { score, matchedFields };
186
- }
187
-
188
- /**
189
- * Search memories matching a query with optional filters.
190
- */
191
- function searchMemories(
192
- query: string,
193
- options: { domain?: string; source?: string; limit?: number }
194
- ): MemoryMatch[] {
195
- const memoriesPath = getMemoriesPath();
196
- const keywords = extractKeywords(query);
197
- const limit = options.limit ?? 10;
198
-
199
- if (keywords.length === 0) return [];
200
-
201
- let entries = parseMemories(memoriesPath);
202
-
203
- // Apply filters
204
- if (options.domain) {
205
- entries = entries.filter(e => e.domain.toLowerCase() === options.domain!.toLowerCase());
206
- }
207
- if (options.source) {
208
- entries = entries.filter(e => e.source.toLowerCase() === options.source!.toLowerCase());
209
- }
210
-
211
- const matches: MemoryMatch[] = [];
212
-
213
- for (const entry of entries) {
214
- const { score, matchedFields } = scoreMemory(entry, keywords);
215
- if (score > 0) {
216
- matches.push({ ...entry, score, matchedFields });
217
- }
218
- }
219
-
220
- // Sort by score descending
221
- matches.sort((a, b) => b.score - a.score);
222
-
223
- return matches.slice(0, limit);
224
- }
225
-
226
- export function register(program: Command): void {
227
- const memoriesCmd = program
228
- .command('memories')
229
- .description('Search and browse project memories');
230
-
231
- // Search command
232
- memoriesCmd
233
- .command('search <query>')
234
- .description('Search memories by keywords (searches name, domain, source, description)')
235
- .option('--domain <domain>', 'Filter by domain (planning, validation, implementation, harness-tooling, ideation)')
236
- .option('--source <source>', 'Filter by source (user-steering, agent-inferred)')
237
- .option('--limit <n>', 'Maximum number of results', '10')
238
- .action(tracedAction('memories search', async (query: string, options: { domain?: string; source?: string; limit?: string }) => {
239
- const limit = parseInt(options.limit || '10', 10);
240
- const matches = searchMemories(query, {
241
- domain: options.domain,
242
- source: options.source,
243
- limit,
244
- });
245
-
246
- if (matches.length === 0) {
247
- console.log(JSON.stringify({
248
- success: true,
249
- query,
250
- keywords: extractKeywords(query),
251
- results: [],
252
- message: 'No matching memories found',
253
- }, null, 2));
254
- return;
255
- }
256
-
257
- const results = matches.map(match => ({
258
- name: match.name,
259
- domain: match.domain,
260
- source: match.source,
261
- description: match.description,
262
- spec_section: match.specSection,
263
- score: match.score,
264
- matched_fields: match.matchedFields,
265
- }));
266
-
267
- console.log(JSON.stringify({
268
- success: true,
269
- query,
270
- keywords: extractKeywords(query),
271
- result_count: results.length,
272
- results,
273
- }, null, 2));
274
- }));
275
-
276
- // List command
277
- memoriesCmd
278
- .command('list')
279
- .description('List memory sections with entry counts')
280
- .action(tracedAction('memories list', async () => {
281
- const memoriesPath = getMemoriesPath();
282
-
283
- if (!existsSync(memoriesPath)) {
284
- console.log(JSON.stringify({
285
- success: true,
286
- message: 'No memories file found. Create docs/memories.md to start capturing learnings.',
287
- sections: [],
288
- total_entries: 0,
289
- }, null, 2));
290
- return;
291
- }
292
-
293
- const sections = getMemorySections(memoriesPath);
294
- const totalEntries = sections.reduce((sum, s) => sum + s.count, 0);
295
-
296
- console.log(JSON.stringify({
297
- success: true,
298
- sections,
299
- total_entries: totalEntries,
300
- }, null, 2));
301
- }));
302
- }