@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.
@@ -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.4.0",
4
+ "version": "0.4.2",
5
5
  "author": {
6
6
  "name": "mthanhlm"
7
7
  },
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
  };
@@ -10,6 +10,7 @@
10
10
  "mode": "read-only"
11
11
  },
12
12
  "hooks": {
13
+ "auto_format": true,
13
14
  "context_warnings": true,
14
15
  "read_guard": true,
15
16
  "workflow_guard": true,
@@ -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
 
@@ -22,3 +22,6 @@ Status: pending
22
22
 
23
23
  ## Recommendation
24
24
  - [Return to execution or continue to verification]
25
+
26
+ ## Remediation Direction
27
+ - [If blocked, append fix task(s) to the same phase and preserve completed task history]
@@ -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 replace `NN-PLAN.md` from the template. The plan must include:
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. Create task files in the same phase directory:
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
- 9. Update the active track `STATE.md` so it points to:
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: 01` if tasks exist, otherwise `none`
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
- 10. Update `.autodev/STATE.md` so it points to:
111
+ 12. Update `.autodev/STATE.md` so it points to:
88
112
  - `Active Track: <slug>`
89
113
  - `Current Step: plan_review`
90
- - `Current Task: 01` if tasks exist, otherwise `none`
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
- 11. End with a short summary and stop for review.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mthanhlm/autodev",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
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"