moflo 4.9.0-rc.9 → 4.9.1

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 (125) hide show
  1. package/.claude/helpers/gate-hook.mjs +7 -0
  2. package/.claude/helpers/gate.cjs +40 -11
  3. package/.claude/helpers/statusline.cjs +66 -23
  4. package/.claude/helpers/subagent-bootstrap.json +3 -0
  5. package/.claude/helpers/subagent-start.cjs +58 -22
  6. package/.claude/skills/swarm-advanced/SKILL.md +77 -0
  7. package/README.md +26 -21
  8. package/bin/gate-hook.mjs +7 -0
  9. package/bin/gate.cjs +40 -11
  10. package/bin/session-start-launcher.mjs +72 -29
  11. package/dist/src/cli/commands/analyze.js +10 -9
  12. package/dist/src/cli/commands/appliance-advanced.js +9 -11
  13. package/dist/src/cli/commands/appliance.js +7 -9
  14. package/dist/src/cli/commands/benchmark.js +4 -3
  15. package/dist/src/cli/commands/daemon.js +21 -25
  16. package/dist/src/cli/commands/deployment.js +1 -1
  17. package/dist/src/cli/commands/diagnose.js +10 -9
  18. package/dist/src/cli/commands/doctor-checks-deep.js +37 -21
  19. package/dist/src/cli/commands/doctor-checks-functional-shared.js +111 -0
  20. package/dist/src/cli/commands/doctor-checks-memory-access.js +307 -0
  21. package/dist/src/cli/commands/doctor-checks-swarm.js +364 -0
  22. package/dist/src/cli/commands/doctor.js +191 -56
  23. package/dist/src/cli/commands/embeddings.js +21 -22
  24. package/dist/src/cli/commands/epic.js +51 -26
  25. package/dist/src/cli/commands/github.js +12 -11
  26. package/dist/src/cli/commands/guidance.js +14 -13
  27. package/dist/src/cli/commands/hive-mind.js +10 -9
  28. package/dist/src/cli/commands/hooks.js +14 -13
  29. package/dist/src/cli/commands/init.js +11 -10
  30. package/dist/src/cli/commands/issues.js +4 -3
  31. package/dist/src/cli/commands/memory.js +10 -9
  32. package/dist/src/cli/commands/neural.js +13 -12
  33. package/dist/src/cli/commands/process.js +2 -2
  34. package/dist/src/cli/commands/progress.js +2 -1
  35. package/dist/src/cli/commands/route.js +10 -9
  36. package/dist/src/cli/commands/session.js +7 -7
  37. package/dist/src/cli/commands/start.js +3 -2
  38. package/dist/src/cli/commands/status.js +4 -4
  39. package/dist/src/cli/commands/task.js +1 -1
  40. package/dist/src/cli/commands/update.js +4 -4
  41. package/dist/src/cli/embeddings/fastembed-embedding-service.js +4 -3
  42. package/dist/src/cli/embeddings/fastembed-inline/model-loader.js +27 -5
  43. package/dist/src/cli/embeddings/migration/migrate-store.js +3 -2
  44. package/dist/src/cli/epic/index.js +1 -2
  45. package/dist/src/cli/epic/types.js +1 -6
  46. package/dist/src/cli/guidance/hooks.js +3 -2
  47. package/dist/src/cli/hooks/daemons/index.js +3 -2
  48. package/dist/src/cli/hooks/executor/index.js +2 -1
  49. package/dist/src/cli/hooks/workers/index.js +2 -1
  50. package/dist/src/cli/hooks/workers/session-hook.js +2 -1
  51. package/dist/src/cli/index.js +17 -8
  52. package/dist/src/cli/init/executor.js +116 -89
  53. package/dist/src/cli/init/helpers-generator.js +41 -11
  54. package/dist/src/cli/init/moflo-init.js +46 -52
  55. package/dist/src/cli/mcp-client.js +2 -1
  56. package/dist/src/cli/mcp-server.js +2 -1
  57. package/dist/src/cli/mcp-tools/agent-tools.js +332 -211
  58. package/dist/src/cli/mcp-tools/coordinator-views.js +23 -0
  59. package/dist/src/cli/mcp-tools/github-tools.js +2 -1
  60. package/dist/src/cli/mcp-tools/hive-mind-tools.js +145 -49
  61. package/dist/src/cli/mcp-tools/hooks-tools.js +8 -6
  62. package/dist/src/cli/mcp-tools/json-store.js +9 -6
  63. package/dist/src/cli/mcp-tools/memory-tools.js +4 -1
  64. package/dist/src/cli/mcp-tools/neural-tools.js +4 -2
  65. package/dist/src/cli/mcp-tools/session-tools.js +11 -7
  66. package/dist/src/cli/mcp-tools/spell-tools.js +4 -7
  67. package/dist/src/cli/mcp-tools/swarm-coordinator-singleton.js +119 -0
  68. package/dist/src/cli/mcp-tools/swarm-scale-handler.js +211 -0
  69. package/dist/src/cli/mcp-tools/swarm-tools.js +208 -27
  70. package/dist/src/cli/mcp-tools/task-tools.js +299 -166
  71. package/dist/src/cli/memory/bridge-core.js +2 -1
  72. package/dist/src/cli/memory/bridge-embedder.js +2 -1
  73. package/dist/src/cli/memory/bridge-entries.js +25 -14
  74. package/dist/src/cli/memory/controller-registry.js +3 -2
  75. package/dist/src/cli/memory/controllers/nightly-learner.js +2 -1
  76. package/dist/src/cli/memory/ewc-consolidation.js +2 -1
  77. package/dist/src/cli/memory/intelligence.js +2 -1
  78. package/dist/src/cli/memory/memory-bridge.js +3 -1
  79. package/dist/src/cli/memory/memory-initializer.js +9 -8
  80. package/dist/src/cli/parser.js +27 -40
  81. package/dist/src/cli/plugins/manager.js +5 -4
  82. package/dist/src/cli/runtime/headless.js +2 -1
  83. package/dist/src/cli/services/daemon-dashboard.js +4 -3
  84. package/dist/src/cli/services/daemon-readiness.js +5 -8
  85. package/dist/src/cli/services/daemon-service.js +12 -7
  86. package/dist/src/cli/services/daemon-spell-executor.js +3 -2
  87. package/dist/src/cli/services/headless-worker-executor.js +2 -1
  88. package/dist/src/cli/services/index.js +2 -0
  89. package/dist/src/cli/services/moflo-require.js +3 -0
  90. package/dist/src/cli/services/movector-training.js +9 -16
  91. package/dist/src/cli/services/subagent-bootstrap.js +57 -0
  92. package/dist/src/cli/services/worker-daemon.js +5 -4
  93. package/dist/src/cli/services/worker-queue.js +4 -3
  94. package/dist/src/cli/shared/mcp/tool-registry.js +2 -1
  95. package/dist/src/cli/shared/plugins/official/maestro-plugin.js +3 -2
  96. package/dist/src/cli/shared/utils/error-detail.js +18 -0
  97. package/dist/src/cli/spells/commands/composite-command.js +2 -1
  98. package/dist/src/cli/spells/commands/github-command.js +5 -5
  99. package/dist/src/cli/spells/connectors/github-cli.js +32 -35
  100. package/dist/src/cli/spells/connectors/http-tool.js +2 -1
  101. package/dist/src/cli/spells/connectors/imap.js +2 -1
  102. package/dist/src/cli/spells/connectors/slack.js +2 -1
  103. package/dist/src/cli/spells/core/connector-accessor.js +2 -1
  104. package/dist/src/cli/spells/core/dry-run-validator.js +4 -2
  105. package/dist/src/cli/spells/core/gated-connector-accessor.js +2 -1
  106. package/dist/src/cli/spells/core/interpolation.js +7 -0
  107. package/dist/src/cli/spells/core/platform-sandbox.js +80 -59
  108. package/dist/src/cli/spells/core/prerequisite-checker.js +8 -3
  109. package/dist/src/cli/spells/core/rollback-orchestrator.js +2 -1
  110. package/dist/src/cli/spells/core/runner.js +19 -5
  111. package/dist/src/cli/spells/core/shell.js +19 -1
  112. package/dist/src/cli/spells/core/step-executor.js +4 -3
  113. package/dist/src/cli/spells/factory/runner-factory.js +2 -1
  114. package/dist/src/cli/spells/loaders/definition-loader.js +2 -1
  115. package/dist/src/cli/spells/loaders/directory-step-loader.js +2 -1
  116. package/dist/src/cli/spells/loaders/npm-step-loader.js +2 -1
  117. package/dist/src/cli/spells/registry/connector-registry.js +3 -2
  118. package/dist/src/cli/spells/scheduler/scheduler.js +2 -1
  119. package/dist/src/cli/swarm/swarm-persistence.js +144 -0
  120. package/dist/src/cli/swarm/unified-coordinator.js +260 -66
  121. package/dist/src/cli/transfer/ipfs/client.js +3 -2
  122. package/dist/src/cli/transfer/serialization/cfp.js +2 -1
  123. package/dist/src/cli/version.js +1 -1
  124. package/package.json +2 -2
  125. package/dist/src/cli/epic/execution-order.js +0 -58
@@ -25,6 +25,13 @@ try { if (stdinData.trim()) hookContext = JSON.parse(stdinData); } catch (e) {}
25
25
  // Pass tool info as env vars for gate.cjs
26
26
  var env = Object.assign({}, process.env);
27
27
  if (hookContext.tool_name) env.TOOL_NAME = hookContext.tool_name;
28
+ // Forward Claude Code's session_id so gate.cjs can enforce memory-first
29
+ // per-actor (#838) — each spawned subagent gets its own session_id, so a
30
+ // shared workflow-state.json no longer lets one subagent's directive be
31
+ // silently satisfied by the parent's earlier search.
32
+ if (typeof hookContext.session_id === 'string' && hookContext.session_id) {
33
+ env.HOOK_SESSION_ID = hookContext.session_id;
34
+ }
28
35
  if (hookContext.tool_input && typeof hookContext.tool_input === 'object') {
29
36
  Object.keys(hookContext.tool_input).forEach(function(key) {
30
37
  if (typeof hookContext.tool_input[key] === 'string') {
@@ -6,7 +6,37 @@ var path = require('path');
6
6
  var PROJECT_DIR = (process.env.CLAUDE_PROJECT_DIR || process.cwd()).replace(/^\/([a-z])\//i, '$1:/');
7
7
  var STATE_FILE = path.join(PROJECT_DIR, '.claude', 'workflow-state.json');
8
8
 
9
- var STATE_DEFAULTS = { tasksCreated: false, taskCount: 0, memorySearched: false, memoryRequired: true, learningsStored: false, testsRun: false, simplifyRun: false, interactionCount: 0, sessionStart: null, lastBlockedAt: null };
9
+ var STATE_DEFAULTS = { tasksCreated: false, taskCount: 0, memorySearched: false, memorySearchedBy: {}, memoryRequired: true, learningsStored: false, testsRun: false, simplifyRun: false, interactionCount: 0, sessionStart: null, lastBlockedAt: null };
10
+
11
+ // Per-actor memory-search tracking (#838). The legacy `memorySearched` boolean
12
+ // is session-wide, so once the parent searches memory, every spawned subagent
13
+ // inherits the satisfied flag and the directive's "WILL BLOCK" promise becomes
14
+ // false. When gate-hook.mjs forwards Claude Code's stdin `session_id` as
15
+ // HOOK_SESSION_ID, prefer the per-session map so each subagent must search
16
+ // memory itself before its first Glob/Grep/Read. Falls back to the legacy
17
+ // boolean when no session id is present (CLI invocations, tests, older hosts).
18
+ function isMemorySearchedFor(state) {
19
+ var sid = process.env.HOOK_SESSION_ID || '';
20
+ if (sid) {
21
+ var map = state.memorySearchedBy || {};
22
+ return map[sid] === true;
23
+ }
24
+ return state.memorySearched === true;
25
+ }
26
+
27
+ // Stamp the legacy bool plus (when HOOK_SESSION_ID is set) the per-actor map.
28
+ // Returns true if anything actually changed — callers gate writeState() on it
29
+ // to avoid redundant fsyncs in tight bash-memory loops.
30
+ function markMemorySearched(state) {
31
+ var sid = process.env.HOOK_SESSION_ID || '';
32
+ var changed = false;
33
+ if (state.memorySearched !== true) { state.memorySearched = true; changed = true; }
34
+ if (sid) {
35
+ if (!state.memorySearchedBy) state.memorySearchedBy = {};
36
+ if (state.memorySearchedBy[sid] !== true) { state.memorySearchedBy[sid] = true; changed = true; }
37
+ }
38
+ return changed;
39
+ }
10
40
 
11
41
  function readState() {
12
42
  try {
@@ -48,7 +78,7 @@ function loadGateConfig() {
48
78
  var config = loadGateConfig();
49
79
  var command = process.argv[2];
50
80
 
51
- var EXEMPT = ['.claude/', '.claude\\', 'CLAUDE.md', 'MEMORY.md', 'workflow-state', 'node_modules'];
81
+ var EXEMPT = ['.claude/', '.claude\\', 'CLAUDE.md', 'MEMORY.md', 'workflow-state', 'node_modules', 'moflo.yaml'];
52
82
  var DANGEROUS = ['rm -rf /', 'format c:', 'del /s /q c:\\', ':(){:|:&};:', 'mkfs.', '> /dev/sda'];
53
83
  var DIRECTIVE_RE = /^(yes|no|yeah|yep|nope|sure|ok|okay|correct|right|exactly|perfect)\b/i;
54
84
  var TASK_RE = /\b(fix|bug|error|implement|add|create|build|write|refactor|debug|test|feature|issue|security|optimi)\b/i;
@@ -77,7 +107,7 @@ switch (command) {
77
107
  case 'check-before-scan': {
78
108
  if (!config.memory_first) break;
79
109
  var s = readState();
80
- if (s.memorySearched || !s.memoryRequired) break;
110
+ if (!s.memoryRequired || isMemorySearchedFor(s)) break;
81
111
  var target = (process.env.TOOL_INPUT_pattern || '') + ' ' + (process.env.TOOL_INPUT_path || '');
82
112
  if (EXEMPT.some(function(p) { return target.indexOf(p) >= 0; })) break;
83
113
  process.stderr.write('BLOCKED: Search memory before exploring files. Use mcp__moflo__memory_search.\n');
@@ -86,7 +116,7 @@ switch (command) {
86
116
  case 'check-before-read': {
87
117
  if (!config.memory_first) break;
88
118
  var s = readState();
89
- if (s.memorySearched || !s.memoryRequired) break;
119
+ if (!s.memoryRequired || isMemorySearchedFor(s)) break;
90
120
  var fp = process.env.TOOL_INPUT_file_path || '';
91
121
  var isGuidance = fp.indexOf('.claude/guidance/') >= 0 || fp.indexOf('.claude\\guidance\\') >= 0;
92
122
  if (!isGuidance && EXEMPT.some(function(p) { return fp.indexOf(p) >= 0; })) break;
@@ -102,18 +132,14 @@ switch (command) {
102
132
  }
103
133
  case 'record-memory-searched': {
104
134
  var s = readState();
105
- if (!s.memorySearched) {
106
- s.memorySearched = true;
107
- writeState(s);
108
- }
135
+ if (markMemorySearched(s)) writeState(s);
109
136
  break;
110
137
  }
111
138
  case 'check-bash-memory': {
112
139
  var cmd = process.env.TOOL_INPUT_command || '';
113
140
  if (/semantic-search|memory search|memory retrieve|memory-search/.test(cmd)) {
114
141
  var s = readState();
115
- s.memorySearched = true;
116
- writeState(s);
142
+ if (markMemorySearched(s)) writeState(s);
117
143
  }
118
144
  break;
119
145
  }
@@ -196,6 +222,9 @@ switch (command) {
196
222
  case 'prompt-reminder': {
197
223
  var s = readState();
198
224
  s.memorySearched = false;
225
+ // Wipe per-actor memory tracking too — a new user prompt is a fresh window
226
+ // for both parent AND any subagents the parent may spawn during this turn.
227
+ s.memorySearchedBy = {};
199
228
  // learningsStored is session-scoped — once stored, it stays true until session reset.
200
229
  // Resetting per-prompt caused false blocks when PR creation was on a later prompt.
201
230
  var prompt = process.env.CLAUDE_USER_PROMPT || '';
@@ -218,7 +247,7 @@ switch (command) {
218
247
  break;
219
248
  }
220
249
  case 'session-reset': {
221
- writeState({ tasksCreated: false, taskCount: 0, memorySearched: false, memoryRequired: true, learningsStored: false, testsRun: false, simplifyRun: false, interactionCount: 0, sessionStart: new Date().toISOString(), lastBlockedAt: null });
250
+ writeState({ tasksCreated: false, taskCount: 0, memorySearched: false, memorySearchedBy: {}, memoryRequired: true, learningsStored: false, testsRun: false, simplifyRun: false, interactionCount: 0, sessionStart: new Date().toISOString(), lastBlockedAt: null });
222
251
  break;
223
252
  }
224
253
  default:
@@ -382,7 +382,7 @@ function getSwarmStatus() {
382
382
  function getSystemMetrics() {
383
383
  const memoryMB = Math.floor(process.memoryUsage().heapUsed / 1024 / 1024);
384
384
  const learning = getLearningStats();
385
- const agentdb = getAgentDBStats();
385
+ const embeddings = getEmbeddingsStats();
386
386
 
387
387
  // Intelligence from learning.json
388
388
  const learningData = readJSON(path.join(CWD, '.moflo', 'metrics', 'learning.json'));
@@ -393,7 +393,7 @@ function getSystemMetrics() {
393
393
  intelligencePct = Math.min(100, Math.floor(learningData.intelligence.score));
394
394
  } else {
395
395
  const fromPatterns = learning.patterns > 0 ? Math.min(100, Math.floor(learning.patterns / 10)) : 0;
396
- const fromVectors = agentdb.vectorCount > 0 ? Math.min(100, Math.floor(agentdb.vectorCount / 100)) : 0;
396
+ const fromVectors = embeddings.vectorCount > 0 ? Math.min(100, Math.floor(embeddings.vectorCount / 100)) : 0;
397
397
  intelligencePct = Math.max(fromPatterns, fromVectors);
398
398
  }
399
399
 
@@ -423,7 +423,7 @@ function getSystemMetrics() {
423
423
  subAgents = activityData.processes.estimated_agents;
424
424
  }
425
425
 
426
- return { memoryMB, contextPct, intelligencePct, subAgents };
426
+ return { memoryMB, contextPct, intelligencePct, subAgents, embeddings };
427
427
  }
428
428
 
429
429
  // ADR status (count files only — don't read contents)
@@ -484,9 +484,9 @@ function getHooksStatus() {
484
484
  return { enabled, total };
485
485
  }
486
486
 
487
- // AgentDB stats — reads from cache file written by embedding/memory operations.
487
+ // Embeddings stats — reads from cache file written by embedding/memory ops.
488
488
  // No subprocess spawning. Falls back to DB file size estimate if cache is missing.
489
- function getAgentDBStats() {
489
+ function getEmbeddingsStats() {
490
490
  let vectorCount = 0;
491
491
  let dbSizeKB = 0;
492
492
  let namespaces = 0;
@@ -601,20 +601,25 @@ function getIntegrationStatus() {
601
601
  return { mcpServers, hasDatabase, hasApi };
602
602
  }
603
603
 
604
- // Upgrade notice (#636, #738, #743) — written by the session-start launcher
605
- // ONLY while upgrade work is in flight; the launcher deletes the file when
606
- // work completes. We render it strictly for status='in-progress' so a stale
607
- // notice (legacy "complete" file from pre-#738 launchers, zombie write from
608
- // an aborted launcher, future writer mistakes) cannot turn the statusline
609
- // segment into a permanent column. The launcher's section 0-pre also drops
610
- // any leftover file at session start as a second line of defence.
604
+ // Upgrade notice (#636, #738, #743) — written by the session-start launcher.
605
+ // status='in-progress' work is running; rendered with "(updating…)".
606
+ // status='completed' — work just finished; short-TTL post-upgrade badge so
607
+ // the user sees something on the very next render
608
+ // (Claude Code only paints the statusline AFTER the
609
+ // SessionStart hook returns, so the in-progress badge
610
+ // has effectively zero visibility window).
611
+ // Anything else is dropped (legacy "complete" pre-#738 files, zombie writes,
612
+ // future writer mistakes) so a stale notice can never turn the segment into a
613
+ // permanent column. Section 0-pre of the launcher also wipes any leftover at
614
+ // session start as a second line of defence.
611
615
  function getUpgradeNotice() {
612
616
  const data = readJSON(path.join(CWD, '.moflo', 'upgrade-notice.json'));
613
617
  if (!data || typeof data !== 'object') return null;
614
- if (data.status !== 'in-progress') return null;
618
+ if (data.status !== 'in-progress' && data.status !== 'completed') return null;
615
619
  const expiresAt = data.expiresAt ? new Date(data.expiresAt).getTime() : 0;
616
620
  if (!expiresAt || Date.now() > expiresAt) return null;
617
621
  return {
622
+ status: data.status,
618
623
  kind: data.kind === 'repair' ? 'repair' : 'upgrade',
619
624
  from: typeof data.from === 'string' ? data.from : '',
620
625
  to: typeof data.to === 'string' ? data.to : '',
@@ -623,14 +628,20 @@ function getUpgradeNotice() {
623
628
 
624
629
  function formatUpgradeNoticeSegment(notice) {
625
630
  if (!notice) return '';
626
- const suffix = ` ${c.dim}(updating…)${c.reset}`;
631
+ const inFlight = notice.status === 'in-progress';
632
+ const suffix = inFlight ? ` ${c.dim}(updating…)${c.reset}` : '';
633
+ // Pick body text: repair > in-flight version range > completed "upgraded to"
634
+ // > bare "upgraded" fallback when no version is known.
635
+ let body;
627
636
  if (notice.kind === 'repair') {
628
- return `${c.brightYellow}📦 install repaired${c.reset}${suffix}`;
637
+ body = 'install repaired';
638
+ } else if (inFlight) {
639
+ body = notice.from && notice.to ? `${notice.from} → ${notice.to}` : (notice.to || 'upgraded');
640
+ } else {
641
+ const target = notice.to || notice.from || '';
642
+ body = target ? `upgraded to ${target}` : 'upgraded';
629
643
  }
630
- const versions = notice.from && notice.to
631
- ? `${notice.from} → ${notice.to}`
632
- : (notice.to || 'upgraded');
633
- return `${c.brightYellow}📦 ${versions}${c.reset}${suffix}`;
644
+ return `${c.brightYellow}📦 ${body}${c.reset}${suffix}`;
634
645
  }
635
646
 
636
647
  // Session stats (pure file reads)
@@ -784,6 +795,25 @@ function generateDashboard() {
784
795
  );
785
796
  }
786
797
 
798
+ // Embeddings line \u2014 vector store stats from .moflo/vector-stats.json.
799
+ // Reuses `system.embeddings` (already computed by getSystemMetrics()) instead
800
+ // of re-probing the cache file on every render.
801
+ {
802
+ const vec = system.embeddings;
803
+ if (vec.vectorCount > 0) {
804
+ const hnswInd = vec.hasHnsw ? `${c.brightGreen}\u26A1${c.reset}` : '';
805
+ const sizeDisp = vec.dbSizeKB >= 1024 ? `${(vec.dbSizeKB / 1024).toFixed(1)}MB` : `${vec.dbSizeKB}KB`;
806
+ const eParts = [
807
+ `${c.cyan}Vectors${c.reset} ${c.brightGreen}\u25CF${vec.vectorCount}${c.reset}${hnswInd}`,
808
+ `${c.cyan}Size${c.reset} ${c.brightWhite}${sizeDisp}${c.reset}`,
809
+ ];
810
+ if (vec.namespaces > 0) {
811
+ eParts.push(`${c.cyan}NS${c.reset} ${c.brightWhite}${vec.namespaces}${c.reset}`);
812
+ }
813
+ lines.push(`${c.brightCyan}\uD83D\uDCCA Embeddings${c.reset} ${eParts.join(` ${c.dim}\u2502${c.reset} `)}`);
814
+ }
815
+ }
816
+
787
817
  // MCP line
788
818
  if (SL_CONFIG.show_mcp) {
789
819
  const parts = [];
@@ -795,7 +825,7 @@ function generateDashboard() {
795
825
  }
796
826
  if (integration.hasDatabase) parts.push(`${c.brightGreen}\u25C6${c.reset}DB`);
797
827
  if (parts.length > 0) {
798
- lines.push(`${c.brightCyan}\uD83D\uDCCA MCP${c.reset} ${parts.join(` ${c.dim}\u2502${c.reset} `)}`);
828
+ lines.push(`${c.brightCyan}\uD83D\uDD0C MCP${c.reset} ${parts.join(` ${c.dim}\u2502${c.reset} `)}`);
799
829
  }
800
830
  }
801
831
 
@@ -835,7 +865,7 @@ function generateCompactDashboard() {
835
865
  pushUpgradeNoticeSegment(lines);
836
866
  lines.push(header);
837
867
 
838
- // Combined swarm + mcp line
868
+ // Combined swarm + embeddings + mcp line
839
869
  const segments = [];
840
870
  if (SL_CONFIG.show_swarm) {
841
871
  const swarm = getSwarmStatus();
@@ -845,6 +875,18 @@ function generateCompactDashboard() {
845
875
  `${c.brightYellow}\uD83E\uDD16${c.reset} ${swarmInd}[${agentsColor}${swarm.activeAgents}${c.reset}/${c.brightWhite}${swarm.maxAgents}${c.reset}]`
846
876
  );
847
877
  }
878
+ // Embeddings \u2014 always-on when vectorCount > 0; self-hides on a fresh install.
879
+ // Compact doesn't call getSystemMetrics() so this is the only probe per render.
880
+ {
881
+ const vec = getEmbeddingsStats();
882
+ if (vec.vectorCount > 0) {
883
+ const hnswInd = vec.hasHnsw ? '\u26A1' : '';
884
+ const sizeDisp = vec.dbSizeKB >= 1024 ? `${(vec.dbSizeKB / 1024).toFixed(1)}MB` : `${vec.dbSizeKB}KB`;
885
+ segments.push(
886
+ `${c.brightCyan}\uD83D\uDCCA${c.reset} ${c.brightGreen}${vec.vectorCount}${hnswInd}${c.reset} ${c.dim}(${sizeDisp})${c.reset}`
887
+ );
888
+ }
889
+ }
848
890
  if (SL_CONFIG.show_mcp) {
849
891
  const integration = getIntegrationStatus();
850
892
  if (integration.mcpServers.total > 0) {
@@ -863,15 +905,16 @@ function generateCompactDashboard() {
863
905
  // JSON output
864
906
  function generateJSON() {
865
907
  const git = getGitInfo();
908
+ const system = getSystemMetrics();
866
909
  return {
867
910
  user: { name: git.name, gitBranch: git.gitBranch, modelName: getModelName() },
868
911
  v3Progress: getV3Progress(),
869
912
  security: getSecurityStatus(),
870
913
  swarm: getSwarmStatus(),
871
- system: getSystemMetrics(),
914
+ system,
872
915
  adrs: getADRStatus(),
873
916
  hooks: getHooksStatus(),
874
- agentdb: getAgentDBStats(),
917
+ embeddings: system.embeddings,
875
918
  tests: getTestStats(),
876
919
  git: { modified: git.modified, untracked: git.untracked, staged: git.staged, ahead: git.ahead, behind: git.behind },
877
920
  upgradeNotice: getUpgradeNotice(),
@@ -0,0 +1,3 @@
1
+ {
2
+ "directive": "MANDATORY FIRST ACTION: Your very first tool call MUST be mcp__moflo__memory_search (any query, any namespace). The memory-first gate WILL BLOCK all Glob, Grep, and Read calls until you do this. After memory search, follow `.claude/guidance/shipped/moflo-subagents.md` protocol."
3
+ }
@@ -1,22 +1,58 @@
1
- #!/usr/bin/env node
2
- /**
3
- * SubagentStart Hook — injects a directive into every subagent's context
4
- * telling it to read the subagent protocol guidance before doing any work.
5
- *
6
- * Output format: JSON with additionalContext (Claude Code hook protocol).
7
- * Exit 0 = allow (SubagentStart cannot block).
8
- */
9
- 'use strict';
10
-
11
- const output = {
12
- hookSpecificOutput: {
13
- hookEventName: 'SubagentStart',
14
- additionalContext:
15
- 'MANDATORY FIRST ACTION: Your very first tool call MUST be mcp__moflo__memory_search (any query, any namespace). ' +
16
- 'The memory-first gate WILL BLOCK all Glob, Grep, and Read calls until you do this. ' +
17
- 'After memory search, follow `.claude/guidance/shipped/moflo-subagents.md` protocol.',
18
- },
19
- };
20
-
21
- process.stdout.write(JSON.stringify(output));
22
- process.exit(0);
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SubagentStart Hook — injects a directive into every subagent's context
4
+ * telling it to read the subagent protocol guidance before doing any work.
5
+ *
6
+ * Output format: JSON with additionalContext (Claude Code hook protocol).
7
+ * Exit 0 = allow (SubagentStart cannot block).
8
+ *
9
+ * Source of truth: ./subagent-bootstrap.json (sibling). The TS export at
10
+ * `src/cli/services/subagent-bootstrap.ts` reads the same file so future
11
+ * agent_spawn surfaces (epic #798 stories 3 + 9) inject byte-identical text.
12
+ *
13
+ * Inline FALLBACK keeps the hook functional if the JSON sibling is ever
14
+ * missing — a SubagentStart that emits nothing leaves the memory-first gate
15
+ * un-announced and silently regresses subagent behavior.
16
+ */
17
+ 'use strict';
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+
22
+ // Defense-in-depth copy of the canonical directive in subagent-bootstrap.json.
23
+ // Kept as a single-line literal so the parity test in tests/bin/subagent-start.test.ts
24
+ // can verify it matches the JSON via plain substring containment.
25
+ const FALLBACK_DIRECTIVE = 'MANDATORY FIRST ACTION: Your very first tool call MUST be mcp__moflo__memory_search (any query, any namespace). The memory-first gate WILL BLOCK all Glob, Grep, and Read calls until you do this. After memory search, follow `.claude/guidance/shipped/moflo-subagents.md` protocol.';
26
+
27
+ function loadDirective() {
28
+ const jsonPath = path.join(__dirname, 'subagent-bootstrap.json');
29
+ let raw;
30
+ try {
31
+ raw = fs.readFileSync(jsonPath, 'utf8');
32
+ } catch (err) {
33
+ if (err && err.code !== 'ENOENT') {
34
+ process.stderr.write(`[subagent-start] read failed: ${err.message} — using inline fallback\n`);
35
+ }
36
+ return FALLBACK_DIRECTIVE;
37
+ }
38
+ try {
39
+ const data = JSON.parse(raw);
40
+ if (typeof data.directive === 'string' && data.directive.length > 0) {
41
+ return data.directive;
42
+ }
43
+ process.stderr.write('[subagent-start] subagent-bootstrap.json missing string `directive` — using inline fallback\n');
44
+ } catch (err) {
45
+ process.stderr.write(`[subagent-start] subagent-bootstrap.json parse failed: ${err.message} — using inline fallback\n`);
46
+ }
47
+ return FALLBACK_DIRECTIVE;
48
+ }
49
+
50
+ const output = {
51
+ hookSpecificOutput: {
52
+ hookEventName: 'SubagentStart',
53
+ additionalContext: loadDirective(),
54
+ },
55
+ };
56
+
57
+ process.stdout.write(JSON.stringify(output));
58
+ process.exit(0);
@@ -33,6 +33,83 @@ mcp__moflo__agent_spawn({ type: "researcher", name: "swarm-advanced" })
33
33
  // 3. Orchestrate tasks
34
34
  ```
35
35
 
36
+ ### Dynamic Scaling
37
+ Use `mcp__moflo__swarm_scale` to grow or shrink the agent pool to a target size
38
+ without re-initializing the swarm. Three strategies are available:
39
+
40
+ ```javascript
41
+ // Burst-spawn 8 workers all at once (load test, big batch)
42
+ mcp__moflo__swarm_scale({
43
+ targetAgents: 8,
44
+ scaleStrategy: "immediate",
45
+ agentTypes: ["worker"],
46
+ reason: "load-test ramp"
47
+ })
48
+
49
+ // Rate-limited ramp (1 agent / 200ms) — gentler on the coordinator
50
+ mcp__moflo__swarm_scale({
51
+ targetAgents: 12,
52
+ scaleStrategy: "gradual",
53
+ agentTypes: ["coder", "tester"]
54
+ })
55
+
56
+ // Adaptive: chunks scale with current coordinator load
57
+ mcp__moflo__swarm_scale({ targetAgents: 4, scaleStrategy: "adaptive" })
58
+ ```
59
+
60
+ The tool returns `{ previousAgents, currentAgents, scalingStatus, addedAgents,
61
+ removedAgents }` so callers can verify the swarm reached the target. Scale-down
62
+ prefers idle agents first, then oldest by heartbeat.
63
+
64
+ ### Task Orchestration
65
+
66
+ The `task_*` family talks to the same UnifiedSwarmCoordinator that
67
+ `swarm_init` / `agent_spawn` use, so tasks created here flow through the
68
+ same scoring scheduler that load-balances across idle agents.
69
+
70
+ ```javascript
71
+ // Single task — coordinator picks the lowest-workload agent automatically
72
+ mcp__moflo__task_create({
73
+ type: "coding",
74
+ description: "Implement OAuth refresh flow",
75
+ priority: "high"
76
+ })
77
+
78
+ // Direct dispatch to a known agent (skip the scheduler)
79
+ mcp__moflo__task_assign({
80
+ taskId: "task_swarm-…_3",
81
+ agentId: "agent-coder-…"
82
+ })
83
+
84
+ // Domain-routed dispatch (queen / security / core / integration / support)
85
+ mcp__moflo__task_assign({
86
+ taskId: "task_swarm-…_4",
87
+ domain: "security"
88
+ })
89
+
90
+ // Submit a batch — load-balanced across available agents in one call.
91
+ // 5 tasks across 3 idle agents → no agent ends up with more than 2.
92
+ mcp__moflo__task_orchestrate({
93
+ tasks: [
94
+ { type: "coding", description: "endpoint A" },
95
+ { type: "coding", description: "endpoint B" },
96
+ { type: "testing", description: "tests for A" },
97
+ { type: "testing", description: "tests for B" },
98
+ { type: "review", description: "PR review" }
99
+ ]
100
+ })
101
+
102
+ // Mark a task done and record its outcome
103
+ mcp__moflo__task_complete({
104
+ taskId: "task_swarm-…_3",
105
+ result: { ok: true, summary: "merged in PR #842" }
106
+ })
107
+ ```
108
+
109
+ `task_orchestrate` returns `{ submitted, assigned, queued, rejected, tasks,
110
+ errors }` — `assigned` is the count whose agents accepted them on submit;
111
+ the rest are queued and will be picked up as agents go idle.
112
+
36
113
  ## Core Concepts
37
114
 
38
115
  ### Swarm Topologies
package/README.md CHANGED
@@ -21,7 +21,7 @@ Restart Claude Code (or your MCP client). That's it — memory, indexing, gates,
21
21
 
22
22
  Or — just ask Claude to install MoFlo into your project and initialize it!
23
23
 
24
- To verify everything is running, ask Claude to run `flo doctor` with full diagnostics after restarting. If anything fails, ask Claude to fix it with `flo doctor --fix`.
24
+ To verify everything is running, ask Claude to run `flo healer` with full diagnostics after restarting. If anything fails, ask Claude to fix it with `flo healer --fix`. (`flo doctor` is still accepted as an alias.)
25
25
 
26
26
  ## Opinionated Defaults
27
27
 
@@ -97,7 +97,7 @@ In interactive mode (`flo init` without `--yes`), it shows what it found and let
97
97
  If `flo init` detects an existing `.claude/settings.json` or `.claude-flow/` directory (from a prior Claude Flow or Ruflo installation), it treats the project as already initialized and runs in **update mode** — merging MoFlo's hooks and configuration into your existing setup without overwriting your data. Specifically:
98
98
 
99
99
  - **Hooks** — If your `.claude/settings.json` already has MoFlo-style gate hooks (`flo gate`), the hooks step is skipped. Otherwise, MoFlo's hooks are written into the file (existing non-MoFlo hooks are not removed).
100
- - **MCP servers** — MoFlo registers itself as the `moflo` server in `.mcp.json`. If you had `claude-flow` or `ruflo` MCP servers configured previously, those entries remain untouched — you can remove them manually once you've verified MoFlo is working. The `flo doctor` command checks for the `moflo` server specifically.
100
+ - **MCP servers** — MoFlo registers itself as the `moflo` server in `.mcp.json`. If you had `claude-flow` or `ruflo` MCP servers configured previously, those entries remain untouched — you can remove them manually once you've verified MoFlo is working. The `flo healer` command checks for the `moflo` server specifically.
101
101
  - **Config files** — `moflo.yaml`, `CLAUDE.md`, and `.claude/skills/flo/` follow the same skip-if-exists logic. Use `--force` to regenerate them.
102
102
 
103
103
  To force a clean re-initialization over an existing setup:
@@ -162,7 +162,7 @@ code_map:
162
162
  ```bash
163
163
  flo memory index-guidance # Index your guidance docs
164
164
  flo memory code-map # Index your code structure
165
- flo doctor # Verify everything works
165
+ flo healer # Verify everything works (alias: flo doctor)
166
166
  ```
167
167
 
168
168
  Both indexes run automatically at session start after this, so you only need to run them manually on first setup or after major structural changes. The first index may take a minute or two on large codebases (1,000+ files) but runs in the background — you can start working immediately. Subsequent indexes are incremental and typically finish in under a second. To reindex everything at once:
@@ -293,16 +293,16 @@ For simple epics with independent stories, `/flo <epic>` is all you need. For co
293
293
  `flo epic` is the robust epic runner — it adds persistent state, resume from failure, and per-story auto-merge on top of `/flo`. It takes a GitHub epic issue number:
294
294
 
295
295
  ```bash
296
- flo epic run 42 # Fetch epic #42, run all stories sequentially
297
- flo epic run 42 --dry-run # Preview execution plan without running
298
- flo epic run 42 --strategy auto-merge # Per-story PRs with auto-merge between stories
296
+ flo epic 42 # Fetch epic #42, run all stories sequentially
297
+ flo epic 42 --dry-run # Preview execution plan without running
298
+ flo epic 42 --strategy auto-merge # Per-story PRs with auto-merge between stories
299
299
  flo epic status 42 # Check progress (which stories passed/failed)
300
300
  flo epic reset 42 # Reset state for re-run
301
301
  ```
302
302
 
303
- `flo epic` fetches the epic from GitHub, extracts child stories from checklists, numbered references, and `## Stories` / `## Tasks` sections, then runs each through `/flo` with state tracking. If a story fails, you can fix the issue and `flo epic run 42` again — it resumes from where it left off, skipping already-passed stories.
303
+ `flo epic` fetches the epic from GitHub, extracts child stories from checklists, numbered references, and `## Stories` / `## Tasks` sections, then runs each through `/flo` with state tracking. If a story fails, you can fix the issue and re-run `flo epic 42` — it resumes from where it left off, skipping already-passed stories. (`flo epic run 42` is an explicit alias for the same shorthand.)
304
304
 
305
- | | `/flo <epic>` | `flo epic run <epic>` |
305
+ | | `/flo <epic>` | `flo epic <epic>` |
306
306
  |---|---|---|
307
307
  | **State tracking** | No | Yes (`epic-state` memory namespace) |
308
308
  | **Resume from failure** | No | Yes (skips passed stories) |
@@ -484,16 +484,18 @@ flo gate session-reset # Reset gate state
484
484
  ### Diagnostics
485
485
 
486
486
  ```bash
487
- flo doctor # Quick health check (environment, deps, config)
488
- flo doctor --fix # Auto-fix issues (memory DB, daemon, config, MCP, zombies)
487
+ flo healer # Quick health check (environment, deps, config)
488
+ flo healer --fix # Auto-fix issues (memory DB, daemon, config, MCP, zombies)
489
489
  flo diagnose # Full integration test (memory, swarm, hive, hooks, neural)
490
490
  flo diagnose --suite memory # Run only memory tests
491
491
  flo diagnose --json # JSON output for CI/automation
492
492
  ```
493
493
 
494
- #### `flo doctor` — Health Check
494
+ `flo doctor` is still accepted as an alias for `flo healer` every flag and subcommand below works under either name.
495
495
 
496
- `flo doctor` runs 28 parallel health checks against your environment and reports pass/warn/fail for each:
496
+ #### `flo healer` Health Check
497
+
498
+ `flo healer` runs 28 parallel health checks against your environment and reports pass/warn/fail for each:
497
499
 
498
500
  | Check | What it verifies |
499
501
  |-------|-----------------|
@@ -526,7 +528,7 @@ flo diagnose --json # JSON output for CI/automation
526
528
  | **MofloDb Bridge** | Memory DB adapter (sql.js + HNSW) is wired and routable |
527
529
  | **Sandbox Tier** | Detects which sandbox backend is available (Docker / bwrap / sandbox-exec / none) |
528
530
 
529
- **Auto-fix mode** (`flo doctor --fix`) attempts to repair each failing check automatically:
531
+ **Auto-fix mode** (`flo healer --fix`) attempts to repair each failing check automatically:
530
532
 
531
533
  | Issue | What `--fix` does |
532
534
  |-------|------------------|
@@ -539,21 +541,21 @@ flo diagnose --json # JSON output for CI/automation
539
541
  | Claude Code CLI missing | Installs `@anthropic-ai/claude-code` globally |
540
542
  | Zombie processes | Kills orphaned MoFlo processes (tracked + OS-level scan) |
541
543
 
542
- After auto-fixing, doctor re-runs all checks and shows the updated results. Issues that can't be fixed automatically are listed with manual fix commands.
544
+ After auto-fixing, healer re-runs all checks and shows the updated results. Issues that can't be fixed automatically are listed with manual fix commands.
543
545
 
544
546
  Additional flags:
545
547
 
546
548
  ```bash
547
- flo doctor --install # Auto-install missing Claude Code CLI
548
- flo doctor --kill-zombies # Find and kill orphaned MoFlo processes
549
- flo doctor -c memory # Check only a specific component
550
- flo doctor -c embeddings # Check only embeddings health
551
- flo doctor --verbose # Verbose output
549
+ flo healer --install # Auto-install missing Claude Code CLI
550
+ flo healer --kill-zombies # Find and kill orphaned MoFlo processes
551
+ flo healer -c memory # Check only a specific component
552
+ flo healer -c embeddings # Check only embeddings health
553
+ flo healer --verbose # Verbose output
552
554
  ```
553
555
 
554
556
  #### `flo diagnose` — Integration Tests
555
557
 
556
- While `doctor` checks your environment, `diagnose` exercises every subsystem end-to-end: memory CRUD, embedding generation, semantic search, swarm lifecycle, hive-mind consensus, task management, hooks, config, neural patterns, and init idempotency. All test data is cleaned up after each test — nothing is left behind.
558
+ While `healer` checks your environment, `diagnose` exercises every subsystem end-to-end: memory CRUD, embedding generation, semantic search, swarm lifecycle, hive-mind consensus, task management, hooks, config, neural patterns, and init idempotency. All test data is cleaned up after each test — nothing is left behind.
557
559
 
558
560
  ### GitHub Repository Setup
559
561
 
@@ -583,7 +585,7 @@ flo --version # Show version
583
585
 
584
586
  ### Hooks (enabled OOTB)
585
587
 
586
- Hooks are shell commands that Claude Code runs automatically at specific points in its lifecycle. MoFlo installs 20 hook bindings across 8 lifecycle events. You don't invoke these — they fire automatically.
588
+ Hooks are shell commands that Claude Code runs automatically at specific points in its lifecycle. MoFlo installs 23 hook bindings across 8 lifecycle events. You don't invoke these — they fire automatically.
587
589
 
588
590
  | Hook Event | What fires | What it does | Enabled OOTB |
589
591
  |------------|-----------|-------------|:---:|
@@ -593,9 +595,12 @@ Hooks are shell commands that Claude Code runs automatically at specific points
593
595
  | **PreToolUse: Bash** | `flo gate check-dangerous-command` | Safety check on shell commands | Yes |
594
596
  | **PreToolUse: Bash** | `flo gate check-before-pr` | Validates PR readiness before `gh pr create` | Yes |
595
597
  | **PostToolUse: Write/Edit** | `flo hooks post-edit` | Records edit outcome, optionally trains neural patterns | Yes |
598
+ | **PostToolUse: Write/Edit** | `flo gate reset-edit-gates` | Resets edit-related gate state after the write completes | Yes |
596
599
  | **PostToolUse: Agent** | `flo hooks post-task` | Records task completion, feeds outcome into routing learner | Yes |
597
600
  | **PostToolUse: TaskCreate** | `flo gate record-task-created` | Records that a task was registered (clears TaskCreate gate) | Yes |
598
601
  | **PostToolUse: Bash** | `flo gate check-bash-memory` | Detects memory search commands in Bash (clears memory gate) | Yes |
602
+ | **PostToolUse: Bash** | `flo gate record-test-run` | Records test runs from Bash for the test-output gate | Yes |
603
+ | **PostToolUse: Skill** | `flo gate record-skill-run` | Records that a skill was invoked (clears skill-related gates) | Yes |
599
604
  | **PostToolUse: memory_search** | `flo gate record-memory-searched` | Records that memory was searched (clears memory-first gate) | Yes |
600
605
  | **PostToolUse: TaskUpdate** | `flo gate check-task-transition` | Validates task state transitions (prevents skipping states) | Yes |
601
606
  | **PostToolUse: memory_store** | `flo gate record-learnings-stored` | Records that learnings were persisted to memory | Yes |
package/bin/gate-hook.mjs CHANGED
@@ -25,6 +25,13 @@ try { if (stdinData.trim()) hookContext = JSON.parse(stdinData); } catch (e) {}
25
25
  // Pass tool info as env vars for gate.cjs
26
26
  var env = Object.assign({}, process.env);
27
27
  if (hookContext.tool_name) env.TOOL_NAME = hookContext.tool_name;
28
+ // Forward Claude Code's session_id so gate.cjs can enforce memory-first
29
+ // per-actor (#838) — each spawned subagent gets its own session_id, so a
30
+ // shared workflow-state.json no longer lets one subagent's directive be
31
+ // silently satisfied by the parent's earlier search.
32
+ if (typeof hookContext.session_id === 'string' && hookContext.session_id) {
33
+ env.HOOK_SESSION_ID = hookContext.session_id;
34
+ }
28
35
  if (hookContext.tool_input && typeof hookContext.tool_input === 'object') {
29
36
  Object.keys(hookContext.tool_input).forEach(function(key) {
30
37
  if (typeof hookContext.tool_input[key] === 'string') {