@mthanhlm/autodev 0.3.6 → 0.4.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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "autodev",
3
3
  "description": "A lean Claude Code workflow system with a single entrypoint, task-based phase execution, and read-only git.",
4
- "version": "0.3.6",
4
+ "version": "0.4.0",
5
5
  "author": {
6
6
  "name": "mthanhlm"
7
7
  },
package/PUBLISH.md CHANGED
@@ -40,7 +40,7 @@ npm publish --access public
40
40
 
41
41
  ## Later Releases
42
42
 
43
- 1. Bump the version in `package.json`.
43
+ 1. Bump the version in both `package.json` and `.claude-plugin/plugin.json`.
44
44
  2. Regenerate the tarball:
45
45
 
46
46
  ```bash
package/README.md CHANGED
@@ -8,14 +8,16 @@
8
8
  - Keeps project state in `.autodev/`
9
9
  - Uses `/autodev` as the main command
10
10
  - Organizes work as `project -> track -> phase -> tasks`
11
- - Maps brownfield repos with parallel codebase agents when the environment supports them
12
- - Runs a multi-lens review pass, using parallel review agents when the environment supports them
11
+ - Resolves `.autodev/` state from the repo root even when Claude is started in a nested subdirectory
12
+ - Maps brownfield repos with foreground delegated agents when the environment supports them
13
+ - Runs a multi-lens review pass, using foreground review agents when the environment supports them
13
14
  - Ships manual commands when you want direct control:
14
15
  - `/autodev-help`
15
16
  - `/autodev-new-project`
16
17
  - `/autodev-explore-codebase`
17
18
  - `/autodev-plan-phase`
18
19
  - `/autodev-execute-phase`
20
+ - `/autodev-review-task`
19
21
  - `/autodev-review-phase`
20
22
  - `/autodev-verify-work`
21
23
  - `/autodev-cleanup`
@@ -43,6 +45,12 @@ Local install for one repo:
43
45
  npx @mthanhlm/autodev@latest --local
44
46
  ```
45
47
 
48
+ Install and also disable Claude Code background tasks in the target `settings.json`:
49
+
50
+ ```bash
51
+ npx @mthanhlm/autodev@latest --global --disable-background-tasks
52
+ ```
53
+
46
54
  Uninstall:
47
55
 
48
56
  ```bash
@@ -64,8 +72,10 @@ Typical brownfield route:
64
72
  -> explore codebase
65
73
  -> plan phase
66
74
  -> review phase plan
67
- -> execute next task
68
- -> execute next task
75
+ -> execute one task
76
+ -> review task
77
+ -> execute one task
78
+ -> review task
69
79
  -> review
70
80
  -> verify
71
81
  ```
@@ -99,10 +109,12 @@ project -> track -> phase -> tasks
99
109
  - `/autodev` stops after planning so the user can review the phase before any execution starts
100
110
  - The phase keeps one user-facing orchestration session
101
111
  - Each task is preferably executed by a fresh foreground delegated agent
102
- - After each task, the worker reports back with files changed, verification, and blockers
112
+ - After each task, the delegated agent reports back with files changed, verification, and blockers
113
+ - `/autodev` stops after each completed task and reopens a task review checkpoint before any further execution
103
114
  - No waves by default
104
- - No parallel execution unless the user explicitly asks for it later
115
+ - No automatic parallel execution
105
116
  - If specialized agents are unavailable in the current Claude Code environment, the workflow falls back cleanly to current-session execution instead of stopping on platform wording
117
+ - If you want Claude Code itself to hard-disable background task functionality, install with `--disable-background-tasks`, which writes `CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1` into Claude Code `settings.json`
106
118
 
107
119
  ## Git Policy
108
120
 
@@ -9,12 +9,9 @@ const DEFAULT_CONFIG = {
9
9
  },
10
10
  workflow: {
11
11
  research: false,
12
- review_after_execute: true,
13
- codebase_parallel_agents: 4
14
- },
15
- execution: {
16
- parallel: false
12
+ review_after_execute: true
17
13
  },
14
+ execution: {},
18
15
  git: {
19
16
  mode: 'read-only'
20
17
  },
@@ -31,13 +28,38 @@ const DEFAULT_CONFIG = {
31
28
 
32
29
  const CODEBASE_FILES = ['structure.md', 'domain.md', 'runtime.md', 'quality.md', 'summary.md'];
33
30
 
31
+ function findWorkspaceRoot(startDir) {
32
+ let cursor = path.resolve(startDir || process.cwd());
33
+ let gitRoot = null;
34
+
35
+ while (true) {
36
+ if (fileExists(path.join(cursor, '.autodev'))) {
37
+ return cursor;
38
+ }
39
+
40
+ if (!gitRoot && fileExists(path.join(cursor, '.git'))) {
41
+ gitRoot = cursor;
42
+ }
43
+
44
+ const parent = path.dirname(cursor);
45
+ if (parent === cursor) {
46
+ break;
47
+ }
48
+ cursor = parent;
49
+ }
50
+
51
+ return gitRoot || path.resolve(startDir || process.cwd());
52
+ }
53
+
34
54
  function autodevDir(cwd) {
35
- return path.join(cwd, '.autodev');
55
+ return path.join(findWorkspaceRoot(cwd), '.autodev');
36
56
  }
37
57
 
38
58
  function rootPaths(cwd) {
59
+ const workspaceRoot = findWorkspaceRoot(cwd);
39
60
  const root = autodevDir(cwd);
40
61
  return {
62
+ workspaceRoot,
41
63
  root,
42
64
  config: path.join(root, 'config.json'),
43
65
  project: path.join(root, 'PROJECT.md'),
@@ -86,6 +108,7 @@ function padPhase(number) {
86
108
  }
87
109
 
88
110
  function detectExistingCodebase(cwd) {
111
+ const workspaceRoot = findWorkspaceRoot(cwd);
89
112
  const knownFiles = [
90
113
  'package.json',
91
114
  'package-lock.json',
@@ -117,16 +140,16 @@ function detectExistingCodebase(cwd) {
117
140
  '__tests__'
118
141
  ];
119
142
 
120
- if (knownFiles.some(name => fileExists(path.join(cwd, name)))) {
143
+ if (knownFiles.some(name => fileExists(path.join(workspaceRoot, name)))) {
121
144
  return true;
122
145
  }
123
146
 
124
- if (knownDirs.some(name => fileExists(path.join(cwd, name)))) {
147
+ if (knownDirs.some(name => fileExists(path.join(workspaceRoot, name)))) {
125
148
  return true;
126
149
  }
127
150
 
128
151
  try {
129
- const entries = fs.readdirSync(cwd, { withFileTypes: true });
152
+ const entries = fs.readdirSync(workspaceRoot, { withFileTypes: true });
130
153
  return entries.some(entry => {
131
154
  if (entry.name === '.autodev' || entry.name === '.claude' || entry.name.startsWith('.')) {
132
155
  return false;
@@ -257,6 +280,35 @@ function readSingleLineField(content, label) {
257
280
  return match ? match[1].trim() : null;
258
281
  }
259
282
 
283
+ function parseStateSnapshot(content) {
284
+ if (!content) {
285
+ return null;
286
+ }
287
+
288
+ const currentPhaseRaw = readSingleLineField(content, 'Current Phase');
289
+ const currentPhaseNumber = currentPhaseRaw && /^\d+$/.test(currentPhaseRaw)
290
+ ? Number(currentPhaseRaw)
291
+ : null;
292
+
293
+ return {
294
+ currentPhase: currentPhaseNumber,
295
+ currentPhaseType: readSingleLineField(content, 'Current Phase Type'),
296
+ currentStep: readSingleLineField(content, 'Current Step'),
297
+ currentTask: readSingleLineField(content, 'Current Task'),
298
+ currentTaskStatus: readSingleLineField(content, 'Current Task Status'),
299
+ status: readSingleLineField(content, 'Status'),
300
+ nextCommand: readSingleLineField(content, 'Next Command')
301
+ };
302
+ }
303
+
304
+ function readStateSnapshot(filePath) {
305
+ return parseStateSnapshot(readText(filePath));
306
+ }
307
+
308
+ function isBlockedTaskStatus(value) {
309
+ return typeof value === 'string' && /^blocked/i.test(value.trim());
310
+ }
311
+
260
312
  function parseTaskDependsOn(value) {
261
313
  if (!value || /^none$/i.test(value)) {
262
314
  return [];
@@ -336,9 +388,16 @@ function listTasksForPhaseDetails(phaseDetails) {
336
388
  }
337
389
 
338
390
  function nextExecutableTask(tasks) {
339
- return tasks.find(task => !task.summaryExists && task.ready)
340
- || tasks.find(task => !task.summaryExists)
341
- || null;
391
+ return tasks.find(task => !task.summaryExists && task.ready) || null;
392
+ }
393
+
394
+ function lastCompletedTask(tasks) {
395
+ const completed = tasks.filter(task => task.summaryExists);
396
+ return completed.length > 0 ? completed[completed.length - 1] : null;
397
+ }
398
+
399
+ function hasDependencyDeadlock(tasks) {
400
+ return tasks.some(task => !task.summaryExists) && !nextExecutableTask(tasks);
342
401
  }
343
402
 
344
403
  function listPhases(cwd, slug = readActiveTrack(cwd)) {
@@ -396,19 +455,51 @@ function resolvePhase(cwd, slug, requestedPhase, mode) {
396
455
  return phases.find(phase => phase.number === numeric) || null;
397
456
  }
398
457
 
458
+ const track = trackPaths(cwd, slug);
459
+ const trackState = track ? readStateSnapshot(track.state) : null;
460
+ const currentStatePhase = trackState?.currentPhase
461
+ ? phases.find(phase => phase.number === trackState.currentPhase) || null
462
+ : null;
463
+
399
464
  if (mode === 'plan') {
465
+ if (currentStatePhase && (
466
+ isBlockedTaskStatus(trackState.currentTaskStatus)
467
+ || trackState.currentStep === 'planning'
468
+ || trackState.currentStep === 'plan_review'
469
+ )) {
470
+ return currentStatePhase;
471
+ }
400
472
  return phases.find(phase => !phase.planExists) || phases[0];
401
473
  }
402
474
 
403
475
  if (mode === 'execute') {
476
+ if (currentStatePhase && (
477
+ trackState?.currentStep === 'execution'
478
+ || trackState?.currentStep === 'task_review'
479
+ )) {
480
+ return currentStatePhase;
481
+ }
404
482
  return phases.find(phase => phase.planExists && !phase.summaryExists) || null;
405
483
  }
406
484
 
485
+ if (mode === 'task_review') {
486
+ if (currentStatePhase && trackState?.currentStep === 'task_review') {
487
+ return currentStatePhase;
488
+ }
489
+ return phases.find(phase => phase.planExists && !phase.summaryExists && phase.taskDoneCount > 0) || null;
490
+ }
491
+
407
492
  if (mode === 'review') {
493
+ if (currentStatePhase && trackState?.currentStep === 'review') {
494
+ return currentStatePhase;
495
+ }
408
496
  return phases.find(phase => phase.summaryExists && !phase.reviewExists) || null;
409
497
  }
410
498
 
411
499
  if (mode === 'verify') {
500
+ if (currentStatePhase && trackState?.currentStep === 'verification') {
501
+ return currentStatePhase;
502
+ }
412
503
  return phases.find(phase => phase.reviewExists && !phase.uatExists)
413
504
  || [...phases].reverse().find(phase => phase.reviewExists)
414
505
  || null;
@@ -470,6 +561,7 @@ function buildRoute(cwd) {
470
561
  }
471
562
 
472
563
  const phases = listPhases(cwd, activeTrack);
564
+ const trackState = readStateSnapshot(track.state);
473
565
  if (phases.length === 0) {
474
566
  return {
475
567
  kind: 'track_setup',
@@ -481,6 +573,84 @@ function buildRoute(cwd) {
481
573
  };
482
574
  }
483
575
 
576
+ const currentStatePhase = trackState?.currentPhase
577
+ ? phases.find(phase => phase.number === trackState.currentPhase) || null
578
+ : null;
579
+
580
+ if (currentStatePhase && (
581
+ isBlockedTaskStatus(trackState?.currentTaskStatus)
582
+ )) {
583
+ return {
584
+ kind: 'plan_phase',
585
+ command: '/autodev',
586
+ manualCommand: `/autodev-plan-phase ${currentStatePhase.number}`,
587
+ reason: 'blocked_phase_requires_replanning',
588
+ projectType,
589
+ trackSlug: activeTrack,
590
+ phaseNumber: currentStatePhase.number
591
+ };
592
+ }
593
+
594
+ if (currentStatePhase && trackState?.currentStep === 'execution') {
595
+ return {
596
+ kind: 'execute_phase',
597
+ command: '/autodev',
598
+ manualCommand: `/autodev-execute-phase ${currentStatePhase.number}`,
599
+ reason: 'phase_execution_in_progress',
600
+ projectType,
601
+ trackSlug: activeTrack,
602
+ phaseNumber: currentStatePhase.number
603
+ };
604
+ }
605
+
606
+ if (currentStatePhase && trackState?.currentStep === 'task_review') {
607
+ return {
608
+ kind: 'task_review',
609
+ command: '/autodev',
610
+ manualCommand: `/autodev-review-task ${currentStatePhase.number}`,
611
+ reason: 'task_execution_checkpoint_pending_user_review',
612
+ projectType,
613
+ trackSlug: activeTrack,
614
+ phaseNumber: currentStatePhase.number
615
+ };
616
+ }
617
+
618
+ if (currentStatePhase && trackState?.currentStep === 'plan_review') {
619
+ return {
620
+ kind: 'plan_review',
621
+ command: '/autodev',
622
+ manualCommand: `/autodev-execute-phase ${currentStatePhase.number}`,
623
+ reason: 'phase_plan_awaits_review',
624
+ projectType,
625
+ trackSlug: activeTrack,
626
+ phaseNumber: currentStatePhase.number
627
+ };
628
+ }
629
+
630
+ if (currentStatePhase && trackState?.currentStep === 'review' && !currentStatePhase.reviewExists) {
631
+ return {
632
+ kind: 'review_phase',
633
+ command: '/autodev',
634
+ manualCommand: `/autodev-review-phase ${currentStatePhase.number}`,
635
+ reason: 'phase_review_in_progress',
636
+ projectType,
637
+ trackSlug: activeTrack,
638
+ phaseNumber: currentStatePhase.number
639
+ };
640
+ }
641
+
642
+ if (currentStatePhase && trackState?.currentStep === 'verification' && !currentStatePhase.uatExists) {
643
+ return {
644
+ kind: 'verify_phase',
645
+ command: '/autodev',
646
+ manualCommand: `/autodev-verify-work ${currentStatePhase.number}`,
647
+ reason: 'phase_verification_in_progress',
648
+ projectType,
649
+ trackSlug: activeTrack,
650
+ phaseNumber: currentStatePhase.number
651
+ };
652
+ }
653
+
484
654
  const nextReview = phases.find(phase => phase.summaryExists && !phase.reviewExists);
485
655
  if (nextReview && config.workflow.review_after_execute !== false) {
486
656
  return {
@@ -507,6 +677,19 @@ function buildRoute(cwd) {
507
677
  };
508
678
  }
509
679
 
680
+ const nextTaskReview = phases.find(phase => phase.planExists && !phase.summaryExists && phase.taskDoneCount > 0);
681
+ if (nextTaskReview) {
682
+ return {
683
+ kind: 'task_review',
684
+ command: '/autodev',
685
+ manualCommand: `/autodev-review-task ${nextTaskReview.number}`,
686
+ reason: 'phase_task_completed_awaiting_user_review',
687
+ projectType,
688
+ trackSlug: activeTrack,
689
+ phaseNumber: nextTaskReview.number
690
+ };
691
+ }
692
+
510
693
  const nextPlannedReview = phases.find(phase => phase.planExists && !phase.summaryExists);
511
694
  if (nextPlannedReview) {
512
695
  return {
@@ -643,6 +826,7 @@ function buildStatus(cwd) {
643
826
 
644
827
  return {
645
828
  cwd,
829
+ workspace_root: paths.workspaceRoot,
646
830
  autodev_exists: fileExists(paths.root),
647
831
  project_exists: fileExists(paths.project),
648
832
  existing_code_detected: existingCodebase,
@@ -706,6 +890,8 @@ function initPayload(cwd, mode, requestedPhase) {
706
890
  ? 'review'
707
891
  : mode === 'verify-work'
708
892
  ? 'verify'
893
+ : mode === 'review-task'
894
+ ? 'task_review'
709
895
  : mode === 'execute-phase' || mode === 'review-plan'
710
896
  ? 'execute'
711
897
  : mode === 'plan-phase'
@@ -714,6 +900,17 @@ function initPayload(cwd, mode, requestedPhase) {
714
900
  const phase = phaseMode && activeTrack ? resolvePhase(cwd, activeTrack, requestedPhase, phaseMode) : null;
715
901
  const tasks = phase ? listTasksForPhaseDetails(phase) : [];
716
902
  const nextTask = nextExecutableTask(tasks);
903
+ const lastCompleted = lastCompletedTask(tasks);
904
+ const dependencyDeadlock = hasDependencyDeadlock(tasks);
905
+ const trackStateSnapshot = track ? readStateSnapshot(track.state) : null;
906
+ const currentTaskNumber = (() => {
907
+ const raw = trackStateSnapshot?.currentTask;
908
+ if (raw && /^\d+$/.test(raw)) {
909
+ return Number(raw);
910
+ }
911
+ return lastCompleted ? lastCompleted.number : null;
912
+ })();
913
+ const currentTask = tasks.find(task => task.number === currentTaskNumber) || lastCompleted || null;
717
914
  const codebase = codebasePaths(cwd);
718
915
 
719
916
  return {
@@ -742,6 +939,7 @@ function initPayload(cwd, mode, requestedPhase) {
742
939
  requirements_path: track ? track.requirements : null,
743
940
  roadmap_path: track ? track.roadmap : null,
744
941
  track_state_path: track ? track.state : null,
942
+ track_state: trackStateSnapshot,
745
943
  phases_dir: track ? track.phasesDir : null,
746
944
  route,
747
945
  phase_found: Boolean(phase),
@@ -761,9 +959,15 @@ function initPayload(cwd, mode, requestedPhase) {
761
959
  task_count: tasks.length,
762
960
  task_done_count: tasks.filter(task => task.summaryExists).length,
763
961
  all_tasks_done: tasks.length > 0 && tasks.every(task => task.summaryExists),
962
+ dependency_deadlock: dependencyDeadlock,
764
963
  next_task_number: nextTask ? nextTask.number : null,
765
964
  next_task_path: nextTask ? nextTask.taskPath : null,
766
965
  next_task_summary_path: nextTask ? nextTask.summaryPath : null,
966
+ current_task_number: currentTask ? currentTask.number : null,
967
+ current_task_path: currentTask ? currentTask.taskPath : null,
968
+ current_task_summary_path: currentTask ? currentTask.summaryPath : null,
969
+ last_completed_task_number: lastCompleted ? lastCompleted.number : null,
970
+ last_completed_task_summary_path: lastCompleted ? lastCompleted.summaryPath : null,
767
971
  tasks: tasks.map(task => ({
768
972
  number: task.number,
769
973
  title: task.title,
@@ -867,6 +1071,7 @@ module.exports = {
867
1071
  buildRoute,
868
1072
  buildStatus,
869
1073
  detectExistingCodebase,
1074
+ findWorkspaceRoot,
870
1075
  initPayload,
871
1076
  listPhases,
872
1077
  listTracks,
@@ -878,5 +1083,7 @@ module.exports = {
878
1083
  rootPaths,
879
1084
  trackPaths,
880
1085
  listTasksForPhaseDetails,
1086
+ lastCompletedTask,
1087
+ hasDependencyDeadlock,
881
1088
  nextExecutableTask
882
1089
  };
@@ -4,11 +4,7 @@
4
4
  },
5
5
  "workflow": {
6
6
  "research": false,
7
- "review_after_execute": true,
8
- "codebase_parallel_agents": 4
9
- },
10
- "execution": {
11
- "parallel": false
7
+ "review_after_execute": true
12
8
  },
13
9
  "git": {
14
10
  "mode": "read-only"
@@ -27,6 +27,7 @@ node "$AUTODEV_ROOT/autodev/bin/autodev-tools.cjs" status
27
27
  - `plan_phase`: load and perform the plan-phase workflow.
28
28
  - `plan_review`: load and perform the review-plan workflow so the user can review the phase plan before any execution starts.
29
29
  - `execute_phase`: load and perform the execute-phase workflow, which should advance one task-sized unit in the current phase session by trying a fresh foreground agent first and falling back cleanly when agent delegation is unavailable.
30
+ - `task_review`: load and perform the review-task workflow so the user can review one completed task before any further execution starts.
30
31
  - `review_phase`: load and perform the review-phase workflow.
31
32
  - `verify_phase`: load and perform the verify-work workflow.
32
33
 
@@ -42,7 +42,7 @@ node "$AUTODEV_ROOT/autodev/bin/autodev-tools.cjs" init execute-phase "$ARGUMENT
42
42
  5. Determine the next executable task:
43
43
  - prefer the first task without a summary whose dependencies are already done
44
44
  - if no executable task remains and all task summaries exist, move to phase aggregation
45
- - if task dependencies are inconsistent, stop and ask the user how to repair the plan
45
+ - if `dependency_deadlock` is true, stop before execution, set both state files to `Current Step: planning`, set `Current Task: none`, set `Current Task Status: blocked_dependencies`, keep `Next Command: /autodev`, and ask the user how to repair the plan
46
46
 
47
47
  6. Before running a task, show the user:
48
48
  - task id and title
@@ -68,10 +68,10 @@ node "$AUTODEV_ROOT/autodev/bin/autodev-tools.cjs" init execute-phase "$ARGUMENT
68
68
 
69
69
  8. If worker delegation is unavailable, do not stop on the raw platform message.
70
70
  Treat messages like `specialized agents aren't available` as a capability limitation, then:
71
- - tell the user plainly that worker-agent delegation is unavailable in this environment
71
+ - tell the user plainly that delegated-agent execution is unavailable in this environment
72
72
  - continue with the same task in the current session
73
73
  - keep the same task boundaries
74
- - still write the task summary before moving on
74
+ - still write the task summary before stopping
75
75
 
76
76
  9. After the delegated agent returns, or after the current-session fallback completes:
77
77
  - confirm `TASK-NN-SUMMARY.md` exists
@@ -80,11 +80,12 @@ node "$AUTODEV_ROOT/autodev/bin/autodev-tools.cjs" init execute-phase "$ARGUMENT
80
80
 
81
81
  10. If more tasks remain:
82
82
  - update project and track state to:
83
- - `Current Step: execution`
84
- - `Current Task: <next-task>`
85
- - `Current Task Status: ready`
83
+ - `Current Step: task_review`
84
+ - `Current Task: <current-task>`
85
+ - `Current Task Status: complete`
86
86
  - `Next Command: /autodev`
87
- - end by telling the user `/autodev` will continue with the next task
87
+ - stop after this task
88
+ - end by telling the user `/autodev` will open the task review checkpoint before any next-task execution
88
89
 
89
90
  11. If all tasks are done:
90
91
  - write or update `NN-SUMMARY.md` from the template with:
@@ -97,26 +98,30 @@ node "$AUTODEV_ROOT/autodev/bin/autodev-tools.cjs" init execute-phase "$ARGUMENT
97
98
  12. Update the active track `STATE.md` so it points to:
98
99
  - `Current Phase: N`
99
100
  - `Current Phase Type: <type>`
100
- - `Current Step: review`
101
- - `Current Task: none`
101
+ - `Current Step: task_review`
102
+ - `Current Task: <current-task>`
102
103
  - `Current Task Status: complete`
103
104
  - `Next Command: /autodev`
104
105
  - current ISO timestamp
105
106
 
106
107
  13. Update `.autodev/STATE.md` so it points to:
107
108
  - `Active Track: <slug>`
108
- - `Current Step: review`
109
- - `Current Task: none`
109
+ - `Current Step: task_review`
110
+ - `Current Task: <current-task>`
110
111
  - `Current Task Status: complete`
111
112
  - `Next Command: /autodev`
112
113
  - current ISO timestamp
113
114
 
114
115
  14. If the current task is blocked or incomplete:
115
116
  - say so clearly in the task summary
116
- - set both state files to `Current Step: execution`
117
+ - set both state files to `Current Step: planning`
117
118
  - set `Current Task: <same-task>`
118
119
  - set `Current Task Status: blocked`
119
120
  - keep `Next Command: /autodev`
121
+ - tell the user the phase should be revised before more execution
120
122
 
121
- 15. End with a short outcome summary and the next recommended command. Mention that the automatic review bundle is the next routed step only when the whole phase is complete.
123
+ 15. End with a short outcome summary and the next recommended command.
124
+ - Never execute a second task in the same run.
125
+ - If all tasks are now done, say `/autodev` will open the phase-review checkpoint next.
126
+ - Otherwise, say `/autodev` will open the task-review checkpoint for the completed task.
122
127
  </process>
@@ -3,7 +3,7 @@ Map a brownfield repository quickly and produce a usable codebase brief without
3
3
  </purpose>
4
4
 
5
5
  <rules>
6
- - Prefer four parallel agents for this workflow when specialized agents are available.
6
+ - Prefer a small set of foreground codebase agents, run one at a time, when agent delegation is available.
7
7
  - Give each agent a disjoint write target.
8
8
  - Use read-only investigation only. No git writes.
9
9
  - Focus on information that will improve planning and execution for the active track.
@@ -26,7 +26,7 @@ node "$AUTODEV_ROOT/autodev/bin/autodev-tools.cjs" init explore-codebase
26
26
  - the active track docs if they exist
27
27
  - representative repo files needed to orient the exploration
28
28
 
29
- 4. If specialized agents are available, explicitly call the `Agent` tool four times with these installed subagents and owned outputs:
29
+ 4. If agent delegation is available, explicitly call the `Agent` tool for these subagents, one at a time in the foreground, with owned outputs:
30
30
  - `autodev-codebase-structure` for standalone installs, or `autodev:autodev-codebase-structure` in direct plugin runs, owns `.autodev/codebase/structure.md`
31
31
  - `autodev-codebase-domain` for standalone installs, or `autodev:autodev-codebase-domain` in direct plugin runs, owns `.autodev/codebase/domain.md`
32
32
  - `autodev-codebase-runtime` for standalone installs, or `autodev:autodev-codebase-runtime` in direct plugin runs, owns `.autodev/codebase/runtime.md`
@@ -1,6 +1,7 @@
1
1
  # autodev
2
2
 
3
3
  Lean Claude Code workflow. No automatic commits. No branches. No worktrees. Git is read-only.
4
+ It resolves `.autodev/` state from the repo root even when you start Claude in a nested subdirectory.
4
5
 
5
6
  ## Main Entry
6
7
 
@@ -14,13 +15,15 @@ Lean Claude Code workflow. No automatic commits. No branches. No worktrees. Git
14
15
  - `/autodev-new-project`
15
16
  Creates project state in `.autodev/` and the first active track under `.autodev/tracks/<track>/`.
16
17
  - `/autodev-explore-codebase`
17
- Uses four parallel agents when available to map a brownfield repo into `.autodev/codebase/`.
18
+ Uses foreground codebase agents, one at a time when available, to map a brownfield repo into `.autodev/codebase/`.
18
19
  - `/autodev-plan-phase [phase]`
19
20
  Creates or revises one phase plan plus task files in `.autodev/tracks/<track>/phases/NN-type-name/`.
20
21
  - `/autodev-execute-phase [phase]`
21
- Orchestrates one phase task-by-task, preferring a fresh foreground delegated agent per task, and writes `TASK-NN-SUMMARY.md` plus the final `NN-SUMMARY.md`.
22
+ Orchestrates exactly one task for the phase, preferring a fresh foreground delegated agent, and writes `TASK-NN-SUMMARY.md`.
23
+ - `/autodev-review-task [phase]`
24
+ Pauses after one completed task so the user can review the result before executing the next task or starting phase review.
22
25
  - `/autodev-review-phase [phase]`
23
- Uses four review agents when available for code quality, security, integration, and polish, then writes `NN-REVIEW.md`.
26
+ Uses foreground review agents, one at a time when available, for code quality, security, integration, and polish, then writes `NN-REVIEW.md`.
24
27
  - `/autodev-verify-work [phase]`
25
28
  Records manual verification in `NN-UAT.md` and captures fix-return gaps when verification fails.
26
29
  - `/autodev-progress`
@@ -58,6 +61,9 @@ Typical brownfield route:
58
61
  -> plan
59
62
  -> review plan
60
63
  -> execute
64
+ -> review task
65
+ -> execute
66
+ -> review task
61
67
  -> review
62
68
  -> verify
63
69
  -> next track or cleanup
@@ -85,9 +91,12 @@ Blocked:
85
91
  - `git merge`
86
92
  - `git rebase`
87
93
  - `git worktree`
94
+ - `git clean`
95
+ - `git cherry-pick`
88
96
  - `git push`
89
97
  - `git pull`
90
98
  - `git stash`
91
99
  - `git reset`
92
100
  - `git fetch`
93
101
  - `git clone`
102
+ - mutating `git branch`, `git tag`, and `git remote` commands
@@ -31,8 +31,6 @@ node "$AUTODEV_ROOT/autodev/bin/autodev-tools.cjs" init new-project
31
31
  - `project.type: "greenfield"` or `"brownfield"`
32
32
  - `workflow.research: false`
33
33
  - `workflow.review_after_execute: true`
34
- - `workflow.codebase_parallel_agents: 4`
35
- - `execution.parallel: false`
36
34
  - `git.mode: "read-only"`
37
35
 
38
36
  6. Write `.autodev/PROJECT.md` with:
@@ -3,7 +3,7 @@ Run the automatic review bundle after execution so the user sees code quality, s
3
3
  </purpose>
4
4
 
5
5
  <rules>
6
- - Prefer four review agents in parallel when specialized agents are available.
6
+ - Prefer a small set of foreground review agents, run one at a time, when agent delegation is available.
7
7
  - Review findings should be concrete and evidence-based.
8
8
  - Do not edit repository code in this step unless the user explicitly asks for fixes.
9
9
  - Write one consolidated review artifact for the phase.
@@ -28,7 +28,7 @@ node "$AUTODEV_ROOT/autodev/bin/autodev-tools.cjs" init review-phase "$ARGUMENTS
28
28
  - `NN-SUMMARY.md`
29
29
  - relevant source files and tests
30
30
 
31
- 4. If specialized agents are available, explicitly call the `Agent` tool four times with these installed review subagents:
31
+ 4. If agent delegation is available, explicitly call the `Agent` tool for these review subagents, one at a time in the foreground:
32
32
  - `autodev-review-quality` for standalone installs, or `autodev:autodev-review-quality` in direct plugin runs
33
33
  - `autodev-review-security` for standalone installs, or `autodev:autodev-review-security` in direct plugin runs
34
34
  - `autodev-review-integration` for standalone installs, or `autodev:autodev-review-integration` in direct plugin runs
@@ -54,9 +54,11 @@ node "$AUTODEV_ROOT/autodev/bin/autodev-tools.cjs" init review-phase "$ARGUMENTS
54
54
  - a final recommendation
55
55
 
56
56
  8. If blockers are found:
57
- - set project and track state back to `Current Step: execution`
57
+ - set project and track state back to `Current Step: planning`
58
+ - set `Current Task: none`
59
+ - set `Current Task Status: blocked_review`
58
60
  - keep `Next Command: /autodev`
59
- - make the recommendation point back to the same phase
61
+ - make the recommendation point back to revising the same phase with blocker-fix tasks
60
62
 
61
63
  9. If blockers are not found:
62
64
  - set project and track state to `Current Step: verification`
@@ -32,13 +32,14 @@ node "$AUTODEV_ROOT/autodev/bin/autodev-tools.cjs" init review-plan "$ARGUMENTS"
32
32
  - shared verification plan
33
33
 
34
34
  5. Ask the user what to do next with `AskUserQuestion`:
35
- - execute the next task now
35
+ - execute one task now
36
36
  - revise the phase plan
37
37
  - stop here
38
38
 
39
39
  6. If the user chooses to execute now:
40
40
  - load the execute-phase workflow
41
- - perform it in the same turn
41
+ - perform exactly one task in the same turn
42
+ - stop after that task completes
42
43
 
43
44
  7. If the user chooses to revise the plan:
44
45
  - load the plan-phase workflow
@@ -0,0 +1,70 @@
1
+ <purpose>
2
+ Pause after one completed task so the user can review the outcome before more execution happens in the same phase.
3
+ </purpose>
4
+
5
+ <rules>
6
+ - This is a control checkpoint, not an execution step.
7
+ - Do not auto-run the next task from this checkpoint without explicit user approval.
8
+ - Prefer `/autodev` as the entrypoint even at this checkpoint.
9
+ - If all tasks are done, do not auto-start phase review without explicit user approval.
10
+ </rules>
11
+
12
+ <process>
13
+ 1. Run:
14
+
15
+ ```bash
16
+ AUTODEV_ROOT="${CLAUDE_PLUGIN_ROOT:-$HOME/.claude}"
17
+ node "$AUTODEV_ROOT/autodev/bin/autodev-tools.cjs" init review-task "$ARGUMENTS"
18
+ ```
19
+
20
+ 2. If no phase is found, stop and direct the user to `/autodev`.
21
+
22
+ 3. Read:
23
+ - `.autodev/STATE.md`
24
+ - the active track `STATE.md`
25
+ - `NN-PLAN.md`
26
+ - all `TASK-*.md`
27
+ - the current task summary from `current_task_summary_path`
28
+ - if `Current Task` is missing or stale, infer the checkpoint task from the most recent existing `TASK-*-SUMMARY.md`
29
+ - `NN-SUMMARY.md` if it exists
30
+
31
+ 4. Show the user a concise checkpoint summary:
32
+ - completed task id and title
33
+ - what changed
34
+ - verification run
35
+ - blockers or open concerns
36
+ - the next ready task, if any
37
+
38
+ 5. If more tasks remain, ask the user what to do next with `AskUserQuestion`:
39
+ - execute the next task now
40
+ - revise the phase plan
41
+ - stop here
42
+
43
+ 6. If all tasks are done, ask the user what to do next with `AskUserQuestion`:
44
+ - run phase review now
45
+ - revise the phase plan
46
+ - stop here
47
+
48
+ 7. If the user chooses to execute the next task:
49
+ - load the execute-phase workflow
50
+ - perform exactly one task in the same turn
51
+ - stop again after that task completes
52
+
53
+ 8. If the user chooses to run phase review:
54
+ - load the review-phase workflow
55
+ - perform it in the same turn
56
+
57
+ 9. If the user chooses to revise the plan:
58
+ - load the plan-phase workflow
59
+ - revise the same phase in the same turn
60
+
61
+ 10. If the user chooses to stop here:
62
+ - keep both state files on:
63
+ - `Current Step: task_review`
64
+ - `Current Task: <current-task>`
65
+ - `Current Task Status: complete`
66
+ - `Next Command: /autodev`
67
+ - end with a short note that no further execution has started
68
+
69
+ 11. End with a short outcome summary and `/autodev` as the default next command.
70
+ </process>
@@ -42,9 +42,9 @@ node "$AUTODEV_ROOT/autodev/bin/autodev-tools.cjs" init verify-work "$ARGUMENTS"
42
42
  7. Update the active track `STATE.md`:
43
43
  - if verification passed and another phase remains, move to the next phase and set `Current Step: planning`
44
44
  - if verification passed and no phase remains, set `Current Step: complete`
45
- - if verification failed, keep the same phase active and set `Current Step: execution`
45
+ - if verification failed, keep the same phase active and set `Current Step: planning`
46
46
  - when moving to another phase, set `Current Task: none` and `Current Task Status: idle`
47
- - when verification failed, set `Current Task: none` and `Current Task Status: idle`
47
+ - when verification failed, set `Current Task: none` and `Current Task Status: blocked_verification`
48
48
  - always set `Next Command: /autodev`
49
49
  - always refresh the ISO timestamp
50
50
 
package/bin/install.js CHANGED
@@ -14,6 +14,7 @@ const MANAGED_PREFIX = 'autodev-';
14
14
  const ROOT_COMMAND = 'autodev';
15
15
  const HOOK_FILES = [
16
16
  'hooks.json',
17
+ 'autodev-paths.js',
17
18
  'autodev-context-monitor.js',
18
19
  'autodev-git-guard.js',
19
20
  'autodev-phase-boundary.sh',
@@ -268,6 +269,13 @@ function removeManagedSettings(settings) {
268
269
  delete settings.statusLine;
269
270
  }
270
271
 
272
+ if (settings.env && typeof settings.env === 'object') {
273
+ delete settings.env.CLAUDE_CODE_DISABLE_BACKGROUND_TASKS;
274
+ if (Object.keys(settings.env).length === 0) {
275
+ delete settings.env;
276
+ }
277
+ }
278
+
271
279
  if (!settings.hooks || typeof settings.hooks !== 'object') {
272
280
  return settings;
273
281
  }
@@ -321,6 +329,13 @@ function ensureHook(settings, eventName, matcher, command, timeout) {
321
329
  settings.hooks[eventName].push(entry);
322
330
  }
323
331
 
332
+ function ensureEnvSetting(settings, name, value) {
333
+ if (!settings.env || typeof settings.env !== 'object') {
334
+ settings.env = {};
335
+ }
336
+ settings.env[name] = String(value);
337
+ }
338
+
324
339
  function setExecutableBits(targetDir) {
325
340
  const candidates = [
326
341
  path.join(targetDir, 'hooks', 'autodev-phase-boundary.sh'),
@@ -397,6 +412,10 @@ function configureSettings(targetDir, isGlobal, options = {}) {
397
412
  };
398
413
  }
399
414
 
415
+ if (options.disableBackgroundTasks) {
416
+ ensureEnvSetting(settings, 'CLAUDE_CODE_DISABLE_BACKGROUND_TASKS', '1');
417
+ }
418
+
400
419
  writeSettings(settingsPath, settings);
401
420
  return settingsPath;
402
421
  }
@@ -546,6 +565,8 @@ function printHelp() {
546
565
  Options:
547
566
  --global Install to Claude Code config directory
548
567
  --local Install to the current project (.claude/)
568
+ --disable-background-tasks
569
+ Set CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1 in Claude Code settings.json
549
570
  --uninstall Remove autodev from the selected location
550
571
  --help Show this help
551
572
 
@@ -553,6 +574,7 @@ Examples:
553
574
  npx @mthanhlm/autodev@latest
554
575
  npx @mthanhlm/autodev@latest --global
555
576
  npx @mthanhlm/autodev@latest --local
577
+ npx @mthanhlm/autodev@latest --global --disable-background-tasks
556
578
  npx @mthanhlm/autodev@latest --global --uninstall
557
579
  `);
558
580
  }
@@ -582,6 +604,27 @@ Location:
582
604
  });
583
605
  }
584
606
 
607
+ function askDisableBackgroundTasks() {
608
+ return new Promise(resolve => {
609
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
610
+ console.log(`
611
+ Background tasks:
612
+ ${cyan}1${reset}) Disable in Claude Code settings ${yellow}(Recommended for autodev)${reset}
613
+ -> sets CLAUDE_CODE_DISABLE_BACKGROUND_TASKS=1
614
+ ${cyan}2${reset}) Leave background-task settings unchanged
615
+ `);
616
+ rl.question(`Select background-task policy ${yellow}[1]${reset}: `, answer => {
617
+ rl.close();
618
+ const trimmed = answer.trim().toLowerCase();
619
+ if (trimmed === '2' || trimmed === 'n' || trimmed === 'no' || trimmed === 'leave') {
620
+ resolve(false);
621
+ return;
622
+ }
623
+ resolve(true);
624
+ });
625
+ });
626
+ }
627
+
585
628
  async function main() {
586
629
  const args = process.argv.slice(2);
587
630
  if (args.includes('--help') || args.includes('-h')) {
@@ -590,20 +633,30 @@ async function main() {
590
633
  }
591
634
 
592
635
  let scope = null;
636
+ let scopeWasExplicit = false;
593
637
  if (args.includes('--global') || args.includes('-g')) {
594
638
  scope = 'global';
639
+ scopeWasExplicit = true;
595
640
  } else if (args.includes('--local') || args.includes('-l')) {
596
641
  scope = 'local';
642
+ scopeWasExplicit = true;
597
643
  }
598
644
 
599
645
  if (!scope) {
600
646
  scope = await askScope();
601
647
  }
602
648
 
603
- if (args.includes('--uninstall') || args.includes('-u')) {
649
+ const uninstallRequested = args.includes('--uninstall') || args.includes('-u');
650
+ const disableBackgroundTasks = args.includes('--disable-background-tasks')
651
+ ? true
652
+ : uninstallRequested || scopeWasExplicit
653
+ ? false
654
+ : await askDisableBackgroundTasks();
655
+
656
+ if (uninstallRequested) {
604
657
  uninstall({ scope });
605
658
  } else {
606
- install({ scope });
659
+ install({ scope, disableBackgroundTasks });
607
660
  }
608
661
  }
609
662
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: autodev:execute-phase
3
- description: Execute an active-track phase task by task, preferring fresh foreground agents with clean fallback when unavailable
3
+ description: Execute exactly one active-track task at a time, preferring a fresh foreground agent with clean fallback when unavailable
4
4
  argument-hint: "[phase-number]"
5
5
  allowed-tools:
6
6
  - Read
@@ -14,7 +14,7 @@ allowed-tools:
14
14
  - Agent
15
15
  ---
16
16
  <objective>
17
- Execute one active-track phase through task-sized delegated runs and record both task-level and phase-level summaries.
17
+ Execute exactly one task-sized unit for the active phase, then stop at the post-task checkpoint.
18
18
  </objective>
19
19
 
20
20
  <execution_context>
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: autodev:explore-codebase
3
- description: Map an existing codebase, preferring parallel agents with clean fallback when unavailable
3
+ description: Map an existing codebase, preferring foreground delegated agents with clean fallback when unavailable
4
4
  allowed-tools:
5
5
  - Read
6
6
  - Write
@@ -12,7 +12,7 @@ allowed-tools:
12
12
  - Agent
13
13
  ---
14
14
  <objective>
15
- Explore the current repository, use parallel codebase agents when available, and write the brownfield map into `.autodev/codebase/`.
15
+ Explore the current repository, use foreground codebase agents when available, and write the brownfield map into `.autodev/codebase/`.
16
16
  </objective>
17
17
 
18
18
  <execution_context>
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: autodev:review-phase
3
- description: Run a review pass for code quality, security, integration, and product polish, preferring parallel agents when available
3
+ description: Run a review pass for code quality, security, integration, and product polish, preferring foreground agents when available
4
4
  argument-hint: "[phase-number]"
5
5
  allowed-tools:
6
6
  - Read
@@ -0,0 +1,25 @@
1
+ ---
2
+ name: autodev:review-task
3
+ description: Pause after one completed task so the user can review it before any further phase execution
4
+ argument-hint: "[phase-number]"
5
+ allowed-tools:
6
+ - Read
7
+ - Write
8
+ - Edit
9
+ - Bash
10
+ - Grep
11
+ - Glob
12
+ - AskUserQuestion
13
+ - TodoWrite
14
+ ---
15
+ <objective>
16
+ Review one completed task and decide whether to continue execution, revise the phase, or stop here.
17
+ </objective>
18
+
19
+ <execution_context>
20
+ @~/.claude/autodev/workflows/review-task.md
21
+ </execution_context>
22
+
23
+ <process>
24
+ Execute the workflow in @~/.claude/autodev/workflows/review-task.md end-to-end.
25
+ </process>
@@ -3,18 +3,11 @@
3
3
  const fs = require('fs');
4
4
  const os = require('os');
5
5
  const path = require('path');
6
+ const { readProjectConfig } = require('./autodev-paths.js');
6
7
 
7
8
  const WARNING_THRESHOLD = 35;
8
9
  const CRITICAL_THRESHOLD = 20;
9
10
 
10
- function readProjectConfig(cwd) {
11
- try {
12
- return JSON.parse(fs.readFileSync(path.join(cwd, '.autodev', 'config.json'), 'utf8'));
13
- } catch {
14
- return null;
15
- }
16
- }
17
-
18
11
  let input = '';
19
12
  const stdinTimeout = setTimeout(() => process.exit(0), 10000);
20
13
  process.stdin.setEncoding('utf8');
@@ -1,15 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const fs = require('fs');
4
- const path = require('path');
5
-
6
- function readProjectConfig(cwd) {
7
- try {
8
- return JSON.parse(fs.readFileSync(path.join(cwd, '.autodev', 'config.json'), 'utf8'));
9
- } catch {
10
- return null;
11
- }
12
- }
4
+ const { readProjectConfig } = require('./autodev-paths.js');
13
5
 
14
6
  function shouldBlock(command) {
15
7
  const blockedPatterns = [
@@ -0,0 +1,60 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ function fileExists(filePath) {
7
+ try {
8
+ return fs.existsSync(filePath);
9
+ } catch {
10
+ return false;
11
+ }
12
+ }
13
+
14
+ function findWorkspaceRoot(startDir) {
15
+ let cursor = path.resolve(startDir || process.cwd());
16
+ let gitRoot = null;
17
+
18
+ while (true) {
19
+ if (fileExists(path.join(cursor, '.autodev'))) {
20
+ return cursor;
21
+ }
22
+
23
+ if (!gitRoot && fileExists(path.join(cursor, '.git'))) {
24
+ gitRoot = cursor;
25
+ }
26
+
27
+ const parent = path.dirname(cursor);
28
+ if (parent === cursor) {
29
+ break;
30
+ }
31
+ cursor = parent;
32
+ }
33
+
34
+ return gitRoot || path.resolve(startDir || process.cwd());
35
+ }
36
+
37
+ function findAutodevRoot(startDir) {
38
+ const workspaceRoot = findWorkspaceRoot(startDir);
39
+ const autodevRoot = path.join(workspaceRoot, '.autodev');
40
+ return fileExists(autodevRoot) ? autodevRoot : null;
41
+ }
42
+
43
+ function readProjectConfig(startDir) {
44
+ const autodevRoot = findAutodevRoot(startDir);
45
+ if (!autodevRoot) {
46
+ return null;
47
+ }
48
+
49
+ try {
50
+ return JSON.parse(fs.readFileSync(path.join(autodevRoot, 'config.json'), 'utf8'));
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+
56
+ module.exports = {
57
+ findAutodevRoot,
58
+ findWorkspaceRoot,
59
+ readProjectConfig
60
+ };
@@ -1,12 +1,41 @@
1
1
  #!/bin/bash
2
2
 
3
- CONFIG=".autodev/config.json"
3
+ find_workspace_root() {
4
+ local cursor
5
+ cursor="$(pwd -P)"
6
+ local git_root=""
4
7
 
5
- if [ ! -f "$CONFIG" ]; then
6
- exit 0
7
- fi
8
+ while true; do
9
+ if [ -d "$cursor/.autodev" ]; then
10
+ printf '%s\n' "$cursor"
11
+ return 0
12
+ fi
13
+
14
+ if [ -z "$git_root" ] && [ -e "$cursor/.git" ]; then
15
+ git_root="$cursor"
16
+ fi
17
+
18
+ if [ "$cursor" = "/" ]; then
19
+ break
20
+ fi
21
+
22
+ cursor="$(dirname "$cursor")"
23
+ done
24
+
25
+ if [ -n "$git_root" ]; then
26
+ printf '%s\n' "$git_root"
27
+ return 0
28
+ fi
29
+
30
+ return 1
31
+ }
32
+
33
+ WORKSPACE_ROOT="$(find_workspace_root)" || exit 0
34
+ CONFIG="$WORKSPACE_ROOT/.autodev/config.json"
35
+
36
+ [ -f "$CONFIG" ] || exit 0
8
37
 
9
- ENABLED=$(node -e "try{const c=require('./.autodev/config.json');process.stdout.write(c.hooks?.phase_boundary===false?'0':'1')}catch{process.stdout.write('0')}" 2>/dev/null)
38
+ ENABLED=$(node -e "const fs=require('fs');const p=process.argv[1];try{const c=JSON.parse(fs.readFileSync(p,'utf8'));process.stdout.write(c.hooks?.phase_boundary===false?'0':'1')}catch{process.stdout.write('0')}" "$CONFIG" 2>/dev/null)
10
39
  if [ "$ENABLED" != "1" ]; then
11
40
  exit 0
12
41
  fi
@@ -2,14 +2,7 @@
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
-
6
- function readProjectConfig(cwd) {
7
- try {
8
- return JSON.parse(fs.readFileSync(path.join(cwd, '.autodev', 'config.json'), 'utf8'));
9
- } catch {
10
- return null;
11
- }
12
- }
5
+ const { readProjectConfig } = require('./autodev-paths.js');
13
6
 
14
7
  let input = '';
15
8
  const stdinTimeout = setTimeout(() => process.exit(0), 3000);
@@ -1,13 +1,42 @@
1
1
  #!/bin/bash
2
2
 
3
- CONFIG=".autodev/config.json"
4
- STATE=".autodev/STATE.md"
3
+ find_workspace_root() {
4
+ local cursor
5
+ cursor="$(pwd -P)"
6
+ local git_root=""
5
7
 
6
- if [ ! -f "$CONFIG" ]; then
7
- exit 0
8
- fi
8
+ while true; do
9
+ if [ -d "$cursor/.autodev" ]; then
10
+ printf '%s\n' "$cursor"
11
+ return 0
12
+ fi
13
+
14
+ if [ -z "$git_root" ] && [ -e "$cursor/.git" ]; then
15
+ git_root="$cursor"
16
+ fi
17
+
18
+ if [ "$cursor" = "/" ]; then
19
+ break
20
+ fi
21
+
22
+ cursor="$(dirname "$cursor")"
23
+ done
24
+
25
+ if [ -n "$git_root" ]; then
26
+ printf '%s\n' "$git_root"
27
+ return 0
28
+ fi
29
+
30
+ return 1
31
+ }
32
+
33
+ WORKSPACE_ROOT="$(find_workspace_root)" || exit 0
34
+ CONFIG="$WORKSPACE_ROOT/.autodev/config.json"
35
+ STATE="$WORKSPACE_ROOT/.autodev/STATE.md"
36
+
37
+ [ -f "$CONFIG" ] || exit 0
9
38
 
10
- ENABLED=$(node -e "try{const c=require('./.autodev/config.json');process.stdout.write(c.hooks?.session_state===false?'0':'1')}catch{process.stdout.write('0')}" 2>/dev/null)
39
+ ENABLED=$(node -e "const fs=require('fs');const p=process.argv[1];try{const c=JSON.parse(fs.readFileSync(p,'utf8'));process.stdout.write(c.hooks?.session_state===false?'0':'1')}catch{process.stdout.write('0')}" "$CONFIG" 2>/dev/null)
11
40
  if [ "$ENABLED" != "1" ]; then
12
41
  exit 0
13
42
  fi
@@ -3,19 +3,12 @@
3
3
  const fs = require('fs');
4
4
  const os = require('os');
5
5
  const path = require('path');
6
+ const { findAutodevRoot } = require('./autodev-paths.js');
6
7
 
7
8
  function readStateFields(cwd) {
8
9
  try {
9
- let cursor = cwd;
10
- let statePath = null;
11
- while (cursor && cursor !== path.dirname(cursor)) {
12
- const candidate = path.join(cursor, '.autodev', 'STATE.md');
13
- if (fs.existsSync(candidate)) {
14
- statePath = candidate;
15
- break;
16
- }
17
- cursor = path.dirname(cursor);
18
- }
10
+ const autodevRoot = findAutodevRoot(cwd);
11
+ const statePath = autodevRoot ? path.join(autodevRoot, 'STATE.md') : null;
19
12
 
20
13
  if (!statePath) {
21
14
  return {
@@ -1,15 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require('fs');
4
3
  const path = require('path');
5
-
6
- function readProjectConfig(cwd) {
7
- try {
8
- return JSON.parse(fs.readFileSync(path.join(cwd, '.autodev', 'config.json'), 'utf8'));
9
- } catch {
10
- return null;
11
- }
12
- }
4
+ const { readProjectConfig } = require('./autodev-paths.js');
13
5
 
14
6
  let input = '';
15
7
  const stdinTimeout = setTimeout(() => process.exit(0), 3000);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mthanhlm/autodev",
3
- "version": "0.3.6",
3
+ "version": "0.4.0",
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"