claude-all-hands 1.0.2 → 1.0.3
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/.claude/agents/curator.md +1 -5
- package/.claude/agents/documentation-taxonomist.md +255 -0
- package/.claude/agents/documentation-writer.md +366 -0
- package/.claude/agents/surveyor.md +1 -1
- package/.claude/commands/continue.md +12 -10
- package/.claude/commands/create-skill.md +2 -2
- package/.claude/commands/create-specialist.md +3 -3
- package/.claude/commands/debug.md +5 -5
- package/.claude/commands/docs-adjust.md +214 -0
- package/.claude/commands/docs-audit.md +172 -0
- package/.claude/commands/docs-init.md +210 -0
- package/.claude/commands/plan.md +6 -6
- package/.claude/commands/whats-next.md +2 -2
- package/.claude/envoy/README.md +5 -5
- package/.claude/envoy/package-lock.json +216 -10
- package/.claude/envoy/package.json +9 -0
- package/.claude/envoy/src/commands/docs.ts +881 -0
- package/.claude/envoy/src/commands/knowledge.ts +33 -42
- package/.claude/envoy/src/lib/ast-queries.ts +261 -0
- package/.claude/envoy/src/lib/knowledge.ts +176 -124
- package/.claude/envoy/src/lib/tree-sitter-utils.ts +301 -0
- package/.claude/envoy/src/types/tree-sitter.d.ts +76 -0
- package/.claude/hooks/scripts/enforce_research_fetch.py +1 -1
- package/.claude/protocols/bug-discovery.yaml +1 -1
- package/.claude/protocols/discovery.yaml +1 -1
- package/.claude/settings.json +4 -3
- package/.claude/skills/discovery-mode/SKILL.md +7 -7
- package/.claude/skills/documentation-taxonomy/SKILL.md +287 -0
- package/.claude/skills/implementation-mode/SKILL.md +7 -7
- package/.claude/skills/knowledge-discovery/SKILL.md +178 -0
- package/bin/cli.js +41 -1
- package/package.json +1 -1
- package/.claude/agents/documentor.md +0 -147
- package/.claude/commands/audit-docs.md +0 -94
- package/.claude/commands/create-docs.md +0 -100
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tree-sitter utilities for AST parsing and symbol resolution.
|
|
3
|
+
* Used by docs commands for symbol lookup and validation.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync, existsSync } from "fs";
|
|
7
|
+
import { extname } from "path";
|
|
8
|
+
import {
|
|
9
|
+
getLanguageForExtension,
|
|
10
|
+
getQueriesForLanguage,
|
|
11
|
+
type LanguageQueries,
|
|
12
|
+
} from "./ast-queries.js";
|
|
13
|
+
|
|
14
|
+
export interface SymbolLocation {
|
|
15
|
+
name: string;
|
|
16
|
+
startLine: number;
|
|
17
|
+
endLine: number;
|
|
18
|
+
type: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface ParseResult {
|
|
22
|
+
success: boolean;
|
|
23
|
+
language?: string;
|
|
24
|
+
symbols?: SymbolLocation[];
|
|
25
|
+
error?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Tree-sitter Query types
|
|
29
|
+
interface QueryCapture {
|
|
30
|
+
name: string;
|
|
31
|
+
node: {
|
|
32
|
+
text: string;
|
|
33
|
+
startPosition: { row: number; column: number };
|
|
34
|
+
endPosition: { row: number; column: number };
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface QueryMatch {
|
|
39
|
+
pattern: number;
|
|
40
|
+
captures: QueryCapture[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface Query {
|
|
44
|
+
matches(node: unknown): QueryMatch[];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface QueryConstructor {
|
|
48
|
+
new (grammar: unknown, queryString: string): Query;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Parser data type including Query constructor
|
|
52
|
+
interface ParserData {
|
|
53
|
+
parser: unknown;
|
|
54
|
+
grammar: unknown;
|
|
55
|
+
QueryClass: QueryConstructor;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Lazy-loaded parsers cache - stores parser, grammar, and Query class
|
|
59
|
+
const parserCache = new Map<string, ParserData>();
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Get or create a tree-sitter parser for a language.
|
|
63
|
+
* Lazily loads grammars to avoid startup cost.
|
|
64
|
+
* Returns parser, grammar, and Query class (needed for Query API).
|
|
65
|
+
*/
|
|
66
|
+
async function getParser(language: string): Promise<ParserData | null> {
|
|
67
|
+
if (parserCache.has(language)) {
|
|
68
|
+
return parserCache.get(language)!;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
// Dynamic import tree-sitter
|
|
73
|
+
const TreeSitter = await import("tree-sitter");
|
|
74
|
+
const Parser = TreeSitter.default;
|
|
75
|
+
const QueryClass = (TreeSitter as unknown as { Query: QueryConstructor }).Query;
|
|
76
|
+
|
|
77
|
+
// Load language grammar
|
|
78
|
+
let grammar: unknown;
|
|
79
|
+
switch (language) {
|
|
80
|
+
case "typescript":
|
|
81
|
+
grammar = (await import("tree-sitter-typescript")).default.typescript;
|
|
82
|
+
break;
|
|
83
|
+
case "javascript":
|
|
84
|
+
grammar = (await import("tree-sitter-javascript")).default;
|
|
85
|
+
break;
|
|
86
|
+
case "python":
|
|
87
|
+
grammar = (await import("tree-sitter-python")).default;
|
|
88
|
+
break;
|
|
89
|
+
case "go":
|
|
90
|
+
grammar = (await import("tree-sitter-go")).default;
|
|
91
|
+
break;
|
|
92
|
+
case "rust":
|
|
93
|
+
grammar = (await import("tree-sitter-rust")).default;
|
|
94
|
+
break;
|
|
95
|
+
case "java":
|
|
96
|
+
grammar = (await import("tree-sitter-java")).default;
|
|
97
|
+
break;
|
|
98
|
+
case "ruby":
|
|
99
|
+
grammar = (await import("tree-sitter-ruby")).default;
|
|
100
|
+
break;
|
|
101
|
+
case "swift":
|
|
102
|
+
grammar = (await import("tree-sitter-swift")).default;
|
|
103
|
+
break;
|
|
104
|
+
default:
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const parser = new Parser();
|
|
109
|
+
parser.setLanguage(grammar as Parameters<typeof parser.setLanguage>[0]);
|
|
110
|
+
const cached: ParserData = { parser, grammar, QueryClass };
|
|
111
|
+
parserCache.set(language, cached);
|
|
112
|
+
return cached;
|
|
113
|
+
} catch (e) {
|
|
114
|
+
console.error(`Failed to load parser for ${language}:`, e);
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Parse a file and extract all symbol definitions.
|
|
121
|
+
*/
|
|
122
|
+
export async function parseFile(filePath: string): Promise<ParseResult> {
|
|
123
|
+
const ext = extname(filePath);
|
|
124
|
+
const language = getLanguageForExtension(ext);
|
|
125
|
+
|
|
126
|
+
if (!language) {
|
|
127
|
+
return {
|
|
128
|
+
success: false,
|
|
129
|
+
error: `Unsupported file extension: ${ext}`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (!existsSync(filePath)) {
|
|
134
|
+
return {
|
|
135
|
+
success: false,
|
|
136
|
+
error: `File not found: ${filePath}`,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const parserData = await getParser(language);
|
|
141
|
+
if (!parserData) {
|
|
142
|
+
return {
|
|
143
|
+
success: false,
|
|
144
|
+
error: `No parser available for ${language}`,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const sourceCode = readFileSync(filePath, "utf-8");
|
|
150
|
+
const tree = (parserData.parser as { parse(source: string): unknown }).parse(sourceCode);
|
|
151
|
+
const queries = getQueriesForLanguage(language);
|
|
152
|
+
|
|
153
|
+
if (!queries) {
|
|
154
|
+
return {
|
|
155
|
+
success: false,
|
|
156
|
+
error: `No queries defined for ${language}`,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const symbols = extractSymbols(tree, queries, parserData.grammar, parserData.QueryClass);
|
|
161
|
+
|
|
162
|
+
return {
|
|
163
|
+
success: true,
|
|
164
|
+
language,
|
|
165
|
+
symbols,
|
|
166
|
+
};
|
|
167
|
+
} catch (e) {
|
|
168
|
+
return {
|
|
169
|
+
success: false,
|
|
170
|
+
error: e instanceof Error ? e.message : String(e),
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Find a specific symbol in a file.
|
|
177
|
+
*/
|
|
178
|
+
export async function findSymbol(
|
|
179
|
+
filePath: string,
|
|
180
|
+
symbolName: string
|
|
181
|
+
): Promise<SymbolLocation | null> {
|
|
182
|
+
const result = await parseFile(filePath);
|
|
183
|
+
|
|
184
|
+
if (!result.success || !result.symbols) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return result.symbols.find((s) => s.name === symbolName) || null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Check if a symbol exists in a file.
|
|
193
|
+
*/
|
|
194
|
+
export async function symbolExists(
|
|
195
|
+
filePath: string,
|
|
196
|
+
symbolName: string
|
|
197
|
+
): Promise<boolean> {
|
|
198
|
+
const symbol = await findSymbol(filePath, symbolName);
|
|
199
|
+
return symbol !== null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Cache compiled queries to avoid recompilation
|
|
203
|
+
const queryCache = new Map<string, Query>();
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Extract symbols from a parsed tree using tree-sitter's Query API.
|
|
207
|
+
* This uses the declarative query patterns from ast-queries.ts.
|
|
208
|
+
*/
|
|
209
|
+
function extractSymbols(
|
|
210
|
+
tree: unknown,
|
|
211
|
+
queries: LanguageQueries,
|
|
212
|
+
grammar: unknown,
|
|
213
|
+
QueryClass: QueryConstructor
|
|
214
|
+
): SymbolLocation[] {
|
|
215
|
+
const symbols: SymbolLocation[] = [];
|
|
216
|
+
const root = (tree as { rootNode: unknown }).rootNode;
|
|
217
|
+
|
|
218
|
+
for (const [symbolType, queryDef] of Object.entries(queries)) {
|
|
219
|
+
const cacheKey = `${symbolType}:${queryDef.query}`;
|
|
220
|
+
|
|
221
|
+
let query = queryCache.get(cacheKey);
|
|
222
|
+
if (!query) {
|
|
223
|
+
try {
|
|
224
|
+
query = new QueryClass(grammar, queryDef.query);
|
|
225
|
+
queryCache.set(cacheKey, query);
|
|
226
|
+
} catch (e) {
|
|
227
|
+
// Query compilation failed - skip this symbol type
|
|
228
|
+
console.error(`Failed to compile query for ${symbolType}:`, e);
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
const matches = query.matches(root);
|
|
235
|
+
for (const match of matches) {
|
|
236
|
+
const nameCapture = match.captures.find(c => c.name === queryDef.nameCapture);
|
|
237
|
+
if (nameCapture) {
|
|
238
|
+
symbols.push({
|
|
239
|
+
name: nameCapture.node.text,
|
|
240
|
+
startLine: nameCapture.node.startPosition.row + 1,
|
|
241
|
+
endLine: nameCapture.node.endPosition.row + 1,
|
|
242
|
+
type: symbolType,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
} catch (e) {
|
|
247
|
+
// Query execution failed - skip this symbol type
|
|
248
|
+
console.error(`Failed to execute query for ${symbolType}:`, e);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return symbols;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Get complexity metrics for a file.
|
|
258
|
+
*/
|
|
259
|
+
export async function getFileComplexity(filePath: string): Promise<{
|
|
260
|
+
lines: number;
|
|
261
|
+
imports: number;
|
|
262
|
+
exports: number;
|
|
263
|
+
functions: number;
|
|
264
|
+
classes: number;
|
|
265
|
+
} | null> {
|
|
266
|
+
if (!existsSync(filePath)) {
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
const content = readFileSync(filePath, "utf-8");
|
|
272
|
+
const lines = content.split("\n").length;
|
|
273
|
+
|
|
274
|
+
const result = await parseFile(filePath);
|
|
275
|
+
if (!result.success || !result.symbols) {
|
|
276
|
+
// Fallback to regex-based counting for unsupported files
|
|
277
|
+
return {
|
|
278
|
+
lines,
|
|
279
|
+
imports: (content.match(/^import\s/gm) || []).length,
|
|
280
|
+
exports: (content.match(/^export\s/gm) || []).length,
|
|
281
|
+
functions: (content.match(/function\s+\w+/g) || []).length,
|
|
282
|
+
classes: (content.match(/class\s+\w+/g) || []).length,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const functions = result.symbols.filter(
|
|
287
|
+
(s) => s.type === "function" || s.type === "method" || s.type === "arrowFunction"
|
|
288
|
+
).length;
|
|
289
|
+
const classes = result.symbols.filter(
|
|
290
|
+
(s) => s.type === "class" || s.type === "struct" || s.type === "interface"
|
|
291
|
+
).length;
|
|
292
|
+
|
|
293
|
+
// Count imports/exports via regex (simpler than AST for this)
|
|
294
|
+
const imports = (content.match(/^import\s/gm) || []).length;
|
|
295
|
+
const exports = (content.match(/^export\s/gm) || []).length;
|
|
296
|
+
|
|
297
|
+
return { lines, imports, exports, functions, classes };
|
|
298
|
+
} catch {
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type declarations for tree-sitter and language grammars.
|
|
3
|
+
* These packages don't have bundled types, so we declare them here.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
declare module "tree-sitter" {
|
|
7
|
+
export interface SyntaxNode {
|
|
8
|
+
type: string;
|
|
9
|
+
text: string;
|
|
10
|
+
startPosition: { row: number; column: number };
|
|
11
|
+
endPosition: { row: number; column: number };
|
|
12
|
+
children: SyntaxNode[];
|
|
13
|
+
namedChildren: SyntaxNode[];
|
|
14
|
+
childForFieldName(fieldName: string): SyntaxNode | null;
|
|
15
|
+
parent: SyntaxNode | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface Tree {
|
|
19
|
+
rootNode: SyntaxNode;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface Language {}
|
|
23
|
+
|
|
24
|
+
export default class Parser {
|
|
25
|
+
setLanguage(language: Language): void;
|
|
26
|
+
parse(input: string): Tree;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
declare module "tree-sitter-typescript" {
|
|
31
|
+
import { Language } from "tree-sitter";
|
|
32
|
+
const grammar: { typescript: Language; tsx: Language };
|
|
33
|
+
export default grammar;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
declare module "tree-sitter-javascript" {
|
|
37
|
+
import { Language } from "tree-sitter";
|
|
38
|
+
const grammar: Language;
|
|
39
|
+
export default grammar;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
declare module "tree-sitter-python" {
|
|
43
|
+
import { Language } from "tree-sitter";
|
|
44
|
+
const grammar: Language;
|
|
45
|
+
export default grammar;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
declare module "tree-sitter-go" {
|
|
49
|
+
import { Language } from "tree-sitter";
|
|
50
|
+
const grammar: Language;
|
|
51
|
+
export default grammar;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
declare module "tree-sitter-rust" {
|
|
55
|
+
import { Language } from "tree-sitter";
|
|
56
|
+
const grammar: Language;
|
|
57
|
+
export default grammar;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
declare module "tree-sitter-java" {
|
|
61
|
+
import { Language } from "tree-sitter";
|
|
62
|
+
const grammar: Language;
|
|
63
|
+
export default grammar;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
declare module "tree-sitter-ruby" {
|
|
67
|
+
import { Language } from "tree-sitter";
|
|
68
|
+
const grammar: Language;
|
|
69
|
+
export default grammar;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
declare module "tree-sitter-swift" {
|
|
73
|
+
import { Language } from "tree-sitter";
|
|
74
|
+
const grammar: Language;
|
|
75
|
+
export default grammar;
|
|
76
|
+
}
|
|
@@ -12,6 +12,6 @@ if not url:
|
|
|
12
12
|
# JSON output with additionalContext for Claude self-correction
|
|
13
13
|
print(json.dumps({
|
|
14
14
|
"continue": False,
|
|
15
|
-
"additionalContext": "WebFetch blocked.\n\nMain agent: delegate to researcher agent.\nSubagent: use
|
|
15
|
+
"additionalContext": "WebFetch blocked.\n\nMain agent: delegate to researcher agent.\nSubagent: use `envoy tavily extract \"<url>\"` instead."
|
|
16
16
|
}))
|
|
17
17
|
sys.exit(0)
|
|
@@ -19,7 +19,7 @@ outputs:
|
|
|
19
19
|
description: bug hypotheses written to file via envoy commands
|
|
20
20
|
steps:
|
|
21
21
|
1: |
|
|
22
|
-
**Query documentation first**: Call `envoy knowledge search
|
|
22
|
+
**Query documentation first**: Call `envoy knowledge search "<suspected area + symptoms as descriptive phrase>"` (semantic search - full phrases, not keywords)
|
|
23
23
|
* Check for documented anti-patterns that might explain the bug
|
|
24
24
|
* Note any constraints that must be preserved in the fix
|
|
25
25
|
2: |
|
|
@@ -19,7 +19,7 @@ outputs:
|
|
|
19
19
|
description: findings written to file via envoy commands
|
|
20
20
|
steps:
|
|
21
21
|
1: |
|
|
22
|
-
**Query documentation first**: Call `envoy knowledge search
|
|
22
|
+
**Query documentation first**: Call `envoy knowledge search "<focused requirement area as descriptive request>"` (semantic search - full phrases, not keywords)
|
|
23
23
|
* May run multiple searches if requirements span distinct focus areas
|
|
24
24
|
* Use returned docs as context for approach building - reference existing patterns rather than reinventing
|
|
25
25
|
* Note any constraints or anti-patterns documented for this area
|
package/.claude/settings.json
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
"env": {
|
|
4
4
|
"PARALLEL_MAX_WORKERS": "3",
|
|
5
5
|
"PROJECT_NAME": "test-target",
|
|
6
|
-
"SEARCH_SIMILARITY_THRESHOLD": "0.
|
|
7
|
-
"SEARCH_FULL_CONTEXT_SIMILARITY_THRESHOLD": "0.
|
|
6
|
+
"SEARCH_SIMILARITY_THRESHOLD": "0.65",
|
|
7
|
+
"SEARCH_FULL_CONTEXT_SIMILARITY_THRESHOLD": "0.82",
|
|
8
8
|
"SEARCH_CONTEXT_TOKEN_LIMIT": "5000",
|
|
9
9
|
"MAX_LOGS_TOKENS": "10000",
|
|
10
10
|
"BASH_MAX_TIMEOUT_MS": "3600000",
|
|
@@ -21,9 +21,10 @@
|
|
|
21
21
|
"Bash(ls:*)",
|
|
22
22
|
"Bash(rg:*)",
|
|
23
23
|
"Bash(mkdir:*)",
|
|
24
|
+
"Bash(mkdir -p:*)",
|
|
24
25
|
"Bash(npx repomix@latest:*)",
|
|
25
26
|
"Read(~/.claude-code-docs/docs/*)",
|
|
26
|
-
"Bash(
|
|
27
|
+
"Bash(envoy:*)",
|
|
27
28
|
"SlashCommand(/plan:*)"
|
|
28
29
|
],
|
|
29
30
|
"deny": [],
|
|
@@ -10,14 +10,14 @@ Enable agents to perform read-only codebase analysis during planning phase. Agen
|
|
|
10
10
|
<quick_start>
|
|
11
11
|
```bash
|
|
12
12
|
# Write approach (required)
|
|
13
|
-
|
|
13
|
+
envoy plans write-approach \
|
|
14
14
|
--specialist "<your-name>" \
|
|
15
15
|
--summary "2-3 sentence summary" \
|
|
16
16
|
--files '[{"path": "src/auth/service.ts", "purpose": "Core auth logic"}]' \
|
|
17
17
|
--content "Full analysis..."
|
|
18
18
|
|
|
19
19
|
# Write options (when alternatives exist)
|
|
20
|
-
|
|
20
|
+
envoy plans write-option \
|
|
21
21
|
--specialist "<your-name>" \
|
|
22
22
|
--id "1A" --group "Token storage" --name "httpOnly cookies" \
|
|
23
23
|
--summary "Store tokens in httpOnly cookies..." \
|
|
@@ -44,7 +44,7 @@ Use: Glob, Grep, Read
|
|
|
44
44
|
|
|
45
45
|
**Approach (required)**:
|
|
46
46
|
```bash
|
|
47
|
-
|
|
47
|
+
envoy plans write-approach \
|
|
48
48
|
--specialist "<name>" \
|
|
49
49
|
--summary "Brief summary for main agent" \
|
|
50
50
|
--files '[{"path": "...", "purpose": "..."}]' \
|
|
@@ -54,7 +54,7 @@ Use: Glob, Grep, Read
|
|
|
54
54
|
|
|
55
55
|
**Options (only if alternatives exist that the approach file mentions)**:
|
|
56
56
|
```bash
|
|
57
|
-
|
|
57
|
+
envoy plans write-option \
|
|
58
58
|
--specialist "<name>" \
|
|
59
59
|
--id "1A" \
|
|
60
60
|
--group "Token storage" \
|
|
@@ -83,13 +83,13 @@ When re-invoked after user feedback (re-discovery loop):
|
|
|
83
83
|
|
|
84
84
|
```bash
|
|
85
85
|
# Check for existing findings
|
|
86
|
-
|
|
86
|
+
envoy plans has-findings --specialist "<name>"
|
|
87
87
|
|
|
88
88
|
# Read previous approach
|
|
89
|
-
|
|
89
|
+
envoy plans read-finding --specialist "<name>" --type approach
|
|
90
90
|
|
|
91
91
|
# Write new approach with --replace (clears existing options)
|
|
92
|
-
|
|
92
|
+
envoy plans write-approach \
|
|
93
93
|
--specialist "<name>" \
|
|
94
94
|
--replace \
|
|
95
95
|
--summary "..." \
|