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.
- package/.claude/agents/browser/browser-agent.yaml +182 -0
- package/.claude/agents/core/coder.md +265 -265
- package/.claude/agents/core/planner.md +167 -167
- package/.claude/agents/core/researcher.md +189 -189
- package/.claude/agents/core/reviewer.md +325 -325
- package/.claude/agents/core/tester.md +318 -318
- package/.claude/agents/dual-mode/codex-coordinator.md +224 -224
- package/.claude/agents/dual-mode/codex-worker.md +211 -211
- package/.claude/agents/dual-mode/dual-orchestrator.md +291 -291
- package/.claude/agents/github/code-review-swarm.md +537 -537
- package/.claude/agents/github/github-modes.md +172 -172
- package/.claude/agents/github/issue-tracker.md +318 -318
- package/.claude/agents/github/multi-repo-swarm.md +552 -552
- package/.claude/agents/github/pr-manager.md +190 -190
- package/.claude/agents/github/project-board-sync.md +508 -508
- package/.claude/agents/github/release-manager.md +366 -366
- package/.claude/agents/github/release-swarm.md +582 -582
- package/.claude/agents/github/repo-architect.md +397 -397
- package/.claude/agents/github/swarm-issue.md +572 -572
- package/.claude/agents/github/swarm-pr.md +427 -427
- package/.claude/agents/github/sync-coordinator.md +451 -451
- package/.claude/agents/github/workflow-automation.md +634 -634
- package/.claude/agents/goal/code-goal-planner.md +445 -445
- package/.claude/agents/hive-mind/collective-intelligence-coordinator.md +129 -129
- package/.claude/agents/hive-mind/queen-coordinator.md +202 -202
- package/.claude/agents/hive-mind/scout-explorer.md +241 -241
- package/.claude/agents/hive-mind/swarm-memory-manager.md +192 -192
- package/.claude/agents/hive-mind/worker-specialist.md +216 -216
- package/.claude/agents/neural/safla-neural.md +73 -73
- package/.claude/agents/reasoning/goal-planner.md +72 -72
- package/.claude/agents/swarm/adaptive-coordinator.md +395 -395
- package/.claude/agents/swarm/hierarchical-coordinator.md +326 -326
- package/.claude/agents/swarm/mesh-coordinator.md +391 -391
- package/.claude/agents/templates/migration-plan.md +745 -745
- package/.claude/commands/agents/agent-spawning.md +28 -28
- package/.claude/commands/analysis/COMMAND_COMPLIANCE_REPORT.md +53 -53
- package/.claude/commands/analysis/bottleneck-detect.md +162 -162
- package/.claude/commands/analysis/performance-bottlenecks.md +58 -58
- package/.claude/commands/analysis/token-efficiency.md +44 -44
- package/.claude/commands/automation/auto-agent.md +122 -122
- package/.claude/commands/automation/self-healing.md +105 -105
- package/.claude/commands/automation/session-memory.md +89 -89
- package/.claude/commands/automation/smart-agents.md +72 -72
- package/.claude/commands/coordination/init.md +44 -44
- package/.claude/commands/coordination/orchestrate.md +43 -43
- package/.claude/commands/coordination/spawn.md +45 -45
- package/.claude/commands/coordination/swarm-init.md +85 -85
- package/.claude/commands/github/github-modes.md +146 -146
- package/.claude/commands/github/github-swarm.md +121 -121
- package/.claude/commands/github/issue-tracker.md +291 -291
- package/.claude/commands/github/pr-manager.md +169 -169
- package/.claude/commands/github/release-manager.md +337 -337
- package/.claude/commands/github/repo-architect.md +366 -366
- package/.claude/commands/github/sync-coordinator.md +300 -300
- package/.claude/commands/memory/neural.md +47 -47
- package/.claude/commands/monitoring/agents.md +44 -44
- package/.claude/commands/monitoring/status.md +46 -46
- package/.claude/commands/optimization/auto-topology.md +61 -61
- package/.claude/commands/optimization/parallel-execution.md +49 -49
- package/.claude/commands/sparc/analyzer.md +51 -51
- package/.claude/commands/sparc/architect.md +53 -53
- package/.claude/commands/sparc/ask.md +97 -97
- package/.claude/commands/sparc/batch-executor.md +54 -54
- package/.claude/commands/sparc/code.md +89 -89
- package/.claude/commands/sparc/coder.md +54 -54
- package/.claude/commands/sparc/debug.md +83 -83
- package/.claude/commands/sparc/debugger.md +54 -54
- package/.claude/commands/sparc/designer.md +53 -53
- package/.claude/commands/sparc/devops.md +109 -109
- package/.claude/commands/sparc/docs-writer.md +80 -80
- package/.claude/commands/sparc/documenter.md +54 -54
- package/.claude/commands/sparc/innovator.md +54 -54
- package/.claude/commands/sparc/integration.md +83 -83
- package/.claude/commands/sparc/mcp.md +117 -117
- package/.claude/commands/sparc/memory-manager.md +54 -54
- package/.claude/commands/sparc/optimizer.md +54 -54
- package/.claude/commands/sparc/orchestrator.md +131 -131
- package/.claude/commands/sparc/post-deployment-monitoring-mode.md +83 -83
- package/.claude/commands/sparc/refinement-optimization-mode.md +83 -83
- package/.claude/commands/sparc/researcher.md +54 -54
- package/.claude/commands/sparc/reviewer.md +54 -54
- package/.claude/commands/sparc/security-review.md +80 -80
- package/.claude/commands/sparc/sparc-modes.md +174 -174
- package/.claude/commands/sparc/sparc.md +111 -111
- package/.claude/commands/sparc/spec-pseudocode.md +80 -80
- package/.claude/commands/sparc/supabase-admin.md +348 -348
- package/.claude/commands/sparc/swarm-coordinator.md +54 -54
- package/.claude/commands/sparc/tdd.md +54 -54
- package/.claude/commands/sparc/tester.md +54 -54
- package/.claude/commands/sparc/tutorial.md +79 -79
- package/.claude/commands/sparc/workflow-manager.md +54 -54
- package/.claude/commands/sparc.md +166 -166
- package/.claude/commands/swarm/analysis.md +95 -95
- package/.claude/commands/swarm/development.md +96 -96
- package/.claude/commands/swarm/examples.md +168 -168
- package/.claude/commands/swarm/maintenance.md +102 -102
- package/.claude/commands/swarm/optimization.md +117 -117
- package/.claude/commands/swarm/research.md +136 -136
- package/.claude/commands/swarm/testing.md +131 -131
- package/.claude/commands/training/neural-patterns.md +73 -73
- package/.claude/commands/training/specialization.md +62 -62
- package/.claude/commands/workflows/development.md +77 -77
- package/.claude/commands/workflows/research.md +62 -62
- package/.claude/guidance/moflo-bootstrap.md +129 -0
- package/.claude/guidance/{agent-bootstrap.md → shipped/agent-bootstrap.md} +126 -126
- package/.claude/guidance/{guidance-memory-strategy.md → shipped/guidance-memory-strategy.md} +262 -262
- package/.claude/guidance/{memory-strategy.md → shipped/memory-strategy.md} +204 -204
- package/.claude/guidance/{moflo.md → shipped/moflo.md} +45 -31
- package/.claude/guidance/{task-swarm-integration.md → shipped/task-swarm-integration.md} +441 -348
- package/.claude/helpers/gate-hook.mjs +50 -0
- package/.claude/helpers/gate.cjs +138 -236
- package/.claude/helpers/hook-handler.cjs +64 -326
- package/.claude/helpers/post-commit +16 -0
- package/.claude/helpers/pre-commit +26 -0
- package/.claude/helpers/prompt-hook.mjs +72 -0
- package/.claude/scripts/build-embeddings.mjs +549 -0
- package/.claude/scripts/generate-code-map.mjs +697 -0
- package/.claude/scripts/hooks.mjs +656 -0
- package/.claude/scripts/index-guidance.mjs +893 -0
- package/.claude/scripts/index-tests.mjs +710 -0
- package/.claude/scripts/semantic-search.mjs +473 -0
- package/.claude/scripts/session-start-launcher.mjs +226 -0
- package/.claude/settings.json +351 -290
- package/.claude/settings.local.json +4 -3
- package/.claude/skills/browser/SKILL.md +204 -0
- package/.claude/skills/fl/SKILL.md +29 -23
- package/.claude/skills/flo/SKILL.md +29 -23
- package/.claude/skills/github-code-review/SKILL.md +4 -4
- package/.claude/skills/github-multi-repo/SKILL.md +8 -8
- package/.claude/skills/github-project-management/SKILL.md +6 -6
- package/.claude/skills/github-release-management/SKILL.md +12 -12
- package/.claude/skills/github-workflow-automation/SKILL.md +6 -6
- package/.claude/skills/hooks-automation/SKILL.md +1201 -1201
- package/.claude/skills/performance-analysis/SKILL.md +563 -563
- package/.claude/skills/sparc-methodology/SKILL.md +64 -64
- package/.claude/skills/swarm-advanced/SKILL.md +77 -77
- package/.claude/workflow-state.json +9 -0
- package/.claude-plugin/README.md +3 -3
- package/.claude-plugin/docs/PLUGIN_SUMMARY.md +3 -3
- package/.claude-plugin/docs/QUICKSTART.md +4 -4
- package/.claude-plugin/marketplace.json +3 -3
- package/.claude-plugin/plugin.json +3 -3
- package/.claude-plugin/scripts/install.sh +9 -9
- package/.claude-plugin/scripts/verify.sh +7 -7
- package/README.md +311 -116
- package/bin/gate-hook.mjs +50 -0
- package/bin/gate.cjs +138 -0
- package/bin/hook-handler.cjs +83 -0
- package/bin/hooks.mjs +72 -12
- package/bin/index-guidance.mjs +29 -35
- package/bin/index-tests.mjs +710 -0
- package/bin/lib/process-manager.mjs +243 -0
- package/bin/lib/registry-cleanup.cjs +41 -0
- package/bin/prompt-hook.mjs +72 -0
- package/bin/semantic-search.mjs +472 -440
- package/bin/session-start-launcher.mjs +81 -31
- package/bin/setup-project.mjs +65 -65
- package/package.json +4 -2
- package/src/@claude-flow/cli/README.md +1 -1
- package/src/@claude-flow/cli/bin/cli.js +175 -175
- package/src/@claude-flow/cli/dist/src/commands/doctor.js +1091 -736
- package/src/@claude-flow/cli/dist/src/commands/github.d.ts +12 -0
- package/src/@claude-flow/cli/dist/src/commands/github.js +505 -0
- package/src/@claude-flow/cli/dist/src/commands/hive-mind.js +90 -90
- package/src/@claude-flow/cli/dist/src/commands/index.d.ts +1 -0
- package/src/@claude-flow/cli/dist/src/commands/index.js +7 -0
- package/src/@claude-flow/cli/dist/src/config-adapter.js +1 -1
- package/src/@claude-flow/cli/dist/src/init/claudemd-generator.d.ts +29 -24
- package/src/@claude-flow/cli/dist/src/init/claudemd-generator.js +73 -494
- package/src/@claude-flow/cli/dist/src/init/executor.js +109 -5
- package/src/@claude-flow/cli/dist/src/init/helpers-generator.d.ts +14 -0
- package/src/@claude-flow/cli/dist/src/init/helpers-generator.js +156 -24
- package/src/@claude-flow/cli/dist/src/init/mcp-generator.js +20 -20
- package/src/@claude-flow/cli/dist/src/init/moflo-init.d.ts +30 -23
- package/src/@claude-flow/cli/dist/src/init/moflo-init.js +727 -670
- package/src/@claude-flow/cli/dist/src/init/settings-generator.js +23 -14
- package/src/@claude-flow/cli/dist/src/mcp-server.js +3 -3
- package/src/@claude-flow/cli/dist/src/plugins/manager.js +9 -8
- package/src/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +1 -0
- package/src/@claude-flow/cli/dist/src/services/worker-daemon.js +3 -1
- package/src/@claude-flow/cli/dist/src/services/workflow-gate.js +10 -10
- 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
|
|
58
|
-
*
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const
|
|
63
|
-
//
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
//
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
:
|
|
178
|
-
?
|
|
179
|
-
:
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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": "^
|
|
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
|
-
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
if (
|
|
578
|
-
|
|
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
|
-
|
|
664
|
+
mcp__moflo__memory_search — query: "<task description>", namespace: "guidance" or "patterns" or "code-map"
|
|
594
665
|
\`\`\`
|
|
595
666
|
|
|
596
|
-
|
|
597
|
-
When the user asks you to remember something
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
| \`
|
|
616
|
-
| \`
|
|
617
|
-
| \`
|
|
618
|
-
| \`
|
|
619
|
-
| \`
|
|
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
|
-
###
|
|
685
|
+
### CLI Fallback
|
|
622
686
|
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
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
|
-
###
|
|
692
|
+
### Full Reference
|
|
633
693
|
|
|
634
|
-
For
|
|
635
|
-
|
|
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
|
|
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
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
const
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
//
|
|
717
|
-
//
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
const
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
const
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
const
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
//
|
|
738
|
-
//
|
|
739
|
-
//
|
|
740
|
-
//
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
const
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
if
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
const
|
|
775
|
-
|
|
776
|
-
fs.
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
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
|