all-hands-cli 0.1.5 → 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/validation-tooling.md +10 -1
- 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
|
@@ -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
|
-
}
|