devflow-kit 1.7.0 → 1.8.1

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.
Files changed (197) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +31 -1
  3. package/dist/cli.js +3 -1
  4. package/dist/commands/hud.d.ts +22 -0
  5. package/dist/commands/hud.js +180 -0
  6. package/dist/commands/init.d.ts +2 -12
  7. package/dist/commands/init.js +362 -181
  8. package/dist/commands/list.js +1 -0
  9. package/dist/commands/uninstall.js +9 -16
  10. package/dist/hud/cache.d.ts +14 -0
  11. package/dist/hud/cache.js +54 -0
  12. package/dist/hud/colors.d.ts +23 -0
  13. package/dist/hud/colors.js +62 -0
  14. package/dist/hud/components/config-counts.d.ts +8 -0
  15. package/dist/hud/components/config-counts.js +91 -0
  16. package/dist/hud/components/context-usage.d.ts +8 -0
  17. package/dist/hud/components/context-usage.js +49 -0
  18. package/dist/hud/components/diff-stats.d.ts +3 -0
  19. package/dist/hud/components/diff-stats.js +36 -0
  20. package/dist/hud/components/directory.d.ts +3 -0
  21. package/dist/hud/components/directory.js +10 -0
  22. package/dist/hud/components/git-ahead-behind.d.ts +3 -0
  23. package/dist/hud/components/git-ahead-behind.js +16 -0
  24. package/dist/hud/components/git-branch.d.ts +3 -0
  25. package/dist/hud/components/git-branch.js +14 -0
  26. package/dist/hud/components/model.d.ts +3 -0
  27. package/dist/hud/components/model.js +21 -0
  28. package/dist/hud/components/release-info.d.ts +3 -0
  29. package/dist/hud/components/release-info.js +9 -0
  30. package/dist/hud/components/session-cost.d.ts +3 -0
  31. package/dist/hud/components/session-cost.js +9 -0
  32. package/dist/hud/components/session-duration.d.ts +3 -0
  33. package/dist/hud/components/session-duration.js +18 -0
  34. package/dist/hud/components/todo-progress.d.ts +3 -0
  35. package/dist/hud/components/todo-progress.js +11 -0
  36. package/dist/hud/components/usage-quota.d.ts +3 -0
  37. package/dist/hud/components/usage-quota.js +45 -0
  38. package/dist/hud/components/version-badge.d.ts +3 -0
  39. package/dist/hud/components/version-badge.js +80 -0
  40. package/dist/hud/components/worktree-count.d.ts +3 -0
  41. package/dist/hud/components/worktree-count.js +8 -0
  42. package/dist/hud/config.d.ts +10 -0
  43. package/dist/hud/config.js +55 -0
  44. package/dist/hud/credentials.d.ts +17 -0
  45. package/dist/hud/credentials.js +99 -0
  46. package/dist/hud/git.d.ts +7 -0
  47. package/dist/hud/git.js +153 -0
  48. package/dist/hud/index.d.ts +2 -0
  49. package/dist/hud/index.js +81 -0
  50. package/dist/hud/render.d.ts +7 -0
  51. package/dist/hud/render.js +111 -0
  52. package/dist/hud/stdin.d.ts +6 -0
  53. package/dist/hud/stdin.js +25 -0
  54. package/dist/hud/transcript.d.ts +7 -0
  55. package/dist/hud/transcript.js +130 -0
  56. package/dist/hud/types.d.ts +116 -0
  57. package/dist/hud/types.js +2 -0
  58. package/dist/hud/usage-api.d.ts +7 -0
  59. package/dist/hud/usage-api.js +84 -0
  60. package/dist/utils/manifest.d.ts +1 -0
  61. package/dist/utils/post-install.d.ts +8 -1
  62. package/dist/utils/post-install.js +41 -0
  63. package/package.json +5 -2
  64. package/plugins/devflow-accessibility/.claude-plugin/plugin.json +1 -1
  65. package/plugins/devflow-ambient/.claude-plugin/plugin.json +1 -1
  66. package/plugins/devflow-ambient/agents/scrutinizer.md +6 -4
  67. package/plugins/devflow-ambient/agents/shepherd.md +26 -12
  68. package/plugins/devflow-ambient/agents/simplifier.md +24 -15
  69. package/plugins/devflow-audit-claude/.claude-plugin/plugin.json +1 -1
  70. package/plugins/devflow-code-review/.claude-plugin/plugin.json +1 -1
  71. package/plugins/devflow-core-skills/.claude-plugin/plugin.json +1 -1
  72. package/plugins/devflow-debug/.claude-plugin/plugin.json +1 -1
  73. package/plugins/devflow-frontend-design/.claude-plugin/plugin.json +1 -1
  74. package/plugins/devflow-go/.claude-plugin/plugin.json +1 -1
  75. package/plugins/devflow-implement/.claude-plugin/plugin.json +1 -1
  76. package/plugins/devflow-implement/agents/scrutinizer.md +6 -4
  77. package/plugins/devflow-implement/agents/shepherd.md +26 -12
  78. package/plugins/devflow-implement/agents/simplifier.md +24 -15
  79. package/plugins/devflow-implement/skills/self-review/references/stub-detection.md +135 -0
  80. package/plugins/devflow-java/.claude-plugin/plugin.json +1 -1
  81. package/plugins/devflow-python/.claude-plugin/plugin.json +1 -1
  82. package/plugins/devflow-react/.claude-plugin/plugin.json +1 -1
  83. package/plugins/devflow-resolve/.claude-plugin/plugin.json +1 -1
  84. package/plugins/devflow-resolve/agents/simplifier.md +24 -15
  85. package/plugins/devflow-rust/.claude-plugin/plugin.json +1 -1
  86. package/plugins/devflow-self-review/.claude-plugin/plugin.json +1 -1
  87. package/plugins/devflow-self-review/agents/scrutinizer.md +6 -4
  88. package/plugins/devflow-self-review/agents/simplifier.md +24 -15
  89. package/plugins/devflow-self-review/skills/self-review/references/stub-detection.md +135 -0
  90. package/plugins/devflow-specify/.claude-plugin/plugin.json +1 -1
  91. package/plugins/devflow-typescript/.claude-plugin/plugin.json +1 -1
  92. package/scripts/hud/cache.d.ts +14 -0
  93. package/scripts/hud/cache.d.ts.map +1 -0
  94. package/scripts/hud/cache.js +54 -0
  95. package/scripts/hud/cache.js.map +1 -0
  96. package/scripts/hud/colors.d.ts +23 -0
  97. package/scripts/hud/colors.d.ts.map +1 -0
  98. package/scripts/hud/colors.js +62 -0
  99. package/scripts/hud/colors.js.map +1 -0
  100. package/scripts/hud/components/config-counts.d.ts +8 -0
  101. package/scripts/hud/components/config-counts.d.ts.map +1 -0
  102. package/scripts/hud/components/config-counts.js +91 -0
  103. package/scripts/hud/components/config-counts.js.map +1 -0
  104. package/scripts/hud/components/context-usage.d.ts +8 -0
  105. package/scripts/hud/components/context-usage.d.ts.map +1 -0
  106. package/scripts/hud/components/context-usage.js +49 -0
  107. package/scripts/hud/components/context-usage.js.map +1 -0
  108. package/scripts/hud/components/diff-stats.d.ts +3 -0
  109. package/scripts/hud/components/diff-stats.d.ts.map +1 -0
  110. package/scripts/hud/components/diff-stats.js +36 -0
  111. package/scripts/hud/components/diff-stats.js.map +1 -0
  112. package/scripts/hud/components/directory.d.ts +3 -0
  113. package/scripts/hud/components/directory.d.ts.map +1 -0
  114. package/scripts/hud/components/directory.js +10 -0
  115. package/scripts/hud/components/directory.js.map +1 -0
  116. package/scripts/hud/components/git-ahead-behind.d.ts +3 -0
  117. package/scripts/hud/components/git-ahead-behind.d.ts.map +1 -0
  118. package/scripts/hud/components/git-ahead-behind.js +16 -0
  119. package/scripts/hud/components/git-ahead-behind.js.map +1 -0
  120. package/scripts/hud/components/git-branch.d.ts +3 -0
  121. package/scripts/hud/components/git-branch.d.ts.map +1 -0
  122. package/scripts/hud/components/git-branch.js +14 -0
  123. package/scripts/hud/components/git-branch.js.map +1 -0
  124. package/scripts/hud/components/model.d.ts +3 -0
  125. package/scripts/hud/components/model.d.ts.map +1 -0
  126. package/scripts/hud/components/model.js +21 -0
  127. package/scripts/hud/components/model.js.map +1 -0
  128. package/scripts/hud/components/release-info.d.ts +3 -0
  129. package/scripts/hud/components/release-info.d.ts.map +1 -0
  130. package/scripts/hud/components/release-info.js +9 -0
  131. package/scripts/hud/components/release-info.js.map +1 -0
  132. package/scripts/hud/components/session-cost.d.ts +3 -0
  133. package/scripts/hud/components/session-cost.d.ts.map +1 -0
  134. package/scripts/hud/components/session-cost.js +9 -0
  135. package/scripts/hud/components/session-cost.js.map +1 -0
  136. package/scripts/hud/components/session-duration.d.ts +3 -0
  137. package/scripts/hud/components/session-duration.d.ts.map +1 -0
  138. package/scripts/hud/components/session-duration.js +18 -0
  139. package/scripts/hud/components/session-duration.js.map +1 -0
  140. package/scripts/hud/components/todo-progress.d.ts +3 -0
  141. package/scripts/hud/components/todo-progress.d.ts.map +1 -0
  142. package/scripts/hud/components/todo-progress.js +11 -0
  143. package/scripts/hud/components/todo-progress.js.map +1 -0
  144. package/scripts/hud/components/usage-quota.d.ts +3 -0
  145. package/scripts/hud/components/usage-quota.d.ts.map +1 -0
  146. package/scripts/hud/components/usage-quota.js +45 -0
  147. package/scripts/hud/components/usage-quota.js.map +1 -0
  148. package/scripts/hud/components/version-badge.d.ts +3 -0
  149. package/scripts/hud/components/version-badge.d.ts.map +1 -0
  150. package/scripts/hud/components/version-badge.js +80 -0
  151. package/scripts/hud/components/version-badge.js.map +1 -0
  152. package/scripts/hud/components/worktree-count.d.ts +3 -0
  153. package/scripts/hud/components/worktree-count.d.ts.map +1 -0
  154. package/scripts/hud/components/worktree-count.js +8 -0
  155. package/scripts/hud/components/worktree-count.js.map +1 -0
  156. package/scripts/hud/config.d.ts +10 -0
  157. package/scripts/hud/config.d.ts.map +1 -0
  158. package/scripts/hud/config.js +55 -0
  159. package/scripts/hud/config.js.map +1 -0
  160. package/scripts/hud/credentials.d.ts +17 -0
  161. package/scripts/hud/credentials.d.ts.map +1 -0
  162. package/scripts/hud/credentials.js +99 -0
  163. package/scripts/hud/credentials.js.map +1 -0
  164. package/scripts/hud/git.d.ts +7 -0
  165. package/scripts/hud/git.d.ts.map +1 -0
  166. package/scripts/hud/git.js +153 -0
  167. package/scripts/hud/git.js.map +1 -0
  168. package/scripts/hud/index.d.ts +2 -0
  169. package/scripts/hud/index.d.ts.map +1 -0
  170. package/scripts/hud/index.js +81 -0
  171. package/scripts/hud/index.js.map +1 -0
  172. package/scripts/hud/render.d.ts +7 -0
  173. package/scripts/hud/render.d.ts.map +1 -0
  174. package/scripts/hud/render.js +111 -0
  175. package/scripts/hud/render.js.map +1 -0
  176. package/scripts/hud/stdin.d.ts +6 -0
  177. package/scripts/hud/stdin.d.ts.map +1 -0
  178. package/scripts/hud/stdin.js +25 -0
  179. package/scripts/hud/stdin.js.map +1 -0
  180. package/scripts/hud/transcript.d.ts +7 -0
  181. package/scripts/hud/transcript.d.ts.map +1 -0
  182. package/scripts/hud/transcript.js +130 -0
  183. package/scripts/hud/transcript.js.map +1 -0
  184. package/scripts/hud/types.d.ts +116 -0
  185. package/scripts/hud/types.d.ts.map +1 -0
  186. package/scripts/hud/types.js +2 -0
  187. package/scripts/hud/types.js.map +1 -0
  188. package/scripts/hud/usage-api.d.ts +7 -0
  189. package/scripts/hud/usage-api.d.ts.map +1 -0
  190. package/scripts/hud/usage-api.js +84 -0
  191. package/scripts/hud/usage-api.js.map +1 -0
  192. package/scripts/hud.sh +5 -0
  193. package/shared/agents/scrutinizer.md +6 -4
  194. package/shared/agents/shepherd.md +26 -12
  195. package/shared/agents/simplifier.md +24 -15
  196. package/shared/skills/self-review/references/stub-detection.md +135 -0
  197. package/src/templates/settings.json +1 -1
@@ -8,18 +8,21 @@ import color from 'picocolors';
8
8
  import { getInstallationPaths } from '../utils/paths.js';
9
9
  import { getGitRoot } from '../utils/git.js';
10
10
  import { isClaudeCliAvailable } from '../utils/cli.js';
11
- import { installViaCli, installViaFileCopy } from '../utils/installer.js';
12
- import { installSettings, installManagedSettings, installClaudeignore, updateGitignore, createDocsStructure, createMemoryDir, migrateMemoryFiles, } from '../utils/post-install.js';
11
+ import { installViaCli, installViaFileCopy, copyDirectory } from '../utils/installer.js';
12
+ import { installSettings, installManagedSettings, installClaudeignore, discoverProjectGitRoots, updateGitignore, createDocsStructure, createMemoryDir, migrateMemoryFiles, } from '../utils/post-install.js';
13
13
  import { DEVFLOW_PLUGINS, LEGACY_SKILL_NAMES, LEGACY_COMMAND_NAMES, buildAssetMaps } from '../plugins.js';
14
14
  import { detectPlatform, detectShell, getProfilePath, getSafeDeleteInfo, hasSafeDelete } from '../utils/safe-delete.js';
15
15
  import { generateSafeDeleteBlock, installToProfile, removeFromProfile, getInstalledVersion, SAFE_DELETE_BLOCK_VERSION } from '../utils/safe-delete-install.js';
16
16
  import { addAmbientHook } from './ambient.js';
17
17
  import { addMemoryHooks, removeMemoryHooks } from './memory.js';
18
+ import { addHudStatusLine, removeHudStatusLine } from './hud.js';
19
+ import { loadConfig as loadHudConfig, saveConfig as saveHudConfig } from '../hud/config.js';
18
20
  import { readManifest, writeManifest, resolvePluginList, detectUpgrade } from '../utils/manifest.js';
19
21
  // Re-export pure functions for tests (canonical source is post-install.ts)
20
- export { substituteSettingsTemplate, computeGitignoreAppend, applyTeamsConfig, stripTeamsConfig, mergeDenyList } from '../utils/post-install.js';
22
+ export { substituteSettingsTemplate, computeGitignoreAppend, applyTeamsConfig, stripTeamsConfig, mergeDenyList, discoverProjectGitRoots } from '../utils/post-install.js';
21
23
  export { addAmbientHook, removeAmbientHook, hasAmbientHook } from './ambient.js';
22
24
  export { addMemoryHooks, removeMemoryHooks, hasMemoryHooks } from './memory.js';
25
+ export { addHudStatusLine, removeHudStatusLine, hasHudStatusLine } from './hud.js';
23
26
  const __filename = fileURLToPath(import.meta.url);
24
27
  const __dirname = dirname(__filename);
25
28
  /**
@@ -35,26 +38,6 @@ export function parsePluginSelection(input, validPlugins) {
35
38
  const invalid = selected.filter(p => !validNames.includes(p));
36
39
  return { selected, invalid };
37
40
  }
38
- /**
39
- * Build the list of configuration extras available for the given scope/git context.
40
- * Pure function — no I/O, no side effects.
41
- */
42
- export function buildExtrasOptions(scope, gitRoot) {
43
- const options = [
44
- { value: 'settings', label: 'Settings & Working Memory', hint: 'Model defaults, session memory hooks, status line' },
45
- ];
46
- if (gitRoot) {
47
- options.push({ value: 'claudeignore', label: '.claudeignore', hint: 'Exclude secrets, deps, build artifacts from Claude context' });
48
- }
49
- if (scope === 'local' && gitRoot) {
50
- options.push({ value: 'gitignore', label: '.gitignore entries', hint: 'Add .claude/ and .devflow/ to .gitignore' });
51
- }
52
- if (scope === 'local') {
53
- options.push({ value: 'docs', label: '.docs/ directory', hint: 'Review reports, dev logs, status tracking for this project' });
54
- }
55
- options.push({ value: 'safe-delete', label: 'Safe-delete (rm → trash)', hint: 'Override rm to use trash CLI — prevents accidental deletion' });
56
- return options;
57
- }
58
41
  export const initCommand = new Command('init')
59
42
  .description('Initialize DevFlow for Claude Code')
60
43
  .option('--scope <type>', 'Installation scope: user or local (project-only)', /^(user|local)$/i)
@@ -66,6 +49,9 @@ export const initCommand = new Command('init')
66
49
  .option('--no-ambient', 'Disable ambient mode')
67
50
  .option('--memory', 'Enable working memory (session context preservation)')
68
51
  .option('--no-memory', 'Disable working memory hooks')
52
+ .option('--hud', 'Enable HUD (git info, context usage, session stats)')
53
+ .option('--no-hud', 'Disable HUD status line')
54
+ .option('--hud-only', 'Install only the HUD (no plugins, hooks, or extras)')
69
55
  .action(async (options) => {
70
56
  // Get package version
71
57
  const packageJsonPath = path.resolve(__dirname, '../../package.json');
@@ -82,7 +68,11 @@ export const initCommand = new Command('init')
82
68
  p.intro(color.bgCyan(color.black(` DevFlow v${version} `)));
83
69
  // Determine installation scope
84
70
  let scope = 'user';
85
- if (options.scope) {
71
+ if (options.hudOnly) {
72
+ // --hud-only: skip scope prompt, always user scope
73
+ scope = 'user';
74
+ }
75
+ else if (options.scope) {
86
76
  const normalizedScope = options.scope.toLowerCase();
87
77
  if (normalizedScope !== 'user' && normalizedScope !== 'local') {
88
78
  p.log.error('Invalid scope. Use "user" or "local"');
@@ -108,6 +98,70 @@ export const initCommand = new Command('init')
108
98
  }
109
99
  scope = selected;
110
100
  }
101
+ // --hud-only: install only HUD (skip plugins, hooks, extras)
102
+ if (options.hudOnly) {
103
+ // Resolve paths
104
+ const paths = await getInstallationPaths(scope);
105
+ const claudeDir = paths.claudeDir;
106
+ const devflowDir = paths.devflowDir;
107
+ // Save HUD config
108
+ const existingHud = loadHudConfig();
109
+ saveHudConfig({ enabled: true, detail: existingHud.detail });
110
+ // Update statusLine in settings.json
111
+ const settingsPath = path.join(claudeDir, 'settings.json');
112
+ try {
113
+ let content;
114
+ try {
115
+ content = await fs.readFile(settingsPath, 'utf-8');
116
+ }
117
+ catch {
118
+ content = '{}';
119
+ }
120
+ const updated = addHudStatusLine(content, devflowDir);
121
+ await fs.writeFile(settingsPath, updated, 'utf-8');
122
+ }
123
+ catch (error) {
124
+ p.log.error(`Failed to update settings: ${error instanceof Error ? error.message : error}`);
125
+ process.exit(1);
126
+ }
127
+ // Copy HUD scripts to devflow dir
128
+ const rootDir = path.resolve(__dirname, '..', '..');
129
+ const scriptsSource = path.join(rootDir, 'scripts');
130
+ const scriptsTarget = path.join(devflowDir, 'scripts');
131
+ try {
132
+ await fs.mkdir(scriptsTarget, { recursive: true });
133
+ // Copy hud.sh
134
+ await fs.copyFile(path.join(scriptsSource, 'hud.sh'), path.join(scriptsTarget, 'hud.sh'));
135
+ // Copy hud/ directory
136
+ const hudSource = path.join(scriptsSource, 'hud');
137
+ const hudTarget = path.join(scriptsTarget, 'hud');
138
+ await copyDirectory(hudSource, hudTarget);
139
+ if (process.platform !== 'win32') {
140
+ await fs.chmod(path.join(scriptsTarget, 'hud.sh'), 0o755);
141
+ }
142
+ }
143
+ catch (error) {
144
+ p.log.error(`Failed to copy HUD scripts: ${error instanceof Error ? error.message : error}`);
145
+ process.exit(1);
146
+ }
147
+ // Write minimal manifest
148
+ const now = new Date().toISOString();
149
+ try {
150
+ await writeManifest(devflowDir, {
151
+ version,
152
+ plugins: [],
153
+ scope,
154
+ features: { teams: false, ambient: false, memory: false, hud: true },
155
+ installedAt: now,
156
+ updatedAt: now,
157
+ });
158
+ }
159
+ catch { /* non-fatal */ }
160
+ p.log.success('HUD installed');
161
+ p.log.info(`Configure later: ${color.cyan('devflow hud --status')}`);
162
+ p.outro(color.green('HUD-only install complete.'));
163
+ return;
164
+ }
111
165
  // Select plugins to install
112
166
  let selectedPlugins = [];
113
167
  if (options.plugin) {
@@ -120,12 +174,29 @@ export const initCommand = new Command('init')
120
174
  }
121
175
  }
122
176
  else if (process.stdin.isTTY) {
177
+ // Short hints to prevent overflow in multiselect — full descriptions live in plugins.ts
178
+ const pluginHints = {
179
+ 'devflow-specify': 'feature specs → GitHub issues',
180
+ 'devflow-implement': 'explore, plan, code, review',
181
+ 'devflow-code-review': 'parallel specialized reviewers',
182
+ 'devflow-resolve': 'fix review issues by risk',
183
+ 'devflow-debug': 'competing hypotheses',
184
+ 'devflow-self-review': 'Simplifier + Scrutinizer',
185
+ 'devflow-typescript': 'TypeScript patterns',
186
+ 'devflow-react': 'React patterns',
187
+ 'devflow-accessibility': 'WCAG compliance',
188
+ 'devflow-frontend-design': 'typography, color, spacing',
189
+ 'devflow-go': 'Go patterns',
190
+ 'devflow-java': 'Java patterns',
191
+ 'devflow-python': 'Python patterns',
192
+ 'devflow-rust': 'Rust patterns',
193
+ };
123
194
  const choices = DEVFLOW_PLUGINS
124
195
  .filter(pl => pl.name !== 'devflow-core-skills' && pl.name !== 'devflow-ambient' && pl.name !== 'devflow-audit-claude')
125
196
  .map(pl => ({
126
197
  value: pl.name,
127
198
  label: pl.name.replace('devflow-', ''),
128
- hint: pl.description,
199
+ hint: pluginHints[pl.name] ?? pl.description,
129
200
  }));
130
201
  const preSelected = DEVFLOW_PLUGINS
131
202
  .filter(pl => !pl.optional && pl.name !== 'devflow-core-skills' && pl.name !== 'devflow-ambient')
@@ -151,11 +222,14 @@ export const initCommand = new Command('init')
151
222
  teamsEnabled = false;
152
223
  }
153
224
  else {
225
+ p.note('Agent Teams enable peer debate between agents — adversarial\n' +
226
+ 'review, competing hypotheses in debugging, and consensus-driven\n' +
227
+ 'exploration. Experimental: may be unstable.', 'Agent Teams');
154
228
  const teamsChoice = await p.select({
155
229
  message: 'Enable Agent Teams?',
156
230
  options: [
157
- { value: false, label: 'No (Recommended)', hint: 'Experimental — may be unstable' },
158
- { value: true, label: 'Yes', hint: 'Advanced — peer debate in review, exploration, debugging' },
231
+ { value: false, label: 'Not yet', hint: 'Recommended' },
232
+ { value: true, label: 'Yes', hint: 'Experimental' },
159
233
  ],
160
234
  });
161
235
  if (p.isCancel(teamsChoice)) {
@@ -173,11 +247,15 @@ export const initCommand = new Command('init')
173
247
  ambientEnabled = true;
174
248
  }
175
249
  else {
250
+ p.note('Auto-classifies every prompt by intent and depth. Loads relevant\n' +
251
+ 'skills automatically and escalates to full agent pipelines\n' +
252
+ '(review, debug, implement) when the task warrants it.\n\n' +
253
+ 'Adds a small amount of context to each prompt for classification.', 'Ambient Mode');
176
254
  const ambientChoice = await p.select({
177
255
  message: 'Enable ambient mode?',
178
256
  options: [
179
- { value: true, label: 'Yes (Recommended)', hint: 'Classifies intent, loads skills, orchestrates agents' },
180
- { value: false, label: 'No', hint: 'Full control — load skills manually' },
257
+ { value: true, label: 'Yes', hint: 'Recommended' },
258
+ { value: false, label: 'No', hint: 'Manual skill loading via slash commands' },
181
259
  ],
182
260
  });
183
261
  if (p.isCancel(ambientChoice)) {
@@ -195,8 +273,13 @@ export const initCommand = new Command('init')
195
273
  memoryEnabled = true;
196
274
  }
197
275
  else {
276
+ p.note('Preserves session context across /clear, restarts, and context\n' +
277
+ 'compaction. Clear your session at any point and resume right\n' +
278
+ 'where you left off.\n\n' +
279
+ 'Runs a background agent on session stop that consumes additional\n' +
280
+ 'tokens. Consider skipping if token usage is a concern.', 'Working Memory');
198
281
  const memoryChoice = await p.confirm({
199
- message: 'Enable working memory? (automatic session context preservation)',
282
+ message: 'Enable working memory? (Recommended)',
200
283
  initialValue: true,
201
284
  });
202
285
  if (p.isCancel(memoryChoice)) {
@@ -205,14 +288,131 @@ export const initCommand = new Command('init')
205
288
  }
206
289
  memoryEnabled = memoryChoice;
207
290
  }
208
- // Security deny list placement (user scope + TTY only)
291
+ // HUD selection (yes/no)
292
+ let hudEnabled;
293
+ if (options.hud !== undefined) {
294
+ hudEnabled = options.hud;
295
+ }
296
+ else if (!process.stdin.isTTY) {
297
+ hudEnabled = true;
298
+ }
299
+ else {
300
+ p.note('The HUD displays git branch, context usage, and session stats\n' +
301
+ 'in the Claude Code status bar. Configurable via devflow hud.', 'HUD');
302
+ const hudChoice = await p.confirm({
303
+ message: 'Enable HUD? (Recommended)',
304
+ initialValue: true,
305
+ });
306
+ if (p.isCancel(hudChoice)) {
307
+ p.cancel('Installation cancelled.');
308
+ process.exit(0);
309
+ }
310
+ hudEnabled = hudChoice;
311
+ }
312
+ // .claudeignore prompt (needs early git detection)
313
+ const earlyGitRoot = await getGitRoot();
314
+ let claudeignoreEnabled = true;
315
+ let discoveredProjects = [];
316
+ if (earlyGitRoot && process.stdin.isTTY) {
317
+ if (scope === 'user') {
318
+ // User scope: discover all projects and offer batch install
319
+ discoveredProjects = await discoverProjectGitRoots();
320
+ p.note('Scans all projects Claude has worked on and creates a\n' +
321
+ '.claudeignore in each git repository. Excludes secrets,\n' +
322
+ 'API keys, dependencies, and build artifacts from context.', '.claudeignore');
323
+ if (discoveredProjects.length > 0) {
324
+ const maxShow = 5;
325
+ const projectLines = discoveredProjects.slice(0, maxShow).join('\n');
326
+ const overflow = discoveredProjects.length > maxShow
327
+ ? `\n... (${discoveredProjects.length - maxShow} more)`
328
+ : '';
329
+ p.note(projectLines + overflow, `Discovered ${discoveredProjects.length} projects`);
330
+ const claudeignoreChoice = await p.confirm({
331
+ message: `Install .claudeignore to ${discoveredProjects.length} projects? (Recommended)`,
332
+ initialValue: true,
333
+ });
334
+ if (p.isCancel(claudeignoreChoice)) {
335
+ p.cancel('Installation cancelled.');
336
+ process.exit(0);
337
+ }
338
+ claudeignoreEnabled = claudeignoreChoice;
339
+ }
340
+ else {
341
+ // No projects discovered — fall back to current project only
342
+ const claudeignoreChoice = await p.confirm({
343
+ message: 'Create .claudeignore? (Recommended)',
344
+ initialValue: true,
345
+ });
346
+ if (p.isCancel(claudeignoreChoice)) {
347
+ p.cancel('Installation cancelled.');
348
+ process.exit(0);
349
+ }
350
+ claudeignoreEnabled = claudeignoreChoice;
351
+ }
352
+ }
353
+ else {
354
+ // Local scope: single-project note
355
+ p.note('Creates a .claudeignore in this project that excludes\n' +
356
+ 'secrets, API keys, dependencies, and build artifacts from\n' +
357
+ 'Claude\'s context window.', '.claudeignore');
358
+ const claudeignoreChoice = await p.confirm({
359
+ message: 'Create .claudeignore? (Recommended)',
360
+ initialValue: true,
361
+ });
362
+ if (p.isCancel(claudeignoreChoice)) {
363
+ p.cancel('Installation cancelled.');
364
+ process.exit(0);
365
+ }
366
+ claudeignoreEnabled = claudeignoreChoice;
367
+ }
368
+ }
369
+ else if (!earlyGitRoot) {
370
+ claudeignoreEnabled = false;
371
+ }
372
+ // Safe-delete detection + prompt (all detection early, action deferred)
373
+ const platform = detectPlatform();
374
+ const shell = detectShell();
375
+ const safeDeleteInfo = getSafeDeleteInfo(platform);
376
+ const safeDeleteAvailable = hasSafeDelete(platform);
377
+ const profilePath = getProfilePath(shell);
378
+ let safeDeleteAction = 'skip';
379
+ let safeDeleteBlock = null;
380
+ if (process.stdin.isTTY && profilePath && safeDeleteAvailable) {
381
+ const trashCmd = safeDeleteInfo.command;
382
+ safeDeleteBlock = generateSafeDeleteBlock(shell, process.platform, trashCmd);
383
+ if (safeDeleteBlock) {
384
+ const installedVersion = await getInstalledVersion(profilePath);
385
+ if (installedVersion === SAFE_DELETE_BLOCK_VERSION) {
386
+ safeDeleteAction = 'skip'; // already current
387
+ }
388
+ else if (installedVersion > 0) {
389
+ safeDeleteAction = 'upgrade'; // auto-upgrade, no prompt needed
390
+ }
391
+ else {
392
+ // Fresh install — prompt
393
+ p.note('Overrides rm to use your system trash CLI instead of permanent\n' +
394
+ 'deletion. Prevents accidental data loss from rm -rf.', 'Safe Delete');
395
+ const safeDeleteConfirm = await p.confirm({
396
+ message: `Install safe-delete to ${profilePath}? (uses ${trashCmd ?? 'recycle bin'})`,
397
+ initialValue: true,
398
+ });
399
+ if (!p.isCancel(safeDeleteConfirm) && safeDeleteConfirm) {
400
+ safeDeleteAction = 'install';
401
+ }
402
+ }
403
+ }
404
+ }
405
+ // Security deny list placement (user scope + TTY only — last prompt before install)
209
406
  let securityMode = 'user';
210
407
  if (scope === 'user' && process.stdin.isTTY) {
408
+ p.note('DevFlow includes a security deny list that blocks dangerous\n' +
409
+ 'commands (rm -rf, sudo, eval, etc). It can be installed as a\n' +
410
+ 'read-only system file or in your editable settings.json.', 'Security Deny List');
211
411
  const securityChoice = await p.select({
212
- message: 'How should DevFlow install the security deny list?',
412
+ message: 'How should DevFlow install the deny list?',
213
413
  options: [
214
- { value: 'managed', label: 'Managed settings (Recommended)', hint: 'Cannot be overridden, requires admin' },
215
- { value: 'user', label: 'User settings', hint: 'Included in settings.json, editable' },
414
+ { value: 'managed', label: 'Managed settings', hint: 'Recommended — read-only, cannot be overridden' },
415
+ { value: 'user', label: 'User settings', hint: 'Editable in settings.json' },
216
416
  ],
217
417
  });
218
418
  if (p.isCancel(securityChoice)) {
@@ -221,7 +421,32 @@ export const initCommand = new Command('init')
221
421
  }
222
422
  securityMode = securityChoice;
223
423
  }
224
- // Start spinner immediately after prompts — covers path resolution + git detection
424
+ // Managed settings sudo confirmation (last interactive step — may prompt for password)
425
+ let managedSettingsConfirmed = false;
426
+ if (securityMode === 'managed') {
427
+ p.note('This writes a read-only security deny list to a system directory\n' +
428
+ 'and may prompt for your password (sudo).\n\n' +
429
+ 'Not sure about this? Paste this into another Claude Code session:\n\n' +
430
+ ' "I\'m installing DevFlow and it wants to write a\n' +
431
+ ' managed-settings.json file using sudo. Review the source\n' +
432
+ ' at https://github.com/dean0x/devflow and tell me if\n' +
433
+ ' it\'s safe."', 'Managed Settings');
434
+ const sudoChoice = await p.select({
435
+ message: 'Continue with managed settings?',
436
+ options: [
437
+ { value: 'yes', label: 'Yes, continue', hint: 'May prompt for your password' },
438
+ { value: 'no', label: 'No, fall back to settings.json', hint: 'Editable user settings instead' },
439
+ ],
440
+ });
441
+ if (p.isCancel(sudoChoice)) {
442
+ p.cancel('Installation cancelled.');
443
+ process.exit(0);
444
+ }
445
+ managedSettingsConfirmed = sudoChoice === 'yes';
446
+ }
447
+ // ╭──────────────────────────────────────────────────────────╮
448
+ // │ All prompts collected — installation begins │
449
+ // ╰──────────────────────────────────────────────────────────╯
225
450
  const s = p.spinner();
226
451
  s.start('Resolving paths');
227
452
  // Get installation paths
@@ -232,7 +457,7 @@ export const initCommand = new Command('init')
232
457
  const paths = await getInstallationPaths(scope);
233
458
  claudeDir = paths.claudeDir;
234
459
  devflowDir = paths.devflowDir;
235
- gitRoot = paths.gitRoot ?? await getGitRoot();
460
+ gitRoot = paths.gitRoot ?? earlyGitRoot;
236
461
  }
237
462
  catch (error) {
238
463
  s.stop('Path resolution failed');
@@ -316,8 +541,8 @@ export const initCommand = new Command('init')
316
541
  process.exit(1);
317
542
  }
318
543
  }
319
- s.stop('Plugins installed');
320
544
  // Clean up stale skills from previous installations
545
+ s.message('Cleaning up');
321
546
  const skillsDir = path.join(claudeDir, 'skills');
322
547
  let staleRemoved = 0;
323
548
  for (const legacy of LEGACY_SKILL_NAMES) {
@@ -351,114 +576,90 @@ export const initCommand = new Command('init')
351
576
  if (staleCommandsRemoved > 0 && verbose) {
352
577
  p.log.info(`Cleaned up ${staleCommandsRemoved} legacy command(s)`);
353
578
  }
354
- // === Configuration extras ===
355
- const extrasOptions = buildExtrasOptions(scope, gitRoot);
356
- let selectedExtras;
357
- if (process.stdin.isTTY) {
358
- const extrasSelection = await p.multiselect({
359
- message: 'Configure extras',
360
- options: extrasOptions,
361
- initialValues: extrasOptions.map(o => o.value),
362
- required: false,
363
- });
364
- if (p.isCancel(extrasSelection)) {
365
- p.cancel('Installation cancelled.');
366
- process.exit(0);
367
- }
368
- selectedExtras = extrasSelection;
579
+ // === Settings & hooks (all automatic based on collected choices) ===
580
+ s.message('Configuring settings');
581
+ // Determine effective security mode (managed settings executed later, after safe-delete)
582
+ let effectiveSecurityMode = securityMode;
583
+ if (securityMode === 'managed' && !managedSettingsConfirmed) {
584
+ effectiveSecurityMode = 'user';
369
585
  }
370
- else {
371
- selectedExtras = extrasOptions.map(o => o.value);
372
- }
373
- // Settings may trigger its own TTY sub-prompt — run outside spinner
374
- if (selectedExtras.includes('settings')) {
375
- // Attempt managed settings write if user chose managed mode
376
- let effectiveSecurityMode = securityMode;
377
- if (securityMode === 'managed') {
378
- p.note('This writes a read-only security deny list to a system directory\n' +
379
- 'and may prompt for your password (sudo).\n\n' +
380
- 'Not sure about this? Paste this into another Claude Code session:\n\n' +
381
- ' "I\'m installing DevFlow and it wants to write a\n' +
382
- ' managed-settings.json file using sudo. Review the source\n' +
383
- ' at https://github.com/dean0x/devflow and tell me if\n' +
384
- ' it\'s safe."', 'Managed Settings');
385
- const sudoChoice = await p.select({
386
- message: 'Continue with managed settings?',
387
- options: [
388
- { value: 'yes', label: 'Yes, continue', hint: 'May prompt for your password' },
389
- { value: 'no', label: 'No, fall back to settings.json', hint: 'Deny list stored in editable user settings instead' },
390
- ],
391
- });
392
- if (p.isCancel(sudoChoice)) {
393
- p.cancel('Installation cancelled.');
394
- process.exit(0);
395
- }
396
- if (sudoChoice === 'yes') {
397
- const managed = await installManagedSettings(rootDir, verbose);
398
- if (!managed) {
399
- p.log.warn('Managed settings write failed — falling back to user settings');
400
- effectiveSecurityMode = 'user';
401
- }
402
- }
403
- else {
404
- effectiveSecurityMode = 'user';
405
- }
406
- }
407
- await installSettings(claudeDir, rootDir, devflowDir, verbose, teamsEnabled, effectiveSecurityMode);
408
- // Install ambient hook if enabled
586
+ await installSettings(claudeDir, rootDir, devflowDir, verbose, teamsEnabled, effectiveSecurityMode);
587
+ const settingsPath = path.join(claudeDir, 'settings.json');
588
+ // Configure ambient hook, memory hooks, and HUD statusLine in a single read-modify-write pass
589
+ try {
590
+ let content = await fs.readFile(settingsPath, 'utf-8');
591
+ const original = content;
592
+ // Ambient hook
409
593
  if (ambientEnabled) {
410
- const settingsPath = path.join(claudeDir, 'settings.json');
411
- try {
412
- const content = await fs.readFile(settingsPath, 'utf-8');
413
- const updated = addAmbientHook(content, devflowDir);
414
- if (updated !== content) {
415
- await fs.writeFile(settingsPath, updated, 'utf-8');
416
- if (verbose) {
417
- p.log.success('Ambient mode hook installed');
418
- }
419
- }
420
- }
421
- catch { /* settings.json may not exist yet */ }
594
+ content = addAmbientHook(content, devflowDir);
422
595
  }
423
- // Manage memory hooks based on user choice
424
- const settingsPath = path.join(claudeDir, 'settings.json');
425
- try {
426
- const content = await fs.readFile(settingsPath, 'utf-8');
427
- // Always remove-then-add to upgrade hook format (e.g., .sh → run-hook)
428
- const cleaned = removeMemoryHooks(content);
429
- const updated = memoryEnabled
430
- ? addMemoryHooks(cleaned, devflowDir)
431
- : cleaned;
432
- if (updated !== content) {
433
- await fs.writeFile(settingsPath, updated, 'utf-8');
434
- if (verbose) {
435
- p.log.info(`Working memory ${memoryEnabled ? 'enabled' : 'disabled'}`);
436
- }
596
+ // Memory hooks — always remove-then-add to upgrade hook format (e.g., .sh → run-hook)
597
+ const cleaned = removeMemoryHooks(content);
598
+ content = memoryEnabled ? addMemoryHooks(cleaned, devflowDir) : cleaned;
599
+ // HUD statusLine
600
+ content = hudEnabled
601
+ ? addHudStatusLine(content, devflowDir)
602
+ : removeHudStatusLine(content);
603
+ if (content !== original) {
604
+ await fs.writeFile(settingsPath, content, 'utf-8');
605
+ if (verbose) {
606
+ if (ambientEnabled)
607
+ p.log.success('Ambient mode hook installed');
608
+ p.log.info(`Working memory ${memoryEnabled ? 'enabled' : 'disabled'}`);
609
+ p.log.info(`HUD ${hudEnabled ? 'enabled' : 'disabled'}`);
437
610
  }
438
611
  }
439
- catch { /* settings.json may not exist yet */ }
440
- // Ensure .memory/ exists when memory is enabled (hooks are no-ops without it)
441
- if (memoryEnabled) {
442
- await createMemoryDir(verbose);
443
- await migrateMemoryFiles(verbose);
444
- }
445
612
  }
446
- const fileExtras = selectedExtras.filter(e => e !== 'settings' && e !== 'safe-delete');
447
- if (fileExtras.length > 0) {
448
- const sExtras = p.spinner();
449
- sExtras.start('Configuring extras');
450
- if (selectedExtras.includes('claudeignore') && gitRoot) {
451
- await installClaudeignore(gitRoot, rootDir, verbose);
613
+ catch { /* settings.json may not exist yet */ }
614
+ // Ensure .memory/ exists when memory is enabled (hooks are no-ops without it)
615
+ if (memoryEnabled) {
616
+ await createMemoryDir(verbose);
617
+ await migrateMemoryFiles(verbose);
618
+ }
619
+ // Configure HUD
620
+ const existingHud = loadHudConfig();
621
+ saveHudConfig({ enabled: hudEnabled, detail: existingHud.detail });
622
+ // File extras
623
+ if (claudeignoreEnabled) {
624
+ if (scope === 'user' && discoveredProjects.length > 0) {
625
+ const results = await Promise.all(discoveredProjects.map(root => installClaudeignore(root, rootDir, verbose)));
626
+ const created = results.filter(Boolean).length;
627
+ if (created > 0) {
628
+ p.log.success(`.claudeignore created in ${created} project(s)`);
629
+ }
630
+ else {
631
+ p.log.info(`.claudeignore already exists in all ${discoveredProjects.length} project(s)`);
632
+ }
452
633
  }
453
- if (selectedExtras.includes('gitignore') && gitRoot) {
454
- await updateGitignore(gitRoot, verbose);
634
+ else if (gitRoot) {
635
+ await installClaudeignore(gitRoot, rootDir, verbose);
455
636
  }
456
- if (selectedExtras.includes('docs')) {
457
- await createDocsStructure(verbose);
637
+ }
638
+ if (scope === 'local' && gitRoot) {
639
+ await updateGitignore(gitRoot, verbose);
640
+ }
641
+ if (scope === 'local') {
642
+ await createDocsStructure(verbose);
643
+ }
644
+ // Safe-delete execution (decision was captured during prompt phase)
645
+ if (safeDeleteAction === 'install' && safeDeleteBlock && profilePath) {
646
+ await installToProfile(profilePath, safeDeleteBlock);
647
+ }
648
+ else if (safeDeleteAction === 'upgrade' && safeDeleteBlock && profilePath) {
649
+ await removeFromProfile(profilePath);
650
+ await installToProfile(profilePath, safeDeleteBlock);
651
+ }
652
+ // Managed settings (last install step — sudo may prompt for password)
653
+ if (securityMode === 'managed' && managedSettingsConfirmed) {
654
+ s.stop('Installing managed settings (may prompt for password)');
655
+ const managed = await installManagedSettings(rootDir, verbose);
656
+ if (!managed) {
657
+ p.log.warn('Managed settings write failed — falling back to user settings');
458
658
  }
459
- sExtras.stop('Extras configured');
659
+ s.start('Finalizing');
460
660
  }
461
- // Summary output
661
+ s.stop('Installation complete');
662
+ // === Summary ===
462
663
  if (usedNativeCli) {
463
664
  p.log.success('Installed via Claude plugin system');
464
665
  }
@@ -472,54 +673,34 @@ export const initCommand = new Command('init')
472
673
  .join(' ');
473
674
  p.note(commandsNote, 'Available commands');
474
675
  }
475
- // Safe-delete auto-install (gated by extras selection)
476
- if (selectedExtras.includes('safe-delete')) {
477
- const platform = detectPlatform();
478
- const shell = detectShell();
479
- const safeDeleteInfo = getSafeDeleteInfo(platform);
480
- const safeDeleteAvailable = hasSafeDelete(platform);
481
- const profilePath = getProfilePath(shell);
482
- if (process.stdin.isTTY && profilePath) {
483
- if (!safeDeleteAvailable && safeDeleteInfo.installHint) {
484
- p.log.info(`Install ${color.cyan(safeDeleteInfo.command ?? 'trash')} first: ${color.dim(safeDeleteInfo.installHint)}`);
485
- p.log.info(`Then re-run ${color.cyan('devflow init')} to auto-configure safe-delete.`);
486
- }
487
- else if (safeDeleteAvailable) {
488
- const trashCmd = safeDeleteInfo.command;
489
- const block = generateSafeDeleteBlock(shell, process.platform, trashCmd);
490
- if (block) {
491
- const installedVersion = await getInstalledVersion(profilePath);
492
- if (installedVersion === SAFE_DELETE_BLOCK_VERSION) {
493
- p.log.info(`Safe-delete already configured in ${color.dim(profilePath)}`);
494
- }
495
- else if (installedVersion > 0) {
496
- await removeFromProfile(profilePath);
497
- await installToProfile(profilePath, block);
498
- p.log.success(`Safe-delete upgraded in ${color.dim(profilePath)}`);
499
- p.log.info('Restart your shell or run: ' + color.cyan(`source ${profilePath}`));
500
- }
501
- else {
502
- const confirm = await p.confirm({
503
- message: `Install safe-delete to ${profilePath}? (overrides rm to use ${trashCmd ?? 'recycle bin'})`,
504
- initialValue: true,
505
- });
506
- if (!p.isCancel(confirm) && confirm) {
507
- await installToProfile(profilePath, block);
508
- p.log.success(`Safe-delete installed to ${color.dim(profilePath)}`);
509
- p.log.info('Restart your shell or run: ' + color.cyan(`source ${profilePath}`));
510
- }
511
- }
512
- }
513
- }
676
+ // Safe-delete status messages (after spinner)
677
+ if (process.stdin.isTTY && profilePath) {
678
+ if (safeDeleteAction === 'install') {
679
+ p.log.success(`Safe-delete installed to ${color.dim(profilePath)}`);
680
+ p.log.info('Restart your shell or run: ' + color.cyan(`source ${profilePath}`));
514
681
  }
515
- else if (!process.stdin.isTTY) {
516
- if (safeDeleteAvailable && safeDeleteInfo.command) {
517
- p.log.info(`Safe-delete available (${safeDeleteInfo.command}). Run interactively to auto-install.`);
518
- }
519
- else if (safeDeleteInfo.installHint) {
520
- p.log.info(`Protect against accidental ${color.red('rm -rf')}: ${color.cyan(safeDeleteInfo.installHint)}`);
682
+ else if (safeDeleteAction === 'upgrade') {
683
+ p.log.success(`Safe-delete upgraded in ${color.dim(profilePath)}`);
684
+ p.log.info('Restart your shell or run: ' + color.cyan(`source ${profilePath}`));
685
+ }
686
+ else if (safeDeleteAvailable && safeDeleteBlock) {
687
+ const installedVersion = await getInstalledVersion(profilePath);
688
+ if (installedVersion === SAFE_DELETE_BLOCK_VERSION) {
689
+ p.log.info(`Safe-delete already configured in ${color.dim(profilePath)}`);
521
690
  }
522
691
  }
692
+ else if (!safeDeleteAvailable && safeDeleteInfo.installHint) {
693
+ p.log.info(`Install ${color.cyan(safeDeleteInfo.command ?? 'trash')} first: ${color.dim(safeDeleteInfo.installHint)}`);
694
+ p.log.info(`Then re-run ${color.cyan('devflow init')} to auto-configure safe-delete.`);
695
+ }
696
+ }
697
+ else if (!process.stdin.isTTY) {
698
+ if (safeDeleteAvailable && safeDeleteInfo.command) {
699
+ p.log.info(`Safe-delete available (${safeDeleteInfo.command}). Run interactively to auto-install.`);
700
+ }
701
+ else if (safeDeleteInfo.installHint) {
702
+ p.log.info(`Protect against accidental ${color.red('rm -rf')}: ${color.cyan(safeDeleteInfo.installHint)}`);
703
+ }
523
704
  }
524
705
  // Verbose mode: show details
525
706
  if (verbose) {
@@ -542,7 +723,7 @@ export const initCommand = new Command('init')
542
723
  version,
543
724
  plugins: resolvePluginList(installedPluginNames, existingManifest, !!options.plugin),
544
725
  scope,
545
- features: { teams: teamsEnabled, ambient: ambientEnabled, memory: memoryEnabled },
726
+ features: { teams: teamsEnabled, ambient: ambientEnabled, memory: memoryEnabled, hud: hudEnabled },
546
727
  installedAt: existingManifest?.installedAt ?? now,
547
728
  updatedAt: now,
548
729
  };