moflo 4.9.21 → 4.9.23

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 (194) hide show
  1. package/.claude/agents/analysis/analyze-code-quality.md +0 -121
  2. package/.claude/agents/analysis/code-analyzer.md +5 -26
  3. package/.claude/agents/architecture/system-design/arch-system-design.md +0 -119
  4. package/.claude/agents/base-template-generator.md +0 -1
  5. package/.claude/agents/core/coder.md +0 -22
  6. package/.claude/agents/core/planner.md +0 -16
  7. package/.claude/agents/core/researcher.md +0 -16
  8. package/.claude/agents/core/reviewer.md +0 -17
  9. package/.claude/agents/core/tester.md +0 -19
  10. package/.claude/agents/custom/test-long-runner.md +0 -2
  11. package/.claude/agents/development/dev-backend-api.md +0 -167
  12. package/.claude/agents/development/dev-database.md +43 -0
  13. package/.claude/agents/development/dev-frontend.md +42 -0
  14. package/.claude/agents/devops/ci-cd/ops-cicd-github.md +0 -112
  15. package/.claude/agents/documentation/api-docs/docs-api-openapi.md +0 -111
  16. package/.claude/agents/security/security-auditor.md +45 -0
  17. package/.claude/guidance/shipped/moflo-cli-reference.md +19 -16
  18. package/.claude/guidance/shipped/moflo-core-guidance.md +0 -2
  19. package/.claude/guidance/shipped/moflo-guidance-rules.md +5 -5
  20. package/.claude/guidance/shipped/moflo-spell-runner.md +1 -0
  21. package/.claude/guidance/shipped/moflo-spell-scheduling.md +225 -0
  22. package/.claude/guidance/shipped/moflo-spell-troubleshooting.md +1 -0
  23. package/.claude/helpers/gate.cjs +70 -3
  24. package/.claude/skills/fl/execution-modes.md +38 -15
  25. package/.claude/skills/fl/phases.md +67 -0
  26. package/.claude/skills/spell-schedule/SKILL.md +18 -5
  27. package/README.md +1 -1
  28. package/bin/gate.cjs +70 -3
  29. package/bin/index-guidance.mjs +32 -6
  30. package/bin/lib/retired-files.mjs +146 -0
  31. package/bin/session-start-launcher.mjs +116 -8
  32. package/dist/src/cli/appliance/rvfa-builder.js +1 -1
  33. package/dist/src/cli/commands/agent.js +3 -9
  34. package/dist/src/cli/commands/daemon.js +13 -17
  35. package/dist/src/cli/commands/hooks.js +4 -9
  36. package/dist/src/cli/commands/index.js +2 -0
  37. package/dist/src/cli/commands/retire.js +111 -0
  38. package/dist/src/cli/commands/spell-schedule.js +237 -49
  39. package/dist/src/cli/hooks/reasoningbank/index.js +7 -7
  40. package/dist/src/cli/init/executor.js +26 -54
  41. package/dist/src/cli/init/helpers-generator.js +66 -3
  42. package/dist/src/cli/init/settings-generator.js +17 -6
  43. package/dist/src/cli/mcp-tools/agent-tools.js +9 -27
  44. package/dist/src/cli/mcp-tools/hooks-tools.js +23 -21
  45. package/dist/src/cli/mcp-tools/memory-tools.js +16 -5
  46. package/dist/src/cli/memory/bridge-embedder.js +26 -6
  47. package/dist/src/cli/memory/bridge-entries.js +33 -15
  48. package/dist/src/cli/memory/controllers/semantic-router.js +18 -12
  49. package/dist/src/cli/memory/sona-optimizer.js +6 -6
  50. package/dist/src/cli/neural/domain/services/learning-service.js +3 -3
  51. package/dist/src/cli/services/agent-router.js +2 -5
  52. package/dist/src/cli/services/daemon-autostart-lifecycle.js +62 -0
  53. package/dist/src/cli/services/daemon-dashboard.js +187 -18
  54. package/dist/src/cli/services/daemon-readiness.js +19 -31
  55. package/dist/src/cli/services/ephemeral-namespace-purge.js +61 -33
  56. package/dist/src/cli/services/headless-worker-executor.js +7 -94
  57. package/dist/src/cli/services/hook-block-hash.js +4 -0
  58. package/dist/src/cli/services/worker-daemon.js +40 -66
  59. package/dist/src/cli/shared/events/example-usage.js +6 -6
  60. package/dist/src/cli/shared/hooks/task-hooks.js +8 -8
  61. package/dist/src/cli/spells/core/runner.js +12 -0
  62. package/dist/src/cli/spells/scheduler/scheduler.js +24 -9
  63. package/dist/src/cli/spells/schema/validator.js +2 -1
  64. package/dist/src/cli/spells/schema/validators/top-level.js +18 -0
  65. package/dist/src/cli/version.js +1 -1
  66. package/package.json +5 -2
  67. package/retired-files.json +1989 -0
  68. package/src/cli/data/model-registry.json +2 -2
  69. package/.claude/agents/consensus/byzantine-coordinator.md +0 -63
  70. package/.claude/agents/consensus/crdt-synchronizer.md +0 -997
  71. package/.claude/agents/consensus/gossip-coordinator.md +0 -63
  72. package/.claude/agents/consensus/performance-benchmarker.md +0 -851
  73. package/.claude/agents/consensus/quorum-manager.md +0 -823
  74. package/.claude/agents/consensus/raft-manager.md +0 -63
  75. package/.claude/agents/consensus/security-manager.md +0 -622
  76. package/.claude/agents/data/ml/data-ml-model.md +0 -193
  77. package/.claude/agents/github/code-review-swarm.md +0 -538
  78. package/.claude/agents/github/github-modes.md +0 -172
  79. package/.claude/agents/github/issue-tracker.md +0 -311
  80. package/.claude/agents/github/multi-repo-swarm.md +0 -551
  81. package/.claude/agents/github/pr-manager.md +0 -183
  82. package/.claude/agents/github/project-board-sync.md +0 -508
  83. package/.claude/agents/github/release-manager.md +0 -360
  84. package/.claude/agents/github/release-swarm.md +0 -580
  85. package/.claude/agents/github/repo-architect.md +0 -391
  86. package/.claude/agents/github/swarm-issue.md +0 -566
  87. package/.claude/agents/github/swarm-pr.md +0 -414
  88. package/.claude/agents/github/sync-coordinator.md +0 -426
  89. package/.claude/agents/github/workflow-automation.md +0 -606
  90. package/.claude/agents/goal/code-goal-planner.md +0 -440
  91. package/.claude/agents/goal/goal-planner.md +0 -168
  92. package/.claude/agents/hive-mind/collective-intelligence-coordinator.md +0 -127
  93. package/.claude/agents/hive-mind/queen-coordinator.md +0 -198
  94. package/.claude/agents/hive-mind/scout-explorer.md +0 -233
  95. package/.claude/agents/hive-mind/swarm-memory-manager.md +0 -184
  96. package/.claude/agents/hive-mind/worker-specialist.md +0 -208
  97. package/.claude/agents/neural/safla-neural.md +0 -73
  98. package/.claude/agents/optimization/benchmark-suite.md +0 -665
  99. package/.claude/agents/optimization/load-balancer.md +0 -431
  100. package/.claude/agents/optimization/performance-monitor.md +0 -672
  101. package/.claude/agents/optimization/resource-allocator.md +0 -674
  102. package/.claude/agents/optimization/topology-optimizer.md +0 -808
  103. package/.claude/agents/reasoning/goal-planner.md +0 -67
  104. package/.claude/agents/sona/sona-learning-optimizer.md +0 -74
  105. package/.claude/agents/sparc/architecture.md +0 -472
  106. package/.claude/agents/sparc/pseudocode.md +0 -318
  107. package/.claude/agents/sparc/refinement.md +0 -525
  108. package/.claude/agents/sparc/specification.md +0 -276
  109. package/.claude/agents/specialized/mobile/spec-mobile-react-native.md +0 -225
  110. package/.claude/agents/swarm/adaptive-coordinator.md +0 -391
  111. package/.claude/agents/swarm/hierarchical-coordinator.md +0 -321
  112. package/.claude/agents/swarm/mesh-coordinator.md +0 -383
  113. package/.claude/agents/testing/production-validator.md +0 -395
  114. package/.claude/agents/testing/tdd-london-swarm.md +0 -244
  115. package/.claude/agents/v3/adr-architect.md +0 -184
  116. package/.claude/agents/v3/aidefence-guardian.md +0 -277
  117. package/.claude/agents/v3/claims-authorizer.md +0 -208
  118. package/.claude/agents/v3/collective-intelligence-coordinator.md +0 -988
  119. package/.claude/agents/v3/ddd-domain-expert.md +0 -220
  120. package/.claude/agents/v3/injection-analyst.md +0 -232
  121. package/.claude/agents/v3/memory-specialist.md +0 -987
  122. package/.claude/agents/v3/performance-engineer.md +0 -1225
  123. package/.claude/agents/v3/pii-detector.md +0 -146
  124. package/.claude/agents/v3/reasoningbank-learner.md +0 -213
  125. package/.claude/agents/v3/security-architect-aidefence.md +0 -405
  126. package/.claude/agents/v3/security-architect.md +0 -865
  127. package/.claude/agents/v3/security-auditor.md +0 -771
  128. package/.claude/agents/v3/sparc-orchestrator.md +0 -182
  129. package/.claude/agents/v3/swarm-memory-manager.md +0 -142
  130. package/.claude/agents/v3/v3-integration-architect.md +0 -205
  131. package/.claude/commands/claude-flow-help.md +0 -103
  132. package/.claude/commands/claude-flow-memory.md +0 -107
  133. package/.claude/commands/claude-flow-swarm.md +0 -205
  134. package/.claude/commands/flo-simplify.md +0 -101
  135. package/.claude/commands/github/README.md +0 -11
  136. package/.claude/commands/github/code-review-swarm.md +0 -514
  137. package/.claude/commands/github/code-review.md +0 -25
  138. package/.claude/commands/github/github-modes.md +0 -146
  139. package/.claude/commands/github/github-swarm.md +0 -113
  140. package/.claude/commands/github/issue-tracker.md +0 -284
  141. package/.claude/commands/github/issue-triage.md +0 -25
  142. package/.claude/commands/github/multi-repo-swarm.md +0 -519
  143. package/.claude/commands/github/pr-enhance.md +0 -26
  144. package/.claude/commands/github/pr-manager.md +0 -164
  145. package/.claude/commands/github/project-board-sync.md +0 -471
  146. package/.claude/commands/github/release-manager.md +0 -332
  147. package/.claude/commands/github/release-swarm.md +0 -544
  148. package/.claude/commands/github/repo-analyze.md +0 -25
  149. package/.claude/commands/github/repo-architect.md +0 -361
  150. package/.claude/commands/github/swarm-issue.md +0 -482
  151. package/.claude/commands/github/swarm-pr.md +0 -285
  152. package/.claude/commands/github/sync-coordinator.md +0 -294
  153. package/.claude/commands/github/workflow-automation.md +0 -442
  154. package/.claude/commands/hooks/README.md +0 -11
  155. package/.claude/commands/hooks/overview.md +0 -58
  156. package/.claude/commands/hooks/post-edit.md +0 -117
  157. package/.claude/commands/hooks/post-task.md +0 -112
  158. package/.claude/commands/hooks/pre-edit.md +0 -113
  159. package/.claude/commands/hooks/pre-task.md +0 -111
  160. package/.claude/commands/hooks/session-end.md +0 -118
  161. package/.claude/commands/hooks/setup.md +0 -103
  162. package/.claude/commands/sparc/analyzer.md +0 -42
  163. package/.claude/commands/sparc/architect.md +0 -43
  164. package/.claude/commands/sparc/ask.md +0 -86
  165. package/.claude/commands/sparc/batch-executor.md +0 -44
  166. package/.claude/commands/sparc/code.md +0 -78
  167. package/.claude/commands/sparc/coder.md +0 -44
  168. package/.claude/commands/sparc/debug.md +0 -72
  169. package/.claude/commands/sparc/debugger.md +0 -44
  170. package/.claude/commands/sparc/designer.md +0 -43
  171. package/.claude/commands/sparc/devops.md +0 -98
  172. package/.claude/commands/sparc/docs-writer.md +0 -69
  173. package/.claude/commands/sparc/documenter.md +0 -44
  174. package/.claude/commands/sparc/innovator.md +0 -44
  175. package/.claude/commands/sparc/integration.md +0 -72
  176. package/.claude/commands/sparc/mcp.md +0 -106
  177. package/.claude/commands/sparc/memory-manager.md +0 -44
  178. package/.claude/commands/sparc/optimizer.md +0 -44
  179. package/.claude/commands/sparc/orchestrator.md +0 -116
  180. package/.claude/commands/sparc/post-deployment-monitoring-mode.md +0 -72
  181. package/.claude/commands/sparc/refinement-optimization-mode.md +0 -72
  182. package/.claude/commands/sparc/researcher.md +0 -44
  183. package/.claude/commands/sparc/reviewer.md +0 -44
  184. package/.claude/commands/sparc/security-review.md +0 -69
  185. package/.claude/commands/sparc/sparc-modes.md +0 -139
  186. package/.claude/commands/sparc/sparc.md +0 -99
  187. package/.claude/commands/sparc/spec-pseudocode.md +0 -69
  188. package/.claude/commands/sparc/spell-manager.md +0 -44
  189. package/.claude/commands/sparc/supabase-admin.md +0 -337
  190. package/.claude/commands/sparc/swarm-coordinator.md +0 -44
  191. package/.claude/commands/sparc/tdd.md +0 -44
  192. package/.claude/commands/sparc/tester.md +0 -44
  193. package/.claude/commands/sparc/tutorial.md +0 -68
  194. package/.claude/commands/sparc.md +0 -151
package/bin/gate.cjs CHANGED
@@ -7,7 +7,7 @@ var cp = require('child_process');
7
7
  var PROJECT_DIR = (process.env.CLAUDE_PROJECT_DIR || process.cwd()).replace(/^\/([a-z])\//i, '$1:/');
8
8
  var STATE_FILE = path.join(PROJECT_DIR, '.claude', 'workflow-state.json');
9
9
 
10
- var STATE_DEFAULTS = { tasksCreated: false, taskCount: 0, memorySearched: false, memorySearchedBy: {}, memoryRequired: true, learningsStored: false, testsRun: false, simplifyRun: false, simplifySnapshotSha: null, interactionCount: 0, sessionStart: null, lastBlockedAt: null, lastNamespaceHint: '', lastNamespaceHintEmittedBy: {} };
10
+ var STATE_DEFAULTS = { tasksCreated: false, taskCount: 0, memorySearched: false, memorySearchedBy: {}, memoryRequired: true, learningsStored: false, testsRun: false, simplifyRun: false, simplifySnapshotSha: null, interactionCount: 0, sessionStart: null, lastBlockedAt: null, lastNamespaceHint: '', lastNamespaceHintEmittedBy: {}, flMode: null, swarmInitialized: false, hiveInitialized: false };
11
11
 
12
12
  // Per-actor memory-search tracking (#838). The legacy `memorySearched` boolean
13
13
  // is session-wide, so once the parent searches memory, every spawned subagent
@@ -60,7 +60,7 @@ function writeState(s) {
60
60
 
61
61
  // Load moflo.yaml gate config (defaults: all enabled)
62
62
  function loadGateConfig() {
63
- var defaults = { memory_first: true, task_create_first: true, context_tracking: true, testing_gate: true, simplify_gate: true, learnings_gate: true };
63
+ var defaults = { memory_first: true, task_create_first: true, context_tracking: true, testing_gate: true, simplify_gate: true, learnings_gate: true, swarm_invocation_gate: true };
64
64
  try {
65
65
  var yamlPath = path.join(PROJECT_DIR, 'moflo.yaml');
66
66
  if (fs.existsSync(yamlPath)) {
@@ -71,6 +71,7 @@ function loadGateConfig() {
71
71
  if (/testing_gate:\s*false/i.test(content)) defaults.testing_gate = false;
72
72
  if (/simplify_gate:\s*false/i.test(content)) defaults.simplify_gate = false;
73
73
  if (/learnings_gate:\s*false/i.test(content)) defaults.learnings_gate = false;
74
+ if (/swarm_invocation_gate:\s*false/i.test(content)) defaults.swarm_invocation_gate = false;
74
75
  }
75
76
  } catch (e) { /* use defaults */ }
76
77
  return defaults;
@@ -111,6 +112,21 @@ var NS_NAV_RES = [
111
112
  /\b(class|function|method|component|service|entity|module)\b/,
112
113
  ];
113
114
 
115
+ // Detect whether the current prompt invoked /fl or /flo with a swarm/hive flag (#952).
116
+ // When set, check-before-agent BLOCKS the Agent spawn until the matching MCP init
117
+ // (mcp__moflo__swarm_init or mcp__moflo__hive-mind_init) has been recorded — the user
118
+ // explicitly opted in to the protected coordination surface, so falling back to
119
+ // raw Agent dispatch silently regresses headline moflo product capability.
120
+ //
121
+ // SYNC: duplicated verbatim in src/cli/init/helpers-generator.ts.
122
+ function detectFlMode(promptText) {
123
+ var p = promptText || '';
124
+ if (!/^\s*\/(?:fl|flo)\b/i.test(p)) return null;
125
+ if (/(?:^|\s)(?:-s|--swarm)\b/.test(p)) return 'swarm';
126
+ if (/(?:^|\s)(?:-h|--hive)\b/.test(p)) return 'hive';
127
+ return null;
128
+ }
129
+
114
130
  function classifyNamespaceHint(promptText) {
115
131
  var lower = (promptText || '').toLowerCase();
116
132
  if (NS_TEST_RE.test(lower)) return 'Memory namespace hint: use "tests" for test inventory and coverage lookups.';
@@ -154,6 +170,12 @@ function applyPromptStateReset(state, promptText) {
154
170
  // subsequent agents (parent + subagents that spawn their own agents) all
155
171
  // see the new classification on their first check-before-agent.
156
172
  state.lastNamespaceHintEmittedBy = {};
173
+ // #952 — derive flMode from the user prompt, and reset the matching init
174
+ // flag. Each /fl invocation must call its protected MCP init; the previous
175
+ // prompt's swarm/hive registration does not satisfy this prompt's gate.
176
+ state.flMode = detectFlMode(promptText);
177
+ state.swarmInitialized = false;
178
+ state.hiveInitialized = false;
157
179
  }
158
180
  // Match npm/yarn/pnpm/bun test, npx vitest|jest|..., bare runners at command-start only,
159
181
  // and language-native test commands. The bare-runner arm is anchored so that
@@ -305,6 +327,47 @@ switch (command) {
305
327
  writeState(s);
306
328
  }
307
329
  }
330
+ // #952 — when /fl was invoked with -s/-h, the protected MCP init must run
331
+ // BEFORE any Agent spawn. Hard block: the user explicitly opted in to
332
+ // moflo's coordination surface, so silently dispatching `Agent` calls
333
+ // without `mcp__moflo__swarm_init` / `mcp__moflo__hive-mind_init` is the
334
+ // failure mode this gate exists to prevent (CLAUDE.md "⛔ Protected
335
+ // functionality — swarm + hive-mind"). Other Agent uses remain advisory.
336
+ if (config.swarm_invocation_gate) {
337
+ if (s.flMode === 'swarm' && !s.swarmInitialized) {
338
+ process.stderr.write('BLOCKED: /fl was invoked with -s/--swarm but mcp__moflo__swarm_init has not been called.\n');
339
+ process.stderr.write('Run mcp__moflo__swarm_init first, then mcp__moflo__agent_spawn for each role, then dispatch Agent.\n');
340
+ process.stderr.write('See .claude/skills/fl/execution-modes.md "SWARM mode" and CLAUDE.md "⛔ Protected functionality".\n');
341
+ process.stderr.write('Disable via moflo.yaml: gates: swarm_invocation_gate: false\n');
342
+ process.exit(2);
343
+ }
344
+ if (s.flMode === 'hive' && !s.hiveInitialized) {
345
+ process.stderr.write('BLOCKED: /fl was invoked with -h/--hive but mcp__moflo__hive-mind_init has not been called.\n');
346
+ process.stderr.write('Run mcp__moflo__hive-mind_init first, then dispatch Agent or hive-mind workers.\n');
347
+ process.stderr.write('See .claude/skills/fl/execution-modes.md "HIVE-MIND mode" and CLAUDE.md "⛔ Protected functionality".\n');
348
+ process.stderr.write('Disable via moflo.yaml: gates: swarm_invocation_gate: false\n');
349
+ process.exit(2);
350
+ }
351
+ }
352
+ break;
353
+ }
354
+ case 'record-swarm-init': {
355
+ // #952 — wired to mcp__moflo__swarm_init PostToolUse. Marks the gate
356
+ // satisfied so subsequent Agent spawns under /fl -s pass.
357
+ var s = readState();
358
+ if (!s.swarmInitialized) {
359
+ s.swarmInitialized = true;
360
+ writeState(s);
361
+ }
362
+ break;
363
+ }
364
+ case 'record-hive-init': {
365
+ // #952 — wired to mcp__moflo__hive-mind_init PostToolUse.
366
+ var s = readState();
367
+ if (!s.hiveInitialized) {
368
+ s.hiveInitialized = true;
369
+ writeState(s);
370
+ }
308
371
  break;
309
372
  }
310
373
  case 'check-before-scan': {
@@ -508,7 +571,11 @@ switch (command) {
508
571
  break;
509
572
  }
510
573
  case 'session-reset': {
511
- 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: {} });
574
+ // Derive from STATE_DEFAULTS so adding a new state field requires only one
575
+ // edit (the defaults object) — the literal that used to live here drifted
576
+ // every time a field was added and is what motivated #952's audit of state
577
+ // shape consistency.
578
+ writeState(Object.assign({}, STATE_DEFAULTS, { sessionStart: new Date().toISOString() }));
512
579
  break;
513
580
  }
514
581
  default:
@@ -131,6 +131,18 @@ function loadGuidanceDirs() {
131
131
  // 3. CLAUDE.md files are NOT indexed — Claude loads them into context automatically.
132
132
  // Indexing them wastes vectors and creates duplicate keys across subprojects.
133
133
 
134
+ // 4. Project skills — index .claude/skills/<name>/SKILL.md
135
+ const projectSkillsDir = resolve(projectRoot, '.claude/skills');
136
+ if (existsSync(projectSkillsDir)) {
137
+ dirs.push({ path: '.claude/skills', prefix: 'skill', fileFilter: ['SKILL.md'], kind: 'skill' });
138
+ }
139
+
140
+ // 5. Bundled moflo skills — gated by isSelfRef to prevent double-indexing
141
+ const bundledSkillsDir = resolve(mofloRoot, '.claude/skills');
142
+ if (!isSelfRef && existsSync(bundledSkillsDir) && resolve(bundledSkillsDir) !== resolve(projectSkillsDir)) {
143
+ dirs.push({ path: bundledSkillsDir, prefix: 'skill-bundled', fileFilter: ['SKILL.md'], kind: 'skill', absolute: true });
144
+ }
145
+
134
146
  return dirs;
135
147
  }
136
148
 
@@ -513,10 +525,12 @@ function buildHierarchy(chunks, chunkPrefix) {
513
525
  return hierarchy;
514
526
  }
515
527
 
516
- function indexFile(db, filePath, keyPrefix) {
517
- const fileName = basename(filePath, extname(filePath));
528
+ function indexFile(db, filePath, keyPrefix, options = {}) {
529
+ const fileName = options.nameOverride || basename(filePath, extname(filePath));
518
530
  const docKey = `doc-${keyPrefix}-${fileName}`;
519
531
  const chunkPrefix = `chunk-${keyPrefix}-${fileName}`;
532
+ const extraMetadata = options.extraMetadata || {};
533
+ const extraTags = options.extraTags || [];
520
534
 
521
535
  try {
522
536
  const content = readFileSync(filePath, 'utf-8');
@@ -538,6 +552,7 @@ function indexFile(db, filePath, keyPrefix) {
538
552
 
539
553
  // 1. Store full document
540
554
  const docMetadata = {
555
+ ...extraMetadata,
541
556
  type: 'document',
542
557
  filePath: relativePath,
543
558
  fileSize: stats.size,
@@ -547,7 +562,7 @@ function indexFile(db, filePath, keyPrefix) {
547
562
  ragVersion: '2.0', // Mark as full RAG indexed
548
563
  };
549
564
 
550
- storeEntry(db, docKey, content, docMetadata, [keyPrefix, 'document']);
565
+ storeEntry(db, docKey, content, docMetadata, [keyPrefix, 'document', ...extraTags]);
551
566
  debug(`Stored document: ${docKey}`);
552
567
 
553
568
  // 2. Chunk and store semantic pieces with full RAG linking
@@ -567,7 +582,7 @@ function indexFile(db, filePath, keyPrefix) {
567
582
  children: siblings,
568
583
  chunkCount: chunks.length,
569
584
  };
570
- storeEntry(db, docKey, content, docChildrenMeta, [keyPrefix, 'document']);
585
+ storeEntry(db, docKey, content, docChildrenMeta, [keyPrefix, 'document', ...extraTags]);
571
586
 
572
587
  for (let i = 0; i < chunks.length; i++) {
573
588
  const chunk = chunks[i];
@@ -589,6 +604,7 @@ function indexFile(db, filePath, keyPrefix) {
589
604
  const hierInfo = hierarchy[chunkKey];
590
605
 
591
606
  const chunkMetadata = {
607
+ ...extraMetadata,
592
608
  type: 'chunk',
593
609
  ragVersion: '2.0',
594
610
 
@@ -647,7 +663,7 @@ function indexFile(db, filePath, keyPrefix) {
647
663
  chunkKey,
648
664
  searchableContent,
649
665
  chunkMetadata,
650
- [keyPrefix, 'chunk', `level-${chunk.level}`, chunk.title.toLowerCase().replace(/[^a-z0-9]+/g, '-')]
666
+ [keyPrefix, 'chunk', `level-${chunk.level}`, chunk.title.toLowerCase().replace(/[^a-z0-9]+/g, '-'), ...extraTags]
651
667
  );
652
668
 
653
669
  debug(` Stored chunk ${i}: ${chunk.title} (${chunk.content.length} chars, prev=${!!prevChunk}, next=${!!nextChunk})`);
@@ -699,7 +715,17 @@ function indexDirectory(db, dirConfig) {
699
715
  : allMdFiles;
700
716
 
701
717
  for (const filePath of filtered) {
702
- const result = indexFile(db, filePath, dirConfig.prefix);
718
+ let options = {};
719
+ if (dirConfig.kind === 'skill') {
720
+ // kind: 'skill' — key by parent dir name (skill folder), not SKILL.md
721
+ const skillName = basename(dirname(filePath));
722
+ options = {
723
+ nameOverride: skillName,
724
+ extraMetadata: { kind: 'skill', skill_name: skillName },
725
+ extraTags: ['skill', `skill-${skillName}`],
726
+ };
727
+ }
728
+ const result = indexFile(db, filePath, dirConfig.prefix, options);
703
729
  results.push(result);
704
730
  }
705
731
 
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Retired-shipped-files prune helper (#948).
3
+ *
4
+ * Closes the gap that retired agents (#932) and retired skills (#945) leave
5
+ * in consumer projects: the manifest cleanup at section 3 of the launcher
6
+ * only knows about files moflo previously synced, but `.claude/agents/` and
7
+ * `.claude/skills/` were not in the manifest before #948. So files retired
8
+ * before #948 lands stay on disk forever — Claude Code keeps loading them as
9
+ * subagents on every prompt, paying the per-prompt roster tokens we just
10
+ * spent #932 fixing.
11
+ *
12
+ * This module reads `retired-files.json` (shipped at the moflo package
13
+ * root) and, for each entry, only prunes the consumer-side path when the
14
+ * file's sha256 matches one of `knownContentHashes` — i.e. the file matches
15
+ * a version moflo actually shipped, so the consumer didn't customize it.
16
+ * Customized files are preserved and a one-line notice is emitted so the
17
+ * user can act.
18
+ *
19
+ * Manifest format:
20
+ *
21
+ * {
22
+ * "version": 1,
23
+ * "retired": [
24
+ * {
25
+ * "path": ".claude/agents/v3/performance-engineer.md",
26
+ * "retiredIn": "4.9.22",
27
+ * "retiredBy": "#932",
28
+ * "knownContentHashes": ["sha256:abc...", "sha256:def..."]
29
+ * }
30
+ * ]
31
+ * }
32
+ *
33
+ * @module bin/lib/retired-files
34
+ */
35
+
36
+ import { existsSync, readFileSync, unlinkSync } from 'fs';
37
+ import { resolve } from 'path';
38
+ import { createHash } from 'crypto';
39
+
40
+ /**
41
+ * Compute sha256 of file content. Returns null on read errors so the caller
42
+ * can decide what to do — we never want a transient stat/read failure to
43
+ * trigger a prune of a file we couldn't actually verify.
44
+ *
45
+ * @param {string} absPath
46
+ * @returns {string|null} `sha256:<hex>` or null
47
+ */
48
+ export function fileSha256(absPath) {
49
+ try {
50
+ const buf = readFileSync(absPath);
51
+ return 'sha256:' + createHash('sha256').update(buf).digest('hex');
52
+ } catch {
53
+ return null;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Load + validate the retired-files manifest. Returns `{ entries: [] }` for
59
+ * any failure mode (missing file, invalid JSON, wrong shape) — the launcher
60
+ * must never block on a corrupt manifest.
61
+ *
62
+ * @param {string} manifestPath - absolute path to retired-files.json
63
+ * @returns {{ entries: Array<{path:string, retiredIn?:string, retiredBy?:string, knownContentHashes:string[]}> }}
64
+ */
65
+ export function loadRetiredManifest(manifestPath) {
66
+ if (!existsSync(manifestPath)) return { entries: [] };
67
+ let parsed;
68
+ try {
69
+ parsed = JSON.parse(readFileSync(manifestPath, 'utf-8'));
70
+ } catch { return { entries: [] }; }
71
+ if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.retired)) {
72
+ return { entries: [] };
73
+ }
74
+ // Filter out malformed entries — guards against a hand-edited file
75
+ // dropping the wrong shape into a launcher that runs on N consumer
76
+ // machines. A skipped entry never auto-prunes (safest default).
77
+ const entries = parsed.retired.filter((entry) =>
78
+ entry &&
79
+ typeof entry.path === 'string' &&
80
+ entry.path.length > 0 &&
81
+ Array.isArray(entry.knownContentHashes) &&
82
+ entry.knownContentHashes.length > 0 &&
83
+ entry.knownContentHashes.every((h) => typeof h === 'string' && h.startsWith('sha256:'))
84
+ );
85
+ return { entries };
86
+ }
87
+
88
+ /**
89
+ * Decide what to do with a consumer-side path against a retirement entry:
90
+ *
91
+ * - 'absent' — path doesn't exist on disk; nothing to do
92
+ * - 'prune' — file exists and content hash matches a known-shipped
93
+ * value; safe to delete (consumer didn't customize)
94
+ * - 'preserve' — file exists but content differs from every known-shipped
95
+ * hash; consumer customized, leave alone
96
+ * - 'unknown' — file exists but its hash couldn't be read (transient
97
+ * error); leave alone, retry next session
98
+ *
99
+ * @param {string} projectRoot
100
+ * @param {{path:string, knownContentHashes:string[]}} entry
101
+ * @returns {{ action: 'absent'|'prune'|'preserve'|'unknown', actualHash: string|null }}
102
+ */
103
+ export function classifyRetiredFile(projectRoot, entry) {
104
+ const abs = resolve(projectRoot, entry.path);
105
+ if (!existsSync(abs)) return { action: 'absent', actualHash: null };
106
+ const actualHash = fileSha256(abs);
107
+ if (!actualHash) return { action: 'unknown', actualHash: null };
108
+ if (entry.knownContentHashes.includes(actualHash)) {
109
+ return { action: 'prune', actualHash };
110
+ }
111
+ return { action: 'preserve', actualHash };
112
+ }
113
+
114
+ /**
115
+ * Walk the manifest and apply prune decisions. Returns a small report so
116
+ * the caller can emit one summary line (the launcher pattern) instead of
117
+ * forcing it to track per-entry state.
118
+ *
119
+ * Failures are non-fatal — a single un-deletable file (Windows AV hold,
120
+ * EBUSY) must not stop pruning the rest. The caller surfaces the report.
121
+ *
122
+ * @param {string} projectRoot
123
+ * @param {string} manifestPath
124
+ * @returns {{ pruned: string[], preserved: string[], unknown: string[], failed: Array<{path:string, message:string}> }}
125
+ */
126
+ export function applyRetiredPrune(projectRoot, manifestPath) {
127
+ const { entries } = loadRetiredManifest(manifestPath);
128
+ const report = { pruned: [], preserved: [], unknown: [], failed: [] };
129
+ for (const entry of entries) {
130
+ const { action } = classifyRetiredFile(projectRoot, entry);
131
+ if (action === 'absent') continue;
132
+ if (action === 'preserve') { report.preserved.push(entry.path); continue; }
133
+ if (action === 'unknown') { report.unknown.push(entry.path); continue; }
134
+ // action === 'prune'
135
+ try {
136
+ unlinkSync(resolve(projectRoot, entry.path));
137
+ report.pruned.push(entry.path);
138
+ } catch (err) {
139
+ report.failed.push({
140
+ path: entry.path,
141
+ message: err && err.message ? err.message : String(err),
142
+ });
143
+ }
144
+ }
145
+ return report;
146
+ }
@@ -14,6 +14,7 @@ import { fileURLToPath, pathToFileURL } from 'url';
14
14
  import { mofloDir } from './lib/moflo-paths.mjs';
15
15
  import { repairMemoryDbIfCorrupt } from './lib/db-repair.mjs';
16
16
  import { resolveMofloBin } from './lib/resolve-bin.mjs';
17
+ import { applyRetiredPrune } from './lib/retired-files.mjs';
17
18
 
18
19
  // Headless skip (#860). The daemon's headless workers spawn `claude --print`
19
20
  // with CLAUDE_CODE_HEADLESS=true (see src/cli/services/headless-worker-
@@ -681,6 +682,56 @@ try {
681
682
  }
682
683
  }
683
684
 
685
+ // ── Sync .claude/agents/ + .claude/skills/ recursively (#948) ──────
686
+ // Pre-#948, agents and skills weren't manifest-tracked at all, so any
687
+ // file moflo retired (e.g. the 49 ruflo-aspirational agents in #932 or
688
+ // skill-builder in #945) would linger forever in consumer projects —
689
+ // Claude Code kept loading them on every prompt, paying the per-prompt
690
+ // roster tokens we just spent #932 fixing. Walking these dirs through
691
+ // syncFile() puts every shipped file in `currentManifest`, which lets
692
+ // the cleanup loop below auto-prune retired files going forward.
693
+ //
694
+ // Limitation: this only catches retirements that happen AFTER #948.
695
+ // For files retired BEFORE #948 (#932 + #945), see the retired-files.json
696
+ // hash-gated prune block further down — it ships a static list with
697
+ // content hashes so we only delete files the consumer didn't customize.
698
+ //
699
+ // User-authored files at custom paths (.claude/agents/custom/foo.md,
700
+ // .claude/skills/<custom>/SKILL.md) are NEVER passed through syncFile()
701
+ // because they don't exist in node_modules/moflo/, so they never enter
702
+ // the manifest and never get pruned — same proven safety story as
703
+ // scripts/helpers above.
704
+ async function syncDirRecursive(srcDir, destPrefix) {
705
+ if (!existsSync(srcDir)) return;
706
+ let entries;
707
+ try {
708
+ entries = readdirSync(srcDir, { recursive: true, withFileTypes: true });
709
+ } catch (err) {
710
+ emitWarning(`${destPrefix} readdir failed (${errMessage(err)})`);
711
+ return;
712
+ }
713
+ for (const entry of entries) {
714
+ if (!entry.isFile()) continue;
715
+ if (!entry.name.toLowerCase().endsWith('.md')) continue;
716
+ const parent = entry.parentPath || entry.path || srcDir;
717
+ const absSrc = resolve(parent, entry.name);
718
+ const rel = absSrc.slice(srcDir.length + 1).split(/[\\/]/).join('/');
719
+ const absDest = resolve(projectRoot, destPrefix, rel);
720
+ try { mkdirSync(dirname(absDest), { recursive: true }); } catch (err) {
721
+ emitWarning(`${destPrefix} subdir mkdir failed for ${rel} (${errMessage(err)})`);
722
+ }
723
+ await syncFile(absSrc, absDest, `${destPrefix}/${rel}`);
724
+ }
725
+ }
726
+ await syncDirRecursive(
727
+ resolve(projectRoot, 'node_modules/moflo/.claude/agents'),
728
+ '.claude/agents',
729
+ );
730
+ await syncDirRecursive(
731
+ resolve(projectRoot, 'node_modules/moflo/.claude/skills'),
732
+ '.claude/skills',
733
+ );
734
+
684
735
  // Sync all shipped guidance files from node_modules/moflo/.claude/guidance/shipped/
685
736
  const guidanceDir = resolve(projectRoot, '.claude/guidance');
686
737
  const shippedDir = resolve(projectRoot, 'node_modules/moflo/.claude/guidance/shipped');
@@ -725,6 +776,56 @@ try {
725
776
  emitMutation('cleaned up retired files', `${removedFiles} removed`);
726
777
  }
727
778
 
779
+ // ── Hash-gated prune of pre-#948 retirements (Mechanism B) ─────────
780
+ // The manifest cleanup above only knows about files moflo previously
781
+ // synced. Agents and skills retired BEFORE #948 (#932's 49 agents and
782
+ // #945's skill-builder, plus older retirements) were never in any
783
+ // manifest — they exist on disk in consumer projects only because
784
+ // earlier moflo versions shipped them via the npm tarball + flo init.
785
+ // `retired-files.json` is the explicit, reviewable static list that
786
+ // closes that gap. Each entry pairs a path with the sha256 hashes of
787
+ // every content version moflo previously shipped, so we only auto-
788
+ // prune when the consumer's file matches a known-shipped hash —
789
+ // customized files (different hash) get preserved with a one-line
790
+ // notice the user can act on.
791
+ try {
792
+ const retiredManifestPath = resolve(
793
+ projectRoot,
794
+ 'node_modules/moflo/retired-files.json',
795
+ );
796
+ const report = applyRetiredPrune(projectRoot, retiredManifestPath);
797
+ if (report.pruned.length > 0) {
798
+ emitMutation(
799
+ 'pruned retired shipped files',
800
+ `${plural(report.pruned.length, 'file')} matching known-shipped content removed`,
801
+ );
802
+ }
803
+ if (report.preserved.length > 0) {
804
+ // stdout (not stderr) so Claude sees this in `additionalContext`
805
+ // and can surface to the user — these aren't failures, just
806
+ // consumer-customized files we deliberately left alone.
807
+ const sample = report.preserved.slice(0, 5).map((p) => ` - ${p}`).join('\n');
808
+ const more = report.preserved.length > 5
809
+ ? `\n …and ${report.preserved.length - 5} more`
810
+ : '';
811
+ try {
812
+ process.stdout.write(
813
+ `moflo: retained ${plural(report.preserved.length, 'customized retired file')} (delete manually if unwanted):\n${sample}${more}\n`,
814
+ );
815
+ } catch { /* non-fatal */ }
816
+ }
817
+ if (report.failed.length > 0) {
818
+ const sample = report.failed.slice(0, 3)
819
+ .map((f) => ` - ${f.path}: ${f.message}`).join('\n');
820
+ emitWarning(`${plural(report.failed.length, 'retired file')} failed to prune:\n${sample}`);
821
+ }
822
+ } catch (err) {
823
+ // Non-fatal — the consumer just doesn't get the prune this session.
824
+ // Next session retries; manifest-cleanup above still works for
825
+ // future retirements regardless.
826
+ emitWarning(`retired-files prune skipped (${errMessage(err)})`);
827
+ }
828
+
728
829
  // The daemon was already stopped above so the lock file is gone and
729
830
  // there's no live PID to recycle here. Section 4's `hooks.mjs
730
831
  // session-start` will spawn a fresh daemon under the current moflo
@@ -1334,14 +1435,15 @@ try {
1334
1435
  } catch { /* writing the failure itself must not throw */ }
1335
1436
  }
1336
1437
 
1337
- // ── 3e-729. Purge ephemeral-namespace rows (#729) ───────────────────────────
1338
- // Four namespaces (hive-mind, tasklist, epic-state, test-bridge-fix) store
1339
- // internal moflo run-tracking never user knowledge and were polluting the
1340
- // embeddings index. Going forward, writes to those namespaces skip embedding
1341
- // generation (see EPHEMERAL_NAMESPACES in memory/bridge-embedder.ts); existing
1342
- // rows from prior versions get hard-deleted here. Idempotent returns
1343
- // `purged: 0` once the DB is clean. Runs BEFORE background MCP/daemon spawn
1344
- // so the foreground sql.js write isn't overwritten by a concurrent flush.
1438
+ // ── 3e-729. Purge ephemeral-namespace rows + trim tasklist (#729, #968) ─────
1439
+ // Three namespaces (hive-mind, epic-state, test-bridge-fix) store internal
1440
+ // run-tracking and get hard-deleted on every session start. The fourth
1441
+ // embedding-skipped namespace, `tasklist`, backs the dashboard's Flo Runs
1442
+ // tab it's *trimmed* to a retention cap instead of purged so prior runs
1443
+ // survive a session restart (#968). Idempotent: returns
1444
+ // `{ purged: 0, trimmed: 0 }` when nothing needs cleaning. Runs BEFORE the
1445
+ // background MCP/daemon spawn so the foreground sql.js write isn't
1446
+ // overwritten by a concurrent flush.
1345
1447
  try {
1346
1448
  const purgePaths = [
1347
1449
  resolve(projectRoot, 'node_modules/moflo/dist/src/cli/services/ephemeral-namespace-purge.js'),
@@ -1357,6 +1459,12 @@ try {
1357
1459
  `${plural(result.purged, 'row')} from internal run-tracking`,
1358
1460
  );
1359
1461
  }
1462
+ if (result?.trimmed > 0) {
1463
+ emitMutation(
1464
+ 'trimmed flo run history',
1465
+ `${plural(result.trimmed, 'old row')} beyond retention cap`,
1466
+ );
1467
+ }
1360
1468
  }
1361
1469
  } catch (err) {
1362
1470
  // Non-fatal — leftover rows just sit until the next session retries.
@@ -20,7 +20,7 @@ const AES_TAG_LEN = 16;
20
20
  const AES_ALG = 'aes-256-gcm';
21
21
  // ── Catalog ──────────────────────────────────────────────────
22
22
  const RUFLO_COMMANDS = 'init agent swarm memory mcp task session config status start workflow hooks hive-mind daemon neural security performance providers plugins deployment embeddings claims migrate process doctor completions'.split(' ');
23
- const AGENT_TYPES = 'coder reviewer tester planner researcher security-architect security-auditor memory-specialist performance-engineer hierarchical-coordinator mesh-coordinator adaptive-coordinator collective-intelligence-coordinator swarm-memory-manager byzantine-coordinator raft-manager gossip-coordinator consensus-builder crdt-synchronizer quorum-manager security-manager perf-analyzer performance-benchmarker task-orchestrator memory-coordinator smart-agent github-modes pr-manager code-review-swarm issue-tracker release-manager workflow-automation project-board-sync repo-architect multi-repo-swarm sparc-coord sparc-coder specification pseudocode architecture refinement backend-dev mobile-dev ml-developer cicd-engineer api-docs system-architect code-analyzer base-template-generator tdd-london-swarm production-validator'.split(' ');
23
+ const AGENT_TYPES = 'coder reviewer tester planner researcher security-auditor backend-dev frontend-dev database-dev cicd-engineer api-docs system-architect code-analyzer analyst base-template-generator test-long-runner'.split(' ');
24
24
  const HOOK_TYPES = 'pre-edit post-edit pre-command post-command pre-task post-task session-start session-end session-restore notify route explain pretrain build-agents transfer teammate-idle task-completed'.split(' ');
25
25
  const WORKER_TYPES = 'ultralearn optimize consolidate predict audit map preload deepdive document refactor benchmark testgaps'.split(' ');
26
26
  const OFFLINE_MODELS = [
@@ -68,13 +68,8 @@ const AGENT_TYPES = [
68
68
  { value: 'coordinator', label: 'Coordinator', hint: 'Multi-agent orchestration and spell management' },
69
69
  { value: 'analyst', label: 'Analyst', hint: 'Performance analysis and optimization' },
70
70
  { value: 'optimizer', label: 'Optimizer', hint: 'Performance optimization and bottleneck analysis' },
71
- { value: 'security-architect', label: 'Security Architect', hint: 'Security architecture and threat modeling' },
72
71
  { value: 'security-auditor', label: 'Security Auditor', hint: 'CVE remediation and security testing' },
73
- { value: 'memory-specialist', label: 'Memory Specialist', hint: 'AgentDB unification (150x-12,500x faster)' },
74
- { value: 'swarm-specialist', label: 'Swarm Specialist', hint: 'Unified coordination engine' },
75
- { value: 'performance-engineer', label: 'Performance Engineer', hint: '2.49x-7.47x optimization targets' },
76
- { value: 'core-architect', label: 'Core Architect', hint: 'Domain-driven design restructure' },
77
- { value: 'test-architect', label: 'Test Architect', hint: 'TDD London School methodology' }
72
+ { value: 'planner', label: 'Planner', hint: 'Task planning and breakdown' },
78
73
  ];
79
74
  // Agent spawn subcommand
80
75
  const spawnCommand = {
@@ -844,9 +839,8 @@ function getAgentCapabilities(type) {
844
839
  reviewer: ['code-review', 'security-audit', 'quality-check', 'documentation'],
845
840
  architect: ['system-design', 'pattern-analysis', 'scalability', 'documentation'],
846
841
  coordinator: ['task-orchestration', 'agent-management', 'spell-control'],
847
- 'security-architect': ['threat-modeling', 'security-patterns', 'compliance', 'audit'],
848
- 'memory-specialist': ['vector-search', 'agentdb', 'caching', 'optimization'],
849
- 'performance-engineer': ['benchmarking', 'profiling', 'optimization', 'monitoring']
842
+ 'security-auditor': ['threat-modeling', 'security-patterns', 'compliance', 'audit', 'vulnerability-scan'],
843
+ planner: ['task-breakdown', 'sequencing', 'estimation'],
850
844
  };
851
845
  return capabilities[type] || ['general'];
852
846
  }