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
@@ -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
- }