moflo 4.8.11 → 4.8.13

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 (142) hide show
  1. package/.claude/agents/browser/browser-agent.yaml +182 -0
  2. package/.claude/guidance/moflo-bootstrap.md +129 -0
  3. package/.claude/helpers/post-commit +16 -0
  4. package/.claude/helpers/pre-commit +26 -0
  5. package/.claude/settings.json +351 -290
  6. package/.claude/settings.local.json +2 -1
  7. package/.claude/skills/browser/SKILL.md +204 -0
  8. package/.claude/workflow-state.json +9 -0
  9. package/bin/index-guidance.mjs +1 -1
  10. package/bin/index-tests.mjs +1 -1
  11. package/bin/semantic-search.mjs +1 -1
  12. package/bin/setup-project.mjs +61 -64
  13. package/package.json +2 -4
  14. package/src/@claude-flow/cli/dist/src/init/claudemd-generator.d.ts +29 -24
  15. package/src/@claude-flow/cli/dist/src/init/claudemd-generator.js +52 -472
  16. package/src/@claude-flow/cli/dist/src/init/moflo-init.d.ts +30 -30
  17. package/src/@claude-flow/cli/dist/src/init/moflo-init.js +712 -717
  18. package/src/@claude-flow/cli/package.json +1 -1
  19. package/.claude/agents/MIGRATION_SUMMARY.md +0 -222
  20. package/.claude/agents/analysis/code-review/analyze-code-quality.md +0 -179
  21. package/.claude/agents/development/backend/dev-backend-api.md +0 -142
  22. package/.claude/agents/flow-nexus/app-store.md +0 -88
  23. package/.claude/agents/flow-nexus/authentication.md +0 -69
  24. package/.claude/agents/flow-nexus/challenges.md +0 -81
  25. package/.claude/agents/flow-nexus/neural-network.md +0 -88
  26. package/.claude/agents/flow-nexus/payments.md +0 -83
  27. package/.claude/agents/flow-nexus/sandbox.md +0 -76
  28. package/.claude/agents/flow-nexus/swarm.md +0 -76
  29. package/.claude/agents/flow-nexus/user-tools.md +0 -96
  30. package/.claude/agents/flow-nexus/workflow.md +0 -84
  31. package/.claude/agents/payments/agentic-payments.md +0 -126
  32. package/.claude/agents/sona/sona-learning-optimizer.md +0 -74
  33. package/.claude/agents/sublinear/consensus-coordinator.md +0 -338
  34. package/.claude/agents/sublinear/matrix-optimizer.md +0 -185
  35. package/.claude/agents/sublinear/pagerank-analyzer.md +0 -299
  36. package/.claude/agents/sublinear/performance-optimizer.md +0 -368
  37. package/.claude/agents/sublinear/trading-predictor.md +0 -246
  38. package/.claude/agents/testing/unit/tdd-london-swarm.md +0 -244
  39. package/.claude/agents/testing/validation/production-validator.md +0 -395
  40. package/.claude/agents/v3/database-specialist.yaml +0 -21
  41. package/.claude/agents/v3/index.yaml +0 -17
  42. package/.claude/agents/v3/project-coordinator.yaml +0 -15
  43. package/.claude/agents/v3/python-specialist.yaml +0 -21
  44. package/.claude/agents/v3/test-architect.yaml +0 -20
  45. package/.claude/agents/v3/typescript-specialist.yaml +0 -21
  46. package/.claude/agents/v3/v3-integration-architect.md +0 -346
  47. package/.claude/agents/v3/v3-memory-specialist.md +0 -318
  48. package/.claude/agents/v3/v3-performance-engineer.md +0 -397
  49. package/.claude/agents/v3/v3-queen-coordinator.md +0 -98
  50. package/.claude/agents/v3/v3-security-architect.md +0 -174
  51. package/.claude/commands/analysis/COMMAND_COMPLIANCE_REPORT.md +0 -54
  52. package/.claude/commands/analysis/README.md +0 -9
  53. package/.claude/commands/analysis/bottleneck-detect.md +0 -162
  54. package/.claude/commands/analysis/performance-bottlenecks.md +0 -59
  55. package/.claude/commands/analysis/performance-report.md +0 -25
  56. package/.claude/commands/analysis/token-efficiency.md +0 -45
  57. package/.claude/commands/analysis/token-usage.md +0 -25
  58. package/.claude/commands/automation/README.md +0 -9
  59. package/.claude/commands/automation/auto-agent.md +0 -122
  60. package/.claude/commands/automation/self-healing.md +0 -106
  61. package/.claude/commands/automation/session-memory.md +0 -90
  62. package/.claude/commands/automation/smart-agents.md +0 -73
  63. package/.claude/commands/automation/smart-spawn.md +0 -25
  64. package/.claude/commands/automation/workflow-select.md +0 -25
  65. package/.claude/commands/coordination/README.md +0 -9
  66. package/.claude/commands/coordination/agent-spawn.md +0 -25
  67. package/.claude/commands/coordination/init.md +0 -44
  68. package/.claude/commands/coordination/orchestrate.md +0 -43
  69. package/.claude/commands/coordination/spawn.md +0 -45
  70. package/.claude/commands/coordination/swarm-init.md +0 -85
  71. package/.claude/commands/coordination/task-orchestrate.md +0 -25
  72. package/.claude/commands/flow-nexus/app-store.md +0 -124
  73. package/.claude/commands/flow-nexus/challenges.md +0 -120
  74. package/.claude/commands/flow-nexus/login-registration.md +0 -65
  75. package/.claude/commands/flow-nexus/neural-network.md +0 -134
  76. package/.claude/commands/flow-nexus/payments.md +0 -116
  77. package/.claude/commands/flow-nexus/sandbox.md +0 -83
  78. package/.claude/commands/flow-nexus/swarm.md +0 -87
  79. package/.claude/commands/flow-nexus/user-tools.md +0 -152
  80. package/.claude/commands/flow-nexus/workflow.md +0 -115
  81. package/.claude/commands/monitoring/README.md +0 -9
  82. package/.claude/commands/monitoring/agent-metrics.md +0 -25
  83. package/.claude/commands/monitoring/agents.md +0 -44
  84. package/.claude/commands/monitoring/real-time-view.md +0 -25
  85. package/.claude/commands/monitoring/status.md +0 -46
  86. package/.claude/commands/monitoring/swarm-monitor.md +0 -25
  87. package/.claude/commands/optimization/README.md +0 -9
  88. package/.claude/commands/optimization/auto-topology.md +0 -62
  89. package/.claude/commands/optimization/cache-manage.md +0 -25
  90. package/.claude/commands/optimization/parallel-execute.md +0 -25
  91. package/.claude/commands/optimization/parallel-execution.md +0 -50
  92. package/.claude/commands/optimization/topology-optimize.md +0 -25
  93. package/.claude/commands/pair/README.md +0 -261
  94. package/.claude/commands/pair/commands.md +0 -546
  95. package/.claude/commands/pair/config.md +0 -510
  96. package/.claude/commands/pair/examples.md +0 -512
  97. package/.claude/commands/pair/modes.md +0 -348
  98. package/.claude/commands/pair/session.md +0 -407
  99. package/.claude/commands/pair/start.md +0 -209
  100. package/.claude/commands/stream-chain/pipeline.md +0 -121
  101. package/.claude/commands/stream-chain/run.md +0 -70
  102. package/.claude/commands/training/README.md +0 -9
  103. package/.claude/commands/training/model-update.md +0 -25
  104. package/.claude/commands/training/neural-patterns.md +0 -74
  105. package/.claude/commands/training/neural-train.md +0 -25
  106. package/.claude/commands/training/pattern-learn.md +0 -25
  107. package/.claude/commands/training/specialization.md +0 -63
  108. package/.claude/commands/truth/start.md +0 -143
  109. package/.claude/commands/verify/check.md +0 -50
  110. package/.claude/commands/verify/start.md +0 -128
  111. package/.claude/helpers/gate.cjs +0 -236
  112. package/.claude/helpers/hook-handler.cjs +0 -341
  113. package/.claude/skills/agentic-jujutsu/SKILL.md +0 -645
  114. package/.claude/skills/dual-mode/README.md +0 -71
  115. package/.claude/skills/dual-mode/dual-collect.md +0 -103
  116. package/.claude/skills/dual-mode/dual-coordinate.md +0 -85
  117. package/.claude/skills/dual-mode/dual-spawn.md +0 -81
  118. package/.claude/skills/flow-nexus-neural/SKILL.md +0 -738
  119. package/.claude/skills/flow-nexus-platform/SKILL.md +0 -1157
  120. package/.claude/skills/flow-nexus-swarm/SKILL.md +0 -610
  121. package/.claude/skills/pair-programming/SKILL.md +0 -1202
  122. package/.claude/skills/stream-chain/SKILL.md +0 -563
  123. package/.claude/skills/v3-cli-modernization/SKILL.md +0 -872
  124. package/.claude/skills/v3-core-implementation/SKILL.md +0 -797
  125. package/.claude/skills/v3-ddd-architecture/SKILL.md +0 -442
  126. package/.claude/skills/v3-integration-deep/SKILL.md +0 -241
  127. package/.claude/skills/v3-mcp-optimization/SKILL.md +0 -777
  128. package/.claude/skills/v3-memory-unification/SKILL.md +0 -174
  129. package/.claude/skills/v3-performance-optimization/SKILL.md +0 -390
  130. package/.claude/skills/v3-security-overhaul/SKILL.md +0 -82
  131. package/.claude/skills/v3-swarm-coordination/SKILL.md +0 -340
  132. package/.claude-plugin/README.md +0 -720
  133. package/.claude-plugin/docs/INSTALLATION.md +0 -261
  134. package/.claude-plugin/docs/PLUGIN_SUMMARY.md +0 -361
  135. package/.claude-plugin/docs/QUICKSTART.md +0 -361
  136. package/.claude-plugin/docs/STRUCTURE.md +0 -128
  137. package/.claude-plugin/hooks/hooks.json +0 -74
  138. package/.claude-plugin/marketplace.json +0 -96
  139. package/.claude-plugin/plugin.json +0 -71
  140. package/.claude-plugin/scripts/install.sh +0 -234
  141. package/.claude-plugin/scripts/uninstall.sh +0 -36
  142. package/.claude-plugin/scripts/verify.sh +0 -108
@@ -1,275 +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 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'];
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'];
273
273
  const yaml = `# MoFlo — Project Configuration
274
274
  # Generated by: moflo init
275
275
  # Docs: https://github.com/eric-cielo/moflo
@@ -366,280 +366,290 @@ model_routing:
366
366
  # agent_overrides:
367
367
  # security-architect: opus # Always use opus for security
368
368
  # researcher: sonnet # Pin research to sonnet
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
- const MOFLO_MARKER = '<!-- MOFLO:START -->';
624
- const MOFLO_MARKER_END = '<!-- MOFLO:END -->';
625
- function generateClaudeMd(root, force) {
626
- const claudeMdPath = path.join(root, 'CLAUDE.md');
627
- let existing = '';
628
- if (fs.existsSync(claudeMdPath)) {
629
- existing = fs.readFileSync(claudeMdPath, 'utf-8');
630
- // Check if MoFlo section already exists
631
- if (existing.includes(MOFLO_MARKER)) {
632
- if (!force) {
633
- return { name: 'CLAUDE.md', status: 'skipped', detail: 'MoFlo section already present' };
634
- }
635
- // Remove existing MoFlo section for replacement
636
- const startIdx = existing.indexOf(MOFLO_MARKER);
637
- const endIdx = existing.indexOf(MOFLO_MARKER_END);
638
- if (endIdx > startIdx) {
639
- existing = existing.substring(0, startIdx) + existing.substring(endIdx + MOFLO_MARKER_END.length);
640
- }
641
- }
642
- }
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.
643
653
  const mofloSection = `
644
654
  ${MOFLO_MARKER}
645
655
  ## MoFlo — AI Agent Orchestration
@@ -651,25 +661,18 @@ This project uses [MoFlo](https://github.com/eric-cielo/moflo) for AI-assisted d
651
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.
652
662
 
653
663
  \`\`\`
654
- mcp__moflo__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"
655
665
  \`\`\`
656
666
 
657
- For codebase navigation, search the \`code-map\` namespace first. For patterns and domain knowledge, search \`patterns\`, \`knowledge\`, and \`guidance\`.
658
- 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\`.
659
669
 
660
670
  ### Workflow Gates (enforced automatically)
661
671
 
662
- These are enforced by hooks you cannot bypass them:
663
- - **Memory-first**: Must search memory before Glob/Grep/Read on guidance files
672
+ - **Memory-first**: Must search memory before Glob/Grep/Read
664
673
  - **TaskCreate-first**: Must call TaskCreate before spawning Agent tool
665
- - **Context tracking**: Session tracked as FRESH → MODERATE → DEPLETED → CRITICAL
666
674
 
667
- ### /flo Skill Issue Execution
668
-
669
- Use \`/flo <issue-number>\` (or \`/fl\`) to execute GitHub issues through the full workflow:
670
- Research → Enhance → Implement → Test → Simplify → PR
671
-
672
- ### MCP Tools Reference
675
+ ### MCP Tools (preferred over CLI)
673
676
 
674
677
  | Tool | Purpose |
675
678
  |------|---------|
@@ -679,167 +682,159 @@ Research → Enhance → Implement → Test → Simplify → PR
679
682
  | \`mcp__moflo__hooks_pre-task\` | Record task start |
680
683
  | \`mcp__moflo__hooks_post-task\` | Record task completion for learning |
681
684
 
682
- ### Agent Icon Mapping
685
+ ### CLI Fallback
683
686
 
684
- | Icon | Agent Type | Use For |
685
- |------|------------|---------|
686
- | 🔍 | Explore | Research, codebase exploration |
687
- | 📐 | Plan | Architecture, design |
688
- | ⚙️ | General | General coding tasks |
689
- | 🧪 | Test | Writing tests |
690
- | 🔬 | Analyzer | Code review, analysis |
691
- | 🔧 | Backend | API implementation |
687
+ \`\`\`bash
688
+ npx flo-search "[query]" --namespace guidance # Semantic search
689
+ npx flo doctor --fix # Health check
690
+ \`\`\`
692
691
 
693
- ### Non-Trivial Task Workflow
692
+ ### Full Reference
694
693
 
695
- For any task beyond a single-line fix:
696
- 1. Search memory first (mandatory gate)
697
- 2. Create tasks with TaskCreate (mandatory gate)
698
- 3. Spawn agents in waves (Explore first, then Implement + Test)
699
- 4. Update task status as you go
700
- 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\`
701
696
  ${MOFLO_MARKER_END}
702
- `;
703
- const finalContent = existing.trimEnd() + '\n' + mofloSection;
704
- fs.writeFileSync(claudeMdPath, finalContent, 'utf-8');
705
- return {
706
- name: 'CLAUDE.md',
707
- status: existing ? 'updated' : 'created',
708
- detail: 'MoFlo workflow section appended',
709
- };
710
- }
711
- // ============================================================================
712
- // Step 5: .claude/scripts/ — sync from moflo bin/
713
- // These scripts are used by session-start hooks for indexing, code map, etc.
714
- // Always overwrite to keep them in sync with the installed moflo version.
715
- // ============================================================================
716
- const SCRIPT_MAP = {
717
- 'hooks.mjs': 'hooks.mjs',
718
- 'session-start-launcher.mjs': 'session-start-launcher.mjs',
719
- 'index-guidance.mjs': 'index-guidance.mjs',
720
- 'build-embeddings.mjs': 'build-embeddings.mjs',
721
- 'generate-code-map.mjs': 'generate-code-map.mjs',
722
- 'semantic-search.mjs': 'semantic-search.mjs',
723
- 'index-tests.mjs': 'index-tests.mjs',
724
- };
725
- function syncScripts(root, force) {
726
- const scriptsDir = path.join(root, '.claude', 'scripts');
727
- if (!fs.existsSync(scriptsDir)) {
728
- fs.mkdirSync(scriptsDir, { recursive: true });
729
- }
730
- // Find moflo bin/ directory
731
- let syncThisDir;
732
- try {
733
- syncThisDir = path.dirname(fileURLToPath(import.meta.url));
734
- }
735
- catch {
736
- syncThisDir = typeof __dirname !== 'undefined' ? __dirname : '';
737
- }
738
- const candidates = [
739
- path.join(root, 'node_modules', 'moflo', 'bin'),
740
- // When running from moflo repo itself
741
- ...(syncThisDir ? [path.join(syncThisDir, '..', '..', '..', '..', 'bin')] : []),
742
- ];
743
- const binDir = candidates.find(d => { try {
744
- return fs.existsSync(d);
745
- }
746
- catch {
747
- return false;
748
- } });
749
- if (!binDir) {
750
- return { name: '.claude/scripts/', status: 'skipped', detail: 'moflo bin/ not found' };
751
- }
752
- let copied = 0;
753
- for (const [dest, src] of Object.entries(SCRIPT_MAP)) {
754
- const srcPath = path.join(binDir, src);
755
- const destPath = path.join(scriptsDir, dest);
756
- if (!fs.existsSync(srcPath))
757
- continue;
758
- // Always overwrite scripts to keep in sync (they're derived, not user-edited)
759
- if (!fs.existsSync(destPath) || force || isStale(srcPath, destPath)) {
760
- fs.copyFileSync(srcPath, destPath);
761
- copied++;
762
- }
763
- }
764
- if (copied === 0) {
765
- return { name: '.claude/scripts/', status: 'skipped', detail: 'Scripts already up to date' };
766
- }
767
- return { name: '.claude/scripts/', status: 'updated', detail: `${copied} scripts synced from moflo` };
768
- }
769
- function isStale(srcPath, destPath) {
770
- try {
771
- return fs.statSync(srcPath).mtimeMs > fs.statSync(destPath).mtimeMs;
772
- }
773
- catch {
774
- return true;
775
- }
776
- }
777
- // ============================================================================
778
- // Step 6: .gitignore
779
- // ============================================================================
780
- function updateGitignore(root) {
781
- const gitignorePath = path.join(root, '.gitignore');
782
- const entries = ['.claude-orc/', '.swarm/', '.moflo/'];
783
- if (!fs.existsSync(gitignorePath)) {
784
- // Create .gitignore with common defaults + MoFlo entries
785
- const defaultEntries = ['node_modules/', 'dist/', '.env', '.env.*', ''];
786
- const content = '# Dependencies\n' + defaultEntries.join('\n') + '\n# MoFlo state\n' + entries.join('\n') + '\n';
787
- fs.writeFileSync(gitignorePath, content, 'utf-8');
788
- return { name: '.gitignore', status: 'created', detail: 'Created with node_modules, .env, and MoFlo entries' };
789
- }
790
- const existing = fs.readFileSync(gitignorePath, 'utf-8');
791
- const toAdd = entries.filter(e => !existing.includes(e));
792
- if (toAdd.length === 0) {
793
- return { name: '.gitignore', status: 'skipped', detail: 'Entries already present' };
794
- }
795
- fs.appendFileSync(gitignorePath, '\n# MoFlo state (gitignored)\n' + toAdd.join('\n') + '\n');
796
- return { name: '.gitignore', status: 'updated', detail: `Added: ${toAdd.join(', ')}` };
797
- }
798
- // ============================================================================
799
- // Step 7: .claude/guidance/moflo-bootstrap.md
800
- // Copies the agent bootstrap guidance to the project so subagents can read it
801
- // from disk without requiring memory search.
802
- // ============================================================================
803
- function syncBootstrapGuidance(root, force) {
804
- const guidanceDir = path.join(root, '.claude', 'guidance');
805
- const targetFile = path.join(guidanceDir, 'moflo-bootstrap.md');
806
- // Find the source bootstrap file from the moflo package
807
- let sourceDir;
808
- try {
809
- sourceDir = path.dirname(fileURLToPath(import.meta.url));
810
- }
811
- catch {
812
- sourceDir = typeof __dirname !== 'undefined' ? __dirname : '';
813
- }
814
- const candidates = [
815
- path.join(root, 'node_modules', 'moflo', '.claude', 'guidance', 'agent-bootstrap.md'),
816
- // When running from moflo repo itself
817
- ...(sourceDir ? [path.join(sourceDir, '..', '..', '..', '..', '.claude', 'guidance', 'agent-bootstrap.md')] : []),
818
- ];
819
- const sourceFile = candidates.find(f => { try {
820
- return fs.existsSync(f);
821
- }
822
- catch {
823
- return false;
824
- } });
825
- if (!sourceFile) {
826
- return { name: 'guidance/moflo-bootstrap.md', status: 'skipped', detail: 'Source bootstrap not found' };
827
- }
828
- // Check if target exists and is up to date
829
- if (fs.existsSync(targetFile) && !force) {
830
- if (!isStale(sourceFile, targetFile)) {
831
- return { name: 'guidance/moflo-bootstrap.md', status: 'skipped', detail: 'Already up to date' };
832
- }
833
- }
834
- // Read source and prepend header
835
- const content = fs.readFileSync(sourceFile, 'utf-8');
836
- 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`;
837
- fs.mkdirSync(guidanceDir, { recursive: true });
838
- fs.writeFileSync(targetFile, header + content, 'utf-8');
839
- return {
840
- name: 'guidance/moflo-bootstrap.md',
841
- status: fs.existsSync(targetFile) ? 'updated' : 'created',
842
- detail: 'Subagent bootstrap protocol'
843
- };
844
- }
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
+ }
845
840
  //# sourceMappingURL=moflo-init.js.map