@mthanhlm/autodev 0.4.4 → 0.5.0
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-plugin/plugin.json +2 -2
- package/PUBLISH.md +9 -40
- package/README.md +70 -104
- package/autodev/bin/autodev-tools.cjs +521 -990
- package/autodev/templates/brief.md +19 -0
- package/autodev/templates/context.md +16 -0
- package/autodev/templates/plan.md +26 -46
- package/autodev/templates/run.md +20 -0
- package/bin/install.js +219 -422
- package/commands/autodev/index.md +117 -9
- package/commands/autodev/status.md +22 -0
- package/hooks/autodev-auto-format.js +3 -3
- package/hooks/autodev-git-guard.js +5 -7
- package/hooks/autodev-paths.js +3 -3
- package/package.json +4 -5
- package/scripts/run-tests.cjs +10 -0
- package/agents/autodev-codebase-domain.md +0 -25
- package/agents/autodev-codebase-quality.md +0 -25
- package/agents/autodev-codebase-runtime.md +0 -25
- package/agents/autodev-codebase-structure.md +0 -25
- package/agents/autodev-review-integration.md +0 -30
- package/agents/autodev-review-polish.md +0 -30
- package/agents/autodev-review-quality.md +0 -30
- package/agents/autodev-review-security.md +0 -30
- package/agents/autodev-task-worker.md +0 -39
- package/autodev/templates/codebase/domain.md +0 -13
- package/autodev/templates/codebase/quality.md +0 -13
- package/autodev/templates/codebase/runtime.md +0 -13
- package/autodev/templates/codebase/structure.md +0 -13
- package/autodev/templates/codebase/summary.md +0 -13
- package/autodev/templates/config.json +0 -22
- package/autodev/templates/project-state.md +0 -15
- package/autodev/templates/project.md +0 -24
- package/autodev/templates/requirements.md +0 -14
- package/autodev/templates/review.md +0 -27
- package/autodev/templates/roadmap.md +0 -17
- package/autodev/templates/state.md +0 -15
- package/autodev/templates/summary.md +0 -22
- package/autodev/templates/task-summary.md +0 -18
- package/autodev/templates/task.md +0 -23
- package/autodev/templates/track-state.md +0 -16
- package/autodev/templates/track.md +0 -24
- package/autodev/templates/uat.md +0 -18
- package/autodev/workflows/autodev-auto.md +0 -62
- package/autodev/workflows/autodev.md +0 -83
- package/autodev/workflows/cleanup.md +0 -51
- package/autodev/workflows/execute-phase.md +0 -144
- package/autodev/workflows/explore-codebase.md +0 -70
- package/autodev/workflows/help.md +0 -113
- package/autodev/workflows/new-project.md +0 -108
- package/autodev/workflows/plan-phase.md +0 -134
- package/autodev/workflows/progress.md +0 -18
- package/autodev/workflows/review-phase.md +0 -80
- package/autodev/workflows/review-plan.md +0 -55
- package/autodev/workflows/review-task.md +0 -70
- package/autodev/workflows/verify-work.md +0 -71
- package/commands/autodev/auto.md +0 -27
- package/commands/autodev/cleanup.md +0 -23
- package/commands/autodev/execute-phase.md +0 -29
- package/commands/autodev/explore-codebase.md +0 -33
- package/commands/autodev/help.md +0 -18
- package/commands/autodev/new-project.md +0 -30
- package/commands/autodev/plan-phase.md +0 -26
- package/commands/autodev/progress.md +0 -18
- package/commands/autodev/review-phase.md +0 -29
- package/commands/autodev/review-task.md +0 -25
- package/commands/autodev/verify-work.md +0 -24
- package/hooks/autodev-context-monitor.js +0 -59
- package/hooks/autodev-phase-boundary.sh +0 -49
- package/hooks/autodev-prompt-guard.js +0 -78
- package/hooks/autodev-read-guard.js +0 -42
- package/hooks/autodev-session-state.sh +0 -51
- package/hooks/autodev-statusline.js +0 -83
- package/hooks/autodev-workflow-guard.js +0 -43
- package/hooks/hooks.json +0 -89
package/bin/install.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
4
|
const os = require('os');
|
|
6
|
-
const
|
|
5
|
+
const path = require('path');
|
|
7
6
|
|
|
8
7
|
const green = '\x1b[32m';
|
|
9
8
|
const yellow = '\x1b[33m';
|
|
@@ -11,18 +10,45 @@ const cyan = '\x1b[36m';
|
|
|
11
10
|
const reset = '\x1b[0m';
|
|
12
11
|
|
|
13
12
|
const ROOT_COMMAND = 'autodev';
|
|
14
|
-
const
|
|
15
|
-
|
|
13
|
+
const SUPPORTED_SCOPE = 'global';
|
|
14
|
+
const LEGACY_SKILL_NAMES = [
|
|
15
|
+
'autodev',
|
|
16
|
+
'autodev-auto',
|
|
17
|
+
'autodev-cleanup',
|
|
18
|
+
'autodev-execute-phase',
|
|
19
|
+
'autodev-explore-codebase',
|
|
20
|
+
'autodev-help',
|
|
21
|
+
'autodev-new-project',
|
|
22
|
+
'autodev-plan-phase',
|
|
23
|
+
'autodev-progress',
|
|
24
|
+
'autodev-review-phase',
|
|
25
|
+
'autodev-review-task',
|
|
26
|
+
'autodev-status',
|
|
27
|
+
'autodev-verify-work'
|
|
28
|
+
];
|
|
29
|
+
const LEGACY_AGENT_FILES = [
|
|
30
|
+
'autodev-codebase-domain.md',
|
|
31
|
+
'autodev-codebase-quality.md',
|
|
32
|
+
'autodev-codebase-runtime.md',
|
|
33
|
+
'autodev-codebase-structure.md',
|
|
34
|
+
'autodev-review-integration.md',
|
|
35
|
+
'autodev-review-polish.md',
|
|
36
|
+
'autodev-review-quality.md',
|
|
37
|
+
'autodev-review-security.md',
|
|
38
|
+
'autodev-task-worker.md'
|
|
39
|
+
];
|
|
40
|
+
const MANAGED_HOOK_FILES = [
|
|
16
41
|
'autodev-auto-format.js',
|
|
42
|
+
'autodev-git-guard.js',
|
|
17
43
|
'autodev-paths.js',
|
|
18
44
|
'autodev-context-monitor.js',
|
|
19
|
-
'autodev-git-guard.js',
|
|
20
45
|
'autodev-phase-boundary.sh',
|
|
21
46
|
'autodev-prompt-guard.js',
|
|
22
47
|
'autodev-read-guard.js',
|
|
23
48
|
'autodev-session-state.sh',
|
|
24
49
|
'autodev-statusline.js',
|
|
25
|
-
'autodev-workflow-guard.js'
|
|
50
|
+
'autodev-workflow-guard.js',
|
|
51
|
+
'hooks.json'
|
|
26
52
|
];
|
|
27
53
|
|
|
28
54
|
function stripJsonComments(input) {
|
|
@@ -147,7 +173,9 @@ function convertCommandToClaudeSkill(content, skillName) {
|
|
|
147
173
|
const description = extractFrontmatterField(frontmatter, 'description') || '';
|
|
148
174
|
const argumentHint = extractFrontmatterField(frontmatter, 'argument-hint');
|
|
149
175
|
const toolsMatch = frontmatter.match(/^allowed-tools:\s*\n((?:\s+-\s+.+\n?)*)/m);
|
|
150
|
-
const toolsBlock = toolsMatch
|
|
176
|
+
const toolsBlock = toolsMatch
|
|
177
|
+
? `allowed-tools:\n${toolsMatch[1].endsWith('\n') ? toolsMatch[1] : `${toolsMatch[1]}\n`}`
|
|
178
|
+
: '';
|
|
151
179
|
|
|
152
180
|
let rebuilt = `---\nname: ${skillName}\ndescription: ${yamlQuote(description)}\n`;
|
|
153
181
|
if (argumentHint) {
|
|
@@ -172,6 +200,11 @@ function transformInstalledContent(content, pathPrefix) {
|
|
|
172
200
|
.replace(/\.\/\.claude\b/g, bare);
|
|
173
201
|
}
|
|
174
202
|
|
|
203
|
+
function commandNameFromSourceFile(fileName) {
|
|
204
|
+
const baseName = fileName.replace(/\.md$/, '');
|
|
205
|
+
return baseName === 'index' ? ROOT_COMMAND : `${ROOT_COMMAND}-${baseName}`;
|
|
206
|
+
}
|
|
207
|
+
|
|
175
208
|
function copyTextTree(srcDir, destDir, transform) {
|
|
176
209
|
if (!fs.existsSync(srcDir)) {
|
|
177
210
|
return;
|
|
@@ -181,6 +214,7 @@ function copyTextTree(srcDir, destDir, transform) {
|
|
|
181
214
|
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
182
215
|
const srcPath = path.join(srcDir, entry.name);
|
|
183
216
|
const destPath = path.join(destDir, entry.name);
|
|
217
|
+
|
|
184
218
|
if (entry.isDirectory()) {
|
|
185
219
|
copyTextTree(srcPath, destPath, transform);
|
|
186
220
|
continue;
|
|
@@ -194,91 +228,26 @@ function copyTextTree(srcDir, destDir, transform) {
|
|
|
194
228
|
}
|
|
195
229
|
}
|
|
196
230
|
|
|
197
|
-
function commandNameFromSourceFile(fileName) {
|
|
198
|
-
const baseName = fileName.replace(/\.md$/, '');
|
|
199
|
-
return baseName === 'index' ? ROOT_COMMAND : `${ROOT_COMMAND}-${baseName}`;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function skillNameFromSourceFile(fileName, prefix) {
|
|
203
|
-
const baseName = fileName.replace(/\.md$/, '');
|
|
204
|
-
return baseName === 'index' ? prefix : `${prefix}-${baseName}`;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function copyCommandsAsLocal(srcDir, destDir, transform) {
|
|
208
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
209
|
-
|
|
210
|
-
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
211
|
-
if (!entry.isFile() || !entry.name.endsWith('.md')) {
|
|
212
|
-
continue;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const srcPath = path.join(srcDir, entry.name);
|
|
216
|
-
const destPath = path.join(destDir, `${commandNameFromSourceFile(entry.name)}.md`);
|
|
217
|
-
let content = fs.readFileSync(srcPath, 'utf8');
|
|
218
|
-
content = transform(content);
|
|
219
|
-
fs.writeFileSync(destPath, content, 'utf8');
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
231
|
function copyCommandsAsGlobalSkills(srcDir, skillsDir, prefix, transform) {
|
|
224
232
|
fs.mkdirSync(skillsDir, { recursive: true });
|
|
225
|
-
const managedSkillNames = fs.readdirSync(srcDir, { withFileTypes: true })
|
|
226
|
-
.filter(entry => entry.isFile() && entry.name.endsWith('.md'))
|
|
227
|
-
.map(entry => skillNameFromSourceFile(entry.name, prefix));
|
|
228
|
-
|
|
229
|
-
for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
230
|
-
if (entry.isDirectory() && managedSkillNames.includes(entry.name)) {
|
|
231
|
-
fs.rmSync(path.join(skillsDir, entry.name), { recursive: true, force: true });
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
233
|
|
|
235
234
|
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
236
235
|
if (!entry.isFile() || !entry.name.endsWith('.md')) {
|
|
237
236
|
continue;
|
|
238
237
|
}
|
|
239
238
|
|
|
240
|
-
const skillName =
|
|
239
|
+
const skillName = commandNameFromSourceFile(entry.name);
|
|
241
240
|
const skillDir = path.join(skillsDir, skillName);
|
|
242
241
|
fs.mkdirSync(skillDir, { recursive: true });
|
|
243
242
|
|
|
244
243
|
let content = fs.readFileSync(path.join(srcDir, entry.name), 'utf8');
|
|
245
244
|
content = transform(content);
|
|
246
|
-
content = convertCommandToClaudeSkill(content, skillName);
|
|
245
|
+
content = convertCommandToClaudeSkill(content, skillName === prefix ? prefix : skillName);
|
|
247
246
|
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), content, 'utf8');
|
|
248
247
|
}
|
|
249
248
|
}
|
|
250
249
|
|
|
251
|
-
function
|
|
252
|
-
if (!fs.existsSync(srcDir)) {
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
257
|
-
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
258
|
-
if (!entry.isFile()) {
|
|
259
|
-
continue;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
const srcPath = path.join(srcDir, entry.name);
|
|
263
|
-
const destPath = path.join(destDir, entry.name);
|
|
264
|
-
let content = fs.readFileSync(srcPath, 'utf8');
|
|
265
|
-
content = transform(content);
|
|
266
|
-
fs.writeFileSync(destPath, content, 'utf8');
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
function managedAgents(srcRoot = path.resolve(__dirname, '..')) {
|
|
271
|
-
const agentsDir = path.join(srcRoot, 'agents');
|
|
272
|
-
if (!fs.existsSync(agentsDir)) {
|
|
273
|
-
return [];
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
return fs.readdirSync(agentsDir, { withFileTypes: true })
|
|
277
|
-
.filter(entry => entry.isFile() && entry.name.endsWith('.md'))
|
|
278
|
-
.map(entry => entry.name);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function managedCommandNames(srcRoot = path.resolve(__dirname, '..')) {
|
|
250
|
+
function currentManagedSkillNames(srcRoot) {
|
|
282
251
|
const commandsDir = path.join(srcRoot, 'commands', 'autodev');
|
|
283
252
|
if (!fs.existsSync(commandsDir)) {
|
|
284
253
|
return [];
|
|
@@ -289,102 +258,27 @@ function managedCommandNames(srcRoot = path.resolve(__dirname, '..')) {
|
|
|
289
258
|
.map(entry => commandNameFromSourceFile(entry.name));
|
|
290
259
|
}
|
|
291
260
|
|
|
292
|
-
function managedSkillNames(srcRoot
|
|
293
|
-
|
|
294
|
-
if (!fs.existsSync(commandsDir)) {
|
|
295
|
-
return [];
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
return fs.readdirSync(commandsDir, { withFileTypes: true })
|
|
299
|
-
.filter(entry => entry.isFile() && entry.name.endsWith('.md'))
|
|
300
|
-
.map(entry => skillNameFromSourceFile(entry.name, ROOT_COMMAND));
|
|
261
|
+
function managedSkillNames(srcRoot) {
|
|
262
|
+
return [...new Set([...currentManagedSkillNames(srcRoot), ...LEGACY_SKILL_NAMES])];
|
|
301
263
|
}
|
|
302
264
|
|
|
303
|
-
function managedCommandStrings(targetDir
|
|
304
|
-
return
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
: buildLocalCommand('autodev-session-state.sh', 'bash'),
|
|
312
|
-
isGlobal
|
|
313
|
-
? buildGlobalCommand(targetDir, 'autodev-prompt-guard.js')
|
|
314
|
-
: buildLocalCommand('autodev-prompt-guard.js'),
|
|
315
|
-
isGlobal
|
|
316
|
-
? buildGlobalCommand(targetDir, 'autodev-read-guard.js')
|
|
317
|
-
: buildLocalCommand('autodev-read-guard.js'),
|
|
318
|
-
isGlobal
|
|
319
|
-
? buildGlobalCommand(targetDir, 'autodev-workflow-guard.js')
|
|
320
|
-
: buildLocalCommand('autodev-workflow-guard.js'),
|
|
321
|
-
isGlobal
|
|
322
|
-
? buildGlobalCommand(targetDir, 'autodev-git-guard.js')
|
|
323
|
-
: buildLocalCommand('autodev-git-guard.js'),
|
|
324
|
-
isGlobal
|
|
325
|
-
? buildGlobalCommand(targetDir, 'autodev-auto-format.js')
|
|
326
|
-
: buildLocalCommand('autodev-auto-format.js'),
|
|
327
|
-
isGlobal
|
|
328
|
-
? buildGlobalCommand(targetDir, 'autodev-context-monitor.js')
|
|
329
|
-
: buildLocalCommand('autodev-context-monitor.js'),
|
|
330
|
-
isGlobal
|
|
331
|
-
? buildGlobalCommand(targetDir, 'autodev-phase-boundary.sh', 'bash')
|
|
332
|
-
: buildLocalCommand('autodev-phase-boundary.sh', 'bash')
|
|
333
|
-
]
|
|
334
|
-
};
|
|
265
|
+
function managedCommandStrings(targetDir) {
|
|
266
|
+
return [
|
|
267
|
+
`node "${path.join(targetDir, 'hooks', 'autodev-git-guard.js').replace(/\\/g, '/')}"`,
|
|
268
|
+
`node "${path.join(targetDir, 'hooks', 'autodev-auto-format.js').replace(/\\/g, '/')}"`,
|
|
269
|
+
...MANAGED_HOOK_FILES
|
|
270
|
+
.filter(fileName => fileName.endsWith('.js') || fileName.endsWith('.sh'))
|
|
271
|
+
.map(fileName => path.join(targetDir, 'hooks', fileName).replace(/\\/g, '/'))
|
|
272
|
+
];
|
|
335
273
|
}
|
|
336
274
|
|
|
337
|
-
function isManagedCommand(command,
|
|
275
|
+
function isManagedCommand(command, targetDir) {
|
|
338
276
|
if (typeof command !== 'string') {
|
|
339
277
|
return false;
|
|
340
278
|
}
|
|
341
279
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
return /(?:^|[/"\s])autodev-(?:auto-format|context-monitor|git-guard|phase-boundary|prompt-guard|read-guard|session-state|statusline|workflow-guard)\.(?:js|sh)(?:"|$)/.test(command);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
function removeManagedSettings(settings, options = {}) {
|
|
350
|
-
const managedStrings = options.targetDir
|
|
351
|
-
? managedCommandStrings(options.targetDir, Boolean(options.isGlobal))
|
|
352
|
-
: null;
|
|
353
|
-
|
|
354
|
-
if (settings.statusLine?.command && isManagedCommand(settings.statusLine.command, managedStrings ? [managedStrings.statusLine] : null)) {
|
|
355
|
-
delete settings.statusLine;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
if (settings.env && typeof settings.env === 'object') {
|
|
359
|
-
delete settings.env.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS;
|
|
360
|
-
if (Object.keys(settings.env).length === 0) {
|
|
361
|
-
delete settings.env;
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (!settings.hooks || typeof settings.hooks !== 'object') {
|
|
366
|
-
return settings;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
for (const eventName of Object.keys(settings.hooks)) {
|
|
370
|
-
const entries = Array.isArray(settings.hooks[eventName]) ? settings.hooks[eventName] : [];
|
|
371
|
-
const filtered = entries.filter(entry => {
|
|
372
|
-
const hooks = Array.isArray(entry?.hooks) ? entry.hooks : [];
|
|
373
|
-
return !hooks.some(hook => isManagedCommand(hook?.command, managedStrings?.hooks || null));
|
|
374
|
-
});
|
|
375
|
-
|
|
376
|
-
if (filtered.length > 0) {
|
|
377
|
-
settings.hooks[eventName] = filtered;
|
|
378
|
-
} else {
|
|
379
|
-
delete settings.hooks[eventName];
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
if (Object.keys(settings.hooks).length === 0) {
|
|
384
|
-
delete settings.hooks;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
return settings;
|
|
280
|
+
return managedCommandStrings(targetDir).some(pattern => command.includes(pattern))
|
|
281
|
+
|| /autodev-(?:auto-format|context-monitor|git-guard|phase-boundary|prompt-guard|read-guard|session-state|statusline|workflow-guard)\.(?:js|sh)/.test(command);
|
|
388
282
|
}
|
|
389
283
|
|
|
390
284
|
function ensureHook(settings, eventName, matcher, command, timeout) {
|
|
@@ -415,360 +309,263 @@ function ensureHook(settings, eventName, matcher, command, timeout) {
|
|
|
415
309
|
settings.hooks[eventName].push(entry);
|
|
416
310
|
}
|
|
417
311
|
|
|
418
|
-
function
|
|
419
|
-
if (!settings
|
|
420
|
-
|
|
312
|
+
function removeManagedSettings(settings, targetDir) {
|
|
313
|
+
if (!settings || typeof settings !== 'object') {
|
|
314
|
+
return {};
|
|
421
315
|
}
|
|
422
|
-
settings.env[name] = String(value);
|
|
423
|
-
}
|
|
424
316
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
path.join(targetDir, 'hooks', 'autodev-session-state.sh'),
|
|
429
|
-
path.join(targetDir, 'autodev', 'bin', 'autodev-tools.cjs')
|
|
430
|
-
];
|
|
317
|
+
if (settings.statusLine?.command && isManagedCommand(settings.statusLine.command, targetDir)) {
|
|
318
|
+
delete settings.statusLine;
|
|
319
|
+
}
|
|
431
320
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
321
|
+
if (settings.env && typeof settings.env === 'object') {
|
|
322
|
+
delete settings.env.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS;
|
|
323
|
+
if (Object.keys(settings.env).length === 0) {
|
|
324
|
+
delete settings.env;
|
|
435
325
|
}
|
|
436
326
|
}
|
|
437
|
-
}
|
|
438
327
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
328
|
+
if (!settings.hooks || typeof settings.hooks !== 'object') {
|
|
329
|
+
return settings;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
for (const eventName of Object.keys(settings.hooks)) {
|
|
333
|
+
const entries = Array.isArray(settings.hooks[eventName]) ? settings.hooks[eventName] : [];
|
|
334
|
+
const filtered = entries.filter(entry => {
|
|
335
|
+
const hooks = Array.isArray(entry?.hooks) ? entry.hooks : [];
|
|
336
|
+
return !hooks.some(hook => isManagedCommand(hook?.command, targetDir));
|
|
337
|
+
});
|
|
443
338
|
|
|
444
|
-
|
|
445
|
-
|
|
339
|
+
if (filtered.length > 0) {
|
|
340
|
+
settings.hooks[eventName] = filtered;
|
|
341
|
+
} else {
|
|
342
|
+
delete settings.hooks[eventName];
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (settings.hooks && Object.keys(settings.hooks).length === 0) {
|
|
347
|
+
delete settings.hooks;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return settings;
|
|
446
351
|
}
|
|
447
352
|
|
|
448
|
-
function configureSettings(targetDir
|
|
353
|
+
function configureSettings(targetDir) {
|
|
449
354
|
const settingsPath = path.join(targetDir, 'settings.json');
|
|
450
355
|
const settings = readSettings(settingsPath);
|
|
451
356
|
if (settings === null) {
|
|
452
357
|
return null;
|
|
453
358
|
}
|
|
454
359
|
|
|
455
|
-
removeManagedSettings(settings,
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
? buildGlobalCommand(targetDir, 'autodev-workflow-guard.js')
|
|
475
|
-
: buildLocalCommand('autodev-workflow-guard.js');
|
|
476
|
-
const gitGuardCommand = isGlobal
|
|
477
|
-
? buildGlobalCommand(targetDir, 'autodev-git-guard.js')
|
|
478
|
-
: buildLocalCommand('autodev-git-guard.js');
|
|
479
|
-
const sessionStateCommand = isGlobal
|
|
480
|
-
? buildGlobalCommand(targetDir, 'autodev-session-state.sh', 'bash')
|
|
481
|
-
: buildLocalCommand('autodev-session-state.sh', 'bash');
|
|
482
|
-
const phaseBoundaryCommand = isGlobal
|
|
483
|
-
? buildGlobalCommand(targetDir, 'autodev-phase-boundary.sh', 'bash')
|
|
484
|
-
: buildLocalCommand('autodev-phase-boundary.sh', 'bash');
|
|
485
|
-
const statusLineCommand = isGlobal
|
|
486
|
-
? buildGlobalCommand(targetDir, 'autodev-statusline.js')
|
|
487
|
-
: buildLocalCommand('autodev-statusline.js');
|
|
488
|
-
|
|
489
|
-
ensureHook(settings, sessionStartEvent, null, sessionStateCommand);
|
|
490
|
-
ensureHook(settings, preToolEvent, 'Write|Edit|MultiEdit', promptGuardCommand, 5);
|
|
491
|
-
ensureHook(settings, preToolEvent, 'Write|Edit|MultiEdit', readGuardCommand, 5);
|
|
492
|
-
ensureHook(settings, preToolEvent, 'Write|Edit|MultiEdit', workflowGuardCommand, 5);
|
|
493
|
-
ensureHook(settings, preToolEvent, 'Bash', gitGuardCommand, 5);
|
|
494
|
-
ensureHook(settings, postToolEvent, 'Edit|Write|MultiEdit', autoFormatCommand, 10);
|
|
495
|
-
ensureHook(settings, postToolEvent, 'Bash|Edit|Write|MultiEdit|Agent|Task', contextMonitorCommand, 10);
|
|
496
|
-
ensureHook(settings, postToolEvent, 'Write|Edit|MultiEdit', phaseBoundaryCommand, 5);
|
|
497
|
-
|
|
498
|
-
if (options.installStatusLine !== false) {
|
|
499
|
-
settings.statusLine = {
|
|
500
|
-
type: 'command',
|
|
501
|
-
command: statusLineCommand
|
|
502
|
-
};
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
if (options.disableBackgroundTasks) {
|
|
506
|
-
ensureEnvSetting(settings, 'CLAUDE_CODE_DISABLE_BACKGROUND_TASKS', '1');
|
|
360
|
+
removeManagedSettings(settings, targetDir);
|
|
361
|
+
|
|
362
|
+
ensureHook(
|
|
363
|
+
settings,
|
|
364
|
+
'PreToolUse',
|
|
365
|
+
'Bash',
|
|
366
|
+
`node "${path.join(targetDir, 'hooks', 'autodev-git-guard.js').replace(/\\/g, '/')}"`,
|
|
367
|
+
5
|
|
368
|
+
);
|
|
369
|
+
ensureHook(
|
|
370
|
+
settings,
|
|
371
|
+
'PostToolUse',
|
|
372
|
+
'Edit|Write|MultiEdit',
|
|
373
|
+
`node "${path.join(targetDir, 'hooks', 'autodev-auto-format.js').replace(/\\/g, '/')}"`,
|
|
374
|
+
10
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
if (!settings.env || typeof settings.env !== 'object') {
|
|
378
|
+
settings.env = {};
|
|
507
379
|
}
|
|
380
|
+
settings.env.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS = '1';
|
|
508
381
|
|
|
509
382
|
writeSettings(settingsPath, settings);
|
|
510
383
|
return settingsPath;
|
|
511
384
|
}
|
|
512
385
|
|
|
513
|
-
function
|
|
514
|
-
const srcRoot = path.resolve(__dirname, '..');
|
|
386
|
+
function removeManagedFiles(targetDir) {
|
|
515
387
|
const supportDir = path.join(targetDir, 'autodev');
|
|
516
388
|
if (fs.existsSync(supportDir)) {
|
|
517
389
|
fs.rmSync(supportDir, { recursive: true, force: true });
|
|
518
390
|
}
|
|
519
391
|
|
|
520
|
-
const
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
fs.rmSync(agentPath, { force: true });
|
|
526
|
-
}
|
|
392
|
+
const hooksDir = path.join(targetDir, 'hooks');
|
|
393
|
+
for (const fileName of MANAGED_HOOK_FILES) {
|
|
394
|
+
const hookPath = path.join(hooksDir, fileName);
|
|
395
|
+
if (fs.existsSync(hookPath)) {
|
|
396
|
+
fs.rmSync(hookPath, { recursive: true, force: true });
|
|
527
397
|
}
|
|
528
398
|
}
|
|
529
399
|
|
|
530
|
-
const
|
|
531
|
-
for (const
|
|
532
|
-
const
|
|
533
|
-
if (fs.existsSync(
|
|
534
|
-
fs.rmSync(
|
|
400
|
+
const agentsDir = path.join(targetDir, 'agents');
|
|
401
|
+
for (const fileName of LEGACY_AGENT_FILES) {
|
|
402
|
+
const agentPath = path.join(agentsDir, fileName);
|
|
403
|
+
if (fs.existsSync(agentPath)) {
|
|
404
|
+
fs.rmSync(agentPath, { force: true });
|
|
535
405
|
}
|
|
536
406
|
}
|
|
537
407
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
if (fs.existsSync(skillPath)) {
|
|
544
|
-
fs.rmSync(skillPath, { recursive: true, force: true });
|
|
545
|
-
}
|
|
546
|
-
}
|
|
408
|
+
const skillsDir = path.join(targetDir, 'skills');
|
|
409
|
+
for (const skillName of managedSkillNames(path.resolve(__dirname, '..'))) {
|
|
410
|
+
const skillPath = path.join(skillsDir, skillName);
|
|
411
|
+
if (fs.existsSync(skillPath)) {
|
|
412
|
+
fs.rmSync(skillPath, { recursive: true, force: true });
|
|
547
413
|
}
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const legacyCommandsDir = path.join(targetDir, 'commands');
|
|
417
|
+
if (fs.existsSync(legacyCommandsDir)) {
|
|
418
|
+
for (const skillName of managedSkillNames(path.resolve(__dirname, '..'))) {
|
|
419
|
+
const commandPath = path.join(legacyCommandsDir, `${skillName}.md`);
|
|
420
|
+
if (fs.existsSync(commandPath)) {
|
|
421
|
+
fs.rmSync(commandPath, { force: true });
|
|
556
422
|
}
|
|
557
423
|
}
|
|
558
|
-
const
|
|
559
|
-
if (fs.existsSync(
|
|
560
|
-
fs.rmSync(
|
|
561
|
-
}
|
|
562
|
-
const skillsDir = path.join(targetDir, 'skills');
|
|
563
|
-
if (fs.existsSync(skillsDir)) {
|
|
564
|
-
for (const skillName of managedSkillNames(srcRoot)) {
|
|
565
|
-
const skillPath = path.join(skillsDir, skillName);
|
|
566
|
-
if (fs.existsSync(skillPath)) {
|
|
567
|
-
fs.rmSync(skillPath, { recursive: true, force: true });
|
|
568
|
-
}
|
|
569
|
-
}
|
|
424
|
+
const nestedLegacy = path.join(legacyCommandsDir, ROOT_COMMAND);
|
|
425
|
+
if (fs.existsSync(nestedLegacy)) {
|
|
426
|
+
fs.rmSync(nestedLegacy, { recursive: true, force: true });
|
|
570
427
|
}
|
|
571
428
|
}
|
|
572
429
|
}
|
|
573
430
|
|
|
574
|
-
function resolveTargetDir(
|
|
575
|
-
if (targetDir) {
|
|
576
|
-
return path.resolve(targetDir);
|
|
431
|
+
function resolveTargetDir(options = {}) {
|
|
432
|
+
if (options.targetDir) {
|
|
433
|
+
return path.resolve(options.targetDir);
|
|
577
434
|
}
|
|
578
435
|
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
}
|
|
436
|
+
return path.resolve(process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude'));
|
|
437
|
+
}
|
|
582
438
|
|
|
583
|
-
|
|
439
|
+
function setExecutableBits(targetDir) {
|
|
440
|
+
const candidates = [
|
|
441
|
+
path.join(targetDir, 'autodev', 'bin', 'autodev-tools.cjs'),
|
|
442
|
+
path.join(targetDir, 'hooks', 'autodev-auto-format.js'),
|
|
443
|
+
path.join(targetDir, 'hooks', 'autodev-git-guard.js'),
|
|
444
|
+
path.join(targetDir, 'hooks', 'autodev-paths.js')
|
|
445
|
+
];
|
|
446
|
+
|
|
447
|
+
for (const filePath of candidates) {
|
|
448
|
+
if (fs.existsSync(filePath)) {
|
|
449
|
+
fs.chmodSync(filePath, 0o755);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
584
452
|
}
|
|
585
453
|
|
|
586
454
|
function install(options = {}) {
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
const silent = Boolean(options.silent);
|
|
455
|
+
if ((options.scope || SUPPORTED_SCOPE) !== SUPPORTED_SCOPE) {
|
|
456
|
+
throw new Error('local install is no longer supported; install autodev globally');
|
|
457
|
+
}
|
|
458
|
+
|
|
592
459
|
const srcRoot = path.resolve(__dirname, '..');
|
|
593
|
-
const
|
|
594
|
-
|
|
595
|
-
: './.claude/';
|
|
460
|
+
const targetDir = resolveTargetDir(options);
|
|
461
|
+
const pathPrefix = `${targetDir.replace(/\\/g, '/')}/`;
|
|
596
462
|
const transform = content => transformInstalledContent(content, pathPrefix);
|
|
597
463
|
|
|
598
464
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
if (isGlobal) {
|
|
602
|
-
copyCommandsAsGlobalSkills(
|
|
603
|
-
path.join(srcRoot, 'commands', 'autodev'),
|
|
604
|
-
path.join(targetDir, 'skills'),
|
|
605
|
-
'autodev',
|
|
606
|
-
transform
|
|
607
|
-
);
|
|
608
|
-
} else {
|
|
609
|
-
copyCommandsAsLocal(
|
|
610
|
-
path.join(srcRoot, 'commands', 'autodev'),
|
|
611
|
-
path.join(targetDir, 'commands'),
|
|
612
|
-
transform
|
|
613
|
-
);
|
|
614
|
-
}
|
|
465
|
+
removeManagedFiles(targetDir);
|
|
615
466
|
|
|
467
|
+
copyCommandsAsGlobalSkills(
|
|
468
|
+
path.join(srcRoot, 'commands', 'autodev'),
|
|
469
|
+
path.join(targetDir, 'skills'),
|
|
470
|
+
ROOT_COMMAND,
|
|
471
|
+
transform
|
|
472
|
+
);
|
|
616
473
|
copyTextTree(path.join(srcRoot, 'autodev'), path.join(targetDir, 'autodev'), transform);
|
|
617
|
-
|
|
618
|
-
|
|
474
|
+
|
|
475
|
+
const hookDestRoot = path.join(targetDir, 'hooks');
|
|
476
|
+
fs.mkdirSync(hookDestRoot, { recursive: true });
|
|
477
|
+
for (const fileName of ['autodev-auto-format.js', 'autodev-git-guard.js', 'autodev-paths.js']) {
|
|
478
|
+
let content = fs.readFileSync(path.join(srcRoot, 'hooks', fileName), 'utf8');
|
|
479
|
+
content = transform(content);
|
|
480
|
+
fs.writeFileSync(path.join(hookDestRoot, fileName), content, 'utf8');
|
|
481
|
+
}
|
|
482
|
+
|
|
619
483
|
setExecutableBits(targetDir);
|
|
620
|
-
const settingsPath = configureSettings(targetDir
|
|
484
|
+
const settingsPath = configureSettings(targetDir);
|
|
621
485
|
|
|
622
|
-
if (!silent) {
|
|
623
|
-
|
|
624
|
-
console.log(` ${green}Installed${reset} autodev (${locationLabel}) at ${cyan}${targetDir}${reset}`);
|
|
486
|
+
if (!options.silent) {
|
|
487
|
+
console.log(`${cyan}autodev${reset} installed to ${green}${targetDir}${reset}`);
|
|
625
488
|
if (settingsPath) {
|
|
626
489
|
console.log(` ${green}Updated${reset} ${cyan}${settingsPath}${reset}`);
|
|
627
490
|
}
|
|
628
|
-
console.log(`
|
|
491
|
+
console.log(` ${yellow}Background tasks disabled${reset} via CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1`);
|
|
629
492
|
}
|
|
630
493
|
|
|
631
494
|
return { targetDir, settingsPath };
|
|
632
495
|
}
|
|
633
496
|
|
|
634
497
|
function uninstall(options = {}) {
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
const isGlobal = scope === 'global';
|
|
639
|
-
const silent = Boolean(options.silent);
|
|
640
|
-
|
|
641
|
-
removeInstalledFiles(targetDir, isGlobal);
|
|
498
|
+
if ((options.scope || SUPPORTED_SCOPE) !== SUPPORTED_SCOPE) {
|
|
499
|
+
throw new Error('local install is no longer supported; uninstall the global install instead');
|
|
500
|
+
}
|
|
642
501
|
|
|
502
|
+
const targetDir = resolveTargetDir(options);
|
|
643
503
|
const settingsPath = path.join(targetDir, 'settings.json');
|
|
644
504
|
const settings = readSettings(settingsPath);
|
|
645
|
-
|
|
646
|
-
|
|
505
|
+
|
|
506
|
+
removeManagedFiles(targetDir);
|
|
507
|
+
|
|
508
|
+
if (settings !== null) {
|
|
509
|
+
removeManagedSettings(settings, targetDir);
|
|
647
510
|
writeSettings(settingsPath, settings);
|
|
648
511
|
}
|
|
649
512
|
|
|
650
|
-
if (!silent) {
|
|
651
|
-
console.log(
|
|
513
|
+
if (!options.silent) {
|
|
514
|
+
console.log(`${cyan}autodev${reset} removed from ${green}${targetDir}${reset}`);
|
|
515
|
+
if (settings !== null) {
|
|
516
|
+
console.log(` ${green}Updated${reset} ${cyan}${settingsPath}${reset}`);
|
|
517
|
+
}
|
|
652
518
|
}
|
|
653
519
|
|
|
654
520
|
return { targetDir, settingsPath };
|
|
655
521
|
}
|
|
656
522
|
|
|
657
523
|
function printHelp() {
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
--
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
--uninstall Remove autodev from the selected location
|
|
666
|
-
--help Show this help
|
|
667
|
-
|
|
668
|
-
Examples:
|
|
669
|
-
npx @mthanhlm/autodev@latest
|
|
670
|
-
npx @mthanhlm/autodev@latest --global
|
|
671
|
-
npx @mthanhlm/autodev@latest --local
|
|
672
|
-
npx @mthanhlm/autodev@latest --global --disable-background-tasks
|
|
673
|
-
npx @mthanhlm/autodev@latest --global --uninstall
|
|
674
|
-
`);
|
|
524
|
+
process.stdout.write(`Usage: npx @mthanhlm/autodev [--global] [--uninstall]\n\n`);
|
|
525
|
+
process.stdout.write(`Defaults:\n`);
|
|
526
|
+
process.stdout.write(` --global Install into ~/.claude (default)\n`);
|
|
527
|
+
process.stdout.write(` --uninstall Remove autodev from ~/.claude\n`);
|
|
528
|
+
process.stdout.write(`\nNotes:\n`);
|
|
529
|
+
process.stdout.write(` - Global install is the supported mode.\n`);
|
|
530
|
+
process.stdout.write(` - Install always sets CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1.\n`);
|
|
675
531
|
}
|
|
676
532
|
|
|
677
|
-
function
|
|
678
|
-
return new Promise(resolve => {
|
|
679
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
680
|
-
console.log(`
|
|
681
|
-
${cyan}autodev install${reset}
|
|
682
|
-
|
|
683
|
-
Runtime:
|
|
684
|
-
Claude Code only
|
|
685
|
-
|
|
686
|
-
Location:
|
|
687
|
-
${cyan}1${reset}) Global ${yellow}(Recommended)${reset} -> ~/.claude
|
|
688
|
-
${cyan}2${reset}) Local -> ./.claude
|
|
689
|
-
`);
|
|
690
|
-
rl.question(`Select install location ${yellow}[1]${reset}: `, answer => {
|
|
691
|
-
rl.close();
|
|
692
|
-
const trimmed = answer.trim().toLowerCase();
|
|
693
|
-
if (trimmed === '2' || trimmed === 'l' || trimmed === 'local') {
|
|
694
|
-
resolve('local');
|
|
695
|
-
return;
|
|
696
|
-
}
|
|
697
|
-
resolve('global');
|
|
698
|
-
});
|
|
699
|
-
});
|
|
700
|
-
}
|
|
701
|
-
|
|
702
|
-
function askDisableBackgroundTasks() {
|
|
703
|
-
return new Promise(resolve => {
|
|
704
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
705
|
-
console.log(`
|
|
706
|
-
Background tasks:
|
|
707
|
-
${cyan}1${reset}) Disable in Claude Code settings ${yellow}(Recommended for autodev)${reset}
|
|
708
|
-
-> sets CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1
|
|
709
|
-
${cyan}2${reset}) Leave background-task settings unchanged
|
|
710
|
-
`);
|
|
711
|
-
rl.question(`Select background-task policy ${yellow}[1]${reset}: `, answer => {
|
|
712
|
-
rl.close();
|
|
713
|
-
const trimmed = answer.trim().toLowerCase();
|
|
714
|
-
if (trimmed === '2' || trimmed === 'n' || trimmed === 'no' || trimmed === 'leave') {
|
|
715
|
-
resolve(false);
|
|
716
|
-
return;
|
|
717
|
-
}
|
|
718
|
-
resolve(true);
|
|
719
|
-
});
|
|
720
|
-
});
|
|
721
|
-
}
|
|
722
|
-
|
|
723
|
-
async function main() {
|
|
533
|
+
function main() {
|
|
724
534
|
const args = process.argv.slice(2);
|
|
535
|
+
|
|
725
536
|
if (args.includes('--help') || args.includes('-h')) {
|
|
726
537
|
printHelp();
|
|
727
538
|
return;
|
|
728
539
|
}
|
|
729
540
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
scope = 'global';
|
|
734
|
-
scopeWasExplicit = true;
|
|
735
|
-
} else if (args.includes('--local') || args.includes('-l')) {
|
|
736
|
-
scope = 'local';
|
|
737
|
-
scopeWasExplicit = true;
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
if (!scope) {
|
|
741
|
-
scope = await askScope();
|
|
541
|
+
if (args.includes('--local')) {
|
|
542
|
+
process.stderr.write('autodev: local install is no longer supported\n');
|
|
543
|
+
process.exit(1);
|
|
742
544
|
}
|
|
743
545
|
|
|
744
546
|
const uninstallRequested = args.includes('--uninstall') || args.includes('-u');
|
|
745
|
-
const disableBackgroundTasks = args.includes('--disable-background-tasks')
|
|
746
|
-
? true
|
|
747
|
-
: uninstallRequested || scopeWasExplicit
|
|
748
|
-
? false
|
|
749
|
-
: await askDisableBackgroundTasks();
|
|
750
547
|
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
548
|
+
try {
|
|
549
|
+
if (uninstallRequested) {
|
|
550
|
+
uninstall({ scope: SUPPORTED_SCOPE });
|
|
551
|
+
} else {
|
|
552
|
+
install({ scope: SUPPORTED_SCOPE });
|
|
553
|
+
}
|
|
554
|
+
} catch (error) {
|
|
555
|
+
process.stderr.write(`autodev: ${error.message}\n`);
|
|
556
|
+
process.exit(1);
|
|
755
557
|
}
|
|
756
558
|
}
|
|
757
559
|
|
|
758
560
|
if (require.main === module) {
|
|
759
|
-
main()
|
|
760
|
-
console.error(error);
|
|
761
|
-
process.exit(1);
|
|
762
|
-
});
|
|
561
|
+
main();
|
|
763
562
|
}
|
|
764
563
|
|
|
765
564
|
module.exports = {
|
|
766
565
|
configureSettings,
|
|
767
|
-
convertCommandToClaudeSkill,
|
|
768
566
|
install,
|
|
769
|
-
|
|
567
|
+
managedSkillNames,
|
|
568
|
+
removeManagedFiles,
|
|
770
569
|
removeManagedSettings,
|
|
771
|
-
stripJsonComments,
|
|
772
|
-
transformInstalledContent,
|
|
773
570
|
uninstall
|
|
774
571
|
};
|