moflo 4.8.10 → 4.8.12

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 (182) hide show
  1. package/.claude/agents/browser/browser-agent.yaml +182 -0
  2. package/.claude/agents/core/coder.md +265 -265
  3. package/.claude/agents/core/planner.md +167 -167
  4. package/.claude/agents/core/researcher.md +189 -189
  5. package/.claude/agents/core/reviewer.md +325 -325
  6. package/.claude/agents/core/tester.md +318 -318
  7. package/.claude/agents/dual-mode/codex-coordinator.md +224 -224
  8. package/.claude/agents/dual-mode/codex-worker.md +211 -211
  9. package/.claude/agents/dual-mode/dual-orchestrator.md +291 -291
  10. package/.claude/agents/github/code-review-swarm.md +537 -537
  11. package/.claude/agents/github/github-modes.md +172 -172
  12. package/.claude/agents/github/issue-tracker.md +318 -318
  13. package/.claude/agents/github/multi-repo-swarm.md +552 -552
  14. package/.claude/agents/github/pr-manager.md +190 -190
  15. package/.claude/agents/github/project-board-sync.md +508 -508
  16. package/.claude/agents/github/release-manager.md +366 -366
  17. package/.claude/agents/github/release-swarm.md +582 -582
  18. package/.claude/agents/github/repo-architect.md +397 -397
  19. package/.claude/agents/github/swarm-issue.md +572 -572
  20. package/.claude/agents/github/swarm-pr.md +427 -427
  21. package/.claude/agents/github/sync-coordinator.md +451 -451
  22. package/.claude/agents/github/workflow-automation.md +634 -634
  23. package/.claude/agents/goal/code-goal-planner.md +445 -445
  24. package/.claude/agents/hive-mind/collective-intelligence-coordinator.md +129 -129
  25. package/.claude/agents/hive-mind/queen-coordinator.md +202 -202
  26. package/.claude/agents/hive-mind/scout-explorer.md +241 -241
  27. package/.claude/agents/hive-mind/swarm-memory-manager.md +192 -192
  28. package/.claude/agents/hive-mind/worker-specialist.md +216 -216
  29. package/.claude/agents/neural/safla-neural.md +73 -73
  30. package/.claude/agents/reasoning/goal-planner.md +72 -72
  31. package/.claude/agents/swarm/adaptive-coordinator.md +395 -395
  32. package/.claude/agents/swarm/hierarchical-coordinator.md +326 -326
  33. package/.claude/agents/swarm/mesh-coordinator.md +391 -391
  34. package/.claude/agents/templates/migration-plan.md +745 -745
  35. package/.claude/commands/agents/agent-spawning.md +28 -28
  36. package/.claude/commands/analysis/COMMAND_COMPLIANCE_REPORT.md +53 -53
  37. package/.claude/commands/analysis/bottleneck-detect.md +162 -162
  38. package/.claude/commands/analysis/performance-bottlenecks.md +58 -58
  39. package/.claude/commands/analysis/token-efficiency.md +44 -44
  40. package/.claude/commands/automation/auto-agent.md +122 -122
  41. package/.claude/commands/automation/self-healing.md +105 -105
  42. package/.claude/commands/automation/session-memory.md +89 -89
  43. package/.claude/commands/automation/smart-agents.md +72 -72
  44. package/.claude/commands/coordination/init.md +44 -44
  45. package/.claude/commands/coordination/orchestrate.md +43 -43
  46. package/.claude/commands/coordination/spawn.md +45 -45
  47. package/.claude/commands/coordination/swarm-init.md +85 -85
  48. package/.claude/commands/github/github-modes.md +146 -146
  49. package/.claude/commands/github/github-swarm.md +121 -121
  50. package/.claude/commands/github/issue-tracker.md +291 -291
  51. package/.claude/commands/github/pr-manager.md +169 -169
  52. package/.claude/commands/github/release-manager.md +337 -337
  53. package/.claude/commands/github/repo-architect.md +366 -366
  54. package/.claude/commands/github/sync-coordinator.md +300 -300
  55. package/.claude/commands/memory/neural.md +47 -47
  56. package/.claude/commands/monitoring/agents.md +44 -44
  57. package/.claude/commands/monitoring/status.md +46 -46
  58. package/.claude/commands/optimization/auto-topology.md +61 -61
  59. package/.claude/commands/optimization/parallel-execution.md +49 -49
  60. package/.claude/commands/sparc/analyzer.md +51 -51
  61. package/.claude/commands/sparc/architect.md +53 -53
  62. package/.claude/commands/sparc/ask.md +97 -97
  63. package/.claude/commands/sparc/batch-executor.md +54 -54
  64. package/.claude/commands/sparc/code.md +89 -89
  65. package/.claude/commands/sparc/coder.md +54 -54
  66. package/.claude/commands/sparc/debug.md +83 -83
  67. package/.claude/commands/sparc/debugger.md +54 -54
  68. package/.claude/commands/sparc/designer.md +53 -53
  69. package/.claude/commands/sparc/devops.md +109 -109
  70. package/.claude/commands/sparc/docs-writer.md +80 -80
  71. package/.claude/commands/sparc/documenter.md +54 -54
  72. package/.claude/commands/sparc/innovator.md +54 -54
  73. package/.claude/commands/sparc/integration.md +83 -83
  74. package/.claude/commands/sparc/mcp.md +117 -117
  75. package/.claude/commands/sparc/memory-manager.md +54 -54
  76. package/.claude/commands/sparc/optimizer.md +54 -54
  77. package/.claude/commands/sparc/orchestrator.md +131 -131
  78. package/.claude/commands/sparc/post-deployment-monitoring-mode.md +83 -83
  79. package/.claude/commands/sparc/refinement-optimization-mode.md +83 -83
  80. package/.claude/commands/sparc/researcher.md +54 -54
  81. package/.claude/commands/sparc/reviewer.md +54 -54
  82. package/.claude/commands/sparc/security-review.md +80 -80
  83. package/.claude/commands/sparc/sparc-modes.md +174 -174
  84. package/.claude/commands/sparc/sparc.md +111 -111
  85. package/.claude/commands/sparc/spec-pseudocode.md +80 -80
  86. package/.claude/commands/sparc/supabase-admin.md +348 -348
  87. package/.claude/commands/sparc/swarm-coordinator.md +54 -54
  88. package/.claude/commands/sparc/tdd.md +54 -54
  89. package/.claude/commands/sparc/tester.md +54 -54
  90. package/.claude/commands/sparc/tutorial.md +79 -79
  91. package/.claude/commands/sparc/workflow-manager.md +54 -54
  92. package/.claude/commands/sparc.md +166 -166
  93. package/.claude/commands/swarm/analysis.md +95 -95
  94. package/.claude/commands/swarm/development.md +96 -96
  95. package/.claude/commands/swarm/examples.md +168 -168
  96. package/.claude/commands/swarm/maintenance.md +102 -102
  97. package/.claude/commands/swarm/optimization.md +117 -117
  98. package/.claude/commands/swarm/research.md +136 -136
  99. package/.claude/commands/swarm/testing.md +131 -131
  100. package/.claude/commands/training/neural-patterns.md +73 -73
  101. package/.claude/commands/training/specialization.md +62 -62
  102. package/.claude/commands/workflows/development.md +77 -77
  103. package/.claude/commands/workflows/research.md +62 -62
  104. package/.claude/guidance/moflo-bootstrap.md +129 -0
  105. package/.claude/guidance/{agent-bootstrap.md → shipped/agent-bootstrap.md} +126 -126
  106. package/.claude/guidance/{guidance-memory-strategy.md → shipped/guidance-memory-strategy.md} +262 -262
  107. package/.claude/guidance/{memory-strategy.md → shipped/memory-strategy.md} +204 -204
  108. package/.claude/guidance/{moflo.md → shipped/moflo.md} +45 -31
  109. package/.claude/guidance/{task-swarm-integration.md → shipped/task-swarm-integration.md} +441 -348
  110. package/.claude/helpers/gate-hook.mjs +50 -0
  111. package/.claude/helpers/gate.cjs +138 -236
  112. package/.claude/helpers/hook-handler.cjs +64 -326
  113. package/.claude/helpers/post-commit +16 -0
  114. package/.claude/helpers/pre-commit +26 -0
  115. package/.claude/helpers/prompt-hook.mjs +72 -0
  116. package/.claude/scripts/build-embeddings.mjs +549 -0
  117. package/.claude/scripts/generate-code-map.mjs +697 -0
  118. package/.claude/scripts/hooks.mjs +656 -0
  119. package/.claude/scripts/index-guidance.mjs +893 -0
  120. package/.claude/scripts/index-tests.mjs +710 -0
  121. package/.claude/scripts/semantic-search.mjs +473 -0
  122. package/.claude/scripts/session-start-launcher.mjs +226 -0
  123. package/.claude/settings.json +351 -290
  124. package/.claude/settings.local.json +4 -3
  125. package/.claude/skills/browser/SKILL.md +204 -0
  126. package/.claude/skills/fl/SKILL.md +29 -23
  127. package/.claude/skills/flo/SKILL.md +29 -23
  128. package/.claude/skills/github-code-review/SKILL.md +4 -4
  129. package/.claude/skills/github-multi-repo/SKILL.md +8 -8
  130. package/.claude/skills/github-project-management/SKILL.md +6 -6
  131. package/.claude/skills/github-release-management/SKILL.md +12 -12
  132. package/.claude/skills/github-workflow-automation/SKILL.md +6 -6
  133. package/.claude/skills/hooks-automation/SKILL.md +1201 -1201
  134. package/.claude/skills/performance-analysis/SKILL.md +563 -563
  135. package/.claude/skills/sparc-methodology/SKILL.md +64 -64
  136. package/.claude/skills/swarm-advanced/SKILL.md +77 -77
  137. package/.claude/workflow-state.json +9 -0
  138. package/.claude-plugin/README.md +3 -3
  139. package/.claude-plugin/docs/PLUGIN_SUMMARY.md +3 -3
  140. package/.claude-plugin/docs/QUICKSTART.md +4 -4
  141. package/.claude-plugin/marketplace.json +3 -3
  142. package/.claude-plugin/plugin.json +3 -3
  143. package/.claude-plugin/scripts/install.sh +9 -9
  144. package/.claude-plugin/scripts/verify.sh +7 -7
  145. package/README.md +311 -116
  146. package/bin/gate-hook.mjs +50 -0
  147. package/bin/gate.cjs +138 -0
  148. package/bin/hook-handler.cjs +83 -0
  149. package/bin/hooks.mjs +72 -12
  150. package/bin/index-guidance.mjs +29 -35
  151. package/bin/index-tests.mjs +710 -0
  152. package/bin/lib/process-manager.mjs +243 -0
  153. package/bin/lib/registry-cleanup.cjs +41 -0
  154. package/bin/prompt-hook.mjs +72 -0
  155. package/bin/semantic-search.mjs +472 -440
  156. package/bin/session-start-launcher.mjs +81 -31
  157. package/bin/setup-project.mjs +65 -65
  158. package/package.json +4 -2
  159. package/src/@claude-flow/cli/README.md +1 -1
  160. package/src/@claude-flow/cli/bin/cli.js +175 -175
  161. package/src/@claude-flow/cli/dist/src/commands/doctor.js +1091 -736
  162. package/src/@claude-flow/cli/dist/src/commands/github.d.ts +12 -0
  163. package/src/@claude-flow/cli/dist/src/commands/github.js +505 -0
  164. package/src/@claude-flow/cli/dist/src/commands/hive-mind.js +90 -90
  165. package/src/@claude-flow/cli/dist/src/commands/index.d.ts +1 -0
  166. package/src/@claude-flow/cli/dist/src/commands/index.js +7 -0
  167. package/src/@claude-flow/cli/dist/src/config-adapter.js +1 -1
  168. package/src/@claude-flow/cli/dist/src/init/claudemd-generator.d.ts +29 -24
  169. package/src/@claude-flow/cli/dist/src/init/claudemd-generator.js +73 -494
  170. package/src/@claude-flow/cli/dist/src/init/executor.js +109 -5
  171. package/src/@claude-flow/cli/dist/src/init/helpers-generator.d.ts +14 -0
  172. package/src/@claude-flow/cli/dist/src/init/helpers-generator.js +156 -24
  173. package/src/@claude-flow/cli/dist/src/init/mcp-generator.js +20 -20
  174. package/src/@claude-flow/cli/dist/src/init/moflo-init.d.ts +30 -23
  175. package/src/@claude-flow/cli/dist/src/init/moflo-init.js +727 -670
  176. package/src/@claude-flow/cli/dist/src/init/settings-generator.js +23 -14
  177. package/src/@claude-flow/cli/dist/src/mcp-server.js +3 -3
  178. package/src/@claude-flow/cli/dist/src/plugins/manager.js +9 -8
  179. package/src/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +1 -0
  180. package/src/@claude-flow/cli/dist/src/services/worker-daemon.js +3 -1
  181. package/src/@claude-flow/cli/dist/src/services/workflow-gate.js +10 -10
  182. package/src/@claude-flow/cli/package.json +106 -106
@@ -1,224 +1,275 @@
1
- /**
2
- * MoFlo Project Initializer
3
- *
4
- * One-stop setup that makes MoFlo work out of the box:
5
- * 1. Generate moflo.yaml (project config)
6
- * 2. Set up .claude/settings.json hooks
7
- * 3. Create .claude/skills/flo/ skill (with /fl alias)
8
- * 4. Append MoFlo section to CLAUDE.md
9
- * 5. Initialize memory DB
10
- * 6. Auto-index guidance + code map
11
- */
12
- import * as fs from 'fs';
13
- import * as path from 'path';
14
- import { fileURLToPath } from 'url';
15
- // ============================================================================
16
- // Init
17
- // ============================================================================
18
- /**
19
- * Discover guidance directories by checking top-level candidates AND walking
20
- * the project tree for subproject .claude/guidance dirs (monorepo support).
21
- */
22
- function discoverGuidanceDirs(root) {
23
- const TOP_LEVEL = ['.claude/guidance', 'docs/guides', 'docs', 'architecture', 'adr', '.cursor/rules'];
24
- const found = TOP_LEVEL.filter(d => fs.existsSync(path.join(root, d)));
25
- // Walk up to 3 levels deep looking for .claude/guidance in subprojects
26
- const SKIP = new Set(['node_modules', '.git', 'dist', 'build', 'coverage', '.next', '.reports', '.swarm', '.claude-flow', 'packages']);
27
- function walk(dir, depth) {
28
- if (depth > 3)
29
- return;
30
- try {
31
- const entries = fs.readdirSync(path.join(root, dir), { withFileTypes: true });
32
- for (const entry of entries) {
33
- if (!entry.isDirectory() || SKIP.has(entry.name))
34
- continue;
35
- const rel = dir ? `${dir}/${entry.name}` : entry.name;
36
- const guidancePath = `${rel}/.claude/guidance`;
37
- if (fs.existsSync(path.join(root, guidancePath))) {
38
- // Verify it has .md files
39
- try {
40
- const files = fs.readdirSync(path.join(root, guidancePath));
41
- if (files.some(f => f.endsWith('.md')))
42
- found.push(guidancePath);
43
- }
44
- catch { /* skip unreadable */ }
45
- }
46
- else {
47
- walk(rel, depth + 1);
48
- }
49
- }
50
- }
51
- catch { /* skip unreadable directories */ }
52
- }
53
- walk('', 0);
54
- return found;
55
- }
56
- /**
57
- * Discover source directories by walking the project tree.
58
- * Finds directories named 'src' (or top-level 'packages', 'lib', etc.)
59
- * that contain .ts/.tsx/.js/.jsx files. Skips node_modules, dist, etc.
60
- */
61
- function discoverSrcDirs(root) {
62
- const SKIP = new Set(['node_modules', '.git', 'dist', 'build', 'coverage', '.next', '.reports', '.swarm', '.claude-flow']);
63
- // Top-level candidates that are always source roots if they exist
64
- const TOP_LEVEL = ['packages', 'lib', 'app', 'apps', 'services', 'server', 'client'];
65
- const found = [];
66
- // Add top-level candidates first
67
- for (const d of TOP_LEVEL) {
68
- if (fs.existsSync(path.join(root, d)))
69
- found.push(d);
70
- }
71
- // Walk up to 3 levels deep looking for 'src' and 'migrations' directories
72
- const SRC_NAMES = new Set(['src', 'migrations']);
73
- function walk(dir, depth) {
74
- if (depth > 3)
75
- return;
76
- try {
77
- const entries = fs.readdirSync(path.join(root, dir), { withFileTypes: true });
78
- for (const entry of entries) {
79
- if (!entry.isDirectory() || SKIP.has(entry.name))
80
- continue;
81
- const rel = dir ? `${dir}/${entry.name}` : entry.name;
82
- if (SRC_NAMES.has(entry.name)) {
83
- // Check it actually has source files
84
- try {
85
- const files = fs.readdirSync(path.join(root, rel));
86
- const hasSource = files.some(f => /\.(ts|tsx|js|jsx)$/.test(f));
87
- if (hasSource)
88
- found.push(rel);
89
- }
90
- catch { /* skip unreadable */ }
91
- }
92
- else {
93
- walk(rel, depth + 1);
94
- }
95
- }
96
- }
97
- catch { /* skip unreadable directories */ }
98
- }
99
- walk('', 0);
100
- // Deduplicate: if 'packages' is found, don't also include 'packages/foo/src'
101
- // since the code-map walker handles subdirs
102
- return found.filter(d => {
103
- return !found.some(other => other !== d && d.startsWith(other + '/'));
104
- });
105
- }
106
- /**
107
- * Run interactive wizard to collect user preferences.
108
- */
109
- async function runWizard(root) {
110
- const { confirm, input } = await import('../prompt.js');
111
- // Detect project structure
112
- const detectedGuidance = discoverGuidanceDirs(root);
113
- const detectedSrc = discoverSrcDirs(root);
114
- // Ask questions
115
- const guidance = await confirm({
116
- message: detectedGuidance.length > 0
117
- ? `Found guidance docs in ${detectedGuidance.join(', ')}. Enable guidance indexing?`
118
- : 'Do you have project guidance/documentation to index?',
119
- default: true,
120
- });
121
- let guidanceDirs = detectedGuidance.length > 0 ? detectedGuidance : ['.claude/guidance'];
122
- if (guidance) {
123
- const answer = await input({
124
- message: 'Guidance directories (comma-separated):',
125
- default: guidanceDirs.join(', '),
126
- });
127
- guidanceDirs = answer.split(',').map((d) => d.trim()).filter(Boolean);
128
- }
129
- const codeMap = await confirm({
130
- message: detectedSrc.length > 0
131
- ? `Found source in ${detectedSrc.join(', ')}. Enable code map for navigation?`
132
- : 'Enable code map for codebase navigation?',
133
- default: true,
134
- });
135
- let srcDirs = detectedSrc.length > 0 ? detectedSrc : ['src'];
136
- if (codeMap) {
137
- const answer = await input({
138
- message: 'Source directories (comma-separated):',
139
- default: srcDirs.join(', '),
140
- });
141
- srcDirs = answer.split(',').map((d) => d.trim()).filter(Boolean);
142
- }
143
- const gates = await confirm({
144
- message: 'Enable workflow gates (memory-first, task-create-before-agents)?',
145
- default: true,
146
- });
147
- const stopHook = await confirm({
148
- message: 'Enable session-end hook (saves session state)?',
149
- default: true,
150
- });
151
- return { guidance, guidanceDirs, codeMap, srcDirs, gates, stopHook };
152
- }
153
- /**
154
- * Get default answers (--yes mode).
155
- */
156
- function defaultAnswers(root) {
157
- const guidanceDirs = discoverGuidanceDirs(root);
158
- if (guidanceDirs.length === 0)
159
- guidanceDirs.push('.claude/guidance');
160
- const srcDirs = discoverSrcDirs(root);
161
- if (srcDirs.length === 0)
162
- srcDirs.push('src');
163
- return { guidance: true, guidanceDirs, codeMap: true, srcDirs, gates: true, stopHook: true };
164
- }
165
- /**
166
- * Get minimal answers (--minimal mode).
167
- */
168
- function minimalAnswers() {
169
- return { guidance: false, guidanceDirs: [], codeMap: false, srcDirs: [], gates: false, stopHook: false };
170
- }
171
- export async function initMoflo(options) {
172
- const { projectRoot, force, interactive, minimal } = options;
173
- const steps = [];
174
- // Collect answers based on mode
175
- const answers = minimal
176
- ? minimalAnswers()
177
- : interactive
178
- ? await runWizard(projectRoot)
179
- : defaultAnswers(projectRoot);
180
- // Step 1: moflo.yaml
181
- steps.push(generateConfig(projectRoot, force, answers));
182
- // Step 2: .claude/settings.json hooks
183
- steps.push(generateHooks(projectRoot, force, answers));
184
- // Step 3: .claude/skills/flo/ (with /fl alias)
185
- steps.push(generateSkill(projectRoot, force));
186
- // Step 4: CLAUDE.md MoFlo section
187
- steps.push(generateClaudeMd(projectRoot, force));
188
- // Step 5: .claude/scripts/ from moflo bin/
189
- steps.push(syncScripts(projectRoot, force));
190
- // Step 6: .gitignore entries
191
- steps.push(updateGitignore(projectRoot));
192
- // Step 7: .claude/guidance/moflo-bootstrap.md (subagent bootstrap protocol)
193
- steps.push(syncBootstrapGuidance(projectRoot, force));
194
- return { steps };
195
- }
196
- // ============================================================================
197
- // Step 1: moflo.yaml
198
- // ============================================================================
199
- function generateConfig(root, force, answers) {
200
- const configPath = path.join(root, 'moflo.yaml');
201
- if (fs.existsSync(configPath) && !force) {
202
- return { name: 'moflo.yaml', status: 'skipped', detail: 'Already exists (use --force to overwrite)' };
203
- }
204
- const projectName = path.basename(root);
205
- const guidanceDirs = answers?.guidanceDirs ?? ['.claude/guidance'];
206
- const srcDirs = answers?.srcDirs ?? ['src'];
207
- const gatesEnabled = answers?.gates ?? true;
208
- // Detect languages
209
- const extensions = new Set();
210
- for (const dir of srcDirs) {
211
- const fullDir = path.join(root, dir);
212
- if (fs.existsSync(fullDir)) {
213
- try {
214
- scanExtensions(fullDir, extensions, 0, 3);
215
- }
216
- catch { /* skip */ }
217
- }
218
- }
219
- const detectedExts = extensions.size > 0
220
- ? [...extensions].sort()
221
- : ['.ts', '.tsx', '.js', '.jsx'];
1
+ /**
2
+ * MoFlo Project Initializer
3
+ *
4
+ * One-stop setup that makes MoFlo work out of the box:
5
+ * 1. Generate moflo.yaml (project config)
6
+ * 2. Set up .claude/settings.json hooks
7
+ * 3. Create .claude/skills/flo/ skill (with /fl alias)
8
+ * 4. Append MoFlo section to CLAUDE.md
9
+ * 5. Initialize memory DB
10
+ * 6. Auto-index guidance + code map
11
+ */
12
+ import * as fs from 'fs';
13
+ import * as path from 'path';
14
+ import { fileURLToPath } from 'url';
15
+ // ============================================================================
16
+ // Init
17
+ // ============================================================================
18
+ /**
19
+ * Discover guidance directories by checking top-level candidates AND walking
20
+ * the project tree for subproject .claude/guidance dirs (monorepo support).
21
+ */
22
+ function discoverGuidanceDirs(root) {
23
+ const TOP_LEVEL = ['.claude/guidance', 'docs/guides', 'docs', 'architecture', 'adr', '.cursor/rules'];
24
+ const found = TOP_LEVEL.filter(d => fs.existsSync(path.join(root, d)));
25
+ // Walk up to 3 levels deep looking for .claude/guidance in subprojects
26
+ const SKIP = new Set(['node_modules', '.git', 'dist', 'build', 'coverage', '.next', '.reports', '.swarm', '.claude-flow', 'packages']);
27
+ function walk(dir, depth) {
28
+ if (depth > 3)
29
+ return;
30
+ try {
31
+ const entries = fs.readdirSync(path.join(root, dir), { withFileTypes: true });
32
+ for (const entry of entries) {
33
+ if (!entry.isDirectory() || SKIP.has(entry.name))
34
+ continue;
35
+ const rel = dir ? `${dir}/${entry.name}` : entry.name;
36
+ const guidancePath = `${rel}/.claude/guidance`;
37
+ if (fs.existsSync(path.join(root, guidancePath))) {
38
+ // Verify it has .md files
39
+ try {
40
+ const files = fs.readdirSync(path.join(root, guidancePath));
41
+ if (files.some(f => f.endsWith('.md')))
42
+ found.push(guidancePath);
43
+ }
44
+ catch { /* skip unreadable */ }
45
+ }
46
+ else {
47
+ walk(rel, depth + 1);
48
+ }
49
+ }
50
+ }
51
+ catch { /* skip unreadable directories */ }
52
+ }
53
+ walk('', 0);
54
+ return found;
55
+ }
56
+ /**
57
+ * Discover test directories by checking common locations and walking for
58
+ * colocated __tests__ dirs. Returns relative paths.
59
+ */
60
+ export function discoverTestDirs(root) {
61
+ const TOP_LEVEL = ['tests', 'test', '__tests__', 'spec', 'e2e'];
62
+ const found = TOP_LEVEL.filter(d => fs.existsSync(path.join(root, d)));
63
+ // Walk up to 3 levels deep looking for __tests__ dirs inside src
64
+ const SKIP = new Set(['node_modules', '.git', 'dist', 'build', 'coverage', '.next', '.reports', '.swarm', '.claude-flow']);
65
+ function walk(dir, depth) {
66
+ if (depth > 3)
67
+ return;
68
+ try {
69
+ const entries = fs.readdirSync(path.join(root, dir), { withFileTypes: true });
70
+ for (const entry of entries) {
71
+ if (!entry.isDirectory() || SKIP.has(entry.name))
72
+ continue;
73
+ const rel = dir ? `${dir}/${entry.name}` : entry.name;
74
+ if (entry.name === '__tests__') {
75
+ found.push(rel);
76
+ }
77
+ else {
78
+ walk(rel, depth + 1);
79
+ }
80
+ }
81
+ }
82
+ catch { /* skip unreadable directories */ }
83
+ }
84
+ walk('', 0);
85
+ return found;
86
+ }
87
+ /**
88
+ * Discover source directories by walking the project tree.
89
+ * Finds directories named 'src' (or top-level 'packages', 'lib', etc.)
90
+ * that contain .ts/.tsx/.js/.jsx files. Skips node_modules, dist, etc.
91
+ */
92
+ function discoverSrcDirs(root) {
93
+ const SKIP = new Set(['node_modules', '.git', 'dist', 'build', 'coverage', '.next', '.reports', '.swarm', '.claude-flow']);
94
+ // Top-level candidates that are always source roots if they exist
95
+ const TOP_LEVEL = ['packages', 'lib', 'app', 'apps', 'services', 'server', 'client'];
96
+ const found = [];
97
+ // Add top-level candidates first
98
+ for (const d of TOP_LEVEL) {
99
+ if (fs.existsSync(path.join(root, d)))
100
+ found.push(d);
101
+ }
102
+ // Walk up to 3 levels deep looking for 'src' and 'migrations' directories
103
+ const SRC_NAMES = new Set(['src', 'migrations']);
104
+ function walk(dir, depth) {
105
+ if (depth > 3)
106
+ return;
107
+ try {
108
+ const entries = fs.readdirSync(path.join(root, dir), { withFileTypes: true });
109
+ for (const entry of entries) {
110
+ if (!entry.isDirectory() || SKIP.has(entry.name))
111
+ continue;
112
+ const rel = dir ? `${dir}/${entry.name}` : entry.name;
113
+ if (SRC_NAMES.has(entry.name)) {
114
+ // Check it actually has source files
115
+ try {
116
+ const files = fs.readdirSync(path.join(root, rel));
117
+ const hasSource = files.some(f => /\.(ts|tsx|js|jsx)$/.test(f));
118
+ if (hasSource)
119
+ found.push(rel);
120
+ }
121
+ catch { /* skip unreadable */ }
122
+ }
123
+ else {
124
+ walk(rel, depth + 1);
125
+ }
126
+ }
127
+ }
128
+ catch { /* skip unreadable directories */ }
129
+ }
130
+ walk('', 0);
131
+ // Deduplicate: if 'packages' is found, don't also include 'packages/foo/src'
132
+ // since the code-map walker handles subdirs
133
+ return found.filter(d => {
134
+ return !found.some(other => other !== d && d.startsWith(other + '/'));
135
+ });
136
+ }
137
+ /**
138
+ * Run interactive wizard to collect user preferences.
139
+ */
140
+ async function runWizard(root) {
141
+ const { confirm, input } = await import('../prompt.js');
142
+ // Detect project structure
143
+ const detectedGuidance = discoverGuidanceDirs(root);
144
+ const detectedSrc = discoverSrcDirs(root);
145
+ // Ask questions
146
+ const guidance = await confirm({
147
+ message: detectedGuidance.length > 0
148
+ ? `Found guidance docs in ${detectedGuidance.join(', ')}. Enable guidance indexing?`
149
+ : 'Do you have project guidance/documentation to index?',
150
+ default: true,
151
+ });
152
+ let guidanceDirs = detectedGuidance.length > 0 ? detectedGuidance : ['.claude/guidance'];
153
+ if (guidance) {
154
+ const answer = await input({
155
+ message: 'Guidance directories (comma-separated):',
156
+ default: guidanceDirs.join(', '),
157
+ });
158
+ guidanceDirs = answer.split(',').map((d) => d.trim()).filter(Boolean);
159
+ }
160
+ const codeMap = await confirm({
161
+ message: detectedSrc.length > 0
162
+ ? `Found source in ${detectedSrc.join(', ')}. Enable code map for navigation?`
163
+ : 'Enable code map for codebase navigation?',
164
+ default: true,
165
+ });
166
+ let srcDirs = detectedSrc.length > 0 ? detectedSrc : ['src'];
167
+ if (codeMap) {
168
+ const answer = await input({
169
+ message: 'Source directories (comma-separated):',
170
+ default: srcDirs.join(', '),
171
+ });
172
+ srcDirs = answer.split(',').map((d) => d.trim()).filter(Boolean);
173
+ }
174
+ // Detect test directories
175
+ const detectedTests = discoverTestDirs(root);
176
+ const tests = await confirm({
177
+ message: detectedTests.length > 0
178
+ ? `Found tests in ${detectedTests.join(', ')}. Enable test file indexing?`
179
+ : 'Enable test file indexing?',
180
+ default: true,
181
+ });
182
+ let testDirs = detectedTests.length > 0 ? detectedTests : ['tests'];
183
+ if (tests) {
184
+ const answer = await input({
185
+ message: 'Test directories (comma-separated):',
186
+ default: testDirs.join(', '),
187
+ });
188
+ testDirs = answer.split(',').map((d) => d.trim()).filter(Boolean);
189
+ }
190
+ const gates = await confirm({
191
+ message: 'Enable workflow gates (memory-first, task-create-before-agents)?',
192
+ default: true,
193
+ });
194
+ const stopHook = await confirm({
195
+ message: 'Enable session-end hook (saves session state)?',
196
+ default: true,
197
+ });
198
+ return { guidance, guidanceDirs, codeMap, srcDirs, tests, testDirs, gates, stopHook };
199
+ }
200
+ /**
201
+ * Get default answers (--yes mode).
202
+ */
203
+ function defaultAnswers(root) {
204
+ const guidanceDirs = discoverGuidanceDirs(root);
205
+ if (guidanceDirs.length === 0)
206
+ guidanceDirs.push('.claude/guidance');
207
+ const srcDirs = discoverSrcDirs(root);
208
+ if (srcDirs.length === 0)
209
+ srcDirs.push('src');
210
+ const testDirs = discoverTestDirs(root);
211
+ if (testDirs.length === 0)
212
+ testDirs.push('tests');
213
+ return { guidance: true, guidanceDirs, codeMap: true, srcDirs, tests: true, testDirs, gates: true, stopHook: true };
214
+ }
215
+ /**
216
+ * Get minimal answers (--minimal mode).
217
+ */
218
+ function minimalAnswers() {
219
+ return { guidance: false, guidanceDirs: [], codeMap: false, srcDirs: [], tests: false, testDirs: [], gates: false, stopHook: false };
220
+ }
221
+ export async function initMoflo(options) {
222
+ const { projectRoot, force, interactive, minimal } = options;
223
+ const steps = [];
224
+ // Collect answers based on mode
225
+ const answers = minimal
226
+ ? minimalAnswers()
227
+ : interactive
228
+ ? await runWizard(projectRoot)
229
+ : defaultAnswers(projectRoot);
230
+ // Step 1: moflo.yaml
231
+ steps.push(generateConfig(projectRoot, force, answers));
232
+ // Step 2: .claude/settings.json hooks
233
+ steps.push(generateHooks(projectRoot, force, answers));
234
+ // Step 3: .claude/skills/flo/ (with /fl alias)
235
+ steps.push(generateSkill(projectRoot, force));
236
+ // Step 4: CLAUDE.md MoFlo section
237
+ steps.push(generateClaudeMd(projectRoot, force));
238
+ // Step 5: .claude/scripts/ from moflo bin/
239
+ steps.push(syncScripts(projectRoot, force));
240
+ // Step 6: .gitignore entries
241
+ steps.push(updateGitignore(projectRoot));
242
+ // Step 7: .claude/guidance/moflo-bootstrap.md (subagent bootstrap protocol)
243
+ steps.push(syncBootstrapGuidance(projectRoot, force));
244
+ return { steps };
245
+ }
246
+ // ============================================================================
247
+ // Step 1: moflo.yaml
248
+ // ============================================================================
249
+ function generateConfig(root, force, answers) {
250
+ const configPath = path.join(root, 'moflo.yaml');
251
+ if (fs.existsSync(configPath) && !force) {
252
+ return { name: 'moflo.yaml', status: 'skipped', detail: 'Already exists (use --force to overwrite)' };
253
+ }
254
+ const projectName = path.basename(root);
255
+ const guidanceDirs = answers?.guidanceDirs ?? ['.claude/guidance'];
256
+ const srcDirs = answers?.srcDirs ?? ['src'];
257
+ const testDirs = answers?.testDirs ?? ['tests'];
258
+ const gatesEnabled = answers?.gates ?? true;
259
+ // Detect languages
260
+ const extensions = new Set();
261
+ for (const dir of srcDirs) {
262
+ const fullDir = path.join(root, dir);
263
+ if (fs.existsSync(fullDir)) {
264
+ try {
265
+ scanExtensions(fullDir, extensions, 0, 3);
266
+ }
267
+ catch { /* skip */ }
268
+ }
269
+ }
270
+ const detectedExts = extensions.size > 0
271
+ ? [...extensions].sort()
272
+ : ['.ts', '.tsx', '.js', '.jsx'];
222
273
  const yaml = `# MoFlo — Project Configuration
223
274
  # Generated by: moflo init
224
275
  # Docs: https://github.com/eric-cielo/moflo
@@ -240,6 +291,15 @@ ${srcDirs.map(d => ` - ${d}`).join('\n')}
240
291
  exclude: [node_modules, dist, .next, coverage, build, __pycache__, target, .git]
241
292
  namespace: code-map
242
293
 
294
+ # Test file discovery and indexing
295
+ tests:
296
+ directories:
297
+ ${testDirs.map(d => ` - ${d}`).join('\n')}
298
+ patterns: ["*.test.*", "*.spec.*", "*.test-*"]
299
+ extensions: [".ts", ".tsx", ".js", ".jsx"]
300
+ exclude: [node_modules, coverage, dist]
301
+ namespace: tests
302
+
243
303
  # Workflow gates (enforced via Claude Code hooks)
244
304
  gates:
245
305
  memory_first: ${gatesEnabled}
@@ -250,6 +310,7 @@ gates:
250
310
  auto_index:
251
311
  guidance: ${answers?.guidance ?? true}
252
312
  code_map: ${answers?.codeMap ?? true}
313
+ tests: ${answers?.tests ?? true}
253
314
 
254
315
  # Memory backend
255
316
  memory:
@@ -305,280 +366,290 @@ model_routing:
305
366
  # agent_overrides:
306
367
  # security-architect: opus # Always use opus for security
307
368
  # researcher: sonnet # Pin research to sonnet
308
- `;
309
- fs.writeFileSync(configPath, yaml, 'utf-8');
310
- return { name: 'moflo.yaml', status: 'created', detail: `Detected: ${srcDirs.join(', ')} | ${detectedExts.join(', ')}` };
311
- }
312
- function scanExtensions(dir, extensions, depth, maxDepth) {
313
- if (depth > maxDepth)
314
- return;
315
- const entries = fs.readdirSync(dir, { withFileTypes: true });
316
- for (const entry of entries.slice(0, 100)) {
317
- if (entry.isDirectory() && !['node_modules', '.git', 'dist', 'build'].includes(entry.name)) {
318
- scanExtensions(path.join(dir, entry.name), extensions, depth + 1, maxDepth);
319
- }
320
- else if (entry.isFile()) {
321
- const ext = path.extname(entry.name);
322
- if (['.ts', '.tsx', '.js', '.jsx', '.py', '.go', '.rs', '.java', '.kt', '.swift', '.rb', '.cs'].includes(ext)) {
323
- extensions.add(ext);
324
- }
325
- }
326
- }
327
- }
328
- // ============================================================================
329
- // Step 2: .claude/settings.json hooks
330
- // ============================================================================
331
- function generateHooks(root, force, answers) {
332
- const settingsPath = path.join(root, '.claude', 'settings.json');
333
- const settingsDir = path.dirname(settingsPath);
334
- if (!fs.existsSync(settingsDir)) {
335
- fs.mkdirSync(settingsDir, { recursive: true });
336
- }
337
- let existing = {};
338
- if (fs.existsSync(settingsPath)) {
339
- try {
340
- existing = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
341
- }
342
- catch { /* start fresh */ }
343
- // Check if MoFlo hooks already set up
344
- const settingsStr = JSON.stringify(existing);
345
- const hasGateHooks = settingsStr.includes('flo gate') || settingsStr.includes('moflo gate');
346
- if (hasGateHooks && !force) {
347
- return { name: '.claude/settings.json', status: 'skipped', detail: 'MoFlo hooks already configured' };
348
- }
349
- }
350
- // Build hooks config — all on by default (opinionated pit-of-success)
351
- const hooks = {
352
- "PreToolUse": [
353
- {
354
- "matcher": "^(Write|Edit|MultiEdit)$",
355
- "hooks": [{
356
- "type": "command",
357
- "command": "npx flo hooks pre-edit",
358
- "timeout": 5000
359
- }]
360
- },
361
- {
362
- "matcher": "^(Glob|Grep)$",
363
- "hooks": [{
364
- "type": "command",
365
- "command": "npx flo gate check-before-scan",
366
- "timeout": 3000
367
- }]
368
- },
369
- {
370
- "matcher": "^Read$",
371
- "hooks": [{
372
- "type": "command",
373
- "command": "npx flo gate check-before-read",
374
- "timeout": 3000
375
- }]
376
- },
377
- {
378
- "matcher": "^Task$",
379
- "hooks": [
380
- {
381
- "type": "command",
382
- "command": "npx flo gate check-before-agent",
383
- "timeout": 3000
384
- },
385
- {
386
- "type": "command",
387
- "command": "npx flo hooks pre-task",
388
- "timeout": 5000
389
- }
390
- ]
391
- },
392
- {
393
- "matcher": "^Bash$",
394
- "hooks": [{
395
- "type": "command",
396
- "command": "npx flo gate check-dangerous-command",
397
- "timeout": 2000
398
- }]
399
- }
400
- ],
401
- "PostToolUse": [
402
- {
403
- "matcher": "^(Write|Edit|MultiEdit)$",
404
- "hooks": [{
405
- "type": "command",
406
- "command": "npx flo hooks post-edit",
407
- "timeout": 5000
408
- }]
409
- },
410
- {
411
- "matcher": "^Task$",
412
- "hooks": [{
413
- "type": "command",
414
- "command": "npx flo hooks post-task",
415
- "timeout": 5000
416
- }]
417
- },
418
- {
419
- "matcher": "^TaskCreate$",
420
- "hooks": [{
421
- "type": "command",
422
- "command": "npx flo gate record-task-created",
423
- "timeout": 2000
424
- }]
425
- },
426
- {
427
- "matcher": "^Bash$",
428
- "hooks": [{
429
- "type": "command",
430
- "command": "npx flo gate check-bash-memory",
431
- "timeout": 2000
432
- }]
433
- },
434
- {
435
- "matcher": "^mcp__claude-flow__memory_(search|retrieve)$",
436
- "hooks": [{
437
- "type": "command",
438
- "command": "npx flo gate record-memory-searched",
439
- "timeout": 2000
440
- }]
441
- }
442
- ],
443
- "UserPromptSubmit": [
444
- {
445
- "hooks": [
446
- {
447
- "type": "command",
448
- "command": "npx flo gate prompt-reminder",
449
- "timeout": 2000
450
- },
451
- {
452
- "type": "command",
453
- "command": "npx flo hooks route",
454
- "timeout": 5000
455
- }
456
- ]
457
- }
458
- ],
459
- "SessionStart": [
460
- {
461
- "hooks": [
462
- {
463
- "type": "command",
464
- "command": "node \"$CLAUDE_PROJECT_DIR/.claude/scripts/session-start-launcher.mjs\"",
465
- "timeout": 3000
466
- }
467
- ]
468
- }
469
- ],
470
- "Stop": [
471
- {
472
- "hooks": [{
473
- "type": "command",
474
- "command": "npx flo hooks session-end",
475
- "timeout": 5000
476
- }]
477
- }
478
- ],
479
- "PreCompact": [
480
- {
481
- "hooks": [{
482
- "type": "command",
483
- "command": "npx flo gate compact-guidance",
484
- "timeout": 3000
485
- }]
486
- }
487
- ],
488
- "Notification": [
489
- {
490
- "hooks": [{
491
- "type": "command",
492
- "command": "npx flo hooks notification",
493
- "timeout": 3000
494
- }]
495
- }
496
- ]
497
- };
498
- // Merge: preserve existing non-MoFlo hooks, add MoFlo hooks
499
- existing.hooks = hooks;
500
- fs.writeFileSync(settingsPath, JSON.stringify(existing, null, 2), 'utf-8');
501
- return { name: '.claude/settings.json', status: existing.hooks ? 'updated' : 'created', detail: '14 hooks configured (gates, lifecycle, routing, session)' };
502
- }
503
- // ============================================================================
504
- // Step 3: .claude/skills/flo/ skill (with /fl alias)
505
- // ============================================================================
506
- function generateSkill(root, force) {
507
- const skillDir = path.join(root, '.claude', 'skills', 'flo');
508
- const skillFile = path.join(skillDir, 'SKILL.md');
509
- const aliasDir = path.join(root, '.claude', 'skills', 'fl');
510
- const aliasFile = path.join(aliasDir, 'SKILL.md');
511
- if (fs.existsSync(skillFile) && !force) {
512
- return { name: '.claude/skills/flo/', status: 'skipped', detail: 'Already exists' };
513
- }
514
- if (!fs.existsSync(skillDir)) {
515
- fs.mkdirSync(skillDir, { recursive: true });
516
- }
517
- // Copy static SKILL.md from moflo package instead of generating it
518
- let skillContent = '';
519
- // Resolve this file's directory in ESM-safe way
520
- let thisDir;
521
- try {
522
- thisDir = path.dirname(fileURLToPath(import.meta.url));
523
- }
524
- catch {
525
- // Fallback for CJS or environments where import.meta.url is unavailable
526
- thisDir = typeof __dirname !== 'undefined' ? __dirname : '';
527
- }
528
- const staticSkillCandidates = [
529
- // Installed via npm (most common)
530
- path.join(root, 'node_modules', 'moflo', '.claude', 'skills', 'flo', 'SKILL.md'),
531
- // Running from moflo repo itself (dev)
532
- ...(thisDir ? [path.join(thisDir, '..', '..', '..', '..', '.claude', 'skills', 'flo', 'SKILL.md')] : []),
533
- ];
534
- for (const candidate of staticSkillCandidates) {
535
- try {
536
- if (fs.existsSync(candidate)) {
537
- skillContent = fs.readFileSync(candidate, 'utf-8');
538
- break;
539
- }
540
- }
541
- catch { /* skip inaccessible paths */ }
542
- }
543
- if (!skillContent) {
544
- return { name: '.claude/skills/flo/', status: 'error', detail: 'Could not find SKILL.md in moflo package' };
545
- }
546
- fs.writeFileSync(skillFile, skillContent, 'utf-8');
547
- // Create /fl alias (same content)
548
- if (!fs.existsSync(aliasDir)) {
549
- fs.mkdirSync(aliasDir, { recursive: true });
550
- }
551
- fs.writeFileSync(aliasFile, skillContent.replace('name: flo', 'name: fl'), 'utf-8');
552
- // Clean up old /mf skill directory if it exists
553
- const oldSkillDir = path.join(root, '.claude', 'skills', 'mf');
554
- if (fs.existsSync(oldSkillDir)) {
555
- fs.rmSync(oldSkillDir, { recursive: true });
556
- }
557
- return { name: '.claude/skills/flo/', status: 'created', detail: '/flo skill ready (alias: /fl)' };
558
- }
559
- // ============================================================================
560
- // Step 4: CLAUDE.md MoFlo section
561
- // ============================================================================
562
- const MOFLO_MARKER = '<!-- MOFLO:START -->';
563
- const MOFLO_MARKER_END = '<!-- MOFLO:END -->';
564
- function generateClaudeMd(root, force) {
565
- const claudeMdPath = path.join(root, 'CLAUDE.md');
566
- let existing = '';
567
- if (fs.existsSync(claudeMdPath)) {
568
- existing = fs.readFileSync(claudeMdPath, 'utf-8');
569
- // Check if MoFlo section already exists
570
- if (existing.includes(MOFLO_MARKER)) {
571
- if (!force) {
572
- return { name: 'CLAUDE.md', status: 'skipped', detail: 'MoFlo section already present' };
573
- }
574
- // Remove existing MoFlo section for replacement
575
- const startIdx = existing.indexOf(MOFLO_MARKER);
576
- const endIdx = existing.indexOf(MOFLO_MARKER_END);
577
- if (endIdx > startIdx) {
578
- existing = existing.substring(0, startIdx) + existing.substring(endIdx + MOFLO_MARKER_END.length);
579
- }
580
- }
581
- }
369
+ `;
370
+ fs.writeFileSync(configPath, yaml, 'utf-8');
371
+ return { name: 'moflo.yaml', status: 'created', detail: `Detected: ${srcDirs.join(', ')} | ${detectedExts.join(', ')}` };
372
+ }
373
+ function scanExtensions(dir, extensions, depth, maxDepth) {
374
+ if (depth > maxDepth)
375
+ return;
376
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
377
+ for (const entry of entries.slice(0, 100)) {
378
+ if (entry.isDirectory() && !['node_modules', '.git', 'dist', 'build'].includes(entry.name)) {
379
+ scanExtensions(path.join(dir, entry.name), extensions, depth + 1, maxDepth);
380
+ }
381
+ else if (entry.isFile()) {
382
+ const ext = path.extname(entry.name);
383
+ if (['.ts', '.tsx', '.js', '.jsx', '.py', '.go', '.rs', '.java', '.kt', '.swift', '.rb', '.cs'].includes(ext)) {
384
+ extensions.add(ext);
385
+ }
386
+ }
387
+ }
388
+ }
389
+ // ============================================================================
390
+ // Step 2: .claude/settings.json hooks
391
+ // ============================================================================
392
+ function generateHooks(root, force, answers) {
393
+ const settingsPath = path.join(root, '.claude', 'settings.json');
394
+ const settingsDir = path.dirname(settingsPath);
395
+ if (!fs.existsSync(settingsDir)) {
396
+ fs.mkdirSync(settingsDir, { recursive: true });
397
+ }
398
+ let existing = {};
399
+ if (fs.existsSync(settingsPath)) {
400
+ try {
401
+ existing = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
402
+ }
403
+ catch { /* start fresh */ }
404
+ // Check if MoFlo hooks already set up
405
+ const settingsStr = JSON.stringify(existing);
406
+ const hasGateHooks = settingsStr.includes('flo gate') || settingsStr.includes('moflo gate');
407
+ if (hasGateHooks && !force) {
408
+ return { name: '.claude/settings.json', status: 'skipped', detail: 'MoFlo hooks already configured' };
409
+ }
410
+ }
411
+ // Build hooks config — all on by default (opinionated pit-of-success)
412
+ const hooks = {
413
+ "PreToolUse": [
414
+ {
415
+ "matcher": "^(Write|Edit|MultiEdit)$",
416
+ "hooks": [{
417
+ "type": "command",
418
+ "command": "npx flo hooks pre-edit",
419
+ "timeout": 5000
420
+ }]
421
+ },
422
+ {
423
+ "matcher": "^(Glob|Grep)$",
424
+ "hooks": [{
425
+ "type": "command",
426
+ "command": "npx flo gate check-before-scan",
427
+ "timeout": 3000
428
+ }]
429
+ },
430
+ {
431
+ "matcher": "^Read$",
432
+ "hooks": [{
433
+ "type": "command",
434
+ "command": "npx flo gate check-before-read",
435
+ "timeout": 3000
436
+ }]
437
+ },
438
+ {
439
+ "matcher": "^Task$",
440
+ "hooks": [
441
+ {
442
+ "type": "command",
443
+ "command": "npx flo gate check-before-agent",
444
+ "timeout": 3000
445
+ },
446
+ {
447
+ "type": "command",
448
+ "command": "npx flo hooks pre-task",
449
+ "timeout": 5000
450
+ }
451
+ ]
452
+ },
453
+ {
454
+ "matcher": "^Bash$",
455
+ "hooks": [{
456
+ "type": "command",
457
+ "command": "npx flo gate check-dangerous-command",
458
+ "timeout": 2000
459
+ }]
460
+ }
461
+ ],
462
+ "PostToolUse": [
463
+ {
464
+ "matcher": "^(Write|Edit|MultiEdit)$",
465
+ "hooks": [{
466
+ "type": "command",
467
+ "command": "npx flo hooks post-edit",
468
+ "timeout": 5000
469
+ }]
470
+ },
471
+ {
472
+ "matcher": "^Task$",
473
+ "hooks": [{
474
+ "type": "command",
475
+ "command": "npx flo hooks post-task",
476
+ "timeout": 5000
477
+ }]
478
+ },
479
+ {
480
+ "matcher": "^TaskCreate$",
481
+ "hooks": [{
482
+ "type": "command",
483
+ "command": "npx flo gate record-task-created",
484
+ "timeout": 2000
485
+ }]
486
+ },
487
+ {
488
+ "matcher": "^Bash$",
489
+ "hooks": [{
490
+ "type": "command",
491
+ "command": "npx flo gate check-bash-memory",
492
+ "timeout": 2000
493
+ }]
494
+ },
495
+ {
496
+ "matcher": "^mcp__moflo__memory_(search|retrieve)$",
497
+ "hooks": [{
498
+ "type": "command",
499
+ "command": "npx flo gate record-memory-searched",
500
+ "timeout": 2000
501
+ }]
502
+ }
503
+ ],
504
+ "UserPromptSubmit": [
505
+ {
506
+ "hooks": [
507
+ {
508
+ "type": "command",
509
+ "command": "npx flo gate prompt-reminder",
510
+ "timeout": 2000
511
+ },
512
+ {
513
+ "type": "command",
514
+ "command": "npx flo hooks route",
515
+ "timeout": 5000
516
+ }
517
+ ]
518
+ }
519
+ ],
520
+ "SessionStart": [
521
+ {
522
+ "hooks": [
523
+ {
524
+ "type": "command",
525
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/scripts/session-start-launcher.mjs\"",
526
+ "timeout": 3000
527
+ }
528
+ ]
529
+ }
530
+ ],
531
+ "Stop": [
532
+ {
533
+ "hooks": [{
534
+ "type": "command",
535
+ "command": "npx flo hooks session-end",
536
+ "timeout": 5000
537
+ }]
538
+ }
539
+ ],
540
+ "PreCompact": [
541
+ {
542
+ "hooks": [{
543
+ "type": "command",
544
+ "command": "npx flo gate compact-guidance",
545
+ "timeout": 3000
546
+ }]
547
+ }
548
+ ],
549
+ "Notification": [
550
+ {
551
+ "hooks": [{
552
+ "type": "command",
553
+ "command": "npx flo hooks notification",
554
+ "timeout": 3000
555
+ }]
556
+ }
557
+ ]
558
+ };
559
+ // Merge: preserve existing non-MoFlo hooks, add MoFlo hooks
560
+ existing.hooks = hooks;
561
+ fs.writeFileSync(settingsPath, JSON.stringify(existing, null, 2), 'utf-8');
562
+ return { name: '.claude/settings.json', status: existing.hooks ? 'updated' : 'created', detail: '14 hooks configured (gates, lifecycle, routing, session)' };
563
+ }
564
+ // ============================================================================
565
+ // Step 3: .claude/skills/flo/ skill (with /fl alias)
566
+ // ============================================================================
567
+ function generateSkill(root, force) {
568
+ const skillDir = path.join(root, '.claude', 'skills', 'flo');
569
+ const skillFile = path.join(skillDir, 'SKILL.md');
570
+ const aliasDir = path.join(root, '.claude', 'skills', 'fl');
571
+ const aliasFile = path.join(aliasDir, 'SKILL.md');
572
+ if (fs.existsSync(skillFile) && !force) {
573
+ return { name: '.claude/skills/flo/', status: 'skipped', detail: 'Already exists' };
574
+ }
575
+ if (!fs.existsSync(skillDir)) {
576
+ fs.mkdirSync(skillDir, { recursive: true });
577
+ }
578
+ // Copy static SKILL.md from moflo package instead of generating it
579
+ let skillContent = '';
580
+ // Resolve this file's directory in ESM-safe way
581
+ let thisDir;
582
+ try {
583
+ thisDir = path.dirname(fileURLToPath(import.meta.url));
584
+ }
585
+ catch {
586
+ // Fallback for CJS or environments where import.meta.url is unavailable
587
+ thisDir = typeof __dirname !== 'undefined' ? __dirname : '';
588
+ }
589
+ const staticSkillCandidates = [
590
+ // Installed via npm (most common)
591
+ path.join(root, 'node_modules', 'moflo', '.claude', 'skills', 'flo', 'SKILL.md'),
592
+ // Running from moflo repo itself (dev)
593
+ ...(thisDir ? [path.join(thisDir, '..', '..', '..', '..', '.claude', 'skills', 'flo', 'SKILL.md')] : []),
594
+ ];
595
+ for (const candidate of staticSkillCandidates) {
596
+ try {
597
+ if (fs.existsSync(candidate)) {
598
+ skillContent = fs.readFileSync(candidate, 'utf-8');
599
+ break;
600
+ }
601
+ }
602
+ catch { /* skip inaccessible paths */ }
603
+ }
604
+ if (!skillContent) {
605
+ return { name: '.claude/skills/flo/', status: 'error', detail: 'Could not find SKILL.md in moflo package' };
606
+ }
607
+ fs.writeFileSync(skillFile, skillContent, 'utf-8');
608
+ // Create /fl alias (same content)
609
+ if (!fs.existsSync(aliasDir)) {
610
+ fs.mkdirSync(aliasDir, { recursive: true });
611
+ }
612
+ fs.writeFileSync(aliasFile, skillContent.replace('name: flo', 'name: fl'), 'utf-8');
613
+ // Clean up old /mf skill directory if it exists
614
+ const oldSkillDir = path.join(root, '.claude', 'skills', 'mf');
615
+ if (fs.existsSync(oldSkillDir)) {
616
+ fs.rmSync(oldSkillDir, { recursive: true });
617
+ }
618
+ return { name: '.claude/skills/flo/', status: 'created', detail: '/flo skill ready (alias: /fl)' };
619
+ }
620
+ // ============================================================================
621
+ // Step 4: CLAUDE.md MoFlo section
622
+ // ============================================================================
623
+ // Markers for idempotent CLAUDE.md injection — keep in sync with claudemd-generator.ts
624
+ const MOFLO_MARKER = '<!-- MOFLO:INJECTED:START -->';
625
+ const MOFLO_MARKER_END = '<!-- MOFLO:INJECTED:END -->';
626
+ // Also detect legacy markers so we can replace them
627
+ const LEGACY_MARKERS = ['<!-- MOFLO:START -->', '<!-- MOFLO:SUBAGENT-PROTOCOL:START -->'];
628
+ const LEGACY_MARKERS_END = ['<!-- MOFLO:END -->', '<!-- MOFLO:SUBAGENT-PROTOCOL:END -->'];
629
+ function generateClaudeMd(root, force) {
630
+ const claudeMdPath = path.join(root, 'CLAUDE.md');
631
+ let existing = '';
632
+ if (fs.existsSync(claudeMdPath)) {
633
+ existing = fs.readFileSync(claudeMdPath, 'utf-8');
634
+ // Check for current or legacy markers
635
+ const allStartMarkers = [MOFLO_MARKER, ...LEGACY_MARKERS];
636
+ const allEndMarkers = [MOFLO_MARKER_END, ...LEGACY_MARKERS_END];
637
+ for (let i = 0; i < allStartMarkers.length; i++) {
638
+ if (existing.includes(allStartMarkers[i])) {
639
+ if (!force && allStartMarkers[i] === MOFLO_MARKER) {
640
+ return { name: 'CLAUDE.md', status: 'skipped', detail: 'MoFlo section already present' };
641
+ }
642
+ // Remove existing section for replacement
643
+ const startIdx = existing.indexOf(allStartMarkers[i]);
644
+ const endIdx = existing.indexOf(allEndMarkers[i]);
645
+ if (endIdx > startIdx) {
646
+ existing = existing.substring(0, startIdx) + existing.substring(endIdx + allEndMarkers[i].length);
647
+ }
648
+ }
649
+ }
650
+ }
651
+ // Minimal injection — just enough for Claude to work with moflo.
652
+ // All detailed docs live in .claude/guidance/shipped/moflo.md.
582
653
  const mofloSection = `
583
654
  ${MOFLO_MARKER}
584
655
  ## MoFlo — AI Agent Orchestration
@@ -590,194 +661,180 @@ This project uses [MoFlo](https://github.com/eric-cielo/moflo) for AI-assisted d
590
661
  Your first tool call for every new user prompt MUST be a memory search. Do this BEFORE Glob, Grep, Read, or any file exploration.
591
662
 
592
663
  \`\`\`
593
- mcp__claude-flow__memory_search — query: "<task description>", namespace: "guidance" or "patterns" or "knowledge" or "code-map"
664
+ mcp__moflo__memory_search — query: "<task description>", namespace: "guidance" or "patterns" or "code-map"
594
665
  \`\`\`
595
666
 
596
- For codebase navigation, search the \`code-map\` namespace first. For patterns and domain knowledge, search \`patterns\`, \`knowledge\`, and \`guidance\`.
597
- When the user asks you to remember something, store it: \`memory store --namespace knowledge --key "[topic]" --value "[what to remember]"\`
667
+ Search \`guidance\` and \`patterns\` namespaces on every prompt. Search \`code-map\` when navigating the codebase.
668
+ When the user asks you to remember something: \`mcp__moflo__memory_store\` with namespace \`knowledge\`.
598
669
 
599
670
  ### Workflow Gates (enforced automatically)
600
671
 
601
- These are enforced by hooks you cannot bypass them:
602
- - **Memory-first**: Must search memory before Glob/Grep/Read on guidance files
672
+ - **Memory-first**: Must search memory before Glob/Grep/Read
603
673
  - **TaskCreate-first**: Must call TaskCreate before spawning Agent tool
604
- - **Context tracking**: Session tracked as FRESH → MODERATE → DEPLETED → CRITICAL
605
-
606
- ### /flo Skill — Issue Execution
607
674
 
608
- Use \`/flo <issue-number>\` (or \`/fl\`) to execute GitHub issues through the full workflow:
609
- Research → Enhance → Implement → Test → Simplify → PR
610
-
611
- ### MCP Tools Reference
675
+ ### MCP Tools (preferred over CLI)
612
676
 
613
677
  | Tool | Purpose |
614
678
  |------|---------|
615
- | \`mcp__claude-flow__memory_search\` | Semantic search across indexed knowledge |
616
- | \`mcp__claude-flow__memory_store\` | Store patterns and decisions |
617
- | \`mcp__claude-flow__hooks_route\` | Route task to optimal agent type |
618
- | \`mcp__claude-flow__hooks_pre-task\` | Record task start |
619
- | \`mcp__claude-flow__hooks_post-task\` | Record task completion for learning |
679
+ | \`mcp__moflo__memory_search\` | Semantic search across indexed knowledge |
680
+ | \`mcp__moflo__memory_store\` | Store patterns and decisions |
681
+ | \`mcp__moflo__hooks_route\` | Route task to optimal agent type |
682
+ | \`mcp__moflo__hooks_pre-task\` | Record task start |
683
+ | \`mcp__moflo__hooks_post-task\` | Record task completion for learning |
620
684
 
621
- ### Agent Icon Mapping
685
+ ### CLI Fallback
622
686
 
623
- | Icon | Agent Type | Use For |
624
- |------|------------|---------|
625
- | 🔍 | Explore | Research, codebase exploration |
626
- | 📐 | Plan | Architecture, design |
627
- | ⚙️ | General | General coding tasks |
628
- | 🧪 | Test | Writing tests |
629
- | 🔬 | Analyzer | Code review, analysis |
630
- | 🔧 | Backend | API implementation |
687
+ \`\`\`bash
688
+ npx flo-search "[query]" --namespace guidance # Semantic search
689
+ npx flo doctor --fix # Health check
690
+ \`\`\`
631
691
 
632
- ### Non-Trivial Task Workflow
692
+ ### Full Reference
633
693
 
634
- For any task beyond a single-line fix:
635
- 1. Search memory first (mandatory gate)
636
- 2. Create tasks with TaskCreate (mandatory gate)
637
- 3. Spawn agents in waves (Explore first, then Implement + Test)
638
- 4. Update task status as you go
639
- 5. Store learnings after completion
694
+ For CLI commands, hooks, agents, swarm config, memory commands, and moflo.yaml options, see:
695
+ \`.claude/guidance/shipped/moflo.md\`
640
696
  ${MOFLO_MARKER_END}
641
- `;
642
- const finalContent = existing.trimEnd() + '\n' + mofloSection;
643
- fs.writeFileSync(claudeMdPath, finalContent, 'utf-8');
644
- return {
645
- name: 'CLAUDE.md',
646
- status: existing ? 'updated' : 'created',
647
- detail: 'MoFlo workflow section appended',
648
- };
649
- }
650
- // ============================================================================
651
- // Step 5: .claude/scripts/ — sync from moflo bin/
652
- // These scripts are used by session-start hooks for indexing, code map, etc.
653
- // Always overwrite to keep them in sync with the installed moflo version.
654
- // ============================================================================
655
- const SCRIPT_MAP = {
656
- 'hooks.mjs': 'hooks.mjs',
657
- 'session-start-launcher.mjs': 'session-start-launcher.mjs',
658
- 'index-guidance.mjs': 'index-guidance.mjs',
659
- 'build-embeddings.mjs': 'build-embeddings.mjs',
660
- 'generate-code-map.mjs': 'generate-code-map.mjs',
661
- 'semantic-search.mjs': 'semantic-search.mjs',
662
- };
663
- function syncScripts(root, force) {
664
- const scriptsDir = path.join(root, '.claude', 'scripts');
665
- if (!fs.existsSync(scriptsDir)) {
666
- fs.mkdirSync(scriptsDir, { recursive: true });
667
- }
668
- // Find moflo bin/ directory
669
- let syncThisDir;
670
- try {
671
- syncThisDir = path.dirname(fileURLToPath(import.meta.url));
672
- }
673
- catch {
674
- syncThisDir = typeof __dirname !== 'undefined' ? __dirname : '';
675
- }
676
- const candidates = [
677
- path.join(root, 'node_modules', 'moflo', 'bin'),
678
- // When running from moflo repo itself
679
- ...(syncThisDir ? [path.join(syncThisDir, '..', '..', '..', '..', 'bin')] : []),
680
- ];
681
- const binDir = candidates.find(d => { try {
682
- return fs.existsSync(d);
683
- }
684
- catch {
685
- return false;
686
- } });
687
- if (!binDir) {
688
- return { name: '.claude/scripts/', status: 'skipped', detail: 'moflo bin/ not found' };
689
- }
690
- let copied = 0;
691
- for (const [dest, src] of Object.entries(SCRIPT_MAP)) {
692
- const srcPath = path.join(binDir, src);
693
- const destPath = path.join(scriptsDir, dest);
694
- if (!fs.existsSync(srcPath))
695
- continue;
696
- // Always overwrite scripts to keep in sync (they're derived, not user-edited)
697
- if (!fs.existsSync(destPath) || force || isStale(srcPath, destPath)) {
698
- fs.copyFileSync(srcPath, destPath);
699
- copied++;
700
- }
701
- }
702
- if (copied === 0) {
703
- return { name: '.claude/scripts/', status: 'skipped', detail: 'Scripts already up to date' };
704
- }
705
- return { name: '.claude/scripts/', status: 'updated', detail: `${copied} scripts synced from moflo` };
706
- }
707
- function isStale(srcPath, destPath) {
708
- try {
709
- return fs.statSync(srcPath).mtimeMs > fs.statSync(destPath).mtimeMs;
710
- }
711
- catch {
712
- return true;
713
- }
714
- }
715
- // ============================================================================
716
- // Step 6: .gitignore
717
- // ============================================================================
718
- function updateGitignore(root) {
719
- const gitignorePath = path.join(root, '.gitignore');
720
- const entries = ['.claude-orc/', '.swarm/', '.moflo/'];
721
- if (!fs.existsSync(gitignorePath)) {
722
- // Create .gitignore with common defaults + MoFlo entries
723
- const defaultEntries = ['node_modules/', 'dist/', '.env', '.env.*', ''];
724
- const content = '# Dependencies\n' + defaultEntries.join('\n') + '\n# MoFlo state\n' + entries.join('\n') + '\n';
725
- fs.writeFileSync(gitignorePath, content, 'utf-8');
726
- return { name: '.gitignore', status: 'created', detail: 'Created with node_modules, .env, and MoFlo entries' };
727
- }
728
- const existing = fs.readFileSync(gitignorePath, 'utf-8');
729
- const toAdd = entries.filter(e => !existing.includes(e));
730
- if (toAdd.length === 0) {
731
- return { name: '.gitignore', status: 'skipped', detail: 'Entries already present' };
732
- }
733
- fs.appendFileSync(gitignorePath, '\n# MoFlo state (gitignored)\n' + toAdd.join('\n') + '\n');
734
- return { name: '.gitignore', status: 'updated', detail: `Added: ${toAdd.join(', ')}` };
735
- }
736
- // ============================================================================
737
- // Step 7: .claude/guidance/moflo-bootstrap.md
738
- // Copies the agent bootstrap guidance to the project so subagents can read it
739
- // from disk without requiring memory search.
740
- // ============================================================================
741
- function syncBootstrapGuidance(root, force) {
742
- const guidanceDir = path.join(root, '.claude', 'guidance');
743
- const targetFile = path.join(guidanceDir, 'moflo-bootstrap.md');
744
- // Find the source bootstrap file from the moflo package
745
- let sourceDir;
746
- try {
747
- sourceDir = path.dirname(fileURLToPath(import.meta.url));
748
- }
749
- catch {
750
- sourceDir = typeof __dirname !== 'undefined' ? __dirname : '';
751
- }
752
- const candidates = [
753
- path.join(root, 'node_modules', 'moflo', '.claude', 'guidance', 'agent-bootstrap.md'),
754
- // When running from moflo repo itself
755
- ...(sourceDir ? [path.join(sourceDir, '..', '..', '..', '..', '.claude', 'guidance', 'agent-bootstrap.md')] : []),
756
- ];
757
- const sourceFile = candidates.find(f => { try {
758
- return fs.existsSync(f);
759
- }
760
- catch {
761
- return false;
762
- } });
763
- if (!sourceFile) {
764
- return { name: 'guidance/moflo-bootstrap.md', status: 'skipped', detail: 'Source bootstrap not found' };
765
- }
766
- // Check if target exists and is up to date
767
- if (fs.existsSync(targetFile) && !force) {
768
- if (!isStale(sourceFile, targetFile)) {
769
- return { name: 'guidance/moflo-bootstrap.md', status: 'skipped', detail: 'Already up to date' };
770
- }
771
- }
772
- // Read source and prepend header
773
- const content = fs.readFileSync(sourceFile, 'utf-8');
774
- const header = `<!-- AUTO-GENERATED by moflo init. Do not edit — changes will be overwritten on next init. -->\n<!-- Source: moflo/.claude/guidance/agent-bootstrap.md -->\n<!-- To customize, create .claude/guidance/agent-bootstrap.md for project-specific rules. -->\n\n`;
775
- fs.mkdirSync(guidanceDir, { recursive: true });
776
- fs.writeFileSync(targetFile, header + content, 'utf-8');
777
- return {
778
- name: 'guidance/moflo-bootstrap.md',
779
- status: fs.existsSync(targetFile) ? 'updated' : 'created',
780
- detail: 'Subagent bootstrap protocol'
781
- };
782
- }
697
+ `;
698
+ const finalContent = existing.trimEnd() + '\n' + mofloSection;
699
+ fs.writeFileSync(claudeMdPath, finalContent, 'utf-8');
700
+ return {
701
+ name: 'CLAUDE.md',
702
+ status: existing ? 'updated' : 'created',
703
+ detail: 'MoFlo section injected (~35 lines)',
704
+ };
705
+ }
706
+ // ============================================================================
707
+ // Step 5: .claude/scripts/ — sync from moflo bin/
708
+ // These scripts are used by session-start hooks for indexing, code map, etc.
709
+ // Always overwrite to keep them in sync with the installed moflo version.
710
+ // ============================================================================
711
+ const SCRIPT_MAP = {
712
+ 'hooks.mjs': 'hooks.mjs',
713
+ 'session-start-launcher.mjs': 'session-start-launcher.mjs',
714
+ 'index-guidance.mjs': 'index-guidance.mjs',
715
+ 'build-embeddings.mjs': 'build-embeddings.mjs',
716
+ 'generate-code-map.mjs': 'generate-code-map.mjs',
717
+ 'semantic-search.mjs': 'semantic-search.mjs',
718
+ 'index-tests.mjs': 'index-tests.mjs',
719
+ };
720
+ function syncScripts(root, force) {
721
+ const scriptsDir = path.join(root, '.claude', 'scripts');
722
+ if (!fs.existsSync(scriptsDir)) {
723
+ fs.mkdirSync(scriptsDir, { recursive: true });
724
+ }
725
+ // Find moflo bin/ directory
726
+ let syncThisDir;
727
+ try {
728
+ syncThisDir = path.dirname(fileURLToPath(import.meta.url));
729
+ }
730
+ catch {
731
+ syncThisDir = typeof __dirname !== 'undefined' ? __dirname : '';
732
+ }
733
+ const candidates = [
734
+ path.join(root, 'node_modules', 'moflo', 'bin'),
735
+ // When running from moflo repo itself
736
+ ...(syncThisDir ? [path.join(syncThisDir, '..', '..', '..', '..', 'bin')] : []),
737
+ ];
738
+ const binDir = candidates.find(d => { try {
739
+ return fs.existsSync(d);
740
+ }
741
+ catch {
742
+ return false;
743
+ } });
744
+ if (!binDir) {
745
+ return { name: '.claude/scripts/', status: 'skipped', detail: 'moflo bin/ not found' };
746
+ }
747
+ let copied = 0;
748
+ for (const [dest, src] of Object.entries(SCRIPT_MAP)) {
749
+ const srcPath = path.join(binDir, src);
750
+ const destPath = path.join(scriptsDir, dest);
751
+ if (!fs.existsSync(srcPath))
752
+ continue;
753
+ // Always overwrite scripts to keep in sync (they're derived, not user-edited)
754
+ if (!fs.existsSync(destPath) || force || isStale(srcPath, destPath)) {
755
+ fs.copyFileSync(srcPath, destPath);
756
+ copied++;
757
+ }
758
+ }
759
+ if (copied === 0) {
760
+ return { name: '.claude/scripts/', status: 'skipped', detail: 'Scripts already up to date' };
761
+ }
762
+ return { name: '.claude/scripts/', status: 'updated', detail: `${copied} scripts synced from moflo` };
763
+ }
764
+ function isStale(srcPath, destPath) {
765
+ try {
766
+ return fs.statSync(srcPath).mtimeMs > fs.statSync(destPath).mtimeMs;
767
+ }
768
+ catch {
769
+ return true;
770
+ }
771
+ }
772
+ // ============================================================================
773
+ // Step 6: .gitignore
774
+ // ============================================================================
775
+ function updateGitignore(root) {
776
+ const gitignorePath = path.join(root, '.gitignore');
777
+ const entries = ['.claude-orc/', '.swarm/', '.moflo/'];
778
+ if (!fs.existsSync(gitignorePath)) {
779
+ // Create .gitignore with common defaults + MoFlo entries
780
+ const defaultEntries = ['node_modules/', 'dist/', '.env', '.env.*', ''];
781
+ const content = '# Dependencies\n' + defaultEntries.join('\n') + '\n# MoFlo state\n' + entries.join('\n') + '\n';
782
+ fs.writeFileSync(gitignorePath, content, 'utf-8');
783
+ return { name: '.gitignore', status: 'created', detail: 'Created with node_modules, .env, and MoFlo entries' };
784
+ }
785
+ const existing = fs.readFileSync(gitignorePath, 'utf-8');
786
+ const toAdd = entries.filter(e => !existing.includes(e));
787
+ if (toAdd.length === 0) {
788
+ return { name: '.gitignore', status: 'skipped', detail: 'Entries already present' };
789
+ }
790
+ fs.appendFileSync(gitignorePath, '\n# MoFlo state (gitignored)\n' + toAdd.join('\n') + '\n');
791
+ return { name: '.gitignore', status: 'updated', detail: `Added: ${toAdd.join(', ')}` };
792
+ }
793
+ // ============================================================================
794
+ // Step 7: .claude/guidance/moflo-bootstrap.md
795
+ // Copies the agent bootstrap guidance to the project so subagents can read it
796
+ // from disk without requiring memory search.
797
+ // ============================================================================
798
+ function syncBootstrapGuidance(root, force) {
799
+ const guidanceDir = path.join(root, '.claude', 'guidance');
800
+ const targetFile = path.join(guidanceDir, 'moflo-bootstrap.md');
801
+ // Find the source bootstrap file from the moflo package
802
+ let sourceDir;
803
+ try {
804
+ sourceDir = path.dirname(fileURLToPath(import.meta.url));
805
+ }
806
+ catch {
807
+ sourceDir = typeof __dirname !== 'undefined' ? __dirname : '';
808
+ }
809
+ const candidates = [
810
+ path.join(root, 'node_modules', 'moflo', '.claude', 'guidance', 'agent-bootstrap.md'),
811
+ // When running from moflo repo itself
812
+ ...(sourceDir ? [path.join(sourceDir, '..', '..', '..', '..', '.claude', 'guidance', 'agent-bootstrap.md')] : []),
813
+ ];
814
+ const sourceFile = candidates.find(f => { try {
815
+ return fs.existsSync(f);
816
+ }
817
+ catch {
818
+ return false;
819
+ } });
820
+ if (!sourceFile) {
821
+ return { name: 'guidance/moflo-bootstrap.md', status: 'skipped', detail: 'Source bootstrap not found' };
822
+ }
823
+ // Check if target exists and is up to date
824
+ if (fs.existsSync(targetFile) && !force) {
825
+ if (!isStale(sourceFile, targetFile)) {
826
+ return { name: 'guidance/moflo-bootstrap.md', status: 'skipped', detail: 'Already up to date' };
827
+ }
828
+ }
829
+ // Read source and prepend header
830
+ const content = fs.readFileSync(sourceFile, 'utf-8');
831
+ const header = `<!-- AUTO-GENERATED by moflo init. Do not edit — changes will be overwritten on next init. -->\n<!-- Source: moflo/.claude/guidance/agent-bootstrap.md -->\n<!-- To customize, create .claude/guidance/agent-bootstrap.md for project-specific rules. -->\n\n`;
832
+ fs.mkdirSync(guidanceDir, { recursive: true });
833
+ fs.writeFileSync(targetFile, header + content, 'utf-8');
834
+ return {
835
+ name: 'guidance/moflo-bootstrap.md',
836
+ status: fs.existsSync(targetFile) ? 'updated' : 'created',
837
+ detail: 'Subagent bootstrap protocol'
838
+ };
839
+ }
783
840
  //# sourceMappingURL=moflo-init.js.map