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.
- package/.claude/agents/browser/browser-agent.yaml +182 -0
- package/.claude/guidance/moflo-bootstrap.md +129 -0
- package/.claude/helpers/post-commit +16 -0
- package/.claude/helpers/pre-commit +26 -0
- package/.claude/settings.json +351 -290
- package/.claude/settings.local.json +2 -1
- package/.claude/skills/browser/SKILL.md +204 -0
- package/.claude/workflow-state.json +9 -0
- package/bin/index-guidance.mjs +1 -1
- package/bin/index-tests.mjs +1 -1
- package/bin/semantic-search.mjs +1 -1
- package/bin/setup-project.mjs +61 -64
- package/package.json +2 -4
- 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 +52 -472
- package/src/@claude-flow/cli/dist/src/init/moflo-init.d.ts +30 -30
- package/src/@claude-flow/cli/dist/src/init/moflo-init.js +712 -717
- package/src/@claude-flow/cli/package.json +1 -1
- package/.claude/agents/MIGRATION_SUMMARY.md +0 -222
- package/.claude/agents/analysis/code-review/analyze-code-quality.md +0 -179
- package/.claude/agents/development/backend/dev-backend-api.md +0 -142
- package/.claude/agents/flow-nexus/app-store.md +0 -88
- package/.claude/agents/flow-nexus/authentication.md +0 -69
- package/.claude/agents/flow-nexus/challenges.md +0 -81
- package/.claude/agents/flow-nexus/neural-network.md +0 -88
- package/.claude/agents/flow-nexus/payments.md +0 -83
- package/.claude/agents/flow-nexus/sandbox.md +0 -76
- package/.claude/agents/flow-nexus/swarm.md +0 -76
- package/.claude/agents/flow-nexus/user-tools.md +0 -96
- package/.claude/agents/flow-nexus/workflow.md +0 -84
- package/.claude/agents/payments/agentic-payments.md +0 -126
- package/.claude/agents/sona/sona-learning-optimizer.md +0 -74
- package/.claude/agents/sublinear/consensus-coordinator.md +0 -338
- package/.claude/agents/sublinear/matrix-optimizer.md +0 -185
- package/.claude/agents/sublinear/pagerank-analyzer.md +0 -299
- package/.claude/agents/sublinear/performance-optimizer.md +0 -368
- package/.claude/agents/sublinear/trading-predictor.md +0 -246
- package/.claude/agents/testing/unit/tdd-london-swarm.md +0 -244
- package/.claude/agents/testing/validation/production-validator.md +0 -395
- package/.claude/agents/v3/database-specialist.yaml +0 -21
- package/.claude/agents/v3/index.yaml +0 -17
- package/.claude/agents/v3/project-coordinator.yaml +0 -15
- package/.claude/agents/v3/python-specialist.yaml +0 -21
- package/.claude/agents/v3/test-architect.yaml +0 -20
- package/.claude/agents/v3/typescript-specialist.yaml +0 -21
- package/.claude/agents/v3/v3-integration-architect.md +0 -346
- package/.claude/agents/v3/v3-memory-specialist.md +0 -318
- package/.claude/agents/v3/v3-performance-engineer.md +0 -397
- package/.claude/agents/v3/v3-queen-coordinator.md +0 -98
- package/.claude/agents/v3/v3-security-architect.md +0 -174
- package/.claude/commands/analysis/COMMAND_COMPLIANCE_REPORT.md +0 -54
- package/.claude/commands/analysis/README.md +0 -9
- package/.claude/commands/analysis/bottleneck-detect.md +0 -162
- package/.claude/commands/analysis/performance-bottlenecks.md +0 -59
- package/.claude/commands/analysis/performance-report.md +0 -25
- package/.claude/commands/analysis/token-efficiency.md +0 -45
- package/.claude/commands/analysis/token-usage.md +0 -25
- package/.claude/commands/automation/README.md +0 -9
- package/.claude/commands/automation/auto-agent.md +0 -122
- package/.claude/commands/automation/self-healing.md +0 -106
- package/.claude/commands/automation/session-memory.md +0 -90
- package/.claude/commands/automation/smart-agents.md +0 -73
- package/.claude/commands/automation/smart-spawn.md +0 -25
- package/.claude/commands/automation/workflow-select.md +0 -25
- package/.claude/commands/coordination/README.md +0 -9
- package/.claude/commands/coordination/agent-spawn.md +0 -25
- package/.claude/commands/coordination/init.md +0 -44
- package/.claude/commands/coordination/orchestrate.md +0 -43
- package/.claude/commands/coordination/spawn.md +0 -45
- package/.claude/commands/coordination/swarm-init.md +0 -85
- package/.claude/commands/coordination/task-orchestrate.md +0 -25
- package/.claude/commands/flow-nexus/app-store.md +0 -124
- package/.claude/commands/flow-nexus/challenges.md +0 -120
- package/.claude/commands/flow-nexus/login-registration.md +0 -65
- package/.claude/commands/flow-nexus/neural-network.md +0 -134
- package/.claude/commands/flow-nexus/payments.md +0 -116
- package/.claude/commands/flow-nexus/sandbox.md +0 -83
- package/.claude/commands/flow-nexus/swarm.md +0 -87
- package/.claude/commands/flow-nexus/user-tools.md +0 -152
- package/.claude/commands/flow-nexus/workflow.md +0 -115
- package/.claude/commands/monitoring/README.md +0 -9
- package/.claude/commands/monitoring/agent-metrics.md +0 -25
- package/.claude/commands/monitoring/agents.md +0 -44
- package/.claude/commands/monitoring/real-time-view.md +0 -25
- package/.claude/commands/monitoring/status.md +0 -46
- package/.claude/commands/monitoring/swarm-monitor.md +0 -25
- package/.claude/commands/optimization/README.md +0 -9
- package/.claude/commands/optimization/auto-topology.md +0 -62
- package/.claude/commands/optimization/cache-manage.md +0 -25
- package/.claude/commands/optimization/parallel-execute.md +0 -25
- package/.claude/commands/optimization/parallel-execution.md +0 -50
- package/.claude/commands/optimization/topology-optimize.md +0 -25
- package/.claude/commands/pair/README.md +0 -261
- package/.claude/commands/pair/commands.md +0 -546
- package/.claude/commands/pair/config.md +0 -510
- package/.claude/commands/pair/examples.md +0 -512
- package/.claude/commands/pair/modes.md +0 -348
- package/.claude/commands/pair/session.md +0 -407
- package/.claude/commands/pair/start.md +0 -209
- package/.claude/commands/stream-chain/pipeline.md +0 -121
- package/.claude/commands/stream-chain/run.md +0 -70
- package/.claude/commands/training/README.md +0 -9
- package/.claude/commands/training/model-update.md +0 -25
- package/.claude/commands/training/neural-patterns.md +0 -74
- package/.claude/commands/training/neural-train.md +0 -25
- package/.claude/commands/training/pattern-learn.md +0 -25
- package/.claude/commands/training/specialization.md +0 -63
- package/.claude/commands/truth/start.md +0 -143
- package/.claude/commands/verify/check.md +0 -50
- package/.claude/commands/verify/start.md +0 -128
- package/.claude/helpers/gate.cjs +0 -236
- package/.claude/helpers/hook-handler.cjs +0 -341
- package/.claude/skills/agentic-jujutsu/SKILL.md +0 -645
- package/.claude/skills/dual-mode/README.md +0 -71
- package/.claude/skills/dual-mode/dual-collect.md +0 -103
- package/.claude/skills/dual-mode/dual-coordinate.md +0 -85
- package/.claude/skills/dual-mode/dual-spawn.md +0 -81
- package/.claude/skills/flow-nexus-neural/SKILL.md +0 -738
- package/.claude/skills/flow-nexus-platform/SKILL.md +0 -1157
- package/.claude/skills/flow-nexus-swarm/SKILL.md +0 -610
- package/.claude/skills/pair-programming/SKILL.md +0 -1202
- package/.claude/skills/stream-chain/SKILL.md +0 -563
- package/.claude/skills/v3-cli-modernization/SKILL.md +0 -872
- package/.claude/skills/v3-core-implementation/SKILL.md +0 -797
- package/.claude/skills/v3-ddd-architecture/SKILL.md +0 -442
- package/.claude/skills/v3-integration-deep/SKILL.md +0 -241
- package/.claude/skills/v3-mcp-optimization/SKILL.md +0 -777
- package/.claude/skills/v3-memory-unification/SKILL.md +0 -174
- package/.claude/skills/v3-performance-optimization/SKILL.md +0 -390
- package/.claude/skills/v3-security-overhaul/SKILL.md +0 -82
- package/.claude/skills/v3-swarm-coordination/SKILL.md +0 -340
- package/.claude-plugin/README.md +0 -720
- package/.claude-plugin/docs/INSTALLATION.md +0 -261
- package/.claude-plugin/docs/PLUGIN_SUMMARY.md +0 -361
- package/.claude-plugin/docs/QUICKSTART.md +0 -361
- package/.claude-plugin/docs/STRUCTURE.md +0 -128
- package/.claude-plugin/hooks/hooks.json +0 -74
- package/.claude-plugin/marketplace.json +0 -96
- package/.claude-plugin/plugin.json +0 -71
- package/.claude-plugin/scripts/install.sh +0 -234
- package/.claude-plugin/scripts/uninstall.sh +0 -36
- 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
|
-
|
|
624
|
-
const
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
if (
|
|
639
|
-
|
|
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
|
|
664
|
+
mcp__moflo__memory_search — query: "<task description>", namespace: "guidance" or "patterns" or "code-map"
|
|
655
665
|
\`\`\`
|
|
656
666
|
|
|
657
|
-
|
|
658
|
-
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\`.
|
|
659
669
|
|
|
660
670
|
### Workflow Gates (enforced automatically)
|
|
661
671
|
|
|
662
|
-
|
|
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
|
-
###
|
|
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
|
-
###
|
|
685
|
+
### CLI Fallback
|
|
683
686
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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
|
-
###
|
|
692
|
+
### Full Reference
|
|
694
693
|
|
|
695
|
-
For
|
|
696
|
-
|
|
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
|
|
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
|