@mthanhlm/autodev 0.4.0 → 0.4.2
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 +1 -1
- package/README.md +3 -0
- package/autodev/bin/autodev-tools.cjs +8 -0
- package/autodev/templates/config.json +1 -0
- package/autodev/templates/plan.md +6 -0
- package/autodev/templates/review.md +3 -0
- package/autodev/workflows/help.md +8 -0
- package/autodev/workflows/new-project.md +1 -0
- package/autodev/workflows/plan-phase.md +33 -7
- package/autodev/workflows/review-phase.md +3 -1
- package/autodev/workflows/verify-work.md +3 -0
- package/bin/install.js +5 -0
- package/hooks/autodev-auto-format.js +210 -0
- package/hooks/hooks.json +10 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
- Resolves `.autodev/` state from the repo root even when Claude is started in a nested subdirectory
|
|
12
12
|
- Maps brownfield repos with foreground delegated agents when the environment supports them
|
|
13
13
|
- Runs a multi-lens review pass, using foreground review agents when the environment supports them
|
|
14
|
+
- Can auto-format edited code after Claude writes files when a local formatter is available
|
|
14
15
|
- Ships manual commands when you want direct control:
|
|
15
16
|
- `/autodev-help`
|
|
16
17
|
- `/autodev-new-project`
|
|
@@ -111,10 +112,12 @@ project -> track -> phase -> tasks
|
|
|
111
112
|
- Each task is preferably executed by a fresh foreground delegated agent
|
|
112
113
|
- After each task, the delegated agent reports back with files changed, verification, and blockers
|
|
113
114
|
- `/autodev` stops after each completed task and reopens a task review checkpoint before any further execution
|
|
115
|
+
- If review or verification finds blockers, the same phase stays open and autodev should append remediation task(s) to that phase instead of replacing it or creating a follow-up phase by default
|
|
114
116
|
- No waves by default
|
|
115
117
|
- No automatic parallel execution
|
|
116
118
|
- 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
119
|
- 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`
|
|
120
|
+
- The post-edit formatter hook uses only locally available tools such as `prettier`, `ruff`, `black`, `gofmt`, `rustfmt`, or `shfmt`, and skips `.autodev/` files
|
|
118
121
|
|
|
119
122
|
## Git Policy
|
|
120
123
|
|
|
@@ -16,6 +16,7 @@ const DEFAULT_CONFIG = {
|
|
|
16
16
|
mode: 'read-only'
|
|
17
17
|
},
|
|
18
18
|
hooks: {
|
|
19
|
+
auto_format: true,
|
|
19
20
|
context_warnings: true,
|
|
20
21
|
read_guard: true,
|
|
21
22
|
workflow_guard: true,
|
|
@@ -396,6 +397,10 @@ function lastCompletedTask(tasks) {
|
|
|
396
397
|
return completed.length > 0 ? completed[completed.length - 1] : null;
|
|
397
398
|
}
|
|
398
399
|
|
|
400
|
+
function lastTaskNumber(tasks) {
|
|
401
|
+
return tasks.reduce((highest, task) => Math.max(highest, task.number), 0);
|
|
402
|
+
}
|
|
403
|
+
|
|
399
404
|
function hasDependencyDeadlock(tasks) {
|
|
400
405
|
return tasks.some(task => !task.summaryExists) && !nextExecutableTask(tasks);
|
|
401
406
|
}
|
|
@@ -901,6 +906,7 @@ function initPayload(cwd, mode, requestedPhase) {
|
|
|
901
906
|
const tasks = phase ? listTasksForPhaseDetails(phase) : [];
|
|
902
907
|
const nextTask = nextExecutableTask(tasks);
|
|
903
908
|
const lastCompleted = lastCompletedTask(tasks);
|
|
909
|
+
const highestTaskNumber = lastTaskNumber(tasks);
|
|
904
910
|
const dependencyDeadlock = hasDependencyDeadlock(tasks);
|
|
905
911
|
const trackStateSnapshot = track ? readStateSnapshot(track.state) : null;
|
|
906
912
|
const currentTaskNumber = (() => {
|
|
@@ -960,6 +966,7 @@ function initPayload(cwd, mode, requestedPhase) {
|
|
|
960
966
|
task_done_count: tasks.filter(task => task.summaryExists).length,
|
|
961
967
|
all_tasks_done: tasks.length > 0 && tasks.every(task => task.summaryExists),
|
|
962
968
|
dependency_deadlock: dependencyDeadlock,
|
|
969
|
+
last_task_number: highestTaskNumber,
|
|
963
970
|
next_task_number: nextTask ? nextTask.number : null,
|
|
964
971
|
next_task_path: nextTask ? nextTask.taskPath : null,
|
|
965
972
|
next_task_summary_path: nextTask ? nextTask.summaryPath : null,
|
|
@@ -1084,6 +1091,7 @@ module.exports = {
|
|
|
1084
1091
|
trackPaths,
|
|
1085
1092
|
listTasksForPhaseDetails,
|
|
1086
1093
|
lastCompletedTask,
|
|
1094
|
+
lastTaskNumber,
|
|
1087
1095
|
hasDependencyDeadlock,
|
|
1088
1096
|
nextExecutableTask
|
|
1089
1097
|
};
|
|
@@ -18,6 +18,7 @@ Type: [feature|bugfix|refactor|research|polish]
|
|
|
18
18
|
- Keep one orchestration session for the phase.
|
|
19
19
|
- Execute one task at a time.
|
|
20
20
|
- No waves by default.
|
|
21
|
+
- If review or verification finds blockers, keep the same phase open and append remediation tasks instead of creating a replacement phase.
|
|
21
22
|
|
|
22
23
|
## Phase-Level Risks
|
|
23
24
|
- [Risk]
|
|
@@ -32,6 +33,11 @@ Type: [feature|bugfix|refactor|research|polish]
|
|
|
32
33
|
| 01 | pending | none | [Concrete task goal] |
|
|
33
34
|
| 02 | pending | 01 | [Concrete task goal] |
|
|
34
35
|
|
|
36
|
+
## Revision Rules
|
|
37
|
+
- Preserve completed task rows and summaries as history.
|
|
38
|
+
- Append remediation tasks after the highest existing task number.
|
|
39
|
+
- Only rewrite pending tasks when needed for clarity.
|
|
40
|
+
|
|
35
41
|
## Notes
|
|
36
42
|
- [Anything the delegated agents must know]
|
|
37
43
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Lean Claude Code workflow. No automatic commits. No branches. No worktrees. Git is read-only.
|
|
4
4
|
It resolves `.autodev/` state from the repo root even when you start Claude in a nested subdirectory.
|
|
5
|
+
It can auto-format edited code after writes when the repo already has a local formatter available.
|
|
5
6
|
|
|
6
7
|
## Main Entry
|
|
7
8
|
|
|
@@ -46,6 +47,13 @@ project -> track -> phase -> tasks
|
|
|
46
47
|
- `task`
|
|
47
48
|
Reviewable execution units inside a phase.
|
|
48
49
|
|
|
50
|
+
## Blocker Recovery
|
|
51
|
+
|
|
52
|
+
- Review or verification blockers should normally stay in the same phase.
|
|
53
|
+
- Preserve completed task history.
|
|
54
|
+
- Append `TASK-XX` remediation work to the same phase.
|
|
55
|
+
- Only create a new phase when the blocker reveals genuinely new scope.
|
|
56
|
+
|
|
49
57
|
## Default Flow
|
|
50
58
|
|
|
51
59
|
```text
|
|
@@ -32,6 +32,7 @@ node "$AUTODEV_ROOT/autodev/bin/autodev-tools.cjs" init new-project
|
|
|
32
32
|
- `workflow.research: false`
|
|
33
33
|
- `workflow.review_after_execute: true`
|
|
34
34
|
- `git.mode: "read-only"`
|
|
35
|
+
- `hooks.auto_format: true`
|
|
35
36
|
|
|
36
37
|
6. Write `.autodev/PROJECT.md` with:
|
|
37
38
|
- one-line summary
|
|
@@ -9,6 +9,7 @@ Create one practical phase plan for the active track, then break it into reviewa
|
|
|
9
9
|
- Optional research is allowed only when the user asks for it or `workflow.research` is true.
|
|
10
10
|
- Never include git write commands in the plan.
|
|
11
11
|
- Do not use waves by default.
|
|
12
|
+
- When revising a blocked phase, preserve the same phase, the same phase goal, and all completed task history.
|
|
12
13
|
</rules>
|
|
13
14
|
|
|
14
15
|
<process>
|
|
@@ -32,6 +33,10 @@ node "$AUTODEV_ROOT/autodev/bin/autodev-tools.cjs" init plan-phase "$ARGUMENTS"
|
|
|
32
33
|
- `.autodev/tracks/<active-track>/ROADMAP.md`
|
|
33
34
|
- `.autodev/tracks/<active-track>/STATE.md`
|
|
34
35
|
- the target phase section
|
|
36
|
+
- any existing `TASK-*.md`
|
|
37
|
+
- any existing `TASK-*-SUMMARY.md`
|
|
38
|
+
- `NN-REVIEW.md` if it exists
|
|
39
|
+
- `NN-UAT.md` if it exists
|
|
35
40
|
- any existing code relevant to this phase
|
|
36
41
|
|
|
37
42
|
5. If research is enabled or explicitly requested, do targeted read-only research only. Keep the output folded into the plan instead of producing a separate research artifact.
|
|
@@ -42,14 +47,28 @@ node "$AUTODEV_ROOT/autodev/bin/autodev-tools.cjs" init plan-phase "$ARGUMENTS"
|
|
|
42
47
|
.autodev/tracks/<active-track>/phases/NN-type-name/
|
|
43
48
|
```
|
|
44
49
|
|
|
45
|
-
7. Write or
|
|
50
|
+
7. Write or update `NN-PLAN.md` from the template. The plan must include:
|
|
46
51
|
- phase type
|
|
47
52
|
- goal
|
|
48
53
|
- shared verification commands
|
|
49
54
|
- task list overview
|
|
50
55
|
- explicit git read-only reminder
|
|
51
56
|
|
|
52
|
-
8.
|
|
57
|
+
8. If this phase is being reopened because of `blocked_review`, `blocked_verification`, `blocked_dependencies`, or another blocker status:
|
|
58
|
+
- keep the same phase directory and phase goal
|
|
59
|
+
- do not create a new phase for ordinary blockers
|
|
60
|
+
- do not delete or rewrite completed task summaries
|
|
61
|
+
- do not renumber completed tasks
|
|
62
|
+
- preserve already-completed task rows in the phase plan
|
|
63
|
+
- append new remediation tasks after `last_task_number`
|
|
64
|
+
- default to one appended remediation task when the blockers are tightly related
|
|
65
|
+
- split into multiple appended remediation tasks only when the blockers are clearly separate
|
|
66
|
+
- only rewrite still-pending tasks when that improves clarity
|
|
67
|
+
- make the new task titles explicit, for example:
|
|
68
|
+
- `TASK-03: Fix review blockers from 01-REVIEW.md`
|
|
69
|
+
- `TASK-04: Fix verification gaps from 01-UAT.md`
|
|
70
|
+
|
|
71
|
+
9. Create or update task files in the same phase directory:
|
|
53
72
|
|
|
54
73
|
```text
|
|
55
74
|
TASK-01.md
|
|
@@ -75,26 +94,33 @@ Each task file must include:
|
|
|
75
94
|
- verification
|
|
76
95
|
- done looks like
|
|
77
96
|
|
|
78
|
-
|
|
97
|
+
10. When revising a blocked phase:
|
|
98
|
+
- point `Current Task` to the first still-pending executable task after the revision
|
|
99
|
+
- if all existing tasks are completed and you add remediation work, `Current Task` should be the first newly appended fix task
|
|
100
|
+
- never reset `Current Task` back to `01` just because the phase was replanned
|
|
101
|
+
|
|
102
|
+
11. Update the active track `STATE.md` so it points to:
|
|
79
103
|
- `Current Phase: N`
|
|
80
104
|
- `Current Phase Type: <type>`
|
|
81
105
|
- `Current Step: plan_review`
|
|
82
|
-
- `Current Task:
|
|
106
|
+
- `Current Task: <first-pending-task>` if tasks exist, otherwise `none`
|
|
83
107
|
- `Current Task Status: pending_review`
|
|
84
108
|
- `Next Command: /autodev`
|
|
85
109
|
- current ISO timestamp
|
|
86
110
|
|
|
87
|
-
|
|
111
|
+
12. Update `.autodev/STATE.md` so it points to:
|
|
88
112
|
- `Active Track: <slug>`
|
|
89
113
|
- `Current Step: plan_review`
|
|
90
|
-
- `Current Task:
|
|
114
|
+
- `Current Task: <first-pending-task>` if tasks exist, otherwise `none`
|
|
91
115
|
- `Current Task Status: pending_review`
|
|
92
116
|
- `Next Command: /autodev`
|
|
93
117
|
- current ISO timestamp
|
|
94
118
|
|
|
95
|
-
|
|
119
|
+
13. End with a short summary and stop for review.
|
|
96
120
|
Tell the user:
|
|
97
121
|
- the plan and task files are ready for review
|
|
122
|
+
- completed task history from the phase was preserved
|
|
123
|
+
- any blocker remediation was added as appended task work inside the same phase
|
|
98
124
|
- `/autodev` will open the review checkpoint before any execution starts
|
|
99
125
|
- `/autodev-execute-phase N` is only the optional manual bypass after review
|
|
100
126
|
</process>
|
|
@@ -58,7 +58,9 @@ node "$AUTODEV_ROOT/autodev/bin/autodev-tools.cjs" init review-phase "$ARGUMENTS
|
|
|
58
58
|
- set `Current Task: none`
|
|
59
59
|
- set `Current Task Status: blocked_review`
|
|
60
60
|
- keep `Next Command: /autodev`
|
|
61
|
-
- make the recommendation point back to revising the same phase with blocker-fix tasks
|
|
61
|
+
- make the recommendation point back to revising the same phase with appended blocker-fix tasks
|
|
62
|
+
- do not recommend creating a new phase for ordinary review blockers
|
|
63
|
+
- preserve completed task history and phase goal
|
|
62
64
|
|
|
63
65
|
9. If blockers are not found:
|
|
64
66
|
- set project and track state to `Current Step: verification`
|
|
@@ -6,6 +6,7 @@ Run lightweight user acceptance testing after review and keep the next action ob
|
|
|
6
6
|
- Verify against what the phase plan, summary, and review claim.
|
|
7
7
|
- Keep the interaction concise and test-oriented.
|
|
8
8
|
- Do not auto-generate a new plan unless a real gap appears.
|
|
9
|
+
- Verification failures should normally reopen the same phase with appended remediation tasks, not create a new phase.
|
|
9
10
|
</rules>
|
|
10
11
|
|
|
11
12
|
<process>
|
|
@@ -38,6 +39,7 @@ node "$AUTODEV_ROOT/autodev/bin/autodev-tools.cjs" init verify-work "$ARGUMENTS"
|
|
|
38
39
|
- exact failed behaviors
|
|
39
40
|
- reproduction notes
|
|
40
41
|
- the smallest acceptable fix target
|
|
42
|
+
- a note that the follow-up should be appended to the same phase as remediation task work unless scope truly changed
|
|
41
43
|
|
|
42
44
|
7. Update the active track `STATE.md`:
|
|
43
45
|
- if verification passed and another phase remains, move to the next phase and set `Current Step: planning`
|
|
@@ -47,6 +49,7 @@ node "$AUTODEV_ROOT/autodev/bin/autodev-tools.cjs" init verify-work "$ARGUMENTS"
|
|
|
47
49
|
- when verification failed, set `Current Task: none` and `Current Task Status: blocked_verification`
|
|
48
50
|
- always set `Next Command: /autodev`
|
|
49
51
|
- always refresh the ISO timestamp
|
|
52
|
+
- when verification failed, the next planning pass should preserve completed tasks and append remediation tasks to the same phase
|
|
50
53
|
|
|
51
54
|
8. Update `.autodev/STATE.md` with the matching project-level status and refreshed timestamp.
|
|
52
55
|
|
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-auto-format.js',
|
|
17
18
|
'autodev-paths.js',
|
|
18
19
|
'autodev-context-monitor.js',
|
|
19
20
|
'autodev-git-guard.js',
|
|
@@ -375,6 +376,9 @@ function configureSettings(targetDir, isGlobal, options = {}) {
|
|
|
375
376
|
const contextMonitorCommand = isGlobal
|
|
376
377
|
? buildGlobalCommand(targetDir, 'autodev-context-monitor.js')
|
|
377
378
|
: buildLocalCommand('autodev-context-monitor.js');
|
|
379
|
+
const autoFormatCommand = isGlobal
|
|
380
|
+
? buildGlobalCommand(targetDir, 'autodev-auto-format.js')
|
|
381
|
+
: buildLocalCommand('autodev-auto-format.js');
|
|
378
382
|
const promptGuardCommand = isGlobal
|
|
379
383
|
? buildGlobalCommand(targetDir, 'autodev-prompt-guard.js')
|
|
380
384
|
: buildLocalCommand('autodev-prompt-guard.js');
|
|
@@ -402,6 +406,7 @@ function configureSettings(targetDir, isGlobal, options = {}) {
|
|
|
402
406
|
ensureHook(settings, preToolEvent, 'Write|Edit', readGuardCommand, 5);
|
|
403
407
|
ensureHook(settings, preToolEvent, 'Write|Edit', workflowGuardCommand, 5);
|
|
404
408
|
ensureHook(settings, preToolEvent, 'Bash', gitGuardCommand, 5);
|
|
409
|
+
ensureHook(settings, postToolEvent, 'Edit|Write|MultiEdit', autoFormatCommand, 10);
|
|
405
410
|
ensureHook(settings, postToolEvent, 'Bash|Edit|Write|MultiEdit|Agent|Task', contextMonitorCommand, 10);
|
|
406
411
|
ensureHook(settings, postToolEvent, 'Write|Edit', phaseBoundaryCommand, 5);
|
|
407
412
|
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { spawnSync } = require('child_process');
|
|
6
|
+
const { findWorkspaceRoot, readProjectConfig } = require('./autodev-paths.js');
|
|
7
|
+
|
|
8
|
+
const PRETTIER_EXTENSIONS = new Set([
|
|
9
|
+
'.js', '.jsx', '.cjs', '.mjs',
|
|
10
|
+
'.ts', '.tsx',
|
|
11
|
+
'.json',
|
|
12
|
+
'.css', '.scss', '.less',
|
|
13
|
+
'.html',
|
|
14
|
+
'.yml', '.yaml',
|
|
15
|
+
'.vue', '.svelte'
|
|
16
|
+
]);
|
|
17
|
+
|
|
18
|
+
const PYTHON_EXTENSIONS = new Set(['.py']);
|
|
19
|
+
const SHELL_EXTENSIONS = new Set(['.sh', '.bash', '.zsh']);
|
|
20
|
+
|
|
21
|
+
function fileExists(filePath) {
|
|
22
|
+
try {
|
|
23
|
+
return fs.existsSync(filePath);
|
|
24
|
+
} catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isFile(filePath) {
|
|
30
|
+
try {
|
|
31
|
+
return fs.statSync(filePath).isFile();
|
|
32
|
+
} catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isInside(targetPath, rootPath) {
|
|
38
|
+
const relative = path.relative(rootPath, targetPath);
|
|
39
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function resolveHookFilePath(data, cwd) {
|
|
43
|
+
const raw = data.tool_input?.file_path || data.tool_input?.path || '';
|
|
44
|
+
if (!raw) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
return path.isAbsolute(raw) ? raw : path.resolve(cwd, raw);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function resolveExecutable(workspaceRoot, candidates) {
|
|
51
|
+
for (const candidate of candidates) {
|
|
52
|
+
if (Array.isArray(candidate)) {
|
|
53
|
+
const absolute = path.join(workspaceRoot, ...candidate);
|
|
54
|
+
if (isFile(absolute)) {
|
|
55
|
+
return absolute;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (process.platform === 'win32' && isFile(`${absolute}.cmd`)) {
|
|
59
|
+
return `${absolute}.cmd`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (process.platform === 'win32' && isFile(`${absolute}.exe`)) {
|
|
63
|
+
return `${absolute}.exe`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (isFile(`${absolute}.cmd`)) {
|
|
67
|
+
return `${absolute}.cmd`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (isFile(`${absolute}.exe`)) {
|
|
71
|
+
return `${absolute}.exe`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (isFile(`${absolute}.js`)) {
|
|
75
|
+
return `${absolute}.js`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (fileExists(absolute)) {
|
|
79
|
+
return absolute;
|
|
80
|
+
}
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const result = spawnSync('sh', ['-lc', `command -v "${candidate}"`], {
|
|
85
|
+
cwd: workspaceRoot,
|
|
86
|
+
encoding: 'utf8'
|
|
87
|
+
});
|
|
88
|
+
if (result.status === 0 && result.stdout.trim()) {
|
|
89
|
+
return result.stdout.trim();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function resolveFile(workspaceRoot, candidates) {
|
|
97
|
+
for (const candidate of candidates) {
|
|
98
|
+
const absolute = path.join(workspaceRoot, ...candidate);
|
|
99
|
+
if (isFile(absolute)) {
|
|
100
|
+
return absolute;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function formatterCommand(filePath, workspaceRoot) {
|
|
108
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
109
|
+
|
|
110
|
+
if (PRETTIER_EXTENSIONS.has(ext)) {
|
|
111
|
+
const prettierScript = resolveFile(workspaceRoot, [
|
|
112
|
+
['node_modules', 'prettier', 'bin', 'prettier.cjs'],
|
|
113
|
+
['node_modules', 'prettier', 'bin', 'prettier.js']
|
|
114
|
+
]);
|
|
115
|
+
if (prettierScript) {
|
|
116
|
+
return [process.execPath, [prettierScript, '--write', filePath]];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const prettier = resolveExecutable(workspaceRoot, [
|
|
120
|
+
['node_modules', '.bin', 'prettier'],
|
|
121
|
+
'prettier'
|
|
122
|
+
]);
|
|
123
|
+
return prettier ? [prettier, ['--write', filePath]] : null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (PYTHON_EXTENSIONS.has(ext)) {
|
|
127
|
+
const ruff = resolveExecutable(workspaceRoot, [
|
|
128
|
+
['.venv', 'bin', 'ruff'],
|
|
129
|
+
['.venv', 'Scripts', 'ruff.exe'],
|
|
130
|
+
'ruff'
|
|
131
|
+
]);
|
|
132
|
+
if (ruff) {
|
|
133
|
+
return [ruff, ['format', filePath]];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const black = resolveExecutable(workspaceRoot, [
|
|
137
|
+
['.venv', 'bin', 'black'],
|
|
138
|
+
['.venv', 'Scripts', 'black.exe'],
|
|
139
|
+
'black'
|
|
140
|
+
]);
|
|
141
|
+
return black ? [black, [filePath]] : null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (ext === '.go') {
|
|
145
|
+
const gofmt = resolveExecutable(workspaceRoot, ['gofmt']);
|
|
146
|
+
return gofmt ? [gofmt, ['-w', filePath]] : null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (ext === '.rs') {
|
|
150
|
+
const rustfmt = resolveExecutable(workspaceRoot, ['rustfmt']);
|
|
151
|
+
return rustfmt ? [rustfmt, [filePath]] : null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (SHELL_EXTENSIONS.has(ext)) {
|
|
155
|
+
const shfmt = resolveExecutable(workspaceRoot, ['shfmt']);
|
|
156
|
+
return shfmt ? [shfmt, ['-w', filePath]] : null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const input = fs.readFileSync(0, 'utf8');
|
|
164
|
+
const data = JSON.parse(input);
|
|
165
|
+
if (!['Edit', 'Write', 'MultiEdit'].includes(data.tool_name)) {
|
|
166
|
+
process.exit(0);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const cwd = data.cwd || process.cwd();
|
|
170
|
+
const workspaceRoot = findWorkspaceRoot(cwd);
|
|
171
|
+
const config = readProjectConfig(cwd);
|
|
172
|
+
if (config && config.hooks?.auto_format === false) {
|
|
173
|
+
process.exit(0);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const filePath = resolveHookFilePath(data, cwd);
|
|
177
|
+
if (!filePath || !isFile(filePath)) {
|
|
178
|
+
process.exit(0);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const autodevRoot = path.join(workspaceRoot, '.autodev');
|
|
182
|
+
if (fileExists(autodevRoot) && isInside(filePath, autodevRoot)) {
|
|
183
|
+
process.exit(0);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const formatter = formatterCommand(filePath, workspaceRoot);
|
|
187
|
+
if (!formatter) {
|
|
188
|
+
process.exit(0);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const [command, args] = formatter;
|
|
192
|
+
const result = spawnSync(command, args, {
|
|
193
|
+
cwd: workspaceRoot,
|
|
194
|
+
encoding: 'utf8'
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
if (result.status !== 0) {
|
|
198
|
+
const stderr = (result.stderr || '').trim();
|
|
199
|
+
process.stdout.write(JSON.stringify({
|
|
200
|
+
hookSpecificOutput: {
|
|
201
|
+
hookEventName: 'PostToolUse',
|
|
202
|
+
additionalContext: stderr
|
|
203
|
+
? `AUTO-FORMAT WARNING: formatting failed for ${path.basename(filePath)}: ${stderr}`
|
|
204
|
+
: `AUTO-FORMAT WARNING: formatting failed for ${path.basename(filePath)}.`
|
|
205
|
+
}
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
} catch {
|
|
209
|
+
process.exit(0);
|
|
210
|
+
}
|
package/hooks/hooks.json
CHANGED
|
@@ -54,6 +54,16 @@
|
|
|
54
54
|
}
|
|
55
55
|
],
|
|
56
56
|
"PostToolUse": [
|
|
57
|
+
{
|
|
58
|
+
"matcher": "Edit|Write|MultiEdit",
|
|
59
|
+
"hooks": [
|
|
60
|
+
{
|
|
61
|
+
"type": "command",
|
|
62
|
+
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/autodev-auto-format.js\"",
|
|
63
|
+
"timeout": 10
|
|
64
|
+
}
|
|
65
|
+
]
|
|
66
|
+
},
|
|
57
67
|
{
|
|
58
68
|
"matcher": "Bash|Edit|Write|MultiEdit|Agent|Task",
|
|
59
69
|
"hooks": [
|
package/package.json
CHANGED