moflo 4.9.20 → 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.
Files changed (82) hide show
  1. package/.claude/commands/{simplify.md → flo-simplify.md} +4 -4
  2. package/.claude/guidance/shipped/moflo-agent-rules.md +172 -0
  3. package/.claude/guidance/shipped/moflo-claude-swarm-cohesion.md +73 -265
  4. package/.claude/guidance/shipped/moflo-cli-reference.md +6 -6
  5. package/.claude/guidance/shipped/moflo-core-guidance.md +66 -184
  6. package/.claude/guidance/shipped/moflo-cross-platform.md +1 -1
  7. package/.claude/guidance/shipped/moflo-error-handling.md +3 -3
  8. package/.claude/guidance/shipped/moflo-guidance-rules.md +17 -7
  9. package/.claude/guidance/shipped/moflo-memory-strategy.md +76 -182
  10. package/.claude/guidance/shipped/moflo-memorydb-maintenance.md +6 -8
  11. package/.claude/guidance/shipped/moflo-settings-injection.md +7 -9
  12. package/.claude/guidance/shipped/moflo-source-hygiene.md +5 -5
  13. package/.claude/guidance/shipped/moflo-spell-connectors.md +3 -4
  14. package/.claude/guidance/shipped/moflo-spell-custom-steps.md +3 -4
  15. package/.claude/guidance/shipped/moflo-spell-engine.md +40 -162
  16. package/.claude/guidance/shipped/moflo-spell-runner.md +134 -0
  17. package/.claude/guidance/shipped/moflo-spell-sandboxing.md +10 -57
  18. package/.claude/guidance/shipped/moflo-spell-troubleshooting.md +149 -0
  19. package/.claude/guidance/shipped/moflo-subagents.md +43 -114
  20. package/.claude/guidance/shipped/moflo-task-icons.md +4 -4
  21. package/.claude/guidance/shipped/moflo-user-facing-language.md +3 -3
  22. package/.claude/guidance/shipped/moflo-verbose-command-filtering.md +3 -3
  23. package/.claude/guidance/shipped/moflo-yaml-reference.md +4 -5
  24. package/.claude/helpers/gate.cjs +124 -14
  25. package/.claude/helpers/prompt-hook.mjs +4 -38
  26. package/.claude/helpers/simplify-classify.cjs +32 -11
  27. package/.claude/helpers/subagent-bootstrap.json +1 -1
  28. package/.claude/helpers/subagent-start.cjs +1 -1
  29. package/.claude/skills/connector-builder/SKILL.md +42 -429
  30. package/.claude/skills/connector-builder/templates/connector.md +189 -0
  31. package/.claude/skills/connector-builder/templates/step-command.md +176 -0
  32. package/.claude/skills/eldar/SKILL.md +7 -7
  33. package/.claude/skills/fl/SKILL.md +3 -3
  34. package/.claude/skills/fl/execution-modes.md +3 -3
  35. package/.claude/skills/fl/phases.md +3 -3
  36. package/.claude/skills/{simplify → flo-simplify}/SKILL.md +11 -11
  37. package/.claude/skills/guidance/SKILL.md +17 -9
  38. package/.claude/skills/memory-patterns/SKILL.md +1 -1
  39. package/.claude/skills/publish/SKILL.md +121 -36
  40. package/.claude/skills/reset-epic/SKILL.md +2 -2
  41. package/.claude/skills/spell-builder/SKILL.md +39 -226
  42. package/.claude/skills/spell-builder/architecture.md +1 -1
  43. package/.claude/skills/spell-builder/permissions.md +107 -0
  44. package/.claude/skills/spell-builder/preflight.md +101 -0
  45. package/.claude/skills/spell-schedule/SKILL.md +2 -3
  46. package/bin/gate.cjs +124 -14
  47. package/bin/prompt-hook.mjs +4 -38
  48. package/bin/session-start-launcher.mjs +19 -1
  49. package/bin/setup-project.mjs +63 -69
  50. package/bin/simplify-classify.cjs +32 -11
  51. package/dist/src/cli/commands/doctor-checks-deep.js +4 -0
  52. package/dist/src/cli/init/claudemd-generator.js +30 -33
  53. package/dist/src/cli/init/executor.js +28 -16
  54. package/dist/src/cli/init/helpers-generator.js +101 -51
  55. package/dist/src/cli/init/moflo-init.js +41 -114
  56. package/dist/src/cli/init/settings-generator.js +32 -14
  57. package/dist/src/cli/services/hook-block-hash.js +7 -2
  58. package/dist/src/cli/services/hook-wiring.js +86 -3
  59. package/dist/src/cli/services/subagent-bootstrap.js +1 -1
  60. package/dist/src/cli/version.js +1 -1
  61. package/package.json +2 -2
  62. package/scripts/post-install-bootstrap.mjs +19 -0
  63. package/.claude/guidance/shipped/moflo-session-start.md +0 -154
  64. package/.claude/guidance/shipped/moflo-spell-engine-architecture.md +0 -145
  65. package/.claude/skills/browser/SKILL.md +0 -204
  66. package/.claude/skills/github-code-review/SKILL.md +0 -1140
  67. package/.claude/skills/github-multi-repo/SKILL.md +0 -866
  68. package/.claude/skills/github-project-management/SKILL.md +0 -1272
  69. package/.claude/skills/github-release-management/SKILL.md +0 -1074
  70. package/.claude/skills/github-workflow-automation/SKILL.md +0 -1060
  71. package/.claude/skills/hive-mind-advanced/SKILL.md +0 -712
  72. package/.claude/skills/hooks-automation/SKILL.md +0 -1193
  73. package/.claude/skills/pair-programming/SKILL.md +0 -1202
  74. package/.claude/skills/performance-analysis/SKILL.md +0 -563
  75. package/.claude/skills/skill-builder/SKILL.md +0 -910
  76. package/.claude/skills/sparc-methodology/SKILL.md +0 -904
  77. package/.claude/skills/stream-chain/SKILL.md +0 -563
  78. package/.claude/skills/swarm-advanced/SKILL.md +0 -811
  79. package/.claude/skills/swarm-orchestration/SKILL.md +0 -179
  80. package/.claude/skills/verification-quality/SKILL.md +0 -649
  81. package/.claude/skills/worker-benchmarks/skill.md +0 -135
  82. 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/shipped/moflo-core-guidance.md (copied at install).
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
- * ~40 lines. Points to moflo-core-guidance.md for everything else.
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
- MUST call \`mcp__moflo__memory_search\` BEFORE any Glob/Grep/Read/file exploration. Namespaces: \`guidance\`+\`patterns\`+\`learnings\` every prompt; \`code-map\` when navigating code; \`tests\` when looking for test inventory or coverage. When the user says "remember this": \`mcp__moflo__memory_store\` with namespace \`learnings\`.
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
- ### MCP Tools (preferred over CLI)
34
+ ### Auto-enforced gates
34
35
 
35
- | Tool | Purpose |
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
- ### CLI Fallback
39
+ ### Tools
41
40
 
42
- \`\`\`bash
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 running \`npm install moflo@*\` (or \`npm install\` that touches moflo), check \`.moflo/restart-pending.json\`. If it exists, surface the \`message\` field to the user verbatim (Claude Code can't pick up new hooks/MCP tools/launcher mid-session — they're only loaded at session-start), then delete the file.
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
- - **Subagents protocol:** \`.claude/guidance/shipped/moflo-subagents.md\`
54
- - **Task + swarm coordination:** \`.claude/guidance/shipped/moflo-claude-swarm-cohesion.md\`
55
- - **CLI, hooks, swarm, memory, moflo.yaml:** \`.claude/guidance/shipped/moflo-core-guidance.md\`
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, spell gates, MCP tools (~40 lines injected)' },
77
- { name: 'standard', description: 'Same as minimal (detailed docs in .claude/guidance/shipped/moflo-core-guidance.md)' },
78
- { name: 'full', description: 'Same as minimal (detailed docs in .claude/guidance/shipped/moflo-core-guidance.md)' },
79
- { name: 'security', description: 'Same as minimal (detailed docs in .claude/guidance/shipped/moflo-core-guidance.md)' },
80
- { name: 'performance', description: 'Same as minimal (detailed docs in .claude/guidance/shipped/moflo-core-guidance.md)' },
81
- { name: 'solo', description: 'Same as minimal (detailed docs in .claude/guidance/shipped/moflo-core-guidance.md)' },
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
- 'swarm-orchestration',
29
- 'swarm-advanced',
30
- 'sparc-methodology',
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
- browser: ['browser'],
39
- github: [
40
- 'github-code-review',
41
- 'github-multi-repo',
42
- 'github-project-management',
43
- 'github-release-management',
44
- 'github-workflow-automation',
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
- if ((process.env.TOOL_INPUT_skill || '') === 'simplify') {
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.memoryRequired = prompt.length >= 4 && !DIRECTIVE_RE.test(prompt) && (TASK_RE.test(prompt) || prompt.length > 80);
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
- // Classify prompt for namespace hint
564
- var lower = userPrompt.toLowerCase();
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: .claude/guidance/moflo-bootstrap.md (subagent bootstrap protocol)
142
- steps.push(syncBootstrapGuidance(projectRoot, force));
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 9: Install global `flo` shim so bare `flo` command works without npx
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-reminder is REQUIRED to reset memorySearched/memorySearchedBy on each
276
- // new prompt and reclassify memoryRequired. Without it, gate state leaks across
277
- // prompts. Separate hook entry so a prompt-hook.mjs exception doesn't skip the reset.
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-reminder'), "timeout": 3000 }
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
- // Markers for idempotent CLAUDE.md injection — keep in sync with claudemd-generator.ts
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
- // Check for current or legacy markers
396
- const allStartMarkers = [MOFLO_MARKER, ...LEGACY_MARKERS];
397
- const allEndMarkers = [MOFLO_MARKER_END, ...LEGACY_MARKERS_END];
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
- // Minimal injection just enough for Claude to work with moflo.
411
- // All detailed docs live in .claude/guidance/shipped/moflo-core-guidance.md.
412
- const mofloSection = `
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 (~35 lines)',
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: .claude/guidance/moflo-bootstrap.md
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/. Skips moflo-subagents.md (handled by Step 7).
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, skip moflo-subagents.md (synced separately as moflo-bootstrap.md)
625
- const files = fs.readdirSync(shippedDir).filter(f => f.endsWith('.md') && f !== 'moflo-subagents.md');
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
  }