@zcy2nn/agent-forge 1.1.3 → 1.1.5
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/README.md +188 -247
- package/agent-forge.schema.json +2 -265
- package/dist/agents/orchestrator.d.ts +1 -1
- package/dist/cli/index.js +90 -259
- package/dist/cli/providers.d.ts +0 -44
- package/dist/cli/types.d.ts +0 -2
- package/dist/config/constants.d.ts +3 -8
- package/dist/config/index.d.ts +0 -1
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/schema.d.ts +1 -184
- package/dist/hooks/index.d.ts +0 -6
- package/dist/hooks/json-error-recovery/hook.d.ts +1 -1
- package/dist/hooks/todo-continuation/index.d.ts +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +7875 -31853
- package/dist/tools/index.d.ts +0 -3
- package/dist/tui.js +5 -61
- package/dist/utils/index.d.ts +0 -2
- package/package.json +95 -104
- package/src/skills/brainstorming/SKILL.md +185 -186
- package/src/skills/brainstorming/scripts/frame-template.html +214 -214
- package/src/skills/brainstorming/scripts/server.cjs +354 -354
- package/src/skills/brainstorming/spec-document-reviewer-prompt.md +1 -1
- package/src/skills/requesting-code-review/SKILL.md +1 -1
- package/src/skills/subagent-driven-development/SKILL.md +1 -1
- package/src/skills/systematic-debugging/SKILL.md +318 -318
- package/src/skills/test-driven-development/SKILL.md +392 -392
- package/src/skills/verification-before-completion/SKILL.md +153 -153
- package/src/skills/writing-plans/SKILL.md +2 -2
- package/src/skills/writing-skills/graphviz-conventions.dot +171 -171
- package/dist/agents/council.d.ts +0 -27
- package/dist/agents/councillor.d.ts +0 -2
- package/dist/agents/designer.d.ts +0 -2
- package/dist/agents/explorer.d.ts +0 -2
- package/dist/agents/fixer.d.ts +0 -2
- package/dist/agents/implementer.d.ts +0 -2
- package/dist/agents/librarian.d.ts +0 -2
- package/dist/agents/observer.d.ts +0 -2
- package/dist/agents/oracle.d.ts +0 -2
- package/dist/agents/reviewer.d.ts +0 -2
- package/dist/cli/migration.d.ts +0 -46
- package/dist/config/council-schema.d.ts +0 -127
- package/dist/council/council-manager.d.ts +0 -49
- package/dist/council/index.d.ts +0 -1
- package/dist/hooks/phase-reminder/index.d.ts +0 -26
- package/dist/hooks/post-file-tool-nudge/index.d.ts +0 -19
- package/dist/skills/systematic-debugging/condition-based-waiting-example.d.ts +0 -51
- package/dist/tools/council.d.ts +0 -10
- package/src/skills/codemap/README.md +0 -59
- package/src/skills/codemap/SKILL.md +0 -163
- package/src/skills/codemap/codemap.md +0 -36
- package/src/skills/codemap/scripts/codemap.mjs +0 -483
- package/src/skills/codemap/scripts/codemap.test.ts +0 -129
- package/src/skills/codemap.md +0 -40
- package/src/skills/simplify/README.md +0 -19
- package/src/skills/simplify/SKILL.md +0 -138
- package/src/skills/simplify/codemap.md +0 -36
- package/src/skills/using-git-worktrees/SKILL.md +0 -226
|
@@ -1,483 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { createHash } from 'node:crypto';
|
|
4
|
-
import {
|
|
5
|
-
existsSync,
|
|
6
|
-
mkdirSync,
|
|
7
|
-
readdirSync,
|
|
8
|
-
readFileSync,
|
|
9
|
-
renameSync,
|
|
10
|
-
statSync,
|
|
11
|
-
writeFileSync,
|
|
12
|
-
} from 'node:fs';
|
|
13
|
-
import path from 'node:path';
|
|
14
|
-
import { fileURLToPath } from 'node:url';
|
|
15
|
-
|
|
16
|
-
export const VERSION = '1.0.0';
|
|
17
|
-
export const STATE_DIR = '.slim';
|
|
18
|
-
export const STATE_FILE = 'codemap.json';
|
|
19
|
-
export const LEGACY_STATE_FILE = 'cartography.json';
|
|
20
|
-
export const CODEMAP_FILE = 'codemap.md';
|
|
21
|
-
|
|
22
|
-
export class PatternMatcher {
|
|
23
|
-
regex;
|
|
24
|
-
|
|
25
|
-
constructor(patterns) {
|
|
26
|
-
if (!patterns.length) {
|
|
27
|
-
this.regex = null;
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const regexParts = patterns.map((pattern) => {
|
|
32
|
-
let reg = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
33
|
-
reg = reg.replace(/\\\*\\\*\//g, '(?:.*/)?');
|
|
34
|
-
reg = reg.replace(/\\\*\\\*/g, '.*');
|
|
35
|
-
reg = reg.replace(/\\\*/g, '[^/]*');
|
|
36
|
-
reg = reg.replace(/\\\?/g, '.');
|
|
37
|
-
|
|
38
|
-
if (pattern.endsWith('/')) {
|
|
39
|
-
reg += '.*';
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (pattern.startsWith('/')) {
|
|
43
|
-
reg = `^${reg.slice(1)}`;
|
|
44
|
-
} else {
|
|
45
|
-
reg = `(?:^|.*/)${reg}`;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return `(?:${reg}$)`;
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
this.regex = new RegExp(regexParts.join('|'));
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
matches(filePath) {
|
|
55
|
-
if (!this.regex) return false;
|
|
56
|
-
return this.regex.test(filePath);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function loadGitignore(root) {
|
|
61
|
-
const gitignorePath = path.join(root, '.gitignore');
|
|
62
|
-
if (!existsSync(gitignorePath)) return [];
|
|
63
|
-
|
|
64
|
-
return readFileSync(gitignorePath, 'utf8')
|
|
65
|
-
.split('\n')
|
|
66
|
-
.map((line) => line.trim())
|
|
67
|
-
.filter((line) => line && !line.startsWith('#'));
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function walkFiles(root) {
|
|
71
|
-
const files = [];
|
|
72
|
-
|
|
73
|
-
function visit(currentDir) {
|
|
74
|
-
for (const entry of readdirSync(currentDir, { withFileTypes: true })) {
|
|
75
|
-
const fullPath = path.join(currentDir, entry.name);
|
|
76
|
-
if (entry.isDirectory()) {
|
|
77
|
-
if (!entry.name.startsWith('.')) {
|
|
78
|
-
visit(fullPath);
|
|
79
|
-
}
|
|
80
|
-
continue;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
if (entry.isFile()) {
|
|
84
|
-
files.push(fullPath);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
visit(root);
|
|
90
|
-
return files.sort();
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export function selectFiles(
|
|
94
|
-
root,
|
|
95
|
-
includePatterns,
|
|
96
|
-
excludePatterns,
|
|
97
|
-
exceptions,
|
|
98
|
-
gitignorePatterns,
|
|
99
|
-
) {
|
|
100
|
-
const includeMatcher = new PatternMatcher(includePatterns);
|
|
101
|
-
const excludeMatcher = new PatternMatcher(excludePatterns);
|
|
102
|
-
const gitignoreMatcher = new PatternMatcher(gitignorePatterns);
|
|
103
|
-
const exceptionSet = new Set(exceptions);
|
|
104
|
-
|
|
105
|
-
return walkFiles(root).filter((fullPath) => {
|
|
106
|
-
let relPath = path.relative(root, fullPath).replaceAll(path.sep, '/');
|
|
107
|
-
if (relPath.startsWith('./')) {
|
|
108
|
-
relPath = relPath.slice(2);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
if (gitignoreMatcher.matches(relPath)) return false;
|
|
112
|
-
if (excludeMatcher.matches(relPath) && !exceptionSet.has(relPath)) {
|
|
113
|
-
return false;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
return includeMatcher.matches(relPath) || exceptionSet.has(relPath);
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export function computeFileHash(filePath) {
|
|
121
|
-
try {
|
|
122
|
-
const buffer = readFileSync(filePath);
|
|
123
|
-
return createHash('md5').update(buffer).digest('hex');
|
|
124
|
-
} catch {
|
|
125
|
-
return '';
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
export function computeFolderHash(folder, fileHashes) {
|
|
130
|
-
const folderFiles = Object.entries(fileHashes)
|
|
131
|
-
.filter(
|
|
132
|
-
([filePath]) =>
|
|
133
|
-
filePath.startsWith(`${folder}/`) ||
|
|
134
|
-
(folder === '.' && !filePath.includes('/')),
|
|
135
|
-
)
|
|
136
|
-
.sort(([a], [b]) => a.localeCompare(b));
|
|
137
|
-
|
|
138
|
-
if (!folderFiles.length) return '';
|
|
139
|
-
|
|
140
|
-
const hasher = createHash('md5');
|
|
141
|
-
for (const [filePath, hash] of folderFiles) {
|
|
142
|
-
hasher.update(`${filePath}:${hash}\n`);
|
|
143
|
-
}
|
|
144
|
-
return hasher.digest('hex');
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
export function getFoldersWithFiles(files, root) {
|
|
148
|
-
const folders = new Set(['.']);
|
|
149
|
-
|
|
150
|
-
for (const filePath of files) {
|
|
151
|
-
const relPath = path.relative(root, filePath).replaceAll(path.sep, '/');
|
|
152
|
-
const parts = relPath.split('/').slice(0, -1);
|
|
153
|
-
for (let i = 0; i < parts.length; i++) {
|
|
154
|
-
folders.add(parts.slice(0, i + 1).join('/'));
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return folders;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
export function migrateLegacyState(root) {
|
|
162
|
-
const stateDir = path.join(root, STATE_DIR);
|
|
163
|
-
const legacyPath = path.join(stateDir, LEGACY_STATE_FILE);
|
|
164
|
-
const statePath = path.join(stateDir, STATE_FILE);
|
|
165
|
-
|
|
166
|
-
if (existsSync(statePath) || !existsSync(legacyPath)) {
|
|
167
|
-
return false;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
mkdirSync(stateDir, { recursive: true });
|
|
171
|
-
renameSync(legacyPath, statePath);
|
|
172
|
-
console.log(
|
|
173
|
-
`Migrated ${STATE_DIR}/${LEGACY_STATE_FILE} -> ${STATE_DIR}/${STATE_FILE}`,
|
|
174
|
-
);
|
|
175
|
-
return true;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
export function loadState(root) {
|
|
179
|
-
migrateLegacyState(root);
|
|
180
|
-
const statePath = path.join(root, STATE_DIR, STATE_FILE);
|
|
181
|
-
if (!existsSync(statePath)) return null;
|
|
182
|
-
|
|
183
|
-
try {
|
|
184
|
-
return JSON.parse(readFileSync(statePath, 'utf8'));
|
|
185
|
-
} catch {
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
export function saveState(root, state) {
|
|
191
|
-
const stateDir = path.join(root, STATE_DIR);
|
|
192
|
-
mkdirSync(stateDir, { recursive: true });
|
|
193
|
-
writeFileSync(
|
|
194
|
-
path.join(stateDir, STATE_FILE),
|
|
195
|
-
`${JSON.stringify(state, null, 2)}\n`,
|
|
196
|
-
);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
export function createEmptyCodemap(folderPath, folderName) {
|
|
200
|
-
const codemapPath = path.join(folderPath, CODEMAP_FILE);
|
|
201
|
-
if (existsSync(codemapPath)) return;
|
|
202
|
-
|
|
203
|
-
const content = `# ${folderName}/
|
|
204
|
-
|
|
205
|
-
<!-- Fixer: Fill in this section with architectural understanding -->
|
|
206
|
-
|
|
207
|
-
## Responsibility
|
|
208
|
-
|
|
209
|
-
<!-- What is this folder's job in the system? -->
|
|
210
|
-
|
|
211
|
-
## Design
|
|
212
|
-
|
|
213
|
-
<!-- Key patterns, abstractions, architectural decisions -->
|
|
214
|
-
|
|
215
|
-
## Flow
|
|
216
|
-
|
|
217
|
-
<!-- How does data/control flow through this module? -->
|
|
218
|
-
|
|
219
|
-
## Integration
|
|
220
|
-
|
|
221
|
-
<!-- How does it connect to other parts of the system? -->
|
|
222
|
-
`;
|
|
223
|
-
|
|
224
|
-
writeFileSync(codemapPath, content);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
function buildState(
|
|
228
|
-
root,
|
|
229
|
-
includePatterns,
|
|
230
|
-
excludePatterns,
|
|
231
|
-
exceptions,
|
|
232
|
-
selectedFiles,
|
|
233
|
-
) {
|
|
234
|
-
const fileHashes = {};
|
|
235
|
-
for (const filePath of selectedFiles) {
|
|
236
|
-
const relPath = path.relative(root, filePath).replaceAll(path.sep, '/');
|
|
237
|
-
fileHashes[relPath] = computeFileHash(filePath);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const folders = getFoldersWithFiles(selectedFiles, root);
|
|
241
|
-
const folderHashes = {};
|
|
242
|
-
for (const folder of folders) {
|
|
243
|
-
folderHashes[folder] = computeFolderHash(folder, fileHashes);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const state = {
|
|
247
|
-
metadata: {
|
|
248
|
-
version: VERSION,
|
|
249
|
-
last_run: new Date().toISOString(),
|
|
250
|
-
root,
|
|
251
|
-
include_patterns: includePatterns,
|
|
252
|
-
exclude_patterns: excludePatterns,
|
|
253
|
-
exceptions,
|
|
254
|
-
},
|
|
255
|
-
file_hashes: fileHashes,
|
|
256
|
-
folder_hashes: folderHashes,
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
return { state, folders };
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
export function cmdInit({ root, include = [], exclude = [], exception = [] }) {
|
|
263
|
-
const resolvedRoot = path.resolve(root);
|
|
264
|
-
if (!existsSync(resolvedRoot) || !statSync(resolvedRoot).isDirectory()) {
|
|
265
|
-
console.error(`Error: ${resolvedRoot} is not a directory`);
|
|
266
|
-
return 1;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const includePatterns = include.length ? include : ['**/*'];
|
|
270
|
-
const excludePatterns = exclude;
|
|
271
|
-
const exceptions = exception;
|
|
272
|
-
const gitignore = loadGitignore(resolvedRoot);
|
|
273
|
-
|
|
274
|
-
console.log(`Scanning ${resolvedRoot}...`);
|
|
275
|
-
console.log(`Include patterns: ${JSON.stringify(includePatterns)}`);
|
|
276
|
-
console.log(`Exclude patterns: ${JSON.stringify(excludePatterns)}`);
|
|
277
|
-
console.log(`Exceptions: ${JSON.stringify(exceptions)}`);
|
|
278
|
-
|
|
279
|
-
const selectedFiles = selectFiles(
|
|
280
|
-
resolvedRoot,
|
|
281
|
-
includePatterns,
|
|
282
|
-
excludePatterns,
|
|
283
|
-
exceptions,
|
|
284
|
-
gitignore,
|
|
285
|
-
);
|
|
286
|
-
|
|
287
|
-
console.log(`Selected ${selectedFiles.length} files`);
|
|
288
|
-
|
|
289
|
-
const { state, folders } = buildState(
|
|
290
|
-
resolvedRoot,
|
|
291
|
-
includePatterns,
|
|
292
|
-
excludePatterns,
|
|
293
|
-
exceptions,
|
|
294
|
-
selectedFiles,
|
|
295
|
-
);
|
|
296
|
-
|
|
297
|
-
saveState(resolvedRoot, state);
|
|
298
|
-
console.log(`Created ${STATE_DIR}/${STATE_FILE}`);
|
|
299
|
-
|
|
300
|
-
for (const folder of folders) {
|
|
301
|
-
const folderPath =
|
|
302
|
-
folder === '.' ? resolvedRoot : path.join(resolvedRoot, folder);
|
|
303
|
-
const folderName = folder === '.' ? path.basename(resolvedRoot) : folder;
|
|
304
|
-
createEmptyCodemap(folderPath, folderName);
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
console.log(`Created ${folders.size} empty codemap.md files`);
|
|
308
|
-
return 0;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
export function cmdChanges({ root }) {
|
|
312
|
-
const resolvedRoot = path.resolve(root);
|
|
313
|
-
const state = loadState(resolvedRoot);
|
|
314
|
-
if (!state) {
|
|
315
|
-
console.error("No codemap state found. Run 'init' first.");
|
|
316
|
-
return 1;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const metadata = state.metadata ?? {};
|
|
320
|
-
const includePatterns = metadata.include_patterns ?? ['**/*'];
|
|
321
|
-
const excludePatterns = metadata.exclude_patterns ?? [];
|
|
322
|
-
const exceptions = metadata.exceptions ?? [];
|
|
323
|
-
const gitignore = loadGitignore(resolvedRoot);
|
|
324
|
-
|
|
325
|
-
const currentFiles = selectFiles(
|
|
326
|
-
resolvedRoot,
|
|
327
|
-
includePatterns,
|
|
328
|
-
excludePatterns,
|
|
329
|
-
exceptions,
|
|
330
|
-
gitignore,
|
|
331
|
-
);
|
|
332
|
-
|
|
333
|
-
const currentHashes = Object.fromEntries(
|
|
334
|
-
currentFiles.map((filePath) => [
|
|
335
|
-
path.relative(resolvedRoot, filePath).replaceAll(path.sep, '/'),
|
|
336
|
-
computeFileHash(filePath),
|
|
337
|
-
]),
|
|
338
|
-
);
|
|
339
|
-
|
|
340
|
-
const savedHashes = state.file_hashes ?? {};
|
|
341
|
-
const currentPaths = new Set(Object.keys(currentHashes));
|
|
342
|
-
const savedPaths = new Set(Object.keys(savedHashes));
|
|
343
|
-
|
|
344
|
-
const added = [...currentPaths]
|
|
345
|
-
.filter((filePath) => !savedPaths.has(filePath))
|
|
346
|
-
.sort();
|
|
347
|
-
const removed = [...savedPaths]
|
|
348
|
-
.filter((filePath) => !currentPaths.has(filePath))
|
|
349
|
-
.sort();
|
|
350
|
-
const modified = [...currentPaths]
|
|
351
|
-
.filter((filePath) => savedPaths.has(filePath))
|
|
352
|
-
.filter((filePath) => currentHashes[filePath] !== savedHashes[filePath])
|
|
353
|
-
.sort();
|
|
354
|
-
|
|
355
|
-
if (!added.length && !removed.length && !modified.length) {
|
|
356
|
-
console.log('No changes detected.');
|
|
357
|
-
return 0;
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
if (added.length) {
|
|
361
|
-
console.log(`\n${added.length} added:`);
|
|
362
|
-
for (const filePath of added) console.log(` + ${filePath}`);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (removed.length) {
|
|
366
|
-
console.log(`\n${removed.length} removed:`);
|
|
367
|
-
for (const filePath of removed) console.log(` - ${filePath}`);
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
if (modified.length) {
|
|
371
|
-
console.log(`\n${modified.length} modified:`);
|
|
372
|
-
for (const filePath of modified) console.log(` ~ ${filePath}`);
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
const affectedFolders = new Set(['.']);
|
|
376
|
-
for (const filePath of [...added, ...removed, ...modified]) {
|
|
377
|
-
const parts = filePath.split('/').slice(0, -1);
|
|
378
|
-
for (let i = 0; i < parts.length; i++) {
|
|
379
|
-
affectedFolders.add(parts.slice(0, i + 1).join('/'));
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
const sortedFolders = [...affectedFolders].sort();
|
|
384
|
-
console.log(`\n${sortedFolders.length} folders affected:`);
|
|
385
|
-
for (const folder of sortedFolders) {
|
|
386
|
-
console.log(` ${folder}/`);
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
return 0;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
export function cmdUpdate({ root }) {
|
|
393
|
-
const resolvedRoot = path.resolve(root);
|
|
394
|
-
const state = loadState(resolvedRoot);
|
|
395
|
-
if (!state) {
|
|
396
|
-
console.error("No codemap state found. Run 'init' first.");
|
|
397
|
-
return 1;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
const metadata = state.metadata ?? {};
|
|
401
|
-
const includePatterns = metadata.include_patterns ?? ['**/*'];
|
|
402
|
-
const excludePatterns = metadata.exclude_patterns ?? [];
|
|
403
|
-
const exceptions = metadata.exceptions ?? [];
|
|
404
|
-
const gitignore = loadGitignore(resolvedRoot);
|
|
405
|
-
|
|
406
|
-
const selectedFiles = selectFiles(
|
|
407
|
-
resolvedRoot,
|
|
408
|
-
includePatterns,
|
|
409
|
-
excludePatterns,
|
|
410
|
-
exceptions,
|
|
411
|
-
gitignore,
|
|
412
|
-
);
|
|
413
|
-
|
|
414
|
-
const { state: nextState } = buildState(
|
|
415
|
-
resolvedRoot,
|
|
416
|
-
includePatterns,
|
|
417
|
-
excludePatterns,
|
|
418
|
-
exceptions,
|
|
419
|
-
selectedFiles,
|
|
420
|
-
);
|
|
421
|
-
|
|
422
|
-
saveState(resolvedRoot, nextState);
|
|
423
|
-
console.log(
|
|
424
|
-
`Updated ${STATE_DIR}/${STATE_FILE} with ${selectedFiles.length} files`,
|
|
425
|
-
);
|
|
426
|
-
return 0;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
export function parseArgs(argv) {
|
|
430
|
-
const [command, ...rest] = argv;
|
|
431
|
-
const options = { include: [], exclude: [], exception: [] };
|
|
432
|
-
|
|
433
|
-
for (let i = 0; i < rest.length; i++) {
|
|
434
|
-
const arg = rest[i];
|
|
435
|
-
const value = rest[i + 1];
|
|
436
|
-
|
|
437
|
-
if (!arg?.startsWith('--')) continue;
|
|
438
|
-
if (value === undefined || value.startsWith('--')) {
|
|
439
|
-
throw new Error(`Missing value for ${arg}`);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
const key = arg.slice(2);
|
|
443
|
-
if (key === 'include' || key === 'exclude' || key === 'exception') {
|
|
444
|
-
options[key].push(value);
|
|
445
|
-
} else if (key === 'root') {
|
|
446
|
-
options.root = value;
|
|
447
|
-
} else {
|
|
448
|
-
throw new Error(`Unknown option: ${arg}`);
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
i++;
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
return { command, options };
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
export function main(argv = process.argv.slice(2)) {
|
|
458
|
-
try {
|
|
459
|
-
const { command, options } = parseArgs(argv);
|
|
460
|
-
|
|
461
|
-
if (!command || !options.root) {
|
|
462
|
-
console.error(
|
|
463
|
-
'Usage: codemap.mjs <init|changes|update> --root /path [--include glob] [--exclude glob] [--exception path]',
|
|
464
|
-
);
|
|
465
|
-
return 1;
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
if (command === 'init') return cmdInit(options);
|
|
469
|
-
if (command === 'changes') return cmdChanges(options);
|
|
470
|
-
if (command === 'update') return cmdUpdate(options);
|
|
471
|
-
|
|
472
|
-
console.error(`Unknown command: ${command}`);
|
|
473
|
-
return 1;
|
|
474
|
-
} catch (error) {
|
|
475
|
-
console.error(error instanceof Error ? error.message : String(error));
|
|
476
|
-
return 1;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
const currentFilePath = fileURLToPath(import.meta.url);
|
|
481
|
-
if (process.argv[1] && path.resolve(process.argv[1]) === currentFilePath) {
|
|
482
|
-
process.exit(main());
|
|
483
|
-
}
|
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { afterEach, describe, expect, mock, test } from 'bun:test';
|
|
2
|
-
import {
|
|
3
|
-
existsSync,
|
|
4
|
-
mkdirSync,
|
|
5
|
-
mkdtempSync,
|
|
6
|
-
readFileSync,
|
|
7
|
-
rmSync,
|
|
8
|
-
writeFileSync,
|
|
9
|
-
} from 'node:fs';
|
|
10
|
-
import os from 'node:os';
|
|
11
|
-
import path from 'node:path';
|
|
12
|
-
|
|
13
|
-
mock.restore();
|
|
14
|
-
|
|
15
|
-
const {
|
|
16
|
-
computeFileHash,
|
|
17
|
-
computeFolderHash,
|
|
18
|
-
loadState,
|
|
19
|
-
PatternMatcher,
|
|
20
|
-
selectFiles,
|
|
21
|
-
} = await import('./codemap.mjs');
|
|
22
|
-
|
|
23
|
-
const tempDirs: string[] = [];
|
|
24
|
-
|
|
25
|
-
function createTempDir() {
|
|
26
|
-
const dir = mkdtempSync(path.join(os.tmpdir(), 'codemap-'));
|
|
27
|
-
tempDirs.push(dir);
|
|
28
|
-
return dir;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
afterEach(() => {
|
|
32
|
-
for (const dir of tempDirs.splice(0)) {
|
|
33
|
-
rmSync(dir, { force: true, recursive: true });
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
describe('PatternMatcher', () => {
|
|
38
|
-
test('matches expected paths', () => {
|
|
39
|
-
const matcher = new PatternMatcher([
|
|
40
|
-
'node_modules/',
|
|
41
|
-
'dist/',
|
|
42
|
-
'*.log',
|
|
43
|
-
'src/**/*.ts',
|
|
44
|
-
]);
|
|
45
|
-
|
|
46
|
-
expect(matcher.matches('node_modules/foo.js')).toBe(true);
|
|
47
|
-
expect(matcher.matches('vendor/node_modules/bar.js')).toBe(true);
|
|
48
|
-
expect(matcher.matches('dist/main.js')).toBe(true);
|
|
49
|
-
expect(matcher.matches('src/dist/output.js')).toBe(true);
|
|
50
|
-
expect(matcher.matches('error.log')).toBe(true);
|
|
51
|
-
expect(matcher.matches('logs/access.log')).toBe(true);
|
|
52
|
-
expect(matcher.matches('src/index.ts')).toBe(true);
|
|
53
|
-
expect(matcher.matches('src/utils/helper.ts')).toBe(true);
|
|
54
|
-
expect(matcher.matches('README.md')).toBe(false);
|
|
55
|
-
expect(matcher.matches('tests/test.py')).toBe(false);
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
describe('hash helpers', () => {
|
|
60
|
-
test('computes file hash', () => {
|
|
61
|
-
const dir = createTempDir();
|
|
62
|
-
const filePath = path.join(dir, 'file.txt');
|
|
63
|
-
writeFileSync(filePath, 'test content');
|
|
64
|
-
|
|
65
|
-
expect(computeFileHash(filePath)).toBe('9473fdd0d880a43c21b7778d34872157');
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test('computes stable folder hash', () => {
|
|
69
|
-
const fileHashes = {
|
|
70
|
-
'src/a.ts': 'hash-a',
|
|
71
|
-
'src/b.ts': 'hash-b',
|
|
72
|
-
'tests/test.ts': 'hash-test',
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const hash1 = computeFolderHash('src', fileHashes);
|
|
76
|
-
const hash2 = computeFolderHash('src', fileHashes);
|
|
77
|
-
const hash3 = computeFolderHash('src', {
|
|
78
|
-
'src/a.ts': 'hash-a-modified',
|
|
79
|
-
'src/b.ts': 'hash-b',
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
expect(hash1).toBe(hash2);
|
|
83
|
-
expect(hash1).not.toBe(hash3);
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
describe('selectFiles', () => {
|
|
88
|
-
test('respects include and exclude patterns', () => {
|
|
89
|
-
const root = createTempDir();
|
|
90
|
-
mkdirSync(path.join(root, 'src'));
|
|
91
|
-
mkdirSync(path.join(root, 'node_modules'));
|
|
92
|
-
writeFileSync(path.join(root, 'src', 'index.ts'), 'code');
|
|
93
|
-
writeFileSync(path.join(root, 'src', 'index.test.ts'), 'test');
|
|
94
|
-
writeFileSync(path.join(root, 'node_modules', 'foo.js'), 'dep');
|
|
95
|
-
writeFileSync(path.join(root, 'package.json'), '{}');
|
|
96
|
-
|
|
97
|
-
const selected = selectFiles(
|
|
98
|
-
root,
|
|
99
|
-
['src/**/*.ts', 'package.json'],
|
|
100
|
-
['**/*.test.ts', 'node_modules/'],
|
|
101
|
-
[],
|
|
102
|
-
[],
|
|
103
|
-
).map((filePath) =>
|
|
104
|
-
path.relative(root, filePath).split(path.sep).join('/'),
|
|
105
|
-
);
|
|
106
|
-
|
|
107
|
-
expect(selected).toEqual(['package.json', 'src/index.ts']);
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
describe('loadState', () => {
|
|
112
|
-
test('migrates legacy cartography state', () => {
|
|
113
|
-
const root = createTempDir();
|
|
114
|
-
const slimDir = path.join(root, '.slim');
|
|
115
|
-
mkdirSync(slimDir);
|
|
116
|
-
|
|
117
|
-
const legacyState = { metadata: { version: '1.0.0' } };
|
|
118
|
-
writeFileSync(
|
|
119
|
-
path.join(slimDir, 'cartography.json'),
|
|
120
|
-
JSON.stringify(legacyState),
|
|
121
|
-
);
|
|
122
|
-
|
|
123
|
-
expect(loadState(root)).toEqual(legacyState);
|
|
124
|
-
expect(existsSync(path.join(slimDir, 'cartography.json'))).toBe(false);
|
|
125
|
-
expect(
|
|
126
|
-
JSON.parse(readFileSync(path.join(slimDir, 'codemap.json'), 'utf8')),
|
|
127
|
-
).toEqual(legacyState);
|
|
128
|
-
});
|
|
129
|
-
});
|
package/src/skills/codemap.md
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# src/skills/
|
|
2
|
-
|
|
3
|
-
## Responsibility
|
|
4
|
-
|
|
5
|
-
- Own metadata-driven OpenCode custom skills shipped with this package.
|
|
6
|
-
- Maintain the skill contract artifacts (`SKILL.md`, `README.md`, per-skill helper files) that are copied into
|
|
7
|
-
`${configDir}/skills` at install time.
|
|
8
|
-
- Preserve a canonical registry boundary: runtime code consumes skill definitions as data, not as executable
|
|
9
|
-
plugin dependencies.
|
|
10
|
-
|
|
11
|
-
## Design
|
|
12
|
-
|
|
13
|
-
- `CUSTOM_SKILLS` in `src/cli/custom-skills.ts` is the authoritative skill manifest for bundled
|
|
14
|
-
skills; each entry maps folder name + `sourcePath` to an install-time consumer.
|
|
15
|
-
- `install.ts` runs `installCustomSkill()` which recursively copies `src/skills/codemap` and
|
|
16
|
-
`src/skills/simplify` into the OpenCode skills directory.
|
|
17
|
-
- This directory is partitioned by skill:
|
|
18
|
-
- `src/skills/codemap/` (command-style repository mapping skill)
|
|
19
|
-
- `src/skills/simplify/` (readability/refactor guidance skill)
|
|
20
|
-
- Files are considered static runtime payload. No plugin TS module in `src/` imports these files directly; they
|
|
21
|
-
are loaded by OpenCode via filesystem installation.
|
|
22
|
-
|
|
23
|
-
## Flow
|
|
24
|
-
|
|
25
|
-
- `bun run install` delegates to `src/cli/install.ts`, where `installCustomSkills` gates copying of
|
|
26
|
-
each `CUSTOM_SKILLS` entry.
|
|
27
|
-
- `installCustomSkill()` computes `packageRoot`, validates `sourcePath`, then performs a recursive
|
|
28
|
-
directory copy via `copyDirRecursive()`.
|
|
29
|
-
- During plugin release, the `files` whitelist in `package.json` must include `src/skills` so
|
|
30
|
-
`src/skills/**` survive `npm pack`.
|
|
31
|
-
- OpenCode plugin startup discovers these installed folders and reads each `SKILL.md` as a prompt-level contract.
|
|
32
|
-
|
|
33
|
-
## Integration
|
|
34
|
-
|
|
35
|
-
- `src/cli/custom-skills.ts`: source-of-truth registry consumed by installer and permission helpers.
|
|
36
|
-
- `src/cli/skills.ts:getSkillPermissionsForAgent()` auto-populates permission rules for
|
|
37
|
-
`codemap` and `simplify` when agent policy is derived from built-in recommendations.
|
|
38
|
-
- `verify-release-artifact.ts` enforces artifact completeness by asserting `src/skills/simplify/SKILL.md`
|
|
39
|
-
and `src/skills/codemap/SKILL.md` are present in the tarball.
|
|
40
|
-
- `package.json` scripts (`verify:release`, `build`) rely on these assets to ensure install-time skill availability.
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# Simplify Skill
|
|
2
|
-
|
|
3
|
-
Behavior-preserving code simplification and readability-focused refactoring.
|
|
4
|
-
|
|
5
|
-
## Overview
|
|
6
|
-
|
|
7
|
-
This bundled skill helps the `reviewer` agent review and simplify code without
|
|
8
|
-
changing behavior. It focuses on readability, maintainability, and reducing
|
|
9
|
-
unnecessary complexity.
|
|
10
|
-
|
|
11
|
-
## Source
|
|
12
|
-
|
|
13
|
-
Adapted from Addy Osmani's
|
|
14
|
-
[`code-simplification` skill](https://github.com/addyosmani/agent-skills/blob/main/skills/code-simplification/SKILL.md).
|
|
15
|
-
|
|
16
|
-
## Installation
|
|
17
|
-
|
|
18
|
-
Bundled with `agent-forge` and installed automatically when bundled
|
|
19
|
-
skills are enabled via the installer.
|