@mthanhlm/autodev 0.4.2 → 0.4.4

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/bin/install.js CHANGED
@@ -10,7 +10,6 @@ const yellow = '\x1b[33m';
10
10
  const cyan = '\x1b[36m';
11
11
  const reset = '\x1b[0m';
12
12
 
13
- const MANAGED_PREFIX = 'autodev-';
14
13
  const ROOT_COMMAND = 'autodev';
15
14
  const HOOK_FILES = [
16
15
  'hooks.json',
@@ -223,9 +222,12 @@ function copyCommandsAsLocal(srcDir, destDir, transform) {
223
222
 
224
223
  function copyCommandsAsGlobalSkills(srcDir, skillsDir, prefix, transform) {
225
224
  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));
226
228
 
227
229
  for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
228
- if (entry.isDirectory() && (entry.name === prefix || entry.name.startsWith(`${prefix}-`))) {
230
+ if (entry.isDirectory() && managedSkillNames.includes(entry.name)) {
229
231
  fs.rmSync(path.join(skillsDir, entry.name), { recursive: true, force: true });
230
232
  }
231
233
  }
@@ -265,8 +267,91 @@ function copyAgents(srcDir, destDir, transform) {
265
267
  }
266
268
  }
267
269
 
268
- function removeManagedSettings(settings) {
269
- if (settings.statusLine?.command && settings.statusLine.command.includes('autodev-statusline')) {
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, '..')) {
282
+ const commandsDir = path.join(srcRoot, 'commands', 'autodev');
283
+ if (!fs.existsSync(commandsDir)) {
284
+ return [];
285
+ }
286
+
287
+ return fs.readdirSync(commandsDir, { withFileTypes: true })
288
+ .filter(entry => entry.isFile() && entry.name.endsWith('.md'))
289
+ .map(entry => commandNameFromSourceFile(entry.name));
290
+ }
291
+
292
+ function managedSkillNames(srcRoot = path.resolve(__dirname, '..')) {
293
+ const commandsDir = path.join(srcRoot, 'commands', 'autodev');
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));
301
+ }
302
+
303
+ function managedCommandStrings(targetDir, isGlobal) {
304
+ return {
305
+ statusLine: isGlobal
306
+ ? buildGlobalCommand(targetDir, 'autodev-statusline.js')
307
+ : buildLocalCommand('autodev-statusline.js'),
308
+ hooks: [
309
+ isGlobal
310
+ ? buildGlobalCommand(targetDir, 'autodev-session-state.sh', 'bash')
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
+ };
335
+ }
336
+
337
+ function isManagedCommand(command, managedCommands = null) {
338
+ if (typeof command !== 'string') {
339
+ return false;
340
+ }
341
+
342
+ if (managedCommands && managedCommands.includes(command)) {
343
+ return true;
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)) {
270
355
  delete settings.statusLine;
271
356
  }
272
357
 
@@ -285,7 +370,7 @@ function removeManagedSettings(settings) {
285
370
  const entries = Array.isArray(settings.hooks[eventName]) ? settings.hooks[eventName] : [];
286
371
  const filtered = entries.filter(entry => {
287
372
  const hooks = Array.isArray(entry?.hooks) ? entry.hooks : [];
288
- return !hooks.some(hook => typeof hook.command === 'string' && hook.command.includes('autodev-'));
373
+ return !hooks.some(hook => isManagedCommand(hook?.command, managedStrings?.hooks || null));
289
374
  });
290
375
 
291
376
  if (filtered.length > 0) {
@@ -367,7 +452,7 @@ function configureSettings(targetDir, isGlobal, options = {}) {
367
452
  return null;
368
453
  }
369
454
 
370
- removeManagedSettings(settings);
455
+ removeManagedSettings(settings, { targetDir, isGlobal });
371
456
 
372
457
  const preToolEvent = 'PreToolUse';
373
458
  const postToolEvent = 'PostToolUse';
@@ -402,13 +487,13 @@ function configureSettings(targetDir, isGlobal, options = {}) {
402
487
  : buildLocalCommand('autodev-statusline.js');
403
488
 
404
489
  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);
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);
408
493
  ensureHook(settings, preToolEvent, 'Bash', gitGuardCommand, 5);
409
494
  ensureHook(settings, postToolEvent, 'Edit|Write|MultiEdit', autoFormatCommand, 10);
410
495
  ensureHook(settings, postToolEvent, 'Bash|Edit|Write|MultiEdit|Agent|Task', contextMonitorCommand, 10);
411
- ensureHook(settings, postToolEvent, 'Write|Edit', phaseBoundaryCommand, 5);
496
+ ensureHook(settings, postToolEvent, 'Write|Edit|MultiEdit', phaseBoundaryCommand, 5);
412
497
 
413
498
  if (options.installStatusLine !== false) {
414
499
  settings.statusLine = {
@@ -426,6 +511,7 @@ function configureSettings(targetDir, isGlobal, options = {}) {
426
511
  }
427
512
 
428
513
  function removeInstalledFiles(targetDir, isGlobal) {
514
+ const srcRoot = path.resolve(__dirname, '..');
429
515
  const supportDir = path.join(targetDir, 'autodev');
430
516
  if (fs.existsSync(supportDir)) {
431
517
  fs.rmSync(supportDir, { recursive: true, force: true });
@@ -433,9 +519,10 @@ function removeInstalledFiles(targetDir, isGlobal) {
433
519
 
434
520
  const agentsDir = path.join(targetDir, 'agents');
435
521
  if (fs.existsSync(agentsDir)) {
436
- for (const entry of fs.readdirSync(agentsDir, { withFileTypes: true })) {
437
- if (entry.isFile() && entry.name.startsWith(MANAGED_PREFIX) && entry.name.endsWith('.md')) {
438
- fs.rmSync(path.join(agentsDir, entry.name), { force: true });
522
+ for (const agentFile of managedAgents(srcRoot)) {
523
+ const agentPath = path.join(agentsDir, agentFile);
524
+ if (fs.existsSync(agentPath)) {
525
+ fs.rmSync(agentPath, { force: true });
439
526
  }
440
527
  }
441
528
  }
@@ -451,18 +538,20 @@ function removeInstalledFiles(targetDir, isGlobal) {
451
538
  if (isGlobal) {
452
539
  const skillsDir = path.join(targetDir, 'skills');
453
540
  if (fs.existsSync(skillsDir)) {
454
- for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
455
- if (entry.isDirectory() && (entry.name === ROOT_COMMAND || entry.name.startsWith(MANAGED_PREFIX))) {
456
- fs.rmSync(path.join(skillsDir, entry.name), { recursive: true, force: true });
541
+ for (const skillName of managedSkillNames(srcRoot)) {
542
+ const skillPath = path.join(skillsDir, skillName);
543
+ if (fs.existsSync(skillPath)) {
544
+ fs.rmSync(skillPath, { recursive: true, force: true });
457
545
  }
458
546
  }
459
547
  }
460
548
  } else {
461
549
  const commandsDir = path.join(targetDir, 'commands');
462
550
  if (fs.existsSync(commandsDir)) {
463
- for (const entry of fs.readdirSync(commandsDir, { withFileTypes: true })) {
464
- if (entry.isFile() && entry.name.startsWith(ROOT_COMMAND) && entry.name.endsWith('.md')) {
465
- fs.rmSync(path.join(commandsDir, entry.name), { force: true });
551
+ for (const commandName of managedCommandNames(srcRoot)) {
552
+ const commandPath = path.join(commandsDir, `${commandName}.md`);
553
+ if (fs.existsSync(commandPath)) {
554
+ fs.rmSync(commandPath, { force: true });
466
555
  }
467
556
  }
468
557
  }
@@ -472,9 +561,10 @@ function removeInstalledFiles(targetDir, isGlobal) {
472
561
  }
473
562
  const skillsDir = path.join(targetDir, 'skills');
474
563
  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 });
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 });
478
568
  }
479
569
  }
480
570
  }
@@ -553,7 +643,7 @@ function uninstall(options = {}) {
553
643
  const settingsPath = path.join(targetDir, 'settings.json');
554
644
  const settings = readSettings(settingsPath);
555
645
  if (settings) {
556
- removeManagedSettings(settings);
646
+ removeManagedSettings(settings, { targetDir, isGlobal });
557
647
  writeSettings(settingsPath, settings);
558
648
  }
559
649
 
@@ -0,0 +1,27 @@
1
+ ---
2
+ name: autodev:auto
3
+ description: Run autodev continuously until blocked, manual verification is required, or the active track is done
4
+ argument-hint: "[goal, track, or question]"
5
+ allowed-tools:
6
+ - Read
7
+ - Write
8
+ - Edit
9
+ - Bash
10
+ - Grep
11
+ - Glob
12
+ - TodoWrite
13
+ - AskUserQuestion
14
+ - Agent
15
+ - WebFetch
16
+ ---
17
+ <objective>
18
+ Use `/autodev-auto` as the explicit opt-in continuous runner. Keep going until the work is done or a real stop condition appears.
19
+ </objective>
20
+
21
+ <execution_context>
22
+ @~/.claude/autodev/workflows/autodev-auto.md
23
+ </execution_context>
24
+
25
+ <process>
26
+ Execute the auto router workflow in @~/.claude/autodev/workflows/autodev-auto.md end-to-end.
27
+ </process>
@@ -41,7 +41,7 @@ if [ "$ENABLED" != "1" ]; then
41
41
  fi
42
42
 
43
43
  INPUT=$(cat)
44
- FILE=$(echo "$INPUT" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{process.stdout.write(JSON.parse(d).tool_input?.file_path||'')}catch{}})" 2>/dev/null)
44
+ FILE=$(echo "$INPUT" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const t=JSON.parse(d).tool_input||{};process.stdout.write(t.file_path||t.path||'')}catch{}})" 2>/dev/null)
45
45
 
46
46
  if [[ "$FILE" == *.autodev/* ]] || [[ "$FILE" == .autodev/* ]]; then
47
47
  echo ".autodev file updated: $FILE"
@@ -11,6 +11,29 @@ const PATTERNS = [
11
11
  /<\/?(?:system|assistant|human)>/i
12
12
  ];
13
13
 
14
+ function extractWrittenContent(toolInput) {
15
+ if (!toolInput || typeof toolInput !== 'object') {
16
+ return '';
17
+ }
18
+
19
+ if (typeof toolInput.content === 'string' && toolInput.content) {
20
+ return toolInput.content;
21
+ }
22
+
23
+ if (typeof toolInput.new_string === 'string' && toolInput.new_string) {
24
+ return toolInput.new_string;
25
+ }
26
+
27
+ if (Array.isArray(toolInput.edits)) {
28
+ return toolInput.edits
29
+ .map(edit => edit?.new_string || '')
30
+ .filter(Boolean)
31
+ .join('\n');
32
+ }
33
+
34
+ return '';
35
+ }
36
+
14
37
  let input = '';
15
38
  const stdinTimeout = setTimeout(() => process.exit(0), 3000);
16
39
  process.stdin.setEncoding('utf8');
@@ -22,16 +45,16 @@ process.stdin.on('end', () => {
22
45
 
23
46
  try {
24
47
  const data = JSON.parse(input);
25
- if (!['Write', 'Edit'].includes(data.tool_name)) {
48
+ if (!['Write', 'Edit', 'MultiEdit'].includes(data.tool_name)) {
26
49
  process.exit(0);
27
50
  }
28
51
 
29
- const filePath = data.tool_input?.file_path || '';
52
+ const filePath = data.tool_input?.file_path || data.tool_input?.path || '';
30
53
  if (!filePath.includes('.autodev/')) {
31
54
  process.exit(0);
32
55
  }
33
56
 
34
- const content = data.tool_input?.content || data.tool_input?.new_string || '';
57
+ const content = extractWrittenContent(data.tool_input);
35
58
  if (!content) {
36
59
  process.exit(0);
37
60
  }
@@ -15,7 +15,7 @@ process.stdin.on('end', () => {
15
15
 
16
16
  try {
17
17
  const data = JSON.parse(input);
18
- if (!['Write', 'Edit'].includes(data.tool_name)) {
18
+ if (!['Write', 'Edit', 'MultiEdit'].includes(data.tool_name)) {
19
19
  process.exit(0);
20
20
  }
21
21
 
@@ -24,7 +24,7 @@ process.stdin.on('end', () => {
24
24
  process.exit(0);
25
25
  }
26
26
 
27
- const filePath = data.tool_input?.file_path || '';
27
+ const filePath = data.tool_input?.file_path || data.tool_input?.path || '';
28
28
  if (!filePath || !fs.existsSync(filePath)) {
29
29
  process.exit(0);
30
30
  }
@@ -13,21 +13,25 @@ function readStateFields(cwd) {
13
13
  if (!statePath) {
14
14
  return {
15
15
  currentTask: '',
16
- currentTaskStatus: ''
16
+ currentTaskStatus: '',
17
+ runMode: ''
17
18
  };
18
19
  }
19
20
 
20
21
  const content = fs.readFileSync(statePath, 'utf8');
21
22
  const currentTask = content.match(/^Current Task:\s*(.+)$/mi)?.[1]?.trim() || '';
22
23
  const currentTaskStatus = content.match(/^Current Task Status:\s*(.+)$/mi)?.[1]?.trim() || '';
24
+ const runMode = content.match(/^Run Mode:\s*(.+)$/mi)?.[1]?.trim() || '';
23
25
  return {
24
26
  currentTask: currentTask && currentTask !== 'none' ? currentTask : '',
25
- currentTaskStatus: currentTaskStatus && currentTaskStatus !== 'idle' ? currentTaskStatus : ''
27
+ currentTaskStatus: currentTaskStatus && currentTaskStatus !== 'idle' ? currentTaskStatus : '',
28
+ runMode: runMode === 'auto' ? 'auto' : ''
26
29
  };
27
30
  } catch {
28
31
  return {
29
32
  currentTask: '',
30
- currentTaskStatus: ''
33
+ currentTaskStatus: '',
34
+ runMode: ''
31
35
  };
32
36
  }
33
37
  }
@@ -67,11 +71,12 @@ process.stdin.on('end', () => {
67
71
  }
68
72
  }
69
73
 
74
+ const modeLabel = taskState.runMode ? ` | ${taskState.runMode}` : '';
70
75
  const taskLabel = taskState.currentTask
71
76
  ? ` | task ${taskState.currentTask}${taskState.currentTaskStatus ? ` (${taskState.currentTaskStatus})` : ''}`
72
77
  : '';
73
78
 
74
- process.stdout.write(`${model} | ${path.basename(currentDir)}${taskLabel}${contextLabel}`);
79
+ process.stdout.write(`${model} | ${path.basename(currentDir)}${modeLabel}${taskLabel}${contextLabel}`);
75
80
  } catch {
76
81
  process.exit(0);
77
82
  }
@@ -14,7 +14,7 @@ process.stdin.on('end', () => {
14
14
 
15
15
  try {
16
16
  const data = JSON.parse(input);
17
- if (!['Write', 'Edit'].includes(data.tool_name)) {
17
+ if (!['Write', 'Edit', 'MultiEdit'].includes(data.tool_name)) {
18
18
  process.exit(0);
19
19
  }
20
20
 
package/hooks/hooks.json CHANGED
@@ -13,7 +13,7 @@
13
13
  ],
14
14
  "PreToolUse": [
15
15
  {
16
- "matcher": "Write|Edit",
16
+ "matcher": "Write|Edit|MultiEdit",
17
17
  "hooks": [
18
18
  {
19
19
  "type": "command",
@@ -23,7 +23,7 @@
23
23
  ]
24
24
  },
25
25
  {
26
- "matcher": "Write|Edit",
26
+ "matcher": "Write|Edit|MultiEdit",
27
27
  "hooks": [
28
28
  {
29
29
  "type": "command",
@@ -33,7 +33,7 @@
33
33
  ]
34
34
  },
35
35
  {
36
- "matcher": "Write|Edit",
36
+ "matcher": "Write|Edit|MultiEdit",
37
37
  "hooks": [
38
38
  {
39
39
  "type": "command",
@@ -75,7 +75,7 @@
75
75
  ]
76
76
  },
77
77
  {
78
- "matcher": "Write|Edit",
78
+ "matcher": "Write|Edit|MultiEdit",
79
79
  "hooks": [
80
80
  {
81
81
  "type": "command",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mthanhlm/autodev",
3
- "version": "0.4.2",
3
+ "version": "0.4.4",
4
4
  "description": "A lean Claude Code workflow system with a single entrypoint, task-based phase execution, and read-only git.",
5
5
  "bin": {
6
6
  "autodev": "bin/install.js"