moflo 4.8.9 → 4.8.11

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 (166) hide show
  1. package/.claude/agents/core/coder.md +265 -265
  2. package/.claude/agents/core/planner.md +167 -167
  3. package/.claude/agents/core/researcher.md +189 -189
  4. package/.claude/agents/core/reviewer.md +325 -325
  5. package/.claude/agents/core/tester.md +318 -318
  6. package/.claude/agents/dual-mode/codex-coordinator.md +224 -224
  7. package/.claude/agents/dual-mode/codex-worker.md +211 -211
  8. package/.claude/agents/dual-mode/dual-orchestrator.md +291 -291
  9. package/.claude/agents/github/code-review-swarm.md +537 -537
  10. package/.claude/agents/github/github-modes.md +172 -172
  11. package/.claude/agents/github/issue-tracker.md +318 -318
  12. package/.claude/agents/github/multi-repo-swarm.md +552 -552
  13. package/.claude/agents/github/pr-manager.md +190 -190
  14. package/.claude/agents/github/project-board-sync.md +508 -508
  15. package/.claude/agents/github/release-manager.md +366 -366
  16. package/.claude/agents/github/release-swarm.md +582 -582
  17. package/.claude/agents/github/repo-architect.md +397 -397
  18. package/.claude/agents/github/swarm-issue.md +572 -572
  19. package/.claude/agents/github/swarm-pr.md +427 -427
  20. package/.claude/agents/github/sync-coordinator.md +451 -451
  21. package/.claude/agents/github/workflow-automation.md +634 -634
  22. package/.claude/agents/goal/code-goal-planner.md +445 -445
  23. package/.claude/agents/hive-mind/collective-intelligence-coordinator.md +129 -129
  24. package/.claude/agents/hive-mind/queen-coordinator.md +202 -202
  25. package/.claude/agents/hive-mind/scout-explorer.md +241 -241
  26. package/.claude/agents/hive-mind/swarm-memory-manager.md +192 -192
  27. package/.claude/agents/hive-mind/worker-specialist.md +216 -216
  28. package/.claude/agents/neural/safla-neural.md +73 -73
  29. package/.claude/agents/reasoning/goal-planner.md +72 -72
  30. package/.claude/agents/swarm/adaptive-coordinator.md +395 -395
  31. package/.claude/agents/swarm/hierarchical-coordinator.md +326 -326
  32. package/.claude/agents/swarm/mesh-coordinator.md +391 -391
  33. package/.claude/agents/templates/migration-plan.md +745 -745
  34. package/.claude/commands/agents/agent-spawning.md +28 -28
  35. package/.claude/commands/analysis/COMMAND_COMPLIANCE_REPORT.md +53 -53
  36. package/.claude/commands/analysis/bottleneck-detect.md +162 -162
  37. package/.claude/commands/analysis/performance-bottlenecks.md +58 -58
  38. package/.claude/commands/analysis/token-efficiency.md +44 -44
  39. package/.claude/commands/automation/auto-agent.md +122 -122
  40. package/.claude/commands/automation/self-healing.md +105 -105
  41. package/.claude/commands/automation/session-memory.md +89 -89
  42. package/.claude/commands/automation/smart-agents.md +72 -72
  43. package/.claude/commands/coordination/init.md +44 -44
  44. package/.claude/commands/coordination/orchestrate.md +43 -43
  45. package/.claude/commands/coordination/spawn.md +45 -45
  46. package/.claude/commands/coordination/swarm-init.md +85 -85
  47. package/.claude/commands/github/github-modes.md +146 -146
  48. package/.claude/commands/github/github-swarm.md +121 -121
  49. package/.claude/commands/github/issue-tracker.md +291 -291
  50. package/.claude/commands/github/pr-manager.md +169 -169
  51. package/.claude/commands/github/release-manager.md +337 -337
  52. package/.claude/commands/github/repo-architect.md +366 -366
  53. package/.claude/commands/github/sync-coordinator.md +300 -300
  54. package/.claude/commands/memory/neural.md +47 -47
  55. package/.claude/commands/monitoring/agents.md +44 -44
  56. package/.claude/commands/monitoring/status.md +46 -46
  57. package/.claude/commands/optimization/auto-topology.md +61 -61
  58. package/.claude/commands/optimization/parallel-execution.md +49 -49
  59. package/.claude/commands/sparc/analyzer.md +51 -51
  60. package/.claude/commands/sparc/architect.md +53 -53
  61. package/.claude/commands/sparc/ask.md +97 -97
  62. package/.claude/commands/sparc/batch-executor.md +54 -54
  63. package/.claude/commands/sparc/code.md +89 -89
  64. package/.claude/commands/sparc/coder.md +54 -54
  65. package/.claude/commands/sparc/debug.md +83 -83
  66. package/.claude/commands/sparc/debugger.md +54 -54
  67. package/.claude/commands/sparc/designer.md +53 -53
  68. package/.claude/commands/sparc/devops.md +109 -109
  69. package/.claude/commands/sparc/docs-writer.md +80 -80
  70. package/.claude/commands/sparc/documenter.md +54 -54
  71. package/.claude/commands/sparc/innovator.md +54 -54
  72. package/.claude/commands/sparc/integration.md +83 -83
  73. package/.claude/commands/sparc/mcp.md +117 -117
  74. package/.claude/commands/sparc/memory-manager.md +54 -54
  75. package/.claude/commands/sparc/optimizer.md +54 -54
  76. package/.claude/commands/sparc/orchestrator.md +131 -131
  77. package/.claude/commands/sparc/post-deployment-monitoring-mode.md +83 -83
  78. package/.claude/commands/sparc/refinement-optimization-mode.md +83 -83
  79. package/.claude/commands/sparc/researcher.md +54 -54
  80. package/.claude/commands/sparc/reviewer.md +54 -54
  81. package/.claude/commands/sparc/security-review.md +80 -80
  82. package/.claude/commands/sparc/sparc-modes.md +174 -174
  83. package/.claude/commands/sparc/sparc.md +111 -111
  84. package/.claude/commands/sparc/spec-pseudocode.md +80 -80
  85. package/.claude/commands/sparc/supabase-admin.md +348 -348
  86. package/.claude/commands/sparc/swarm-coordinator.md +54 -54
  87. package/.claude/commands/sparc/tdd.md +54 -54
  88. package/.claude/commands/sparc/tester.md +54 -54
  89. package/.claude/commands/sparc/tutorial.md +79 -79
  90. package/.claude/commands/sparc/workflow-manager.md +54 -54
  91. package/.claude/commands/sparc.md +166 -166
  92. package/.claude/commands/swarm/analysis.md +95 -95
  93. package/.claude/commands/swarm/development.md +96 -96
  94. package/.claude/commands/swarm/examples.md +168 -168
  95. package/.claude/commands/swarm/maintenance.md +102 -102
  96. package/.claude/commands/swarm/optimization.md +117 -117
  97. package/.claude/commands/swarm/research.md +136 -136
  98. package/.claude/commands/swarm/testing.md +131 -131
  99. package/.claude/commands/training/neural-patterns.md +73 -73
  100. package/.claude/commands/training/specialization.md +62 -62
  101. package/.claude/commands/workflows/development.md +77 -77
  102. package/.claude/commands/workflows/research.md +62 -62
  103. package/.claude/guidance/{agent-bootstrap.md → shipped/agent-bootstrap.md} +126 -126
  104. package/.claude/guidance/{guidance-memory-strategy.md → shipped/guidance-memory-strategy.md} +262 -262
  105. package/.claude/guidance/{memory-strategy.md → shipped/memory-strategy.md} +204 -204
  106. package/.claude/guidance/{moflo.md → shipped/moflo.md} +45 -31
  107. package/.claude/guidance/{task-swarm-integration.md → shipped/task-swarm-integration.md} +441 -348
  108. package/.claude/helpers/gate.cjs +236 -236
  109. package/.claude/helpers/hook-handler.cjs +42 -46
  110. package/.claude/settings.json +2 -2
  111. package/.claude/settings.local.json +3 -3
  112. package/.claude/skills/fl/SKILL.md +29 -23
  113. package/.claude/skills/flo/SKILL.md +29 -23
  114. package/.claude/skills/github-code-review/SKILL.md +4 -4
  115. package/.claude/skills/github-multi-repo/SKILL.md +8 -8
  116. package/.claude/skills/github-project-management/SKILL.md +6 -6
  117. package/.claude/skills/github-release-management/SKILL.md +12 -12
  118. package/.claude/skills/github-workflow-automation/SKILL.md +6 -6
  119. package/.claude/skills/hooks-automation/SKILL.md +1201 -1201
  120. package/.claude/skills/performance-analysis/SKILL.md +563 -563
  121. package/.claude/skills/sparc-methodology/SKILL.md +64 -64
  122. package/.claude/skills/swarm-advanced/SKILL.md +77 -77
  123. package/.claude-plugin/README.md +3 -3
  124. package/.claude-plugin/docs/PLUGIN_SUMMARY.md +3 -3
  125. package/.claude-plugin/docs/QUICKSTART.md +4 -4
  126. package/.claude-plugin/marketplace.json +3 -3
  127. package/.claude-plugin/plugin.json +3 -3
  128. package/.claude-plugin/scripts/install.sh +9 -9
  129. package/.claude-plugin/scripts/verify.sh +7 -7
  130. package/README.md +311 -116
  131. package/bin/gate-hook.mjs +50 -0
  132. package/bin/gate.cjs +138 -0
  133. package/bin/hook-handler.cjs +83 -0
  134. package/bin/hooks.mjs +72 -12
  135. package/bin/index-guidance.mjs +28 -34
  136. package/bin/index-tests.mjs +710 -0
  137. package/bin/lib/process-manager.mjs +243 -0
  138. package/bin/lib/registry-cleanup.cjs +41 -0
  139. package/bin/prompt-hook.mjs +72 -0
  140. package/bin/semantic-search.mjs +473 -441
  141. package/bin/session-start-launcher.mjs +81 -31
  142. package/bin/setup-project.mjs +13 -10
  143. package/package.json +4 -2
  144. package/src/@claude-flow/cli/README.md +1 -1
  145. package/src/@claude-flow/cli/bin/cli.js +175 -175
  146. package/src/@claude-flow/cli/dist/src/commands/doctor.js +1091 -736
  147. package/src/@claude-flow/cli/dist/src/commands/github.d.ts +12 -0
  148. package/src/@claude-flow/cli/dist/src/commands/github.js +505 -0
  149. package/src/@claude-flow/cli/dist/src/commands/hive-mind.js +90 -90
  150. package/src/@claude-flow/cli/dist/src/commands/index.d.ts +1 -0
  151. package/src/@claude-flow/cli/dist/src/commands/index.js +7 -0
  152. package/src/@claude-flow/cli/dist/src/config-adapter.js +1 -1
  153. package/src/@claude-flow/cli/dist/src/init/claudemd-generator.js +1 -1
  154. package/src/@claude-flow/cli/dist/src/init/executor.js +109 -5
  155. package/src/@claude-flow/cli/dist/src/init/helpers-generator.d.ts +14 -0
  156. package/src/@claude-flow/cli/dist/src/init/helpers-generator.js +156 -24
  157. package/src/@claude-flow/cli/dist/src/init/mcp-generator.js +20 -20
  158. package/src/@claude-flow/cli/dist/src/init/moflo-init.d.ts +7 -0
  159. package/src/@claude-flow/cli/dist/src/init/moflo-init.js +72 -10
  160. package/src/@claude-flow/cli/dist/src/init/settings-generator.js +23 -14
  161. package/src/@claude-flow/cli/dist/src/mcp-server.js +3 -3
  162. package/src/@claude-flow/cli/dist/src/plugins/manager.js +9 -8
  163. package/src/@claude-flow/cli/dist/src/services/worker-daemon.d.ts +1 -0
  164. package/src/@claude-flow/cli/dist/src/services/worker-daemon.js +3 -1
  165. package/src/@claude-flow/cli/dist/src/services/workflow-gate.js +10 -10
  166. package/src/@claude-flow/cli/package.json +1 -1
package/bin/gate.cjs ADDED
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ var fs = require('fs');
4
+ var path = require('path');
5
+
6
+ var PROJECT_DIR = (process.env.CLAUDE_PROJECT_DIR || process.cwd()).replace(/^\/([a-z])\//i, '$1:/');
7
+ var STATE_FILE = path.join(PROJECT_DIR, '.claude', 'workflow-state.json');
8
+
9
+ function readState() {
10
+ try {
11
+ if (fs.existsSync(STATE_FILE)) return JSON.parse(fs.readFileSync(STATE_FILE, 'utf-8'));
12
+ } catch (e) { /* reset on corruption */ }
13
+ return { tasksCreated: false, taskCount: 0, memorySearched: false, memoryRequired: true, interactionCount: 0, sessionStart: null, lastBlockedAt: null };
14
+ }
15
+
16
+ function writeState(s) {
17
+ try {
18
+ var dir = path.dirname(STATE_FILE);
19
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
20
+ fs.writeFileSync(STATE_FILE, JSON.stringify(s, null, 2));
21
+ } catch (e) { /* non-fatal */ }
22
+ }
23
+
24
+ // Load moflo.yaml gate config (defaults: all enabled)
25
+ function loadGateConfig() {
26
+ var defaults = { memory_first: true, task_create_first: true, context_tracking: true };
27
+ try {
28
+ var yamlPath = path.join(PROJECT_DIR, 'moflo.yaml');
29
+ if (fs.existsSync(yamlPath)) {
30
+ var content = fs.readFileSync(yamlPath, 'utf-8');
31
+ if (/memory_first:\s*false/i.test(content)) defaults.memory_first = false;
32
+ if (/task_create_first:\s*false/i.test(content)) defaults.task_create_first = false;
33
+ if (/context_tracking:\s*false/i.test(content)) defaults.context_tracking = false;
34
+ }
35
+ } catch (e) { /* use defaults */ }
36
+ return defaults;
37
+ }
38
+
39
+ var config = loadGateConfig();
40
+ var command = process.argv[2];
41
+
42
+ var EXEMPT = ['.claude/', '.claude\\', 'CLAUDE.md', 'MEMORY.md', 'workflow-state', 'node_modules'];
43
+ var DANGEROUS = ['rm -rf /', 'format c:', 'del /s /q c:\\', ':(){:|:&};:', 'mkfs.', '> /dev/sda'];
44
+ var DIRECTIVE_RE = /^(yes|no|yeah|yep|nope|sure|ok|okay|correct|right|exactly|perfect)\b/i;
45
+ var TASK_RE = /\b(fix|bug|error|implement|add|create|build|write|refactor|debug|test|feature|issue|security|optimi)\b/i;
46
+
47
+ switch (command) {
48
+ case 'check-before-agent': {
49
+ var s = readState();
50
+ // Hard gate: memory must be searched
51
+ if (config.memory_first && s.memoryRequired && !s.memorySearched) {
52
+ process.stderr.write('BLOCKED: Search memory (mcp__moflo__memory_search) before spawning agents.\n');
53
+ process.exit(2);
54
+ }
55
+ // Soft gate: TaskCreate recommended but not blocking
56
+ // (TaskCreate PostToolUse doesn't fire in Claude Code, so we can't track it reliably)
57
+ if (config.task_create_first && !s.tasksCreated) {
58
+ process.stdout.write('REMINDER: Use TaskCreate before spawning agents. Task tool is blocked until then.\n');
59
+ }
60
+ break;
61
+ }
62
+ case 'check-before-scan': {
63
+ if (!config.memory_first) break;
64
+ var s = readState();
65
+ if (s.memorySearched || !s.memoryRequired) break;
66
+ var target = (process.env.TOOL_INPUT_pattern || '') + ' ' + (process.env.TOOL_INPUT_path || '');
67
+ if (EXEMPT.some(function(p) { return target.indexOf(p) >= 0; })) break;
68
+ process.stderr.write('BLOCKED: Search memory before exploring files. Use mcp__moflo__memory_search.\n');
69
+ process.exit(2);
70
+ }
71
+ case 'check-before-read': {
72
+ if (!config.memory_first) break;
73
+ var s = readState();
74
+ if (s.memorySearched || !s.memoryRequired) break;
75
+ var fp = process.env.TOOL_INPUT_file_path || '';
76
+ if (fp.indexOf('.claude/guidance/') < 0 && fp.indexOf('.claude\\guidance\\') < 0) break;
77
+ process.stderr.write('BLOCKED: Search memory before reading guidance files. Use mcp__moflo__memory_search.\n');
78
+ process.exit(2);
79
+ }
80
+ case 'record-task-created': {
81
+ var s = readState();
82
+ s.tasksCreated = true;
83
+ s.taskCount = (s.taskCount || 0) + 1;
84
+ writeState(s);
85
+ break;
86
+ }
87
+ case 'record-memory-searched': {
88
+ var s = readState();
89
+ s.memorySearched = true;
90
+ writeState(s);
91
+ break;
92
+ }
93
+ case 'check-bash-memory': {
94
+ var cmd = process.env.TOOL_INPUT_command || '';
95
+ if (/semantic-search|memory search|memory retrieve|memory-search/.test(cmd)) {
96
+ var s = readState();
97
+ s.memorySearched = true;
98
+ writeState(s);
99
+ }
100
+ break;
101
+ }
102
+ case 'check-dangerous-command': {
103
+ var cmd = (process.env.TOOL_INPUT_command || '').toLowerCase();
104
+ for (var i = 0; i < DANGEROUS.length; i++) {
105
+ if (cmd.indexOf(DANGEROUS[i]) >= 0) {
106
+ console.log('[BLOCKED] Dangerous command: ' + DANGEROUS[i]);
107
+ process.exit(2);
108
+ }
109
+ }
110
+ break;
111
+ }
112
+ case 'prompt-reminder': {
113
+ var s = readState();
114
+ s.memorySearched = false;
115
+ var prompt = process.env.CLAUDE_USER_PROMPT || '';
116
+ s.memoryRequired = prompt.length >= 4 && !DIRECTIVE_RE.test(prompt) && (TASK_RE.test(prompt) || prompt.length > 80);
117
+ s.interactionCount = (s.interactionCount || 0) + 1;
118
+ writeState(s);
119
+ if (!s.tasksCreated) console.log('REMINDER: Use TaskCreate before spawning agents. Task tool is blocked until then.');
120
+ if (config.context_tracking) {
121
+ var ic = s.interactionCount;
122
+ if (ic > 30) console.log('Context: CRITICAL. Commit, store learnings, suggest new session.');
123
+ else if (ic > 20) console.log('Context: DEPLETED. Checkpoint progress. Recommend /compact or fresh session.');
124
+ else if (ic > 10) console.log('Context: MODERATE. Re-state goal before architectural decisions. Use agents for >300 LOC.');
125
+ }
126
+ break;
127
+ }
128
+ case 'compact-guidance': {
129
+ console.log('Pre-Compact: Check CLAUDE.md for rules. Use memory search to recover context after compact.');
130
+ break;
131
+ }
132
+ case 'session-reset': {
133
+ writeState({ tasksCreated: false, taskCount: 0, memorySearched: false, memoryRequired: true, interactionCount: 0, sessionStart: new Date().toISOString(), lastBlockedAt: null });
134
+ break;
135
+ }
136
+ default:
137
+ break;
138
+ }
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ var fs = require('fs');
4
+ var path = require('path');
5
+
6
+ var PROJECT_DIR = process.env.CLAUDE_PROJECT_DIR || process.cwd();
7
+ var METRICS_FILE = path.join(PROJECT_DIR, '.claude-flow', 'metrics', 'learning.json');
8
+ var command = process.argv[2];
9
+
10
+ // Read stdin (Claude Code sends hook data as JSON)
11
+ function readStdin() {
12
+ if (process.stdin.isTTY) return Promise.resolve('');
13
+ return new Promise(function(resolve) {
14
+ var data = '';
15
+ var timer = setTimeout(function() {
16
+ process.stdin.removeAllListeners();
17
+ process.stdin.pause();
18
+ resolve(data);
19
+ }, 500);
20
+ process.stdin.setEncoding('utf8');
21
+ process.stdin.on('data', function(chunk) { data += chunk; });
22
+ process.stdin.on('end', function() { clearTimeout(timer); resolve(data); });
23
+ process.stdin.on('error', function() { clearTimeout(timer); resolve(data); });
24
+ process.stdin.resume();
25
+ });
26
+ }
27
+
28
+ function bumpMetric(key) {
29
+ try {
30
+ var metrics = {};
31
+ if (fs.existsSync(METRICS_FILE)) metrics = JSON.parse(fs.readFileSync(METRICS_FILE, 'utf-8'));
32
+ metrics[key] = (metrics[key] || 0) + 1;
33
+ metrics.lastUpdated = new Date().toISOString();
34
+ var dir = path.dirname(METRICS_FILE);
35
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
36
+ fs.writeFileSync(METRICS_FILE, JSON.stringify(metrics, null, 2));
37
+ } catch (e) { /* non-fatal */ }
38
+ }
39
+
40
+ readStdin().then(function(stdinData) {
41
+ var hookInput = {};
42
+ if (stdinData && stdinData.trim()) {
43
+ try { hookInput = JSON.parse(stdinData); } catch (e) { /* ignore */ }
44
+ }
45
+
46
+ switch (command) {
47
+ case 'route': {
48
+ var prompt = hookInput.prompt || hookInput.command || process.env.PROMPT || '';
49
+ if (prompt) console.log('[INFO] Routing: ' + prompt.substring(0, 80));
50
+ else console.log('[INFO] Ready');
51
+ break;
52
+ }
53
+ case 'pre-edit':
54
+ case 'post-edit':
55
+ bumpMetric('edits');
56
+ console.log('[OK] Edit recorded');
57
+ break;
58
+ case 'pre-task':
59
+ bumpMetric('tasks');
60
+ console.log('[OK] Task started');
61
+ break;
62
+ case 'post-task':
63
+ bumpMetric('tasksCompleted');
64
+ console.log('[OK] Task completed');
65
+ break;
66
+ case 'session-end': {
67
+ // Kill tracked background processes via shared sync helper
68
+ try {
69
+ var cleanup = require('./lib/registry-cleanup.cjs');
70
+ var killed = cleanup.killTrackedSync(PROJECT_DIR);
71
+ if (killed > 0) console.log('[CLEANUP] Killed ' + killed + ' background process(es)');
72
+ } catch (e) { /* non-fatal: cleanup module not available */ }
73
+ console.log('[OK] Session ended');
74
+ break;
75
+ }
76
+ case 'notification':
77
+ // Silent — just acknowledge
78
+ break;
79
+ default:
80
+ if (command) console.log('[OK] Hook: ' + command);
81
+ break;
82
+ }
83
+ });
package/bin/hooks.mjs CHANGED
@@ -23,11 +23,13 @@ import { spawn } from 'child_process';
23
23
  import { existsSync, appendFileSync, readFileSync, writeFileSync, mkdirSync, statSync } from 'fs';
24
24
  import { resolve, dirname } from 'path';
25
25
  import { fileURLToPath } from 'url';
26
+ import { createProcessManager } from './lib/process-manager.mjs';
26
27
 
27
28
  const __filename = fileURLToPath(import.meta.url);
28
29
  const __dirname = dirname(__filename);
29
30
  const projectRoot = resolve(__dirname, '../..');
30
31
  const logFile = resolve(projectRoot, '.swarm/hooks.log');
32
+ const pm = createProcessManager(projectRoot);
31
33
 
32
34
  // Parse command line args
33
35
  const args = process.argv.slice(2);
@@ -263,6 +265,8 @@ async function main() {
263
265
  runIndexGuidanceBackground();
264
266
  // Generate structural code map in background
265
267
  runCodeMapBackground();
268
+ // Index test files in background
269
+ runTestIndexBackground();
266
270
  // Run pretrain in background to extract patterns from repository
267
271
  runBackgroundPretrain();
268
272
  // Force HNSW rebuild to ensure all processes use identical fresh index
@@ -318,6 +322,12 @@ async function main() {
318
322
  }
319
323
 
320
324
  case 'session-end': {
325
+ // Kill all tracked background processes before ending session
326
+ const killResult = pm.killAll();
327
+ if (killResult.killed > 0) {
328
+ log('info', `Killed ${killResult.killed} background process(es) on session end`);
329
+ }
330
+
321
331
  // Run ReasoningBank and MicroLoRA training in background on session end
322
332
  log('info', 'Session ending - starting background learning...');
323
333
 
@@ -397,19 +407,15 @@ async function runIndexGuidance(specificFile = null) {
397
407
  return 0;
398
408
  }
399
409
 
400
- // Spawn a background process that's truly windowless on Windows
410
+ // Spawn a background process via the shared ProcessManager (dedup + PID tracking).
401
411
  function spawnWindowless(cmd, args, description) {
402
- const proc = spawn(cmd, args, {
403
- cwd: projectRoot,
404
- stdio: 'ignore',
405
- detached: true,
406
- shell: false,
407
- windowsHide: true
408
- });
409
-
410
- proc.unref();
411
- log('info', `Started ${description} (PID: ${proc.pid})`);
412
- return proc;
412
+ const result = pm.spawn(cmd, args, description);
413
+ if (result.skipped) {
414
+ log('info', `Skipped ${description} (already running, PID: ${result.pid})`);
415
+ } else if (result.pid) {
416
+ log('info', `Started ${description} (PID: ${result.pid})`);
417
+ }
418
+ return result;
413
419
  }
414
420
 
415
421
  // Resolve a moflo npm bin script, falling back to local .claude/scripts/ copy
@@ -431,6 +437,22 @@ function resolveBinOrLocal(binName, localScript) {
431
437
 
432
438
  // Run the guidance indexer in background (non-blocking - used for session start and file changes)
433
439
  function runIndexGuidanceBackground(specificFile = null) {
440
+ // Check auto_index.guidance flag in moflo.yaml (default: true)
441
+ // Only gate full indexing on session-start; per-file calls from post-edit always run
442
+ if (!specificFile) {
443
+ const yamlPath = resolve(projectRoot, 'moflo.yaml');
444
+ if (existsSync(yamlPath)) {
445
+ try {
446
+ const content = readFileSync(yamlPath, 'utf-8');
447
+ const match = content.match(/auto_index:\s*\n(?:.*\n)*?\s+guidance:\s*(true|false)/);
448
+ if (match && match[1] === 'false') {
449
+ log('info', 'Guidance indexing disabled (auto_index.guidance: false)');
450
+ return;
451
+ }
452
+ } catch { /* ignore, proceed with indexing */ }
453
+ }
454
+ }
455
+
434
456
  const indexScript = resolveBinOrLocal('flo-index', 'index-guidance.mjs');
435
457
 
436
458
  if (!indexScript) {
@@ -447,6 +469,19 @@ function runIndexGuidanceBackground(specificFile = null) {
447
469
 
448
470
  // Run structural code map generator in background (non-blocking)
449
471
  function runCodeMapBackground() {
472
+ // Check auto_index.code_map flag in moflo.yaml (default: true)
473
+ const yamlPath = resolve(projectRoot, 'moflo.yaml');
474
+ if (existsSync(yamlPath)) {
475
+ try {
476
+ const content = readFileSync(yamlPath, 'utf-8');
477
+ const match = content.match(/auto_index:\s*\n(?:.*\n)*?\s+code_map:\s*(true|false)/);
478
+ if (match && match[1] === 'false') {
479
+ log('info', 'Code map generation disabled (auto_index.code_map: false)');
480
+ return;
481
+ }
482
+ } catch { /* ignore, proceed with indexing */ }
483
+ }
484
+
450
485
  const codeMapScript = resolveBinOrLocal('flo-codemap', 'generate-code-map.mjs');
451
486
 
452
487
  if (!codeMapScript) {
@@ -457,6 +492,31 @@ function runCodeMapBackground() {
457
492
  spawnWindowless('node', [codeMapScript], 'background code map generation');
458
493
  }
459
494
 
495
+ // Run test file indexer in background (non-blocking)
496
+ function runTestIndexBackground() {
497
+ // Check auto_index.tests flag in moflo.yaml (default: true)
498
+ const yamlPath = resolve(projectRoot, 'moflo.yaml');
499
+ if (existsSync(yamlPath)) {
500
+ try {
501
+ const content = readFileSync(yamlPath, 'utf-8');
502
+ const match = content.match(/auto_index:\s*\n(?:.*\n)*?\s+tests:\s*(true|false)/);
503
+ if (match && match[1] === 'false') {
504
+ log('info', 'Test indexing disabled (auto_index.tests: false)');
505
+ return;
506
+ }
507
+ } catch { /* ignore, proceed with indexing */ }
508
+ }
509
+
510
+ const testIndexScript = resolveBinOrLocal('flo-testmap', 'index-tests.mjs');
511
+
512
+ if (!testIndexScript) {
513
+ log('info', 'Test indexer not found (checked npm bin + .claude/scripts/)');
514
+ return;
515
+ }
516
+
517
+ spawnWindowless('node', [testIndexScript], 'background test indexing');
518
+ }
519
+
460
520
  // Run ReasoningBank + MicroLoRA training + EWC++ consolidation in background (non-blocking)
461
521
  function runBackgroundTraining() {
462
522
  const localCli = getLocalCliPath();
@@ -100,11 +100,16 @@ function loadGuidanceDirs() {
100
100
 
101
101
  // 2. Include moflo's own bundled guidance (ships with the package)
102
102
  // Only when running inside a consumer project (not moflo itself)
103
- const bundledGuidanceDir = resolve(mofloRoot, '.claude/guidance');
103
+ // Shipped guidance lives in .claude/guidance/shipped/ — internal/ is excluded from npm
104
+ const bundledShippedDir = resolve(mofloRoot, '.claude/guidance/shipped');
105
+ const bundledGuidanceDir = existsSync(bundledShippedDir)
106
+ ? bundledShippedDir
107
+ : resolve(mofloRoot, '.claude/guidance');
104
108
  const projectGuidanceDir = resolve(projectRoot, '.claude/guidance');
105
109
  if (
106
110
  existsSync(bundledGuidanceDir) &&
107
- resolve(bundledGuidanceDir) !== resolve(projectGuidanceDir)
111
+ resolve(bundledGuidanceDir) !== resolve(projectGuidanceDir) &&
112
+ resolve(bundledGuidanceDir) !== resolve(projectGuidanceDir, 'shipped')
108
113
  ) {
109
114
  dirs.push({ path: bundledGuidanceDir, prefix: 'moflo-bundled', absolute: true });
110
115
  }
@@ -689,9 +694,11 @@ function indexDirectory(db, dirConfig) {
689
694
 
690
695
  /**
691
696
  * Remove stale entries for files that no longer exist on disk.
692
- * Runs after indexing to keep the memory DB clean.
697
+ * Uses the set of docKeys seen during the current indexing run to determine
698
+ * which entries are stale, rather than reconstructing file paths from keys
699
+ * (which breaks for files in subdirectories).
693
700
  */
694
- function cleanStaleEntries(db) {
701
+ function cleanStaleEntries(db, currentDocKeys) {
695
702
  const docsStmt = db.prepare(
696
703
  `SELECT DISTINCT key FROM memory_entries WHERE namespace = ? AND key LIKE 'doc-%'`
697
704
  );
@@ -702,36 +709,19 @@ function cleanStaleEntries(db) {
702
709
 
703
710
  let staleCount = 0;
704
711
 
705
- // Build a lookup of all indexed directory configs for stale detection
706
- const prefixToDirMap = {};
707
- for (const dirConfig of GUIDANCE_DIRS) {
708
- const dirPath = dirConfig.absolute ? dirConfig.path : resolve(projectRoot, dirConfig.path);
709
- prefixToDirMap[dirConfig.prefix] = dirPath;
710
- }
711
-
712
712
  for (const { key } of docs) {
713
- // Convert key back to file path by matching doc-{prefix}-{filename}
714
- let filePath;
715
- for (const [prefix, dirPath] of Object.entries(prefixToDirMap)) {
716
- const docPrefix = `doc-${prefix}-`;
717
- if (key.startsWith(docPrefix)) {
718
- filePath = resolve(dirPath, key.replace(docPrefix, '') + '.md');
719
- break;
720
- }
721
- }
722
- if (!filePath) continue; // Unknown prefix, skip
723
-
724
- if (!existsSync(filePath)) {
725
- const chunkPrefix = key.replace('doc-', 'chunk-');
726
- const countBefore = db.exec(`SELECT COUNT(*) as cnt FROM memory_entries WHERE namespace = '${NAMESPACE}'`)[0]?.values[0][0] || 0;
727
- db.run(`DELETE FROM memory_entries WHERE namespace = ? AND key LIKE ?`, [NAMESPACE, `${chunkPrefix}%`]);
728
- db.run(`DELETE FROM memory_entries WHERE namespace = ? AND key = ?`, [NAMESPACE, key]);
729
- const countAfter = db.exec(`SELECT COUNT(*) as cnt FROM memory_entries WHERE namespace = '${NAMESPACE}'`)[0]?.values[0][0] || 0;
730
- const removed = countBefore - countAfter;
731
- if (removed > 0) {
732
- log(` Removed ${removed} stale entries for deleted file: ${key}`);
733
- staleCount += removed;
734
- }
713
+ // If this doc key was seen during the current indexing run, it's not stale
714
+ if (currentDocKeys.has(key)) continue;
715
+
716
+ const chunkPrefix = key.replace('doc-', 'chunk-');
717
+ const countBefore = db.exec(`SELECT COUNT(*) as cnt FROM memory_entries WHERE namespace = '${NAMESPACE}'`)[0]?.values[0][0] || 0;
718
+ db.run(`DELETE FROM memory_entries WHERE namespace = ? AND key LIKE ?`, [NAMESPACE, `${chunkPrefix}%`]);
719
+ db.run(`DELETE FROM memory_entries WHERE namespace = ? AND key = ?`, [NAMESPACE, key]);
720
+ const countAfter = db.exec(`SELECT COUNT(*) as cnt FROM memory_entries WHERE namespace = '${NAMESPACE}'`)[0]?.values[0][0] || 0;
721
+ const removed = countBefore - countAfter;
722
+ if (removed > 0) {
723
+ log(` Removed ${removed} stale entries for deleted file: ${key}`);
724
+ staleCount += removed;
735
725
  }
736
726
  }
737
727
 
@@ -769,6 +759,7 @@ let docsIndexed = 0;
769
759
  let chunksIndexed = 0;
770
760
  let unchanged = 0;
771
761
  let errors = 0;
762
+ const currentDocKeys = new Set();
772
763
 
773
764
  if (specificFile) {
774
765
  // Index single file
@@ -801,6 +792,9 @@ if (specificFile) {
801
792
  const results = indexDirectory(db, dir);
802
793
 
803
794
  for (const result of results) {
795
+ if (result.status === 'indexed' || result.status === 'unchanged') {
796
+ currentDocKeys.add(result.docKey);
797
+ }
804
798
  if (result.status === 'indexed') {
805
799
  log(` ✅ ${result.docKey} (${result.chunks} chunks)`);
806
800
  docsIndexed++;
@@ -819,7 +813,7 @@ if (specificFile) {
819
813
  let staleRemoved = 0;
820
814
  if (!specificFile) {
821
815
  log('Cleaning stale entries for deleted files...');
822
- staleRemoved = cleanStaleEntries(db);
816
+ staleRemoved = cleanStaleEntries(db, currentDocKeys);
823
817
  if (staleRemoved === 0) {
824
818
  log(' No stale entries found');
825
819
  }