moflo 4.9.19 → 4.9.21
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/commands/{simplify.md → flo-simplify.md} +4 -4
- package/.claude/guidance/shipped/moflo-agent-rules.md +172 -0
- package/.claude/guidance/shipped/moflo-claude-swarm-cohesion.md +73 -265
- package/.claude/guidance/shipped/moflo-cli-reference.md +6 -6
- package/.claude/guidance/shipped/moflo-core-guidance.md +66 -184
- package/.claude/guidance/shipped/moflo-cross-platform.md +1 -1
- package/.claude/guidance/shipped/moflo-error-handling.md +3 -3
- package/.claude/guidance/shipped/moflo-guidance-rules.md +17 -7
- package/.claude/guidance/shipped/moflo-memory-strategy.md +76 -182
- package/.claude/guidance/shipped/moflo-memorydb-maintenance.md +6 -8
- package/.claude/guidance/shipped/moflo-settings-injection.md +7 -9
- package/.claude/guidance/shipped/moflo-source-hygiene.md +5 -5
- package/.claude/guidance/shipped/moflo-spell-connectors.md +3 -4
- package/.claude/guidance/shipped/moflo-spell-custom-steps.md +3 -4
- package/.claude/guidance/shipped/moflo-spell-engine.md +40 -162
- package/.claude/guidance/shipped/moflo-spell-runner.md +134 -0
- package/.claude/guidance/shipped/moflo-spell-sandboxing.md +10 -57
- package/.claude/guidance/shipped/moflo-spell-troubleshooting.md +149 -0
- package/.claude/guidance/shipped/moflo-subagents.md +43 -114
- package/.claude/guidance/shipped/moflo-task-icons.md +4 -4
- package/.claude/guidance/shipped/moflo-user-facing-language.md +3 -3
- package/.claude/guidance/shipped/moflo-verbose-command-filtering.md +3 -3
- package/.claude/guidance/shipped/moflo-yaml-reference.md +4 -5
- package/.claude/helpers/gate.cjs +124 -14
- package/.claude/helpers/prompt-hook.mjs +4 -38
- package/.claude/helpers/simplify-classify.cjs +32 -11
- package/.claude/helpers/subagent-bootstrap.json +1 -1
- package/.claude/helpers/subagent-start.cjs +1 -1
- package/.claude/skills/connector-builder/SKILL.md +42 -429
- package/.claude/skills/connector-builder/templates/connector.md +189 -0
- package/.claude/skills/connector-builder/templates/step-command.md +176 -0
- package/.claude/skills/eldar/SKILL.md +7 -7
- package/.claude/skills/fl/SKILL.md +3 -3
- package/.claude/skills/fl/execution-modes.md +3 -3
- package/.claude/skills/fl/phases.md +3 -3
- package/.claude/skills/{simplify → flo-simplify}/SKILL.md +11 -11
- package/.claude/skills/guidance/SKILL.md +17 -9
- package/.claude/skills/memory-patterns/SKILL.md +1 -1
- package/.claude/skills/publish/SKILL.md +121 -36
- package/.claude/skills/reset-epic/SKILL.md +2 -2
- package/.claude/skills/spell-builder/SKILL.md +39 -226
- package/.claude/skills/spell-builder/architecture.md +1 -1
- package/.claude/skills/spell-builder/permissions.md +107 -0
- package/.claude/skills/spell-builder/preflight.md +101 -0
- package/.claude/skills/spell-schedule/SKILL.md +2 -3
- package/bin/gate.cjs +124 -14
- package/bin/prompt-hook.mjs +4 -38
- package/bin/session-start-launcher.mjs +66 -1
- package/bin/setup-project.mjs +63 -69
- package/bin/simplify-classify.cjs +32 -11
- package/dist/src/cli/commands/doctor-checks-deep.js +4 -0
- package/dist/src/cli/init/claudemd-generator.js +30 -33
- package/dist/src/cli/init/executor.js +28 -16
- package/dist/src/cli/init/helpers-generator.js +101 -51
- package/dist/src/cli/init/moflo-init.js +41 -114
- package/dist/src/cli/init/settings-generator.js +32 -14
- package/dist/src/cli/services/hook-block-hash.js +7 -2
- package/dist/src/cli/services/hook-wiring.js +86 -3
- package/dist/src/cli/services/subagent-bootstrap.js +1 -1
- package/dist/src/cli/version.js +1 -1
- package/package.json +2 -2
- package/scripts/post-install-bootstrap.mjs +19 -0
- package/.claude/guidance/shipped/moflo-session-start.md +0 -154
- package/.claude/guidance/shipped/moflo-spell-engine-architecture.md +0 -145
- package/.claude/skills/browser/SKILL.md +0 -204
- package/.claude/skills/github-code-review/SKILL.md +0 -1140
- package/.claude/skills/github-multi-repo/SKILL.md +0 -866
- package/.claude/skills/github-project-management/SKILL.md +0 -1272
- package/.claude/skills/github-release-management/SKILL.md +0 -1074
- package/.claude/skills/github-workflow-automation/SKILL.md +0 -1060
- package/.claude/skills/hive-mind-advanced/SKILL.md +0 -712
- package/.claude/skills/hooks-automation/SKILL.md +0 -1193
- package/.claude/skills/pair-programming/SKILL.md +0 -1202
- package/.claude/skills/performance-analysis/SKILL.md +0 -563
- package/.claude/skills/skill-builder/SKILL.md +0 -910
- package/.claude/skills/sparc-methodology/SKILL.md +0 -904
- package/.claude/skills/stream-chain/SKILL.md +0 -563
- package/.claude/skills/swarm-advanced/SKILL.md +0 -811
- package/.claude/skills/swarm-orchestration/SKILL.md +0 -179
- package/.claude/skills/verification-quality/SKILL.md +0 -649
- package/.claude/skills/worker-benchmarks/skill.md +0 -135
- package/.claude/skills/worker-integration/skill.md +0 -154
|
@@ -3,60 +3,57 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Generates ONLY the MoFlo section to inject into a project's CLAUDE.md.
|
|
5
5
|
* This must be minimal — just enough for Claude to work with moflo.
|
|
6
|
-
* All detailed docs live in .claude/guidance/
|
|
6
|
+
* All detailed docs live in .claude/guidance/moflo-core-guidance.md on consumer projects (synced from .claude/guidance/shipped/ inside node_modules/moflo).
|
|
7
7
|
*
|
|
8
8
|
* Principle: we are guests in the user's CLAUDE.md. Keep it small.
|
|
9
9
|
*/
|
|
10
10
|
const MARKER_START = '<!-- MOFLO:INJECTED:START -->';
|
|
11
11
|
const MARKER_END = '<!-- MOFLO:INJECTED:END -->';
|
|
12
|
+
// Legacy markers from earlier moflo versions — detected and replaced on re-injection.
|
|
13
|
+
// Single source of truth so moflo-init.ts and bin/setup-project.mjs stay in sync.
|
|
14
|
+
const LEGACY_MARKER_STARTS = [
|
|
15
|
+
'<!-- MOFLO:START -->',
|
|
16
|
+
'<!-- MOFLO:SUBAGENT-PROTOCOL:START -->',
|
|
17
|
+
];
|
|
18
|
+
const LEGACY_MARKER_ENDS = [
|
|
19
|
+
'<!-- MOFLO:END -->',
|
|
20
|
+
'<!-- MOFLO:SUBAGENT-PROTOCOL:END -->',
|
|
21
|
+
];
|
|
12
22
|
/**
|
|
13
23
|
* The single moflo section injected into CLAUDE.md.
|
|
14
|
-
* ~
|
|
24
|
+
* ~22 lines. Points to moflo-core-guidance.md for everything else.
|
|
15
25
|
*/
|
|
16
26
|
function mofloSection() {
|
|
17
27
|
return `${MARKER_START}
|
|
18
28
|
## MoFlo — AI Agent Orchestration
|
|
19
29
|
|
|
20
|
-
This project uses [MoFlo](https://github.com/eric-cielo/moflo) for AI-assisted development spells.
|
|
21
|
-
|
|
22
30
|
### FIRST ACTION ON EVERY PROMPT: Search Memory
|
|
23
31
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
### Spell Gates (enforced automatically)
|
|
27
|
-
|
|
28
|
-
- **Memory-first**: Must search memory before Glob/Grep/Read
|
|
29
|
-
- **TaskCreate-first**: Must call TaskCreate before spawning Agent tool
|
|
30
|
-
|
|
31
|
-
- **Task Icons**: \`TaskCreate\` MUST use ICON+[Role] format — see \`.claude/guidance/moflo-task-icons.md\`
|
|
32
|
+
Your first tool call MUST be \`mcp__moflo__memory_search\` — before any Glob/Grep/Read. Search \`guidance\`, \`patterns\`, and \`learnings\` every prompt; add \`code-map\` when navigating code, \`tests\` when looking for test inventory or coverage. When the user says "remember this", call \`mcp__moflo__memory_store\` with namespace \`learnings\`.
|
|
32
33
|
|
|
33
|
-
###
|
|
34
|
+
### Auto-enforced gates
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
| \`mcp__moflo__memory_search\` | Semantic search across indexed knowledge |
|
|
38
|
-
| \`mcp__moflo__memory_store\` | Store patterns and decisions |
|
|
36
|
+
- **TaskCreate-first**: Call \`TaskCreate\` before spawning the Agent tool
|
|
37
|
+
- **Task Icons**: \`TaskCreate\` entries MUST use ICON+[Role] format — see \`.claude/guidance/moflo-task-icons.md\`
|
|
39
38
|
|
|
40
|
-
###
|
|
39
|
+
### Tools
|
|
41
40
|
|
|
42
|
-
|
|
43
|
-
flo-search "[query]" --namespace guidance # Semantic search
|
|
44
|
-
flo doctor --fix # Health check
|
|
45
|
-
\`\`\`
|
|
41
|
+
Prefer MCP (\`mcp__moflo__*\` — memory, swarm, agent, task, hooks, hive-mind, neural) over the CLI. CLI binaries: \`flo\` (main), \`flo-search\` (semantic search), \`flo doctor --fix\` (heal). Full catalog: \`.claude/guidance/moflo-core-guidance.md\`.
|
|
46
42
|
|
|
47
43
|
### After upgrading MoFlo
|
|
48
44
|
|
|
49
|
-
After
|
|
45
|
+
After \`npm install\` touches moflo, check \`.moflo/restart-pending.json\` — if present, surface its \`message\` field to the user verbatim, then delete the file. (Claude Code only loads new hooks/MCP/launcher at session start.)
|
|
50
46
|
|
|
51
47
|
### Full Reference
|
|
52
48
|
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
49
|
+
- Universal agent rules (memory protocol, git/PR conventions, file org, build/test): \`.claude/guidance/moflo-agent-rules.md\`
|
|
50
|
+
- Subagent spawn protocol: \`.claude/guidance/moflo-subagents.md\`
|
|
51
|
+
- Task + swarm coordination: \`.claude/guidance/moflo-claude-swarm-cohesion.md\`
|
|
52
|
+
- CLI, hooks, swarm, memory, moflo.yaml: \`.claude/guidance/moflo-core-guidance.md\`
|
|
56
53
|
${MARKER_END}`;
|
|
57
54
|
}
|
|
58
55
|
// --- Public API ---
|
|
59
|
-
export { MARKER_START, MARKER_END };
|
|
56
|
+
export { MARKER_START, MARKER_END, LEGACY_MARKER_STARTS, LEGACY_MARKER_ENDS };
|
|
60
57
|
/**
|
|
61
58
|
* Generate the MoFlo section to inject into CLAUDE.md.
|
|
62
59
|
* Template parameter is accepted for backward compatibility but ignored —
|
|
@@ -73,12 +70,12 @@ export function generateMinimalClaudeMd(options) {
|
|
|
73
70
|
}
|
|
74
71
|
/** Available template names for CLI wizard (kept for backward compat, all produce same output) */
|
|
75
72
|
export const CLAUDE_MD_TEMPLATES = [
|
|
76
|
-
{ name: 'minimal', description: 'Recommended — memory search,
|
|
77
|
-
{ name: 'standard', description: 'Same as minimal (detailed docs in .claude/guidance/
|
|
78
|
-
{ name: 'full', description: 'Same as minimal (detailed docs in .claude/guidance/
|
|
79
|
-
{ name: 'security', description: 'Same as minimal (detailed docs in .claude/guidance/
|
|
80
|
-
{ name: 'performance', description: 'Same as minimal (detailed docs in .claude/guidance/
|
|
81
|
-
{ name: 'solo', description: 'Same as minimal (detailed docs in .claude/guidance/
|
|
73
|
+
{ name: 'minimal', description: 'Recommended — memory search, gates, tools, upgrade hint (~22 lines injected)' },
|
|
74
|
+
{ name: 'standard', description: 'Same as minimal (detailed docs in .claude/guidance/moflo-core-guidance.md)' },
|
|
75
|
+
{ name: 'full', description: 'Same as minimal (detailed docs in .claude/guidance/moflo-core-guidance.md)' },
|
|
76
|
+
{ name: 'security', description: 'Same as minimal (detailed docs in .claude/guidance/moflo-core-guidance.md)' },
|
|
77
|
+
{ name: 'performance', description: 'Same as minimal (detailed docs in .claude/guidance/moflo-core-guidance.md)' },
|
|
78
|
+
{ name: 'solo', description: 'Same as minimal (detailed docs in .claude/guidance/moflo-core-guidance.md)' },
|
|
82
79
|
];
|
|
83
80
|
export default generateClaudeMd;
|
|
84
81
|
//# sourceMappingURL=claudemd-generator.js.map
|
|
@@ -23,32 +23,44 @@ import { errorDetail } from '../shared/utils/error-detail.js';
|
|
|
23
23
|
/**
|
|
24
24
|
* Skills to copy based on configuration. Exported for integrity tests.
|
|
25
25
|
*/
|
|
26
|
+
// Skills installed into a consumer's `<root>/.claude/skills/` by `flo init`.
|
|
27
|
+
// Every entry must be a moflo-quality, consumer-runnable skill — verified to
|
|
28
|
+
// reference moflo CLI/MCP tools (not upstream `claude-flow` / `agentic-flow`
|
|
29
|
+
// cruft). New additions MUST pass the same audit, otherwise the drift-guard
|
|
30
|
+
// test (skills-classification-drift.test.ts) fails. See INTERNAL_SKILLS for
|
|
31
|
+
// skills that ship in the tarball but are deliberately NOT installed.
|
|
26
32
|
export const SKILLS_MAP = {
|
|
27
33
|
core: [
|
|
28
|
-
'
|
|
29
|
-
'
|
|
30
|
-
'
|
|
31
|
-
'hooks-automation',
|
|
32
|
-
'pair-programming',
|
|
33
|
-
'verification-quality',
|
|
34
|
-
'stream-chain',
|
|
35
|
-
'skill-builder',
|
|
34
|
+
'eldar',
|
|
35
|
+
'guidance',
|
|
36
|
+
'flo-simplify',
|
|
36
37
|
'reasoningbank-intelligence',
|
|
37
38
|
],
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
'
|
|
41
|
-
'
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
'
|
|
39
|
+
memory: [
|
|
40
|
+
'memory-patterns',
|
|
41
|
+
'memory-optimization',
|
|
42
|
+
'vector-search',
|
|
43
|
+
],
|
|
44
|
+
spells: [
|
|
45
|
+
'spell-builder',
|
|
46
|
+
'spell-schedule',
|
|
47
|
+
'connector-builder',
|
|
45
48
|
],
|
|
46
49
|
};
|
|
50
|
+
// Skills that ship in the npm tarball (under `node_modules/moflo/.claude/skills/`)
|
|
51
|
+
// but are deliberately NOT copied into consumer projects by `flo init`. Strictly
|
|
52
|
+
// moflo-internal dev tooling. The drift-guard test asserts every dir under
|
|
53
|
+
// `.claude/skills/` is classified in either SKILLS_MAP or INTERNAL_SKILLS (plus
|
|
54
|
+
// the special `flo` + `fl` install path handled in moflo-init.ts).
|
|
55
|
+
export const INTERNAL_SKILLS = [
|
|
56
|
+
'publish', // moflo's own /publish workflow — not consumer-relevant
|
|
57
|
+
'reset-epic', // moflo's own epic test-data reset — would torch a consumer's repo
|
|
58
|
+
];
|
|
47
59
|
/**
|
|
48
60
|
* Commands to copy based on configuration
|
|
49
61
|
*/
|
|
50
62
|
const COMMANDS_MAP = {
|
|
51
|
-
core: ['claude-flow-help.md', 'claude-flow-swarm.md', 'claude-flow-memory.md', 'simplify.md'],
|
|
63
|
+
core: ['claude-flow-help.md', 'claude-flow-swarm.md', 'claude-flow-memory.md', 'flo-simplify.md'],
|
|
52
64
|
analysis: [],
|
|
53
65
|
automation: [],
|
|
54
66
|
github: ['github'],
|
|
@@ -207,7 +207,7 @@ var path = require('path');
|
|
|
207
207
|
var PROJECT_DIR = (process.env.CLAUDE_PROJECT_DIR || process.cwd()).replace(/^\\/([a-z])\\//i, '$1:/');
|
|
208
208
|
var STATE_FILE = path.join(PROJECT_DIR, '.claude', 'workflow-state.json');
|
|
209
209
|
|
|
210
|
-
var STATE_DEFAULTS = { tasksCreated: false, taskCount: 0, memorySearched: false, memorySearchedBy: {}, memoryRequired: true, learningsStored: false, testsRun: false, simplifyRun: false, interactionCount: 0, sessionStart: null, lastBlockedAt: null };
|
|
210
|
+
var STATE_DEFAULTS = { tasksCreated: false, taskCount: 0, memorySearched: false, memorySearchedBy: {}, memoryRequired: true, learningsStored: false, testsRun: false, simplifyRun: false, interactionCount: 0, sessionStart: null, lastBlockedAt: null, lastNamespaceHint: '', lastNamespaceHintEmittedBy: {} };
|
|
211
211
|
|
|
212
212
|
function readState() {
|
|
213
213
|
try {
|
|
@@ -278,6 +278,63 @@ var EXEMPT = ['.claude/', '.claude\\\\', 'CLAUDE.md', 'MEMORY.md', 'workflow-sta
|
|
|
278
278
|
var DANGEROUS = ['rm -rf /', 'format c:', 'del /s /q c:\\\\', ':(){:|:&};:', 'mkfs.', '> /dev/sda'];
|
|
279
279
|
var DIRECTIVE_RE = /^(yes|no|yeah|yep|nope|sure|ok|okay|correct|right|exactly|perfect)\\b/i;
|
|
280
280
|
var TASK_RE = /\\b(fix|bug|error|implement|add|create|build|write|refactor|debug|test|feature|issue|security|optimi)\\b/i;
|
|
281
|
+
|
|
282
|
+
// Namespace classification (#931). Hint stored on workflow-state and emitted
|
|
283
|
+
// once by check-before-agent at Agent-spawn time — was emitted on every prompt
|
|
284
|
+
// before, costing ~40 tokens × every prompt × every consumer.
|
|
285
|
+
//
|
|
286
|
+
// SYNC: these regexes + classifyNamespaceHint + applyPromptStateReset are
|
|
287
|
+
// duplicated verbatim in bin/gate.cjs (canonical, synced to consumer
|
|
288
|
+
// .claude/helpers/gate.cjs by post-install-bootstrap). Any edit MUST be
|
|
289
|
+
// applied to both — this template is the fallback for the flo-init path
|
|
290
|
+
// where source helpers cannot be located, so it must keep parity.
|
|
291
|
+
var NS_LEARNINGS_RE = /\\b(remember|recall|insight|lesson learned|gotcha|post.?mortem)\\b|we (decid|agree|chose|said)/;
|
|
292
|
+
var NS_TEST_RE = /\\b(test|spec|coverage|tested|test case|test cases|tests for|spec for)\\b/;
|
|
293
|
+
var NS_EXPLICIT = [
|
|
294
|
+
{ pattern: /\\b(pattern|convention|best practice|style|coding rule)\\b/, ns: 'patterns', label: 'code patterns and conventions' },
|
|
295
|
+
{ pattern: /\\b(code.?map|file structure|project structure|directory)\\b/, ns: 'code-map', label: 'codebase navigation' },
|
|
296
|
+
];
|
|
297
|
+
var NS_PATTERN_RES = [/\\b(template|example|similar to|how do we|how should)\\b/];
|
|
298
|
+
var NS_DOMAIN_RES = [
|
|
299
|
+
/\\b(guidance|guide|docs|documentation|rules|how-to)\\b/,
|
|
300
|
+
/\\b(architecture|design|domain|tenant|migrat|schema|deploy)/,
|
|
301
|
+
/\\b(rule|requirement|constraint|compliance)\\b/,
|
|
302
|
+
];
|
|
303
|
+
var NS_NAV_RES = [
|
|
304
|
+
/\\b(find|where|which file|look up|locate|endpoint|route|url|path)\\b/,
|
|
305
|
+
/\\b(class|function|method|component|service|entity|module)\\b/,
|
|
306
|
+
];
|
|
307
|
+
|
|
308
|
+
function classifyNamespaceHint(promptText) {
|
|
309
|
+
var lower = (promptText || '').toLowerCase();
|
|
310
|
+
if (NS_TEST_RE.test(lower)) return 'Memory namespace hint: use "tests" for test inventory and coverage lookups.';
|
|
311
|
+
if (NS_LEARNINGS_RE.test(lower)) return 'Memory namespace hint: use "learnings" for user-directed decisions and distilled insights.';
|
|
312
|
+
for (var i = 0; i < NS_EXPLICIT.length; i++) {
|
|
313
|
+
if (NS_EXPLICIT[i].pattern.test(lower)) return 'Memory namespace hint: use "' + NS_EXPLICIT[i].ns + '" for ' + NS_EXPLICIT[i].label + '.';
|
|
314
|
+
}
|
|
315
|
+
for (var j = 0; j < NS_DOMAIN_RES.length; j++) {
|
|
316
|
+
if (NS_DOMAIN_RES[j].test(lower)) return 'Memory namespace hint: search "guidance" and "learnings" for domain rules and project decisions.';
|
|
317
|
+
}
|
|
318
|
+
for (var k = 0; k < NS_PATTERN_RES.length; k++) {
|
|
319
|
+
if (NS_PATTERN_RES[k].test(lower)) return 'Memory namespace hint: use "patterns" for code patterns and conventions.';
|
|
320
|
+
}
|
|
321
|
+
for (var m = 0; m < NS_NAV_RES.length; m++) {
|
|
322
|
+
if (NS_NAV_RES[m].test(lower)) return 'Memory namespace hint: use "code-map" for codebase navigation.';
|
|
323
|
+
}
|
|
324
|
+
return '';
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function applyPromptStateReset(state, promptText) {
|
|
328
|
+
state.memorySearched = false;
|
|
329
|
+
state.memorySearchedBy = {};
|
|
330
|
+
var DIRECTIVE_MAX_LEN = 20;
|
|
331
|
+
var escaped = /^@@\\s*/.test(promptText || '');
|
|
332
|
+
state.memoryRequired = !escaped && (promptText || '').length >= 4 && (TASK_RE.test(promptText || '') || (promptText || '').length > DIRECTIVE_MAX_LEN);
|
|
333
|
+
state.lastNamespaceHint = classifyNamespaceHint(promptText);
|
|
334
|
+
// Per-actor emission tracking — fresh window each prompt so subagents that
|
|
335
|
+
// spawn their own agents still see the hint on their first check-before-agent.
|
|
336
|
+
state.lastNamespaceHintEmittedBy = {};
|
|
337
|
+
}
|
|
281
338
|
var TEST_RUNNER_RE = /(?:^|[^a-z])(?:npm|yarn|pnpm|bun)\\s+(?:run\\s+)?(?:test|t)(?:[:\\s]|$)|\\b(?:npx|pnpx)\\s+(?:vitest|jest|mocha|ava|tap|jasmine|pytest)\\b|(?:^|;|&&|\\|\\|)\\s*(?:vitest|jest|pytest|mocha|jasmine|tap|ava)\\s|\\b(?:cargo|go|deno|dotnet|mvn)\\s+test\\b|\\bgradle\\w*\\s+test\\b/i;
|
|
282
339
|
var EDIT_RESET_SKIP_BOTH_RE = /\\.(md|markdown|txt|rst|adoc|lock|gitignore)$|(?:^|[\\\\\\/])(CHANGELOG(?:\\.md)?|\\.env\\.example|package-lock\\.json|pnpm-lock\\.yaml|yarn\\.lock|bun\\.lockb)$/i;
|
|
283
340
|
// Test files: invalidate testsRun but preserve simplifyRun (#908) — /simplify
|
|
@@ -290,6 +347,8 @@ switch (command) {
|
|
|
290
347
|
// Advisory only — agent spawning is never blocked.
|
|
291
348
|
// Memory-first enforcement happens at the scan/read gate layer.
|
|
292
349
|
// SubagentStart hook injects guidance directive into subagent context.
|
|
350
|
+
// #931 — TaskCreate REMINDER + namespace hint moved here from
|
|
351
|
+
// prompt-reminder so they emit only when Claude is about to spawn an Agent.
|
|
293
352
|
var s = readState();
|
|
294
353
|
if (config.task_create_first && !s.tasksCreated) {
|
|
295
354
|
process.stdout.write('REMINDER: Use TaskCreate before spawning agents. Task tool is blocked until then.\\n');
|
|
@@ -297,6 +356,23 @@ switch (command) {
|
|
|
297
356
|
if (config.memory_first && s.memoryRequired && !s.memorySearched) {
|
|
298
357
|
process.stdout.write('REMINDER: Search memory (mcp__moflo__memory_search) before spawning agents.\\n');
|
|
299
358
|
}
|
|
359
|
+
if (s.lastNamespaceHint) {
|
|
360
|
+
// Per-actor single-shot — each session_id emits the hint at most once
|
|
361
|
+
// per prompt. Subagents that spawn their own agents still see it on
|
|
362
|
+
// their first check-before-agent because their session_id is its own
|
|
363
|
+
// bucket. Falls back to a _legacy_ bucket when HOOK_SESSION_ID is
|
|
364
|
+
// missing (older Claude Code, direct CLI). The map clears on every
|
|
365
|
+
// new prompt via applyPromptStateReset.
|
|
366
|
+
var sid = process.env.HOOK_SESSION_ID || '';
|
|
367
|
+
var emittedBy = s.lastNamespaceHintEmittedBy || {};
|
|
368
|
+
var bucket = sid || '_legacy_';
|
|
369
|
+
if (!emittedBy[bucket]) {
|
|
370
|
+
process.stdout.write(s.lastNamespaceHint + '\\n');
|
|
371
|
+
emittedBy[bucket] = true;
|
|
372
|
+
s.lastNamespaceHintEmittedBy = emittedBy;
|
|
373
|
+
writeState(s);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
300
376
|
break;
|
|
301
377
|
}
|
|
302
378
|
case 'check-before-scan': {
|
|
@@ -363,7 +439,8 @@ switch (command) {
|
|
|
363
439
|
break;
|
|
364
440
|
}
|
|
365
441
|
case 'record-skill-run': {
|
|
366
|
-
|
|
442
|
+
var skName = (process.env.TOOL_INPUT_skill || '');
|
|
443
|
+
if (skName === 'simplify' || skName === 'flo-simplify') {
|
|
367
444
|
var s = readState();
|
|
368
445
|
if (!s.simplifyRun) {
|
|
369
446
|
s.simplifyRun = true;
|
|
@@ -397,7 +474,7 @@ switch (command) {
|
|
|
397
474
|
var s = readState();
|
|
398
475
|
var missing = [];
|
|
399
476
|
if (config.testing_gate && !s.testsRun) missing.push('tests have not run since the last code edit (run npm test, vitest, jest, pytest, or similar)');
|
|
400
|
-
if (config.simplify_gate && !s.simplifyRun) missing.push('/simplify has not run since the last code edit');
|
|
477
|
+
if (config.simplify_gate && !s.simplifyRun) missing.push('/flo-simplify has not run since the last code edit');
|
|
401
478
|
if (config.learnings_gate && !s.learningsStored) missing.push('learnings have not been stored (call mcp__moflo__memory_store)');
|
|
402
479
|
if (missing.length === 0) break;
|
|
403
480
|
process.stderr.write('BLOCKED: gh pr create requires the following before opening a PR:\\n');
|
|
@@ -422,18 +499,14 @@ switch (command) {
|
|
|
422
499
|
break;
|
|
423
500
|
}
|
|
424
501
|
case 'prompt-reminder': {
|
|
502
|
+
// Full per-prompt reset (first UserPromptSubmit hook via prompt-hook.mjs).
|
|
503
|
+
// Owns interactionCount + Context warnings. TaskCreate REMINDER and
|
|
504
|
+
// namespace hint moved to check-before-agent (#931).
|
|
425
505
|
var s = readState();
|
|
426
|
-
s.memorySearched = false;
|
|
427
|
-
// Wipe per-actor memory tracking too — a new user prompt is a fresh window
|
|
428
|
-
// for both parent AND any subagents the parent may spawn during this turn.
|
|
429
|
-
s.memorySearchedBy = {};
|
|
430
|
-
// learningsStored is session-scoped — once stored, it stays true until session reset.
|
|
431
|
-
// Resetting per-prompt caused false blocks when PR creation was on a later prompt.
|
|
432
506
|
var prompt = process.env.CLAUDE_USER_PROMPT || '';
|
|
433
|
-
s
|
|
507
|
+
applyPromptStateReset(s, prompt);
|
|
434
508
|
s.interactionCount = (s.interactionCount || 0) + 1;
|
|
435
509
|
writeState(s);
|
|
436
|
-
if (!s.tasksCreated) console.log('REMINDER: Use TaskCreate before spawning agents. Task tool is blocked until then.');
|
|
437
510
|
if (config.context_tracking) {
|
|
438
511
|
var ic = s.interactionCount;
|
|
439
512
|
if (ic > 30) console.log('Context: CRITICAL. Commit, store learnings, suggest new session.');
|
|
@@ -442,12 +515,25 @@ switch (command) {
|
|
|
442
515
|
}
|
|
443
516
|
break;
|
|
444
517
|
}
|
|
518
|
+
case 'prompt-state-reset': {
|
|
519
|
+
// Defensive safety-net (second UserPromptSubmit hook). Idempotent state
|
|
520
|
+
// reset only — no interactionCount increment, no emission. Ensures the
|
|
521
|
+
// per-prompt reset still happens if prompt-hook.mjs throws (#931). Skip
|
|
522
|
+
// the disk write when prompt-reminder already wrote the byte-identical
|
|
523
|
+
// post-reset state (the normal no-exception path).
|
|
524
|
+
var s = readState();
|
|
525
|
+
var prompt = process.env.CLAUDE_USER_PROMPT || '';
|
|
526
|
+
var before = JSON.stringify(s);
|
|
527
|
+
applyPromptStateReset(s, prompt);
|
|
528
|
+
if (JSON.stringify(s) !== before) writeState(s);
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
445
531
|
case 'compact-guidance': {
|
|
446
532
|
console.log('Pre-Compact: Check CLAUDE.md for rules. Use memory search to recover context after compact.');
|
|
447
533
|
break;
|
|
448
534
|
}
|
|
449
535
|
case 'session-reset': {
|
|
450
|
-
writeState({ tasksCreated: false, taskCount: 0, memorySearched: false, memorySearchedBy: {}, memoryRequired: true, learningsStored: false, testsRun: false, simplifyRun: false, interactionCount: 0, sessionStart: new Date().toISOString(), lastBlockedAt: null });
|
|
536
|
+
writeState({ tasksCreated: false, taskCount: 0, memorySearched: false, memorySearchedBy: {}, memoryRequired: true, learningsStored: false, testsRun: false, simplifyRun: false, interactionCount: 0, sessionStart: new Date().toISOString(), lastBlockedAt: null, lastNamespaceHint: '', lastNamespaceHintEmittedBy: {} });
|
|
451
537
|
break;
|
|
452
538
|
}
|
|
453
539
|
default:
|
|
@@ -560,45 +646,9 @@ try {
|
|
|
560
646
|
});
|
|
561
647
|
} catch (err) { output = (err && err.stdout) || ''; }
|
|
562
648
|
|
|
563
|
-
//
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
var LEARNINGS_HINTS = /\\b(remember|recall|insight|lesson learned|gotcha|post.?mortem)\\b|we (decid|agree|chose|said)/;
|
|
567
|
-
var TEST_HINTS = /\\b(test|spec|coverage|tested|test case|test cases|tests for|spec for)\\b/;
|
|
568
|
-
var EXPLICIT_NS = [
|
|
569
|
-
{ pattern: /\\b(pattern|convention|best practice|style|coding rule)\\b/, ns: 'patterns', label: 'code patterns and conventions' },
|
|
570
|
-
{ pattern: /\\b(code.?map|file structure|project structure|directory)\\b/, ns: 'code-map', label: 'codebase navigation' },
|
|
571
|
-
];
|
|
572
|
-
var PATTERN_HINTS = [/\\b(template|example|similar to|how do we|how should)\\b/];
|
|
573
|
-
var DOMAIN_HINTS = [
|
|
574
|
-
/\\b(guidance|guide|docs|documentation|rules|how-to)\\b/,
|
|
575
|
-
/\\b(architecture|design|domain|tenant|migrat|schema|deploy)/,
|
|
576
|
-
/\\b(rule|requirement|constraint|compliance)\\b/,
|
|
577
|
-
];
|
|
578
|
-
var NAV_PATTERNS = [
|
|
579
|
-
/\\b(find|where|which file|look up|locate|endpoint|route|url|path)\\b/,
|
|
580
|
-
/\\b(class|function|method|component|service|entity|module)\\b/,
|
|
581
|
-
];
|
|
582
|
-
|
|
583
|
-
var nsHint = '';
|
|
584
|
-
if (TEST_HINTS.test(lower)) {
|
|
585
|
-
nsHint = 'Memory namespace hint: use "tests" for test inventory and coverage lookups.';
|
|
586
|
-
} else if (LEARNINGS_HINTS.test(lower)) {
|
|
587
|
-
nsHint = 'Memory namespace hint: use "learnings" for user-directed decisions and distilled insights.';
|
|
588
|
-
} else {
|
|
589
|
-
var found = EXPLICIT_NS.find(function(e) { return e.pattern.test(lower); });
|
|
590
|
-
if (found) {
|
|
591
|
-
nsHint = 'Memory namespace hint: use "' + found.ns + '" for ' + found.label + '.';
|
|
592
|
-
} else if (DOMAIN_HINTS.some(function(p) { return p.test(lower); })) {
|
|
593
|
-
nsHint = 'Memory namespace hint: search "guidance" and "learnings" for domain rules and project decisions.';
|
|
594
|
-
} else if (PATTERN_HINTS.some(function(p) { return p.test(lower); })) {
|
|
595
|
-
nsHint = 'Memory namespace hint: use "patterns" for code patterns and conventions.';
|
|
596
|
-
} else if (NAV_PATTERNS.some(function(p) { return p.test(lower); })) {
|
|
597
|
-
nsHint = 'Memory namespace hint: use "code-map" for codebase navigation.';
|
|
598
|
-
}
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
var parts = [output.trim(), nsHint].filter(Boolean);
|
|
649
|
+
// #931 — Namespace hint classification moved into gate.cjs (computed by
|
|
650
|
+
// prompt-reminder, stored on workflow-state, emitted once by check-before-agent).
|
|
651
|
+
var parts = [output.trim()].filter(Boolean);
|
|
602
652
|
if (parts.length) process.stdout.write(parts.join('\\n') + '\\n');
|
|
603
653
|
process.exit(0);
|
|
604
654
|
`;
|
|
@@ -15,6 +15,8 @@ import { execSync } from 'child_process';
|
|
|
15
15
|
import { locateMofloRootPath } from '../services/moflo-require.js';
|
|
16
16
|
import { errorDetail } from '../shared/utils/error-detail.js';
|
|
17
17
|
import { discoverGuidanceDirs, discoverSrcDirs, discoverTestDirs, detectExtensions, renderMofloYaml, } from './moflo-yaml-template.js';
|
|
18
|
+
import { generateClaudeMd as generateMofloSection, MARKER_START, MARKER_END, LEGACY_MARKER_STARTS, LEGACY_MARKER_ENDS, } from './claudemd-generator.js';
|
|
19
|
+
import { DEFAULT_INIT_OPTIONS } from './types.js';
|
|
18
20
|
export { discoverTestDirs };
|
|
19
21
|
// ============================================================================
|
|
20
22
|
// Init
|
|
@@ -138,11 +140,10 @@ export async function initMoflo(options) {
|
|
|
138
140
|
steps.push(syncScripts(projectRoot, force));
|
|
139
141
|
// Step 6: .gitignore entries
|
|
140
142
|
steps.push(updateGitignore(projectRoot));
|
|
141
|
-
// Step 7:
|
|
142
|
-
|
|
143
|
-
// Step 8: Sync ALL shipped guidance docs from moflo to project
|
|
143
|
+
// Step 7: Sync ALL shipped guidance docs from moflo to project (includes
|
|
144
|
+
// moflo-subagents.md — no separate rename to moflo-bootstrap.md, see #939)
|
|
144
145
|
steps.push(...syncAllShippedGuidance(projectRoot, force));
|
|
145
|
-
// Step
|
|
146
|
+
// Step 8: Install global `flo` shim so bare `flo` command works without npx
|
|
146
147
|
steps.push(installGlobalFloShim(projectRoot));
|
|
147
148
|
return { steps };
|
|
148
149
|
}
|
|
@@ -222,6 +223,16 @@ function generateHooks(root, force, answers) {
|
|
|
222
223
|
{ "type": "command", "command": gateHook('check-dangerous-command'), "timeout": 2000 },
|
|
223
224
|
{ "type": "command", "command": gateHook('check-before-pr'), "timeout": 2000 }
|
|
224
225
|
]
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
// #931 — Advisory only; never blocks. TaskCreate REMINDER and the
|
|
229
|
+
// namespace hint moved here from UserPromptSubmit so they emit only
|
|
230
|
+
// when Claude is about to spawn an Agent — saves ~90 tokens × every
|
|
231
|
+
// prompt × every consumer. Routed via gate-hook.mjs so Claude Code's
|
|
232
|
+
// session_id is forwarded as HOOK_SESSION_ID, enabling per-actor
|
|
233
|
+
// single-shot emission (mirror of #879's record-memory-searched fix).
|
|
234
|
+
"matcher": "^Agent$",
|
|
235
|
+
"hooks": [{ "type": "command", "command": gateHook('check-before-agent'), "timeout": 2000 }]
|
|
225
236
|
}
|
|
226
237
|
],
|
|
227
238
|
"PostToolUse": [
|
|
@@ -252,12 +263,16 @@ function generateHooks(root, force, answers) {
|
|
|
252
263
|
"hooks": [{ "type": "command", "command": gateHook('record-skill-run'), "timeout": 2000 }]
|
|
253
264
|
},
|
|
254
265
|
{
|
|
266
|
+
// Anchored alternation — Claude Code anchors hook matchers (`^…$` semantics),
|
|
267
|
+
// so a bare `mcp__moflo__memory_` never matches any real MCP tool name and the
|
|
268
|
+
// hook silently no-ops (#929 regression). The explicit suffix list keeps the
|
|
269
|
+
// matcher narrow while catching every memory_* tool we ship.
|
|
255
270
|
// Use gateHook (not gate) so the wrapper forwards Claude Code's session_id as
|
|
256
271
|
// HOOK_SESSION_ID — record-memory-searched needs this to mark the per-actor map
|
|
257
272
|
// (memorySearchedBy[sid]) that check-before-read consults under #838's per-actor gating.
|
|
258
273
|
// Without it, the legacy boolean is set but the per-actor map stays empty, and the gate
|
|
259
274
|
// blocks every Read forever within the turn (issue #879).
|
|
260
|
-
"matcher": "mcp__moflo__memory_",
|
|
275
|
+
"matcher": "^mcp__moflo__memory_(search|retrieve|list|stats|store)$",
|
|
261
276
|
"hooks": [{ "type": "command", "command": gateHook('record-memory-searched'), "timeout": 3000 }]
|
|
262
277
|
},
|
|
263
278
|
{
|
|
@@ -272,11 +287,13 @@ function generateHooks(root, force, answers) {
|
|
|
272
287
|
]
|
|
273
288
|
},
|
|
274
289
|
{
|
|
275
|
-
// prompt-
|
|
276
|
-
// new prompt and reclassify memoryRequired. Without it, gate state leaks
|
|
277
|
-
// prompts. Separate hook entry so a prompt-hook.mjs exception doesn't
|
|
290
|
+
// prompt-state-reset is REQUIRED to reset memorySearched/memorySearchedBy on
|
|
291
|
+
// each new prompt and reclassify memoryRequired. Without it, gate state leaks
|
|
292
|
+
// across prompts. Separate hook entry so a prompt-hook.mjs exception doesn't
|
|
293
|
+
// skip the reset. Idempotent state reset only — no emission, no
|
|
294
|
+
// interactionCount increment (#931 dedupe).
|
|
278
295
|
"hooks": [
|
|
279
|
-
{ "type": "command", "command": gateHook('prompt-
|
|
296
|
+
{ "type": "command", "command": gateHook('prompt-state-reset'), "timeout": 3000 }
|
|
280
297
|
]
|
|
281
298
|
}
|
|
282
299
|
],
|
|
@@ -381,24 +398,16 @@ function generateSkill(root, force) {
|
|
|
381
398
|
// ============================================================================
|
|
382
399
|
// Step 4: CLAUDE.md MoFlo section
|
|
383
400
|
// ============================================================================
|
|
384
|
-
|
|
385
|
-
const MOFLO_MARKER = '<!-- MOFLO:INJECTED:START -->';
|
|
386
|
-
const MOFLO_MARKER_END = '<!-- MOFLO:INJECTED:END -->';
|
|
387
|
-
// Also detect legacy markers so we can replace them
|
|
388
|
-
const LEGACY_MARKERS = ['<!-- MOFLO:START -->', '<!-- MOFLO:SUBAGENT-PROTOCOL:START -->'];
|
|
389
|
-
const LEGACY_MARKERS_END = ['<!-- MOFLO:END -->', '<!-- MOFLO:SUBAGENT-PROTOCOL:END -->'];
|
|
390
|
-
function generateClaudeMd(root, force) {
|
|
401
|
+
function generateClaudeMd(root, _force) {
|
|
391
402
|
const claudeMdPath = path.join(root, 'CLAUDE.md');
|
|
392
403
|
let existing = '';
|
|
393
404
|
if (fs.existsSync(claudeMdPath)) {
|
|
394
405
|
existing = fs.readFileSync(claudeMdPath, 'utf-8');
|
|
395
|
-
//
|
|
396
|
-
const allStartMarkers = [
|
|
397
|
-
const allEndMarkers = [
|
|
406
|
+
// Strip current or legacy MoFlo block so we can re-inject the latest content.
|
|
407
|
+
const allStartMarkers = [MARKER_START, ...LEGACY_MARKER_STARTS];
|
|
408
|
+
const allEndMarkers = [MARKER_END, ...LEGACY_MARKER_ENDS];
|
|
398
409
|
for (let i = 0; i < allStartMarkers.length; i++) {
|
|
399
410
|
if (existing.includes(allStartMarkers[i])) {
|
|
400
|
-
// Always strip the existing section so we can re-inject the latest version.
|
|
401
|
-
// This ensures CLAUDE.md stays current when moflo updates its injected content.
|
|
402
411
|
const startIdx = existing.indexOf(allStartMarkers[i]);
|
|
403
412
|
const endIdx = existing.indexOf(allEndMarkers[i]);
|
|
404
413
|
if (endIdx > startIdx) {
|
|
@@ -407,60 +416,14 @@ function generateClaudeMd(root, force) {
|
|
|
407
416
|
}
|
|
408
417
|
}
|
|
409
418
|
}
|
|
410
|
-
//
|
|
411
|
-
|
|
412
|
-
const
|
|
413
|
-
${MOFLO_MARKER}
|
|
414
|
-
## MoFlo — AI Agent Orchestration
|
|
415
|
-
|
|
416
|
-
This project uses [MoFlo](https://github.com/eric-cielo/moflo) for AI-assisted development spells.
|
|
417
|
-
|
|
418
|
-
### FIRST ACTION ON EVERY PROMPT: Search Memory
|
|
419
|
-
|
|
420
|
-
Your first tool call for every new user prompt MUST be a memory search. Do this BEFORE Glob, Grep, Read, or any file exploration.
|
|
421
|
-
|
|
422
|
-
\`\`\`
|
|
423
|
-
mcp__moflo__memory_search — query: "<task description>", namespace: "guidance" or "patterns" or "learnings" or "code-map" or "tests"
|
|
424
|
-
\`\`\`
|
|
425
|
-
|
|
426
|
-
Search \`guidance\`, \`patterns\`, and \`learnings\` namespaces on every prompt. Search \`code-map\` when navigating the codebase, \`tests\` when looking for test inventory or coverage.
|
|
427
|
-
When the user asks you to remember something: \`mcp__moflo__memory_store\` with namespace \`learnings\`.
|
|
428
|
-
|
|
429
|
-
### Spell Gates (enforced automatically)
|
|
430
|
-
|
|
431
|
-
- **Memory-first**: Must search memory before Glob/Grep/Read
|
|
432
|
-
- **TaskCreate-first**: Must call TaskCreate before spawning Agent tool
|
|
433
|
-
- **Task Icons**: \`TaskCreate\` MUST use ICON+[Role] format — see \`.claude/guidance/moflo-task-icons.md\`
|
|
434
|
-
|
|
435
|
-
### MCP Tools (preferred over CLI)
|
|
436
|
-
|
|
437
|
-
| Tool | Purpose |
|
|
438
|
-
|------|---------|
|
|
439
|
-
| \`mcp__moflo__memory_search\` | Semantic search across indexed knowledge |
|
|
440
|
-
| \`mcp__moflo__memory_store\` | Store patterns and decisions |
|
|
441
|
-
| \`mcp__moflo__hooks_route\` | Route task to optimal agent type |
|
|
442
|
-
| \`mcp__moflo__hooks_pre-task\` | Record task start |
|
|
443
|
-
| \`mcp__moflo__hooks_post-task\` | Record task completion for learning |
|
|
444
|
-
|
|
445
|
-
### CLI Fallback
|
|
446
|
-
|
|
447
|
-
\`\`\`bash
|
|
448
|
-
flo-search "[query]" --namespace guidance # Semantic search
|
|
449
|
-
flo doctor --fix # Health check
|
|
450
|
-
\`\`\`
|
|
451
|
-
|
|
452
|
-
### Full Reference
|
|
453
|
-
|
|
454
|
-
For CLI commands, hooks, agents, swarm config, memory commands, and moflo.yaml options, see:
|
|
455
|
-
\`.claude/guidance/shipped/moflo-core-guidance.md\`
|
|
456
|
-
${MOFLO_MARKER_END}
|
|
457
|
-
`;
|
|
458
|
-
const finalContent = existing.trimEnd() + '\n' + mofloSection;
|
|
419
|
+
// Single source of truth: claudemd-generator.ts owns the section content.
|
|
420
|
+
const canonical = generateMofloSection(DEFAULT_INIT_OPTIONS);
|
|
421
|
+
const finalContent = existing.trimEnd() + '\n\n' + canonical;
|
|
459
422
|
fs.writeFileSync(claudeMdPath, finalContent, 'utf-8');
|
|
460
423
|
return {
|
|
461
424
|
name: 'CLAUDE.md',
|
|
462
425
|
status: existing ? 'updated' : 'created',
|
|
463
|
-
detail: 'MoFlo section injected (~
|
|
426
|
+
detail: 'MoFlo section injected (~22 lines)',
|
|
464
427
|
};
|
|
465
428
|
}
|
|
466
429
|
// ============================================================================
|
|
@@ -561,48 +524,10 @@ function updateGitignore(root) {
|
|
|
561
524
|
return { name: '.gitignore', status: 'updated', detail: `Added: ${toAdd.join(', ')}` };
|
|
562
525
|
}
|
|
563
526
|
// ============================================================================
|
|
564
|
-
// Step 7:
|
|
565
|
-
// Copies the agent bootstrap guidance to the project so subagents can read it
|
|
566
|
-
// from disk without requiring memory search.
|
|
567
|
-
// ============================================================================
|
|
568
|
-
function syncBootstrapGuidance(root, force) {
|
|
569
|
-
const guidanceDir = path.join(root, '.claude', 'guidance');
|
|
570
|
-
const targetFile = path.join(guidanceDir, 'moflo-bootstrap.md');
|
|
571
|
-
const candidates = [
|
|
572
|
-
path.join(root, 'node_modules', 'moflo', '.claude', 'guidance', 'shipped', 'moflo-subagents.md'),
|
|
573
|
-
// Anchor on moflo's own package root (covers dev + installed; #782).
|
|
574
|
-
...mofloRootJoin('.claude', 'guidance', 'shipped', 'moflo-subagents.md'),
|
|
575
|
-
];
|
|
576
|
-
const sourceFile = candidates.find(f => { try {
|
|
577
|
-
return fs.existsSync(f);
|
|
578
|
-
}
|
|
579
|
-
catch {
|
|
580
|
-
return false;
|
|
581
|
-
} });
|
|
582
|
-
if (!sourceFile) {
|
|
583
|
-
return { name: 'guidance/moflo-bootstrap.md', status: 'skipped', detail: 'Source bootstrap not found' };
|
|
584
|
-
}
|
|
585
|
-
// Check if target exists and is up to date
|
|
586
|
-
if (fs.existsSync(targetFile) && !force) {
|
|
587
|
-
if (!isStale(sourceFile, targetFile)) {
|
|
588
|
-
return { name: 'guidance/moflo-bootstrap.md', status: 'skipped', detail: 'Already up to date' };
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
// Read source and prepend header
|
|
592
|
-
const content = fs.readFileSync(sourceFile, 'utf-8');
|
|
593
|
-
const header = `<!-- AUTO-GENERATED by moflo init. Do not edit — changes will be overwritten on next init. -->\n<!-- Source: moflo/.claude/guidance/shipped/moflo-subagents.md -->\n<!-- To customize, create your own project-specific guidance in .claude/guidance/. -->\n\n`;
|
|
594
|
-
fs.mkdirSync(guidanceDir, { recursive: true });
|
|
595
|
-
fs.writeFileSync(targetFile, header + content, 'utf-8');
|
|
596
|
-
return {
|
|
597
|
-
name: 'guidance/moflo-bootstrap.md',
|
|
598
|
-
status: fs.existsSync(targetFile) ? 'updated' : 'created',
|
|
599
|
-
detail: 'Subagent bootstrap protocol'
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
// ============================================================================
|
|
603
|
-
// Step 8: Sync ALL shipped guidance docs
|
|
527
|
+
// Step 7: Sync ALL shipped guidance docs
|
|
604
528
|
// Discovers all .md files in moflo/.claude/guidance/shipped/ and copies them
|
|
605
|
-
// to project .claude/guidance
|
|
529
|
+
// to project .claude/guidance/ (including moflo-subagents.md — see #939, prior
|
|
530
|
+
// versions renamed it to moflo-bootstrap.md, creating a structural duplicate).
|
|
606
531
|
// ============================================================================
|
|
607
532
|
function syncAllShippedGuidance(root, force) {
|
|
608
533
|
const guidanceDir = path.join(root, '.claude', 'guidance');
|
|
@@ -621,8 +546,10 @@ function syncAllShippedGuidance(root, force) {
|
|
|
621
546
|
if (!shippedDir) {
|
|
622
547
|
return [{ name: 'guidance/shipped/*', status: 'skipped', detail: 'Shipped guidance directory not found' }];
|
|
623
548
|
}
|
|
624
|
-
// Discover all .md files
|
|
625
|
-
|
|
549
|
+
// Discover all shipped .md files dynamically — including moflo-subagents.md
|
|
550
|
+
// (#939: prior versions renamed it to moflo-bootstrap.md as a separate step,
|
|
551
|
+
// which left two copies of the same content on consumer disk).
|
|
552
|
+
const files = fs.readdirSync(shippedDir).filter(f => f.endsWith('.md'));
|
|
626
553
|
if (files.length === 0) {
|
|
627
554
|
return [{ name: 'guidance/shipped/*', status: 'skipped', detail: 'No shipped guidance files found' }];
|
|
628
555
|
}
|