@mthanhlm/autodev 0.4.3 → 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 -91
- package/autodev/bin/autodev-tools.cjs +587 -811
- 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 +229 -342
- 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 -13
- 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 -13
- 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 -14
- package/autodev/templates/track.md +0 -24
- package/autodev/templates/uat.md +0 -18
- package/autodev/workflows/autodev.md +0 -79
- package/autodev/workflows/cleanup.md +0 -51
- package/autodev/workflows/execute-phase.md +0 -127
- package/autodev/workflows/explore-codebase.md +0 -66
- package/autodev/workflows/help.md +0 -110
- package/autodev/workflows/new-project.md +0 -101
- package/autodev/workflows/plan-phase.md +0 -126
- package/autodev/workflows/progress.md +0 -18
- package/autodev/workflows/review-phase.md +0 -73
- package/autodev/workflows/review-plan.md +0 -55
- package/autodev/workflows/review-task.md +0 -70
- package/autodev/workflows/verify-work.md +0 -57
- 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 -55
- package/hooks/autodev-read-guard.js +0 -42
- package/hooks/autodev-session-state.sh +0 -51
- package/hooks/autodev-statusline.js +0 -78
- package/hooks/autodev-workflow-guard.js +0 -43
- package/hooks/hooks.json +0 -89
package/bin/install.js
CHANGED
|
@@ -1,29 +1,54 @@
|
|
|
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';
|
|
10
9
|
const cyan = '\x1b[36m';
|
|
11
10
|
const reset = '\x1b[0m';
|
|
12
11
|
|
|
13
|
-
const MANAGED_PREFIX = 'autodev-';
|
|
14
12
|
const ROOT_COMMAND = 'autodev';
|
|
15
|
-
const
|
|
16
|
-
|
|
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 = [
|
|
17
41
|
'autodev-auto-format.js',
|
|
42
|
+
'autodev-git-guard.js',
|
|
18
43
|
'autodev-paths.js',
|
|
19
44
|
'autodev-context-monitor.js',
|
|
20
|
-
'autodev-git-guard.js',
|
|
21
45
|
'autodev-phase-boundary.sh',
|
|
22
46
|
'autodev-prompt-guard.js',
|
|
23
47
|
'autodev-read-guard.js',
|
|
24
48
|
'autodev-session-state.sh',
|
|
25
49
|
'autodev-statusline.js',
|
|
26
|
-
'autodev-workflow-guard.js'
|
|
50
|
+
'autodev-workflow-guard.js',
|
|
51
|
+
'hooks.json'
|
|
27
52
|
];
|
|
28
53
|
|
|
29
54
|
function stripJsonComments(input) {
|
|
@@ -148,7 +173,9 @@ function convertCommandToClaudeSkill(content, skillName) {
|
|
|
148
173
|
const description = extractFrontmatterField(frontmatter, 'description') || '';
|
|
149
174
|
const argumentHint = extractFrontmatterField(frontmatter, 'argument-hint');
|
|
150
175
|
const toolsMatch = frontmatter.match(/^allowed-tools:\s*\n((?:\s+-\s+.+\n?)*)/m);
|
|
151
|
-
const toolsBlock = toolsMatch
|
|
176
|
+
const toolsBlock = toolsMatch
|
|
177
|
+
? `allowed-tools:\n${toolsMatch[1].endsWith('\n') ? toolsMatch[1] : `${toolsMatch[1]}\n`}`
|
|
178
|
+
: '';
|
|
152
179
|
|
|
153
180
|
let rebuilt = `---\nname: ${skillName}\ndescription: ${yamlQuote(description)}\n`;
|
|
154
181
|
if (argumentHint) {
|
|
@@ -173,6 +200,11 @@ function transformInstalledContent(content, pathPrefix) {
|
|
|
173
200
|
.replace(/\.\/\.claude\b/g, bare);
|
|
174
201
|
}
|
|
175
202
|
|
|
203
|
+
function commandNameFromSourceFile(fileName) {
|
|
204
|
+
const baseName = fileName.replace(/\.md$/, '');
|
|
205
|
+
return baseName === 'index' ? ROOT_COMMAND : `${ROOT_COMMAND}-${baseName}`;
|
|
206
|
+
}
|
|
207
|
+
|
|
176
208
|
function copyTextTree(srcDir, destDir, transform) {
|
|
177
209
|
if (!fs.existsSync(srcDir)) {
|
|
178
210
|
return;
|
|
@@ -182,6 +214,7 @@ function copyTextTree(srcDir, destDir, transform) {
|
|
|
182
214
|
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
183
215
|
const srcPath = path.join(srcDir, entry.name);
|
|
184
216
|
const destPath = path.join(destDir, entry.name);
|
|
217
|
+
|
|
185
218
|
if (entry.isDirectory()) {
|
|
186
219
|
copyTextTree(srcPath, destPath, transform);
|
|
187
220
|
continue;
|
|
@@ -195,111 +228,57 @@ function copyTextTree(srcDir, destDir, transform) {
|
|
|
195
228
|
}
|
|
196
229
|
}
|
|
197
230
|
|
|
198
|
-
function commandNameFromSourceFile(fileName) {
|
|
199
|
-
const baseName = fileName.replace(/\.md$/, '');
|
|
200
|
-
return baseName === 'index' ? ROOT_COMMAND : `${ROOT_COMMAND}-${baseName}`;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function skillNameFromSourceFile(fileName, prefix) {
|
|
204
|
-
const baseName = fileName.replace(/\.md$/, '');
|
|
205
|
-
return baseName === 'index' ? prefix : `${prefix}-${baseName}`;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function copyCommandsAsLocal(srcDir, destDir, transform) {
|
|
209
|
-
fs.mkdirSync(destDir, { recursive: true });
|
|
210
|
-
|
|
211
|
-
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
212
|
-
if (!entry.isFile() || !entry.name.endsWith('.md')) {
|
|
213
|
-
continue;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const srcPath = path.join(srcDir, entry.name);
|
|
217
|
-
const destPath = path.join(destDir, `${commandNameFromSourceFile(entry.name)}.md`);
|
|
218
|
-
let content = fs.readFileSync(srcPath, 'utf8');
|
|
219
|
-
content = transform(content);
|
|
220
|
-
fs.writeFileSync(destPath, content, 'utf8');
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
231
|
function copyCommandsAsGlobalSkills(srcDir, skillsDir, prefix, transform) {
|
|
225
232
|
fs.mkdirSync(skillsDir, { recursive: true });
|
|
226
233
|
|
|
227
|
-
for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
228
|
-
if (entry.isDirectory() && (entry.name === prefix || entry.name.startsWith(`${prefix}-`))) {
|
|
229
|
-
fs.rmSync(path.join(skillsDir, entry.name), { recursive: true, force: true });
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
234
|
for (const entry of fs.readdirSync(srcDir, { withFileTypes: true })) {
|
|
234
235
|
if (!entry.isFile() || !entry.name.endsWith('.md')) {
|
|
235
236
|
continue;
|
|
236
237
|
}
|
|
237
238
|
|
|
238
|
-
const skillName =
|
|
239
|
+
const skillName = commandNameFromSourceFile(entry.name);
|
|
239
240
|
const skillDir = path.join(skillsDir, skillName);
|
|
240
241
|
fs.mkdirSync(skillDir, { recursive: true });
|
|
241
242
|
|
|
242
243
|
let content = fs.readFileSync(path.join(srcDir, entry.name), 'utf8');
|
|
243
244
|
content = transform(content);
|
|
244
|
-
content = convertCommandToClaudeSkill(content, skillName);
|
|
245
|
+
content = convertCommandToClaudeSkill(content, skillName === prefix ? prefix : skillName);
|
|
245
246
|
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), content, 'utf8');
|
|
246
247
|
}
|
|
247
248
|
}
|
|
248
249
|
|
|
249
|
-
function
|
|
250
|
-
|
|
251
|
-
|
|
250
|
+
function currentManagedSkillNames(srcRoot) {
|
|
251
|
+
const commandsDir = path.join(srcRoot, 'commands', 'autodev');
|
|
252
|
+
if (!fs.existsSync(commandsDir)) {
|
|
253
|
+
return [];
|
|
252
254
|
}
|
|
253
255
|
|
|
254
|
-
fs.
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
continue;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const srcPath = path.join(srcDir, entry.name);
|
|
261
|
-
const destPath = path.join(destDir, entry.name);
|
|
262
|
-
let content = fs.readFileSync(srcPath, 'utf8');
|
|
263
|
-
content = transform(content);
|
|
264
|
-
fs.writeFileSync(destPath, content, 'utf8');
|
|
265
|
-
}
|
|
256
|
+
return fs.readdirSync(commandsDir, { withFileTypes: true })
|
|
257
|
+
.filter(entry => entry.isFile() && entry.name.endsWith('.md'))
|
|
258
|
+
.map(entry => commandNameFromSourceFile(entry.name));
|
|
266
259
|
}
|
|
267
260
|
|
|
268
|
-
function
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
if (settings.env && typeof settings.env === 'object') {
|
|
274
|
-
delete settings.env.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS;
|
|
275
|
-
if (Object.keys(settings.env).length === 0) {
|
|
276
|
-
delete settings.env;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (!settings.hooks || typeof settings.hooks !== 'object') {
|
|
281
|
-
return settings;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
for (const eventName of Object.keys(settings.hooks)) {
|
|
285
|
-
const entries = Array.isArray(settings.hooks[eventName]) ? settings.hooks[eventName] : [];
|
|
286
|
-
const filtered = entries.filter(entry => {
|
|
287
|
-
const hooks = Array.isArray(entry?.hooks) ? entry.hooks : [];
|
|
288
|
-
return !hooks.some(hook => typeof hook.command === 'string' && hook.command.includes('autodev-'));
|
|
289
|
-
});
|
|
261
|
+
function managedSkillNames(srcRoot) {
|
|
262
|
+
return [...new Set([...currentManagedSkillNames(srcRoot), ...LEGACY_SKILL_NAMES])];
|
|
263
|
+
}
|
|
290
264
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
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
|
+
];
|
|
273
|
+
}
|
|
297
274
|
|
|
298
|
-
|
|
299
|
-
|
|
275
|
+
function isManagedCommand(command, targetDir) {
|
|
276
|
+
if (typeof command !== 'string') {
|
|
277
|
+
return false;
|
|
300
278
|
}
|
|
301
279
|
|
|
302
|
-
return
|
|
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);
|
|
303
282
|
}
|
|
304
283
|
|
|
305
284
|
function ensureHook(settings, eventName, matcher, command, timeout) {
|
|
@@ -330,355 +309,263 @@ function ensureHook(settings, eventName, matcher, command, timeout) {
|
|
|
330
309
|
settings.hooks[eventName].push(entry);
|
|
331
310
|
}
|
|
332
311
|
|
|
333
|
-
function
|
|
334
|
-
if (!settings
|
|
335
|
-
|
|
312
|
+
function removeManagedSettings(settings, targetDir) {
|
|
313
|
+
if (!settings || typeof settings !== 'object') {
|
|
314
|
+
return {};
|
|
336
315
|
}
|
|
337
|
-
settings.env[name] = String(value);
|
|
338
|
-
}
|
|
339
316
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
path.join(targetDir, 'hooks', 'autodev-session-state.sh'),
|
|
344
|
-
path.join(targetDir, 'autodev', 'bin', 'autodev-tools.cjs')
|
|
345
|
-
];
|
|
317
|
+
if (settings.statusLine?.command && isManagedCommand(settings.statusLine.command, targetDir)) {
|
|
318
|
+
delete settings.statusLine;
|
|
319
|
+
}
|
|
346
320
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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;
|
|
350
325
|
}
|
|
351
326
|
}
|
|
352
|
-
}
|
|
353
327
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
}
|
|
328
|
+
if (!settings.hooks || typeof settings.hooks !== 'object') {
|
|
329
|
+
return settings;
|
|
330
|
+
}
|
|
358
331
|
|
|
359
|
-
|
|
360
|
-
|
|
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
|
+
});
|
|
338
|
+
|
|
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;
|
|
361
351
|
}
|
|
362
352
|
|
|
363
|
-
function configureSettings(targetDir
|
|
353
|
+
function configureSettings(targetDir) {
|
|
364
354
|
const settingsPath = path.join(targetDir, 'settings.json');
|
|
365
355
|
const settings = readSettings(settingsPath);
|
|
366
356
|
if (settings === null) {
|
|
367
357
|
return null;
|
|
368
358
|
}
|
|
369
359
|
|
|
370
|
-
removeManagedSettings(settings);
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
? buildGlobalCommand(targetDir, 'autodev-workflow-guard.js')
|
|
390
|
-
: buildLocalCommand('autodev-workflow-guard.js');
|
|
391
|
-
const gitGuardCommand = isGlobal
|
|
392
|
-
? buildGlobalCommand(targetDir, 'autodev-git-guard.js')
|
|
393
|
-
: buildLocalCommand('autodev-git-guard.js');
|
|
394
|
-
const sessionStateCommand = isGlobal
|
|
395
|
-
? buildGlobalCommand(targetDir, 'autodev-session-state.sh', 'bash')
|
|
396
|
-
: buildLocalCommand('autodev-session-state.sh', 'bash');
|
|
397
|
-
const phaseBoundaryCommand = isGlobal
|
|
398
|
-
? buildGlobalCommand(targetDir, 'autodev-phase-boundary.sh', 'bash')
|
|
399
|
-
: buildLocalCommand('autodev-phase-boundary.sh', 'bash');
|
|
400
|
-
const statusLineCommand = isGlobal
|
|
401
|
-
? buildGlobalCommand(targetDir, 'autodev-statusline.js')
|
|
402
|
-
: buildLocalCommand('autodev-statusline.js');
|
|
403
|
-
|
|
404
|
-
ensureHook(settings, sessionStartEvent, null, sessionStateCommand);
|
|
405
|
-
ensureHook(settings, preToolEvent, 'Write|Edit', promptGuardCommand, 5);
|
|
406
|
-
ensureHook(settings, preToolEvent, 'Write|Edit', readGuardCommand, 5);
|
|
407
|
-
ensureHook(settings, preToolEvent, 'Write|Edit', workflowGuardCommand, 5);
|
|
408
|
-
ensureHook(settings, preToolEvent, 'Bash', gitGuardCommand, 5);
|
|
409
|
-
ensureHook(settings, postToolEvent, 'Edit|Write|MultiEdit', autoFormatCommand, 10);
|
|
410
|
-
ensureHook(settings, postToolEvent, 'Bash|Edit|Write|MultiEdit|Agent|Task', contextMonitorCommand, 10);
|
|
411
|
-
ensureHook(settings, postToolEvent, 'Write|Edit', phaseBoundaryCommand, 5);
|
|
412
|
-
|
|
413
|
-
if (options.installStatusLine !== false) {
|
|
414
|
-
settings.statusLine = {
|
|
415
|
-
type: 'command',
|
|
416
|
-
command: statusLineCommand
|
|
417
|
-
};
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
if (options.disableBackgroundTasks) {
|
|
421
|
-
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 = {};
|
|
422
379
|
}
|
|
380
|
+
settings.env.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS = '1';
|
|
423
381
|
|
|
424
382
|
writeSettings(settingsPath, settings);
|
|
425
383
|
return settingsPath;
|
|
426
384
|
}
|
|
427
385
|
|
|
428
|
-
function
|
|
386
|
+
function removeManagedFiles(targetDir) {
|
|
429
387
|
const supportDir = path.join(targetDir, 'autodev');
|
|
430
388
|
if (fs.existsSync(supportDir)) {
|
|
431
389
|
fs.rmSync(supportDir, { recursive: true, force: true });
|
|
432
390
|
}
|
|
433
391
|
|
|
434
|
-
const
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
}
|
|
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 });
|
|
440
397
|
}
|
|
441
398
|
}
|
|
442
399
|
|
|
443
|
-
const
|
|
444
|
-
for (const
|
|
445
|
-
const
|
|
446
|
-
if (fs.existsSync(
|
|
447
|
-
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 });
|
|
448
405
|
}
|
|
449
406
|
}
|
|
450
407
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
fs.rmSync(path.join(skillsDir, entry.name), { recursive: true, force: true });
|
|
457
|
-
}
|
|
458
|
-
}
|
|
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 });
|
|
459
413
|
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
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 });
|
|
467
422
|
}
|
|
468
423
|
}
|
|
469
|
-
const
|
|
470
|
-
if (fs.existsSync(
|
|
471
|
-
fs.rmSync(
|
|
472
|
-
}
|
|
473
|
-
const skillsDir = path.join(targetDir, 'skills');
|
|
474
|
-
if (fs.existsSync(skillsDir)) {
|
|
475
|
-
for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
476
|
-
if (entry.isDirectory() && (entry.name === ROOT_COMMAND || entry.name.startsWith(MANAGED_PREFIX))) {
|
|
477
|
-
fs.rmSync(path.join(skillsDir, entry.name), { recursive: true, force: true });
|
|
478
|
-
}
|
|
479
|
-
}
|
|
424
|
+
const nestedLegacy = path.join(legacyCommandsDir, ROOT_COMMAND);
|
|
425
|
+
if (fs.existsSync(nestedLegacy)) {
|
|
426
|
+
fs.rmSync(nestedLegacy, { recursive: true, force: true });
|
|
480
427
|
}
|
|
481
428
|
}
|
|
482
429
|
}
|
|
483
430
|
|
|
484
|
-
function resolveTargetDir(
|
|
485
|
-
if (targetDir) {
|
|
486
|
-
return path.resolve(targetDir);
|
|
431
|
+
function resolveTargetDir(options = {}) {
|
|
432
|
+
if (options.targetDir) {
|
|
433
|
+
return path.resolve(options.targetDir);
|
|
487
434
|
}
|
|
488
435
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
}
|
|
436
|
+
return path.resolve(process.env.CLAUDE_CONFIG_DIR || path.join(os.homedir(), '.claude'));
|
|
437
|
+
}
|
|
492
438
|
|
|
493
|
-
|
|
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
|
+
}
|
|
494
452
|
}
|
|
495
453
|
|
|
496
454
|
function install(options = {}) {
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
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
|
+
|
|
502
459
|
const srcRoot = path.resolve(__dirname, '..');
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
: './.claude/';
|
|
460
|
+
const targetDir = resolveTargetDir(options);
|
|
461
|
+
const pathPrefix = `${targetDir.replace(/\\/g, '/')}/`;
|
|
506
462
|
const transform = content => transformInstalledContent(content, pathPrefix);
|
|
507
463
|
|
|
508
464
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
if (isGlobal) {
|
|
512
|
-
copyCommandsAsGlobalSkills(
|
|
513
|
-
path.join(srcRoot, 'commands', 'autodev'),
|
|
514
|
-
path.join(targetDir, 'skills'),
|
|
515
|
-
'autodev',
|
|
516
|
-
transform
|
|
517
|
-
);
|
|
518
|
-
} else {
|
|
519
|
-
copyCommandsAsLocal(
|
|
520
|
-
path.join(srcRoot, 'commands', 'autodev'),
|
|
521
|
-
path.join(targetDir, 'commands'),
|
|
522
|
-
transform
|
|
523
|
-
);
|
|
524
|
-
}
|
|
465
|
+
removeManagedFiles(targetDir);
|
|
525
466
|
|
|
467
|
+
copyCommandsAsGlobalSkills(
|
|
468
|
+
path.join(srcRoot, 'commands', 'autodev'),
|
|
469
|
+
path.join(targetDir, 'skills'),
|
|
470
|
+
ROOT_COMMAND,
|
|
471
|
+
transform
|
|
472
|
+
);
|
|
526
473
|
copyTextTree(path.join(srcRoot, 'autodev'), path.join(targetDir, 'autodev'), transform);
|
|
527
|
-
|
|
528
|
-
|
|
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
|
+
|
|
529
483
|
setExecutableBits(targetDir);
|
|
530
|
-
const settingsPath = configureSettings(targetDir
|
|
484
|
+
const settingsPath = configureSettings(targetDir);
|
|
531
485
|
|
|
532
|
-
if (!silent) {
|
|
533
|
-
|
|
534
|
-
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}`);
|
|
535
488
|
if (settingsPath) {
|
|
536
489
|
console.log(` ${green}Updated${reset} ${cyan}${settingsPath}${reset}`);
|
|
537
490
|
}
|
|
538
|
-
console.log(`
|
|
491
|
+
console.log(` ${yellow}Background tasks disabled${reset} via CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1`);
|
|
539
492
|
}
|
|
540
493
|
|
|
541
494
|
return { targetDir, settingsPath };
|
|
542
495
|
}
|
|
543
496
|
|
|
544
497
|
function uninstall(options = {}) {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
const isGlobal = scope === 'global';
|
|
549
|
-
const silent = Boolean(options.silent);
|
|
550
|
-
|
|
551
|
-
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
|
+
}
|
|
552
501
|
|
|
502
|
+
const targetDir = resolveTargetDir(options);
|
|
553
503
|
const settingsPath = path.join(targetDir, 'settings.json');
|
|
554
504
|
const settings = readSettings(settingsPath);
|
|
555
|
-
|
|
556
|
-
|
|
505
|
+
|
|
506
|
+
removeManagedFiles(targetDir);
|
|
507
|
+
|
|
508
|
+
if (settings !== null) {
|
|
509
|
+
removeManagedSettings(settings, targetDir);
|
|
557
510
|
writeSettings(settingsPath, settings);
|
|
558
511
|
}
|
|
559
512
|
|
|
560
|
-
if (!silent) {
|
|
561
|
-
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
|
+
}
|
|
562
518
|
}
|
|
563
519
|
|
|
564
520
|
return { targetDir, settingsPath };
|
|
565
521
|
}
|
|
566
522
|
|
|
567
523
|
function printHelp() {
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
--
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
--uninstall Remove autodev from the selected location
|
|
576
|
-
--help Show this help
|
|
577
|
-
|
|
578
|
-
Examples:
|
|
579
|
-
npx @mthanhlm/autodev@latest
|
|
580
|
-
npx @mthanhlm/autodev@latest --global
|
|
581
|
-
npx @mthanhlm/autodev@latest --local
|
|
582
|
-
npx @mthanhlm/autodev@latest --global --disable-background-tasks
|
|
583
|
-
npx @mthanhlm/autodev@latest --global --uninstall
|
|
584
|
-
`);
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
function askScope() {
|
|
588
|
-
return new Promise(resolve => {
|
|
589
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
590
|
-
console.log(`
|
|
591
|
-
${cyan}autodev install${reset}
|
|
592
|
-
|
|
593
|
-
Runtime:
|
|
594
|
-
Claude Code only
|
|
595
|
-
|
|
596
|
-
Location:
|
|
597
|
-
${cyan}1${reset}) Global ${yellow}(Recommended)${reset} -> ~/.claude
|
|
598
|
-
${cyan}2${reset}) Local -> ./.claude
|
|
599
|
-
`);
|
|
600
|
-
rl.question(`Select install location ${yellow}[1]${reset}: `, answer => {
|
|
601
|
-
rl.close();
|
|
602
|
-
const trimmed = answer.trim().toLowerCase();
|
|
603
|
-
if (trimmed === '2' || trimmed === 'l' || trimmed === 'local') {
|
|
604
|
-
resolve('local');
|
|
605
|
-
return;
|
|
606
|
-
}
|
|
607
|
-
resolve('global');
|
|
608
|
-
});
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
function askDisableBackgroundTasks() {
|
|
613
|
-
return new Promise(resolve => {
|
|
614
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
615
|
-
console.log(`
|
|
616
|
-
Background tasks:
|
|
617
|
-
${cyan}1${reset}) Disable in Claude Code settings ${yellow}(Recommended for autodev)${reset}
|
|
618
|
-
-> sets CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1
|
|
619
|
-
${cyan}2${reset}) Leave background-task settings unchanged
|
|
620
|
-
`);
|
|
621
|
-
rl.question(`Select background-task policy ${yellow}[1]${reset}: `, answer => {
|
|
622
|
-
rl.close();
|
|
623
|
-
const trimmed = answer.trim().toLowerCase();
|
|
624
|
-
if (trimmed === '2' || trimmed === 'n' || trimmed === 'no' || trimmed === 'leave') {
|
|
625
|
-
resolve(false);
|
|
626
|
-
return;
|
|
627
|
-
}
|
|
628
|
-
resolve(true);
|
|
629
|
-
});
|
|
630
|
-
});
|
|
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`);
|
|
631
531
|
}
|
|
632
532
|
|
|
633
|
-
|
|
533
|
+
function main() {
|
|
634
534
|
const args = process.argv.slice(2);
|
|
535
|
+
|
|
635
536
|
if (args.includes('--help') || args.includes('-h')) {
|
|
636
537
|
printHelp();
|
|
637
538
|
return;
|
|
638
539
|
}
|
|
639
540
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
scope = 'global';
|
|
644
|
-
scopeWasExplicit = true;
|
|
645
|
-
} else if (args.includes('--local') || args.includes('-l')) {
|
|
646
|
-
scope = 'local';
|
|
647
|
-
scopeWasExplicit = true;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
if (!scope) {
|
|
651
|
-
scope = await askScope();
|
|
541
|
+
if (args.includes('--local')) {
|
|
542
|
+
process.stderr.write('autodev: local install is no longer supported\n');
|
|
543
|
+
process.exit(1);
|
|
652
544
|
}
|
|
653
545
|
|
|
654
546
|
const uninstallRequested = args.includes('--uninstall') || args.includes('-u');
|
|
655
|
-
const disableBackgroundTasks = args.includes('--disable-background-tasks')
|
|
656
|
-
? true
|
|
657
|
-
: uninstallRequested || scopeWasExplicit
|
|
658
|
-
? false
|
|
659
|
-
: await askDisableBackgroundTasks();
|
|
660
547
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
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);
|
|
665
557
|
}
|
|
666
558
|
}
|
|
667
559
|
|
|
668
560
|
if (require.main === module) {
|
|
669
|
-
main()
|
|
670
|
-
console.error(error);
|
|
671
|
-
process.exit(1);
|
|
672
|
-
});
|
|
561
|
+
main();
|
|
673
562
|
}
|
|
674
563
|
|
|
675
564
|
module.exports = {
|
|
676
565
|
configureSettings,
|
|
677
|
-
convertCommandToClaudeSkill,
|
|
678
566
|
install,
|
|
679
|
-
|
|
567
|
+
managedSkillNames,
|
|
568
|
+
removeManagedFiles,
|
|
680
569
|
removeManagedSettings,
|
|
681
|
-
stripJsonComments,
|
|
682
|
-
transformInstalledContent,
|
|
683
570
|
uninstall
|
|
684
571
|
};
|