moflo 4.7.3 → 4.7.6
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/skills/fl/SKILL.md +577 -0
- package/.claude/skills/flo/SKILL.md +126 -3
- package/.claude/workflow-state.json +9 -0
- package/package.json +2 -1
- package/src/@claude-flow/cli/dist/src/commands/agent.js +6 -2
- package/src/@claude-flow/cli/dist/src/commands/completions.js +1 -1
- package/src/@claude-flow/cli/dist/src/commands/doctor.js +2 -2
- package/src/@claude-flow/cli/dist/src/init/claudemd-generator.js +1 -1
- package/src/@claude-flow/cli/dist/src/init/executor.js +33 -16
- package/src/@claude-flow/cli/dist/src/init/helpers-generator.d.ts +13 -0
- package/src/@claude-flow/cli/dist/src/init/helpers-generator.js +243 -2
- package/src/@claude-flow/cli/dist/src/init/mcp-generator.js +2 -2
- package/src/@claude-flow/cli/dist/src/init/settings-generator.js +25 -20
- package/src/@claude-flow/cli/dist/src/init/types.js +2 -2
- package/src/@claude-flow/cli/dist/src/update/checker.d.ts +1 -1
- package/src/@claude-flow/cli/dist/src/update/checker.js +11 -11
- package/src/@claude-flow/cli/dist/src/update/validator.js +11 -11
- package/src/@claude-flow/cli/package.json +110 -110
|
@@ -150,7 +150,102 @@ Use Task tool with Explore agent to find:
|
|
|
150
150
|
|
|
151
151
|
## Phase 2: Ticket (-t creates or updates a ticket)
|
|
152
152
|
|
|
153
|
-
When given an issue number, `-t` enhances the existing ticket. When given a title (non-numeric argument), `-t` creates a new GitHub issue. Either way, the ticket MUST include all three of the following sections
|
|
153
|
+
When given an issue number, `-t` enhances the existing ticket. When given a title (non-numeric argument), `-t` creates a new GitHub issue. Either way, the ticket MUST include all three of the following sections.
|
|
154
|
+
|
|
155
|
+
### 2.0 Complexity Assessment (MANDATORY before building ticket)
|
|
156
|
+
|
|
157
|
+
After research, assess the complexity of the work. This determines whether the issue stays as a single ticket or gets promoted to an epic with sub-issues.
|
|
158
|
+
|
|
159
|
+
**Complexity Signals — count how many apply:**
|
|
160
|
+
|
|
161
|
+
| Signal | Weight | Example |
|
|
162
|
+
|--------|--------|---------|
|
|
163
|
+
| Multiple files changed (5+) | +2 | Touches models, API, tests, docs, config |
|
|
164
|
+
| New module or package | +2 | Requires new directory structure |
|
|
165
|
+
| Cross-cutting concern | +2 | Auth, logging, error handling across layers |
|
|
166
|
+
| Database/schema changes | +2 | Migrations, new tables, index changes |
|
|
167
|
+
| Multiple independent work streams | +3 | Frontend + backend + infra changes |
|
|
168
|
+
| External API integration | +1 | Third-party service, webhook, OAuth |
|
|
169
|
+
| Breaking change / migration | +2 | Requires deprecation, data migration |
|
|
170
|
+
| Significant test surface | +1 | Needs 10+ new test cases across categories |
|
|
171
|
+
| Security implications | +1 | Authentication, authorization, input validation |
|
|
172
|
+
| UI + backend changes together | +2 | Full-stack feature spanning layers |
|
|
173
|
+
|
|
174
|
+
**Complexity Thresholds:**
|
|
175
|
+
|
|
176
|
+
| Score | Classification | Action |
|
|
177
|
+
|-------|---------------|--------|
|
|
178
|
+
| 0–3 | **Simple** | Single ticket — proceed normally |
|
|
179
|
+
| 4–6 | **Moderate** | Single ticket — flag in description that it may benefit from splitting |
|
|
180
|
+
| 7+ | **Complex** | **PROMOTE TO EPIC** — decompose into sub-issues |
|
|
181
|
+
|
|
182
|
+
**When promoting to epic:**
|
|
183
|
+
|
|
184
|
+
1. Decompose the work into 2–6 independent, shippable stories
|
|
185
|
+
2. Each story should be completable in a single PR
|
|
186
|
+
3. Stories should have clear boundaries (one concern per story)
|
|
187
|
+
4. Order stories by dependency (independent ones first)
|
|
188
|
+
5. Create each story as a GitHub issue with its own Description, Acceptance Criteria, and Test Cases
|
|
189
|
+
6. Create or convert the parent issue into an epic with a `## Stories` checklist
|
|
190
|
+
|
|
191
|
+
```javascript
|
|
192
|
+
// Complexity assessment pseudocode
|
|
193
|
+
function assessComplexity(research) {
|
|
194
|
+
let score = 0;
|
|
195
|
+
if (research.affectedFiles.length >= 5) score += 2;
|
|
196
|
+
if (research.requiresNewModule) score += 2;
|
|
197
|
+
if (research.crossCutting) score += 2;
|
|
198
|
+
if (research.schemaChanges) score += 2;
|
|
199
|
+
if (research.independentWorkStreams >= 2) score += 3;
|
|
200
|
+
if (research.externalAPIs) score += 1;
|
|
201
|
+
if (research.breakingChanges) score += 2;
|
|
202
|
+
if (research.estimatedTestCases >= 10) score += 1;
|
|
203
|
+
if (research.securityImplications) score += 1;
|
|
204
|
+
if (research.fullStack) score += 2;
|
|
205
|
+
return score;
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### 2.0.1 Epic Decomposition (when score >= 7)
|
|
210
|
+
|
|
211
|
+
When complexity warrants an epic, decompose into stories:
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
# Step 1: Create each sub-issue
|
|
215
|
+
gh issue create --title "Story: <story-title>" --body "<## Description + ## Acceptance Criteria + ## Suggested Test Cases>" --label "story"
|
|
216
|
+
# Capture the new issue number from output
|
|
217
|
+
|
|
218
|
+
# Step 2: Repeat for all stories (2-6 stories typically)
|
|
219
|
+
|
|
220
|
+
# Step 3: Build the epic body with checklist referencing ALL story issue numbers
|
|
221
|
+
# Step 4: If updating an existing issue, convert it to epic:
|
|
222
|
+
gh issue edit <parent-number> --add-label "epic" --body "<epic body with ## Stories checklist>"
|
|
223
|
+
|
|
224
|
+
# Step 5: If creating new, create the epic:
|
|
225
|
+
gh issue create --title "Epic: <title>" --label "epic" --body "<epic body>"
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Epic body format (MANDATORY — this is how tracking works):**
|
|
229
|
+
|
|
230
|
+
```markdown
|
|
231
|
+
## Overview
|
|
232
|
+
<High-level description of the epic goal>
|
|
233
|
+
|
|
234
|
+
## Stories
|
|
235
|
+
|
|
236
|
+
- [ ] #<story-1-number> <story-1-title>
|
|
237
|
+
- [ ] #<story-2-number> <story-2-title>
|
|
238
|
+
- [ ] #<story-3-number> <story-3-title>
|
|
239
|
+
|
|
240
|
+
## Complexity Assessment
|
|
241
|
+
Score: <N>/20 — <Simple|Moderate|Complex>
|
|
242
|
+
Signals: <list of signals that triggered>
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
The `## Stories` checklist with `- [ ] #<number>` format is **mandatory** — this is what enables:
|
|
246
|
+
- Epic detection by the `/flo` skill
|
|
247
|
+
- Story extraction for sequential processing
|
|
248
|
+
- Progress tracking via checked/unchecked items
|
|
154
249
|
|
|
155
250
|
### 2.1 Build Ticket Content
|
|
156
251
|
Compile research into a well-structured ticket. The issue MUST include all three of the following sections:
|
|
@@ -279,12 +374,40 @@ An issue is an **epic** if:
|
|
|
279
374
|
|
|
280
375
|
1. DETECT EPIC - Check labels, parse body for ## Stories / ## Tasks, extract issue references
|
|
281
376
|
2. LIST ALL STORIES - Extract from checklist, order top-to-bottom as listed
|
|
282
|
-
3. SEQUENTIAL PROCESSING - For each story:
|
|
283
|
-
|
|
377
|
+
3. SEQUENTIAL PROCESSING - For each story:
|
|
378
|
+
a. Run full /flo workflow (research -> ticket -> implement -> test -> PR)
|
|
379
|
+
b. After PR is created, **check off the story** in the epic body
|
|
380
|
+
c. Move to the next unchecked story
|
|
381
|
+
4. COMPLETION - All stories checked off, epic marked as ready-for-review
|
|
284
382
|
|
|
285
383
|
ONE STORY AT A TIME - NO PARALLEL STORY EXECUTION.
|
|
286
384
|
Each story must complete (PR created) before starting next.
|
|
287
385
|
|
|
386
|
+
### Epic Checklist Tracking (MANDATORY)
|
|
387
|
+
|
|
388
|
+
After each story's PR is created, update the epic body to check off that story:
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
# 1. Fetch current epic body
|
|
392
|
+
EPIC_BODY=$(gh issue view <epic-number> --json body -q '.body')
|
|
393
|
+
|
|
394
|
+
# 2. Replace "- [ ] #<story-number>" with "- [x] #<story-number>"
|
|
395
|
+
UPDATED_BODY=$(echo "$EPIC_BODY" | sed 's/- \[ \] #<story-number>/- [x] #<story-number>/')
|
|
396
|
+
|
|
397
|
+
# 3. Update the epic
|
|
398
|
+
gh issue edit <epic-number> --body "$UPDATED_BODY"
|
|
399
|
+
|
|
400
|
+
# 4. Comment on the epic with progress
|
|
401
|
+
gh issue comment <epic-number> --body "✅ Story #<story-number> completed — PR: <pr-url>"
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
This applies to ALL epics, regardless of how they were created:
|
|
405
|
+
- Epics created by `/flo -t` complexity promotion
|
|
406
|
+
- Epics created manually by users
|
|
407
|
+
- Epics detected from existing issues
|
|
408
|
+
|
|
409
|
+
The checklist state (`[ ]` vs `[x]`) is the **single source of truth** for epic progress.
|
|
410
|
+
|
|
288
411
|
### Epic Detection Code
|
|
289
412
|
|
|
290
413
|
```javascript
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "moflo",
|
|
3
|
-
"version": "4.7.
|
|
3
|
+
"version": "4.7.6",
|
|
4
4
|
"description": "MoFlo — AI agent orchestration for Claude Code. Forked from ruflo/claude-flow with patches applied to source, plus feature-level orchestration.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -85,6 +85,7 @@
|
|
|
85
85
|
"@types/bcrypt": "^5.0.2",
|
|
86
86
|
"@types/node": "^20.0.0",
|
|
87
87
|
"eslint": "^8.0.0",
|
|
88
|
+
"moflo": "^4.7.4",
|
|
88
89
|
"tsx": "^4.21.0",
|
|
89
90
|
"typescript": "^5.0.0",
|
|
90
91
|
"vitest": "^1.0.0"
|
|
@@ -229,7 +229,7 @@ const listCommand = {
|
|
|
229
229
|
}
|
|
230
230
|
// Format for display
|
|
231
231
|
const displayAgents = result.agents.map(agent => ({
|
|
232
|
-
id: agent.
|
|
232
|
+
id: agent.agentId,
|
|
233
233
|
type: agent.agentType,
|
|
234
234
|
status: agent.status,
|
|
235
235
|
created: new Date(agent.createdAt).toLocaleTimeString(),
|
|
@@ -540,7 +540,11 @@ const poolCommand = {
|
|
|
540
540
|
`Auto-Scale: ${result.autoScale ? 'Yes' : 'No'}`,
|
|
541
541
|
`Utilization: ${(utilization * 100).toFixed(1)}%`
|
|
542
542
|
].join('\n'), 'Agent Pool');
|
|
543
|
-
const agents = result.agents ?? []
|
|
543
|
+
const agents = (result.agents ?? []).map((a) => ({
|
|
544
|
+
id: a.agentId || a.id,
|
|
545
|
+
type: a.agentType || a.type,
|
|
546
|
+
status: a.status,
|
|
547
|
+
}));
|
|
544
548
|
if (agents.length > 0) {
|
|
545
549
|
output.writeln();
|
|
546
550
|
output.writeln(output.bold('Pool Agents'));
|
|
@@ -118,7 +118,7 @@ _claude_flow_completions() {
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
complete -F _claude_flow_completions claude-flow
|
|
121
|
-
complete -F _claude_flow_completions npx\\
|
|
121
|
+
complete -F _claude_flow_completions npx\\ moflo
|
|
122
122
|
`;
|
|
123
123
|
}
|
|
124
124
|
// Generate zsh completion script
|
|
@@ -297,8 +297,8 @@ async function checkVersionFreshness() {
|
|
|
297
297
|
(latest.major === current.major && latest.minor === current.minor && latest.patch === current.patch && latest.prerelease > current.prerelease));
|
|
298
298
|
if (isOutdated) {
|
|
299
299
|
const fix = isNpx
|
|
300
|
-
? 'rm -rf ~/.npm/_npx/* && npx -y
|
|
301
|
-
: 'npm update
|
|
300
|
+
? 'rm -rf ~/.npm/_npx/* && npx -y moflo'
|
|
301
|
+
: 'npm update moflo';
|
|
302
302
|
return {
|
|
303
303
|
name: 'Version Freshness',
|
|
304
304
|
status: 'warn',
|
|
@@ -364,7 +364,7 @@ function setupAndBoundary() {
|
|
|
364
364
|
return `## Quick Setup
|
|
365
365
|
|
|
366
366
|
\`\`\`bash
|
|
367
|
-
claude mcp add claude-flow -- npx -y
|
|
367
|
+
claude mcp add claude-flow -- npx -y moflo
|
|
368
368
|
npx moflo daemon start
|
|
369
369
|
npx moflo doctor --fix
|
|
370
370
|
\`\`\`
|
|
@@ -14,7 +14,7 @@ import { detectPlatform, DEFAULT_INIT_OPTIONS } from './types.js';
|
|
|
14
14
|
import { generateSettingsJson, generateSettings } from './settings-generator.js';
|
|
15
15
|
import { generateMCPJson } from './mcp-generator.js';
|
|
16
16
|
import { generateStatuslineScript } from './statusline-generator.js';
|
|
17
|
-
import { generatePreCommitHook, generatePostCommitHook, generateAutoMemoryHook, } from './helpers-generator.js';
|
|
17
|
+
import { generatePreCommitHook, generatePostCommitHook, generateAutoMemoryHook, generateGateScript, generateHookHandlerScript, } from './helpers-generator.js';
|
|
18
18
|
import { generateClaudeMd } from './claudemd-generator.js';
|
|
19
19
|
/**
|
|
20
20
|
* Skills to copy based on configuration
|
|
@@ -284,14 +284,13 @@ function mergeSettingsForUpgrade(existing) {
|
|
|
284
284
|
// Their configuration lives in claudeFlow.agentTeams.hooks instead.
|
|
285
285
|
// 3. Fix statusLine config (remove invalid fields, ensure correct format)
|
|
286
286
|
// Claude Code only supports: type, command, padding
|
|
287
|
+
// Always ensure statusLine is present — add it if missing (e.g. from older inits)
|
|
287
288
|
const existingStatusLine = existing.statusLine;
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
};
|
|
294
|
-
}
|
|
289
|
+
merged.statusLine = {
|
|
290
|
+
type: 'command',
|
|
291
|
+
command: existingStatusLine?.command || `node "$CLAUDE_PROJECT_DIR/.claude/helpers/statusline.cjs"`,
|
|
292
|
+
// Remove invalid fields: refreshMs, enabled (not supported by Claude Code)
|
|
293
|
+
};
|
|
295
294
|
// 4. Merge claudeFlow settings (preserve existing, add agentTeams + memory)
|
|
296
295
|
const existingClaudeFlow = existing.claudeFlow || {};
|
|
297
296
|
const existingMemory = existingClaudeFlow.memory || {};
|
|
@@ -356,7 +355,7 @@ export async function executeUpgrade(targetDir, upgradeSettings = false) {
|
|
|
356
355
|
// 0. ALWAYS update critical helpers (force overwrite)
|
|
357
356
|
const sourceHelpersForUpgrade = findSourceHelpersDir();
|
|
358
357
|
if (sourceHelpersForUpgrade) {
|
|
359
|
-
const criticalHelpers = ['auto-memory-hook.mjs', 'hook-handler.cjs', 'intelligence.cjs'];
|
|
358
|
+
const criticalHelpers = ['auto-memory-hook.mjs', 'hook-handler.cjs', 'gate.cjs', 'intelligence.cjs'];
|
|
360
359
|
for (const helperName of criticalHelpers) {
|
|
361
360
|
const targetPath = path.join(targetDir, '.claude', 'helpers', helperName);
|
|
362
361
|
const sourcePath = path.join(sourceHelpersForUpgrade, helperName);
|
|
@@ -379,6 +378,8 @@ export async function executeUpgrade(targetDir, upgradeSettings = false) {
|
|
|
379
378
|
// Source not found (npx with broken paths) — use generated fallbacks
|
|
380
379
|
const generatedCritical = {
|
|
381
380
|
'auto-memory-hook.mjs': generateAutoMemoryHook(),
|
|
381
|
+
'gate.cjs': generateGateScript(),
|
|
382
|
+
'hook-handler.cjs': generateHookHandlerScript(),
|
|
382
383
|
};
|
|
383
384
|
for (const [helperName, content] of Object.entries(generatedCritical)) {
|
|
384
385
|
const targetPath = path.join(targetDir, '.claude', 'helpers', helperName);
|
|
@@ -494,7 +495,22 @@ export async function executeUpgrade(targetDir, upgradeSettings = false) {
|
|
|
494
495
|
else {
|
|
495
496
|
result.preserved.push('.claude-flow/security/audit-status.json');
|
|
496
497
|
}
|
|
497
|
-
// 3.
|
|
498
|
+
// 3. Fix .mcp.json — replace stale @claude-flow/cli references with moflo
|
|
499
|
+
const mcpPath = path.join(targetDir, '.mcp.json');
|
|
500
|
+
if (fs.existsSync(mcpPath)) {
|
|
501
|
+
try {
|
|
502
|
+
const mcpRaw = fs.readFileSync(mcpPath, 'utf-8');
|
|
503
|
+
if (mcpRaw.includes('@claude-flow/cli')) {
|
|
504
|
+
const mcpFixed = mcpRaw.replace(/@claude-flow\/cli(@latest)?/g, 'moflo');
|
|
505
|
+
fs.writeFileSync(mcpPath, mcpFixed, 'utf-8');
|
|
506
|
+
result.updated.push('.mcp.json (replaced @claude-flow/cli with moflo)');
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
catch {
|
|
510
|
+
// Non-fatal — .mcp.json may be malformed
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
// 4. Merge settings if requested
|
|
498
514
|
if (upgradeSettings) {
|
|
499
515
|
const settingsPath = path.join(targetDir, '.claude', 'settings.json');
|
|
500
516
|
if (fs.existsSync(settingsPath)) {
|
|
@@ -853,7 +869,7 @@ function findSourceHelpersDir(sourceBaseDir) {
|
|
|
853
869
|
// Strategy 1: require.resolve to find package root (most reliable for npx)
|
|
854
870
|
try {
|
|
855
871
|
const esmRequire = createRequire(import.meta.url);
|
|
856
|
-
const pkgJsonPath = esmRequire.resolve('
|
|
872
|
+
const pkgJsonPath = esmRequire.resolve('moflo/package.json');
|
|
857
873
|
const pkgRoot = path.dirname(pkgJsonPath);
|
|
858
874
|
possiblePaths.push(path.join(pkgRoot, '.claude', 'helpers'));
|
|
859
875
|
}
|
|
@@ -924,13 +940,14 @@ async function writeHelpers(targetDir, options, result) {
|
|
|
924
940
|
}
|
|
925
941
|
}
|
|
926
942
|
// Fall back to generating helpers if source not available.
|
|
927
|
-
//
|
|
928
|
-
//
|
|
929
|
-
// are no longer needed.
|
|
943
|
+
// gate.cjs and hook-handler.cjs are required — hooks call them directly
|
|
944
|
+
// via `node` instead of `npx flo` to avoid CLI bootstrap overhead.
|
|
930
945
|
const helpers = {
|
|
931
946
|
'pre-commit': generatePreCommitHook(),
|
|
932
947
|
'post-commit': generatePostCommitHook(),
|
|
933
948
|
'auto-memory-hook.mjs': generateAutoMemoryHook(),
|
|
949
|
+
'gate.cjs': generateGateScript(),
|
|
950
|
+
'hook-handler.cjs': generateHookHandlerScript(),
|
|
934
951
|
};
|
|
935
952
|
for (const [name, content] of Object.entries(helpers)) {
|
|
936
953
|
const filePath = path.join(helpersDir, name);
|
|
@@ -1571,7 +1588,7 @@ npx moflo hive-mind consensus --propose "task"
|
|
|
1571
1588
|
### MCP Server Setup
|
|
1572
1589
|
\`\`\`bash
|
|
1573
1590
|
# Add Claude Flow MCP
|
|
1574
|
-
claude mcp add claude-flow -- npx -y
|
|
1591
|
+
claude mcp add claude-flow -- npx -y moflo
|
|
1575
1592
|
|
|
1576
1593
|
# Optional servers
|
|
1577
1594
|
claude mcp add ruv-swarm -- npx -y ruv-swarm mcp start
|
|
@@ -1653,7 +1670,7 @@ function findSourceDir(type, sourceBaseDir) {
|
|
|
1653
1670
|
}
|
|
1654
1671
|
// IMPORTANT: Check the package's own .claude directory first
|
|
1655
1672
|
// This is the primary path when running as an npm package
|
|
1656
|
-
// __dirname is typically /path/to/node_modules
|
|
1673
|
+
// __dirname is typically /path/to/node_modules/moflo/dist/src/init
|
|
1657
1674
|
// We need to go up 3 levels to reach the package root (dist/src/init -> dist/src -> dist -> root)
|
|
1658
1675
|
const packageRoot = path.resolve(__dirname, '..', '..', '..');
|
|
1659
1676
|
const packageDotClaude = path.join(packageRoot, '.claude', type);
|
|
@@ -21,4 +21,17 @@ export declare function generateAutoMemoryHook(): string;
|
|
|
21
21
|
* Generate all helper files
|
|
22
22
|
*/
|
|
23
23
|
export declare function generateHelpers(options: InitOptions): Record<string, string>;
|
|
24
|
+
/**
|
|
25
|
+
* Generate lightweight gate.cjs — workflow gates without CLI bootstrap.
|
|
26
|
+
* Handles JSON state file read/write for memory-first and TaskCreate gates.
|
|
27
|
+
* This replaces `npx flo gate <command>` to avoid spawning a full CLI process
|
|
28
|
+
* on every tool call (~500ms npx overhead → ~20ms direct node).
|
|
29
|
+
*/
|
|
30
|
+
export declare function generateGateScript(): string;
|
|
31
|
+
/**
|
|
32
|
+
* Generate lightweight hook-handler.cjs — hook dispatch without CLI bootstrap.
|
|
33
|
+
* Handles routing, edit/task tracking, session lifecycle, and notifications.
|
|
34
|
+
* This replaces `npx flo hooks <command>` to avoid spawning a full CLI process.
|
|
35
|
+
*/
|
|
36
|
+
export declare function generateHookHandlerScript(): string;
|
|
24
37
|
//# sourceMappingURL=helpers-generator.d.ts.map
|
|
@@ -177,12 +177,253 @@ export function generateHelpers(options) {
|
|
|
177
177
|
if (options.components.helpers) {
|
|
178
178
|
helpers['pre-commit'] = generatePreCommitHook();
|
|
179
179
|
helpers['post-commit'] = generatePostCommitHook();
|
|
180
|
-
|
|
181
|
-
|
|
180
|
+
helpers['gate.cjs'] = generateGateScript();
|
|
181
|
+
helpers['hook-handler.cjs'] = generateHookHandlerScript();
|
|
182
182
|
}
|
|
183
183
|
if (options.components.statusline) {
|
|
184
184
|
helpers['statusline.cjs'] = generateStatuslineScript(options);
|
|
185
185
|
}
|
|
186
186
|
return helpers;
|
|
187
187
|
}
|
|
188
|
+
/**
|
|
189
|
+
* Generate lightweight gate.cjs — workflow gates without CLI bootstrap.
|
|
190
|
+
* Handles JSON state file read/write for memory-first and TaskCreate gates.
|
|
191
|
+
* This replaces `npx flo gate <command>` to avoid spawning a full CLI process
|
|
192
|
+
* on every tool call (~500ms npx overhead → ~20ms direct node).
|
|
193
|
+
*/
|
|
194
|
+
export function generateGateScript() {
|
|
195
|
+
return `#!/usr/bin/env node
|
|
196
|
+
'use strict';
|
|
197
|
+
var fs = require('fs');
|
|
198
|
+
var path = require('path');
|
|
199
|
+
|
|
200
|
+
var PROJECT_DIR = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
201
|
+
var STATE_FILE = path.join(PROJECT_DIR, '.claude', 'workflow-state.json');
|
|
202
|
+
|
|
203
|
+
function readState() {
|
|
204
|
+
try {
|
|
205
|
+
if (fs.existsSync(STATE_FILE)) return JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8'));
|
|
206
|
+
} catch (e) { /* reset on corruption */ }
|
|
207
|
+
return { tasksCreated: false, taskCount: 0, memorySearched: false, memoryRequired: true, interactionCount: 0, sessionStart: null, lastBlockedAt: null };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function writeState(s) {
|
|
211
|
+
try {
|
|
212
|
+
var dir = path.dirname(STATE_FILE);
|
|
213
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
214
|
+
fs.writeFileSync(STATE_FILE, JSON.stringify(s, null, 2));
|
|
215
|
+
} catch (e) { /* non-fatal */ }
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Load moflo.yaml gate config (defaults: all enabled)
|
|
219
|
+
function loadGateConfig() {
|
|
220
|
+
var defaults = { memory_first: true, task_create_first: true, context_tracking: true };
|
|
221
|
+
try {
|
|
222
|
+
var yamlPath = path.join(PROJECT_DIR, 'moflo.yaml');
|
|
223
|
+
if (fs.existsSync(yamlPath)) {
|
|
224
|
+
var content = fs.readFileSync(yamlPath, 'utf-8');
|
|
225
|
+
if (/memory_first:\\s*false/i.test(content)) defaults.memory_first = false;
|
|
226
|
+
if (/task_create_first:\\s*false/i.test(content)) defaults.task_create_first = false;
|
|
227
|
+
if (/context_tracking:\\s*false/i.test(content)) defaults.context_tracking = false;
|
|
228
|
+
}
|
|
229
|
+
} catch (e) { /* use defaults */ }
|
|
230
|
+
return defaults;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
var config = loadGateConfig();
|
|
234
|
+
var command = process.argv[2];
|
|
235
|
+
|
|
236
|
+
var EXEMPT = ['.claude/', '.claude\\\\', 'CLAUDE.md', 'MEMORY.md', 'workflow-state', 'node_modules'];
|
|
237
|
+
var DANGEROUS = ['rm -rf /', 'format c:', 'del /s /q c:\\\\', ':(){:|:&};:', 'mkfs.', '> /dev/sda'];
|
|
238
|
+
var DIRECTIVE_RE = /^(yes|no|yeah|yep|nope|sure|ok|okay|correct|right|exactly|perfect)\\b/i;
|
|
239
|
+
var TASK_RE = /\\b(fix|bug|error|implement|add|create|build|write|refactor|debug|test|feature|issue|security|optimi)\\b/i;
|
|
240
|
+
|
|
241
|
+
switch (command) {
|
|
242
|
+
case 'check-before-agent': {
|
|
243
|
+
var s = readState();
|
|
244
|
+
if (config.task_create_first && !s.tasksCreated) {
|
|
245
|
+
console.log('BLOCKED: Call TaskCreate before spawning agents.');
|
|
246
|
+
process.exit(1);
|
|
247
|
+
}
|
|
248
|
+
if (config.memory_first && !s.memorySearched) {
|
|
249
|
+
console.log('BLOCKED: Search memory before spawning agents.');
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
break;
|
|
253
|
+
}
|
|
254
|
+
case 'check-before-scan': {
|
|
255
|
+
if (!config.memory_first) break;
|
|
256
|
+
var s = readState();
|
|
257
|
+
if (s.memorySearched || !s.memoryRequired) break;
|
|
258
|
+
var target = (process.env.TOOL_INPUT_pattern || '') + ' ' + (process.env.TOOL_INPUT_path || '');
|
|
259
|
+
if (EXEMPT.some(function(p) { return target.indexOf(p) >= 0; })) break;
|
|
260
|
+
var now = Date.now();
|
|
261
|
+
var last = s.lastBlockedAt ? new Date(s.lastBlockedAt).getTime() : 0;
|
|
262
|
+
if (now - last > 2000) {
|
|
263
|
+
s.lastBlockedAt = new Date(now).toISOString();
|
|
264
|
+
writeState(s);
|
|
265
|
+
console.log('BLOCKED: Search memory before exploring files.');
|
|
266
|
+
}
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
case 'check-before-read': {
|
|
270
|
+
if (!config.memory_first) break;
|
|
271
|
+
var s = readState();
|
|
272
|
+
if (s.memorySearched || !s.memoryRequired) break;
|
|
273
|
+
var fp = process.env.TOOL_INPUT_file_path || '';
|
|
274
|
+
if (fp.indexOf('.claude/guidance/') < 0 && fp.indexOf('.claude\\\\guidance\\\\') < 0) break;
|
|
275
|
+
var now = Date.now();
|
|
276
|
+
var last = s.lastBlockedAt ? new Date(s.lastBlockedAt).getTime() : 0;
|
|
277
|
+
if (now - last > 2000) {
|
|
278
|
+
s.lastBlockedAt = new Date(now).toISOString();
|
|
279
|
+
writeState(s);
|
|
280
|
+
console.log('BLOCKED: Search memory before reading guidance files.');
|
|
281
|
+
}
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
284
|
+
case 'record-task-created': {
|
|
285
|
+
var s = readState();
|
|
286
|
+
s.tasksCreated = true;
|
|
287
|
+
s.taskCount = (s.taskCount || 0) + 1;
|
|
288
|
+
writeState(s);
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
case 'record-memory-searched': {
|
|
292
|
+
var s = readState();
|
|
293
|
+
s.memorySearched = true;
|
|
294
|
+
writeState(s);
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
case 'check-bash-memory': {
|
|
298
|
+
var cmd = process.env.TOOL_INPUT_command || '';
|
|
299
|
+
if (/semantic-search|memory search|memory retrieve|memory-search/.test(cmd)) {
|
|
300
|
+
var s = readState();
|
|
301
|
+
s.memorySearched = true;
|
|
302
|
+
writeState(s);
|
|
303
|
+
}
|
|
304
|
+
break;
|
|
305
|
+
}
|
|
306
|
+
case 'check-dangerous-command': {
|
|
307
|
+
var cmd = (process.env.TOOL_INPUT_command || '').toLowerCase();
|
|
308
|
+
for (var i = 0; i < DANGEROUS.length; i++) {
|
|
309
|
+
if (cmd.indexOf(DANGEROUS[i]) >= 0) {
|
|
310
|
+
console.log('[BLOCKED] Dangerous command: ' + DANGEROUS[i]);
|
|
311
|
+
process.exit(2);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
316
|
+
case 'prompt-reminder': {
|
|
317
|
+
var s = readState();
|
|
318
|
+
s.memorySearched = false;
|
|
319
|
+
var prompt = process.env.CLAUDE_USER_PROMPT || '';
|
|
320
|
+
s.memoryRequired = prompt.length >= 4 && !DIRECTIVE_RE.test(prompt) && (TASK_RE.test(prompt) || prompt.length > 80);
|
|
321
|
+
s.interactionCount = (s.interactionCount || 0) + 1;
|
|
322
|
+
writeState(s);
|
|
323
|
+
if (!s.tasksCreated) console.log('REMINDER: Use TaskCreate before spawning agents. Task tool is blocked until then.');
|
|
324
|
+
if (config.context_tracking) {
|
|
325
|
+
var ic = s.interactionCount;
|
|
326
|
+
if (ic > 30) console.log('Context: CRITICAL. Commit, store learnings, suggest new session.');
|
|
327
|
+
else if (ic > 20) console.log('Context: DEPLETED. Checkpoint progress. Recommend /compact or fresh session.');
|
|
328
|
+
else if (ic > 10) console.log('Context: MODERATE. Re-state goal before architectural decisions.');
|
|
329
|
+
}
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
case 'compact-guidance': {
|
|
333
|
+
console.log('Pre-Compact: Check CLAUDE.md for rules. Use memory search to recover context after compact.');
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
case 'session-reset': {
|
|
337
|
+
writeState({ tasksCreated: false, taskCount: 0, memorySearched: false, memoryRequired: true, interactionCount: 0, sessionStart: new Date().toISOString(), lastBlockedAt: null });
|
|
338
|
+
break;
|
|
339
|
+
}
|
|
340
|
+
default:
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
`;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Generate lightweight hook-handler.cjs — hook dispatch without CLI bootstrap.
|
|
347
|
+
* Handles routing, edit/task tracking, session lifecycle, and notifications.
|
|
348
|
+
* This replaces `npx flo hooks <command>` to avoid spawning a full CLI process.
|
|
349
|
+
*/
|
|
350
|
+
export function generateHookHandlerScript() {
|
|
351
|
+
return `#!/usr/bin/env node
|
|
352
|
+
'use strict';
|
|
353
|
+
var fs = require('fs');
|
|
354
|
+
var path = require('path');
|
|
355
|
+
|
|
356
|
+
var PROJECT_DIR = process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
357
|
+
var METRICS_FILE = path.join(PROJECT_DIR, '.claude-flow', 'metrics', 'learning.json');
|
|
358
|
+
var command = process.argv[2];
|
|
359
|
+
|
|
360
|
+
// Read stdin (Claude Code sends hook data as JSON)
|
|
361
|
+
function readStdin() {
|
|
362
|
+
if (process.stdin.isTTY) return Promise.resolve('');
|
|
363
|
+
return new Promise(function(resolve) {
|
|
364
|
+
var data = '';
|
|
365
|
+
var timer = setTimeout(function() {
|
|
366
|
+
process.stdin.removeAllListeners();
|
|
367
|
+
process.stdin.pause();
|
|
368
|
+
resolve(data);
|
|
369
|
+
}, 500);
|
|
370
|
+
process.stdin.setEncoding('utf8');
|
|
371
|
+
process.stdin.on('data', function(chunk) { data += chunk; });
|
|
372
|
+
process.stdin.on('end', function() { clearTimeout(timer); resolve(data); });
|
|
373
|
+
process.stdin.on('error', function() { clearTimeout(timer); resolve(data); });
|
|
374
|
+
process.stdin.resume();
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function bumpMetric(key) {
|
|
379
|
+
try {
|
|
380
|
+
var metrics = {};
|
|
381
|
+
if (fs.existsSync(METRICS_FILE)) metrics = JSON.parse(fs.readFileSync(METRICS_FILE, 'utf-8'));
|
|
382
|
+
metrics[key] = (metrics[key] || 0) + 1;
|
|
383
|
+
metrics.lastUpdated = new Date().toISOString();
|
|
384
|
+
var dir = path.dirname(METRICS_FILE);
|
|
385
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
386
|
+
fs.writeFileSync(METRICS_FILE, JSON.stringify(metrics, null, 2));
|
|
387
|
+
} catch (e) { /* non-fatal */ }
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
readStdin().then(function(stdinData) {
|
|
391
|
+
var hookInput = {};
|
|
392
|
+
if (stdinData && stdinData.trim()) {
|
|
393
|
+
try { hookInput = JSON.parse(stdinData); } catch (e) { /* ignore */ }
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
switch (command) {
|
|
397
|
+
case 'route': {
|
|
398
|
+
var prompt = hookInput.prompt || hookInput.command || process.env.PROMPT || '';
|
|
399
|
+
if (prompt) console.log('[INFO] Routing: ' + prompt.substring(0, 80));
|
|
400
|
+
else console.log('[INFO] Ready');
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
case 'pre-edit':
|
|
404
|
+
case 'post-edit':
|
|
405
|
+
bumpMetric('edits');
|
|
406
|
+
console.log('[OK] Edit recorded');
|
|
407
|
+
break;
|
|
408
|
+
case 'pre-task':
|
|
409
|
+
bumpMetric('tasks');
|
|
410
|
+
console.log('[OK] Task started');
|
|
411
|
+
break;
|
|
412
|
+
case 'post-task':
|
|
413
|
+
bumpMetric('tasksCompleted');
|
|
414
|
+
console.log('[OK] Task completed');
|
|
415
|
+
break;
|
|
416
|
+
case 'session-end':
|
|
417
|
+
console.log('[OK] Session ended');
|
|
418
|
+
break;
|
|
419
|
+
case 'notification':
|
|
420
|
+
// Silent — just acknowledge
|
|
421
|
+
break;
|
|
422
|
+
default:
|
|
423
|
+
if (command) console.log('[OK] Hook: ' + command);
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
`;
|
|
428
|
+
}
|
|
188
429
|
//# sourceMappingURL=helpers-generator.js.map
|
|
@@ -34,7 +34,7 @@ export function generateMCPConfig(options) {
|
|
|
34
34
|
const deferProps = config.toolDefer ? { toolDefer: 'deferred' } : {};
|
|
35
35
|
// Claude Flow MCP server (core)
|
|
36
36
|
if (config.claudeFlow) {
|
|
37
|
-
mcpServers['claude-flow'] = createMCPServerEntry(['
|
|
37
|
+
mcpServers['claude-flow'] = createMCPServerEntry(['moflo', 'mcp', 'start'], {
|
|
38
38
|
...npmEnv,
|
|
39
39
|
CLAUDE_FLOW_MODE: 'v3',
|
|
40
40
|
CLAUDE_FLOW_HOOKS_ENABLED: 'true',
|
|
@@ -67,7 +67,7 @@ export function generateMCPCommands(options) {
|
|
|
67
67
|
const commands = [];
|
|
68
68
|
const config = options.mcp;
|
|
69
69
|
if (config.claudeFlow) {
|
|
70
|
-
commands.push('claude mcp add claude-flow -- npx -y
|
|
70
|
+
commands.push('claude mcp add claude-flow -- npx -y moflo mcp start');
|
|
71
71
|
}
|
|
72
72
|
if (config.ruvSwarm) {
|
|
73
73
|
commands.push('claude mcp add ruv-swarm -- npx -y ruv-swarm mcp start');
|