@claude-flow/cli 3.56.8 → 3.56.10

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 (120) hide show
  1. package/.claude/helpers/.helpers-version +1 -1
  2. package/.claude/helpers/helpers.manifest.json +4 -4
  3. package/.claude/helpers/hook-handler.cjs +109 -23
  4. package/.claude/helpers/intelligence.cjs +51 -9
  5. package/.claude/helpers/router.cjs +0 -0
  6. package/.claude/skills/verification-quality/SKILL.md +14 -11
  7. package/README.md +1 -1
  8. package/catalog-manifest.json +2 -2
  9. package/dist/src/autopilot-state.d.ts +2 -0
  10. package/dist/src/autopilot-state.js +36 -8
  11. package/dist/src/command-resolution.d.ts +15 -0
  12. package/dist/src/command-resolution.js +25 -0
  13. package/dist/src/commands/agent.js +17 -17
  14. package/dist/src/commands/analyze.js +40 -29
  15. package/dist/src/commands/claims.js +6 -8
  16. package/dist/src/commands/daemon.js +86 -31
  17. package/dist/src/commands/memory.js +14 -2
  18. package/dist/src/commands/security-code-patterns.d.ts +2 -2
  19. package/dist/src/commands/security-code-patterns.js +61 -3
  20. package/dist/src/commands/security.d.ts +24 -0
  21. package/dist/src/commands/security.js +81 -35
  22. package/dist/src/commands/tui.d.ts +9 -0
  23. package/dist/src/commands/tui.js +29 -3
  24. package/dist/src/config/proven-config-refresh.d.ts +9 -0
  25. package/dist/src/config/proven-config-refresh.js +24 -3
  26. package/dist/src/funnel/local-signals.js +2 -1
  27. package/dist/src/index.js +6 -42
  28. package/dist/src/init/helpers-generator.js +109 -23
  29. package/dist/src/init/types.js +1 -1
  30. package/dist/src/mcp-tools/agent-execute-core.d.ts +2 -0
  31. package/dist/src/mcp-tools/agent-execute-core.js +15 -0
  32. package/dist/src/mcp-tools/agent-health-metrics.d.ts +22 -0
  33. package/dist/src/mcp-tools/agent-health-metrics.js +33 -0
  34. package/dist/src/mcp-tools/agent-tools.js +45 -22
  35. package/dist/src/mcp-tools/agentbbs-federation.d.ts +1 -1
  36. package/dist/src/mcp-tools/agentbbs-federation.js +7 -2
  37. package/dist/src/mcp-tools/agentdb-tools.js +25 -2
  38. package/dist/src/mcp-tools/aidefence-known-misses.d.ts +22 -0
  39. package/dist/src/mcp-tools/aidefence-known-misses.js +57 -0
  40. package/dist/src/mcp-tools/analyze-tools.js +10 -11
  41. package/dist/src/mcp-tools/autopilot-tools.js +2 -2
  42. package/dist/src/mcp-tools/browser-intent-tools.js +6 -2
  43. package/dist/src/mcp-tools/embeddings-tools.js +29 -7
  44. package/dist/src/mcp-tools/hooks-tools.js +4 -0
  45. package/dist/src/mcp-tools/memory-tools.js +70 -20
  46. package/dist/src/mcp-tools/neural-tools.js +28 -4
  47. package/dist/src/mcp-tools/ruvllm-tools.js +6 -0
  48. package/dist/src/mcp-tools/security-tools.js +55 -17
  49. package/dist/src/mcp-tools/workflow-tools.js +66 -17
  50. package/dist/src/memory/intelligence.d.ts +5 -1
  51. package/dist/src/memory/intelligence.js +105 -29
  52. package/dist/src/memory/memory-bridge.js +10 -3
  53. package/dist/src/memory/memory-initializer.js +2 -1
  54. package/dist/src/memory/search-defaults.d.ts +54 -0
  55. package/dist/src/memory/search-defaults.js +37 -0
  56. package/dist/src/memory/sona-optimizer.d.ts +28 -0
  57. package/dist/src/memory/sona-optimizer.js +59 -2
  58. package/dist/src/mods/command-registry/schema.d.ts +24 -24
  59. package/dist/src/parser.js +32 -22
  60. package/dist/src/production/monitoring.d.ts +1 -0
  61. package/dist/src/production/monitoring.js +19 -6
  62. package/dist/src/production/rate-limiter.js +5 -4
  63. package/dist/src/production/retry.js +3 -0
  64. package/dist/src/ruvector/agent-wasm.d.ts +10 -0
  65. package/dist/src/ruvector/agent-wasm.js +27 -1
  66. package/dist/src/ruvector/diff-classifier.d.ts +2 -4
  67. package/dist/src/ruvector/diff-classifier.js +202 -69
  68. package/dist/src/ruvector/ruvllm-wasm.d.ts +4 -0
  69. package/dist/src/ruvector/ruvllm-wasm.js +12 -1
  70. package/dist/src/services/config-file-manager.js +3 -3
  71. package/dist/src/services/policy-runtime.d.ts +1 -1
  72. package/dist/src/services/policy-runtime.js +7 -1
  73. package/dist/src/services/worker-daemon.d.ts +11 -0
  74. package/dist/src/services/worker-daemon.js +37 -0
  75. package/dist/src/transfer/store/discovery.js +16 -17
  76. package/dist/src/transfer/store/types.d.ts +4 -0
  77. package/node_modules/@claude-flow/codex/dist/cli.js +0 -0
  78. package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.d.ts +1 -0
  79. package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.d.ts.map +1 -1
  80. package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.js +3 -1
  81. package/node_modules/@claude-flow/codex/dist/dual-mode/orchestrator.js.map +1 -1
  82. package/node_modules/@claude-flow/codex/dist/index.d.ts +3 -3
  83. package/node_modules/@claude-flow/codex/dist/index.js +1 -1
  84. package/node_modules/@claude-flow/codex/package.json +1 -1
  85. package/node_modules/@claude-flow/plugin-agent-federation/dist/bin.js +0 -0
  86. package/node_modules/@claude-flow/security/dist/input-validator.d.ts +6 -6
  87. package/node_modules/@claude-flow/security/dist/policy/envelope.js +6 -6
  88. package/node_modules/@claude-flow/security/dist/policy/envelope.js.map +1 -1
  89. package/node_modules/@claude-flow/security/package.json +1 -1
  90. package/package.json +7 -7
  91. package/plugins/ruflo-console/dist/tui.mjs +162 -160
  92. package/plugins/ruflo-metaharness/.claude-flow/daemon-state.json +179 -0
  93. package/plugins/ruflo-metaharness/.claude-flow/daemon.pid +1 -0
  94. package/plugins/ruflo-metaharness/.claude-flow/logs/daemon.log +32 -0
  95. package/plugins/ruflo-metaharness/.claude-flow/metrics/backup.json +23 -0
  96. package/plugins/ruflo-metaharness/.claude-flow/metrics/codebase-map.json +11 -0
  97. package/plugins/ruflo-metaharness/.claude-flow/metrics/consolidation.json +16 -0
  98. package/plugins/ruflo-metaharness/.claude-flow/metrics/harness-loop.json +91 -0
  99. package/plugins/ruflo-metaharness/.claude-flow/metrics/performance.json +17 -0
  100. package/plugins/ruflo-metaharness/.claude-flow/metrics/security-audit.json +12 -0
  101. package/plugins/ruflo-metaharness/.claude-flow/metrics/test-gaps.json +8 -0
  102. package/plugins/ruflo-metaharness/.claude-flow/neural/patterns.json +2809 -0
  103. package/plugins/ruflo-metaharness/.claude-flow/neural/stats.json +6 -0
  104. package/plugins/ruflo-metaharness/.claude-flow/policy/state.json +11 -0
  105. package/plugins/ruflo-metaharness/.claude-plugin/plugin.json +1 -1
  106. package/plugins/ruflo-metaharness/.swarm/agentdb-memory.db +0 -0
  107. package/plugins/ruflo-metaharness/.swarm/agentdb-memory.db-shm +0 -0
  108. package/plugins/ruflo-metaharness/.swarm/agentdb-memory.db-wal +0 -0
  109. package/plugins/ruflo-metaharness/.swarm/backups/memory-2026-10-14T16-09-38-164Z.db +0 -0
  110. package/plugins/ruflo-metaharness/.swarm/backups/store-agentdb-memory.db-2026-10-14T16-09-38-166Z.db +0 -0
  111. package/plugins/ruflo-metaharness/.swarm/ewc-fisher.json +397 -0
  112. package/plugins/ruflo-metaharness/.swarm/memory.db +0 -0
  113. package/plugins/ruflo-metaharness/.swarm/memory.db-shm +0 -0
  114. package/plugins/ruflo-metaharness/.swarm/memory.db-wal +0 -0
  115. package/plugins/ruflo-metaharness/CHANGELOG.md +3 -0
  116. package/plugins/ruflo-metaharness/agentdb.rvf +0 -0
  117. package/plugins/ruflo-metaharness/agentdb.rvf.lock +0 -0
  118. package/plugins/ruflo-metaharness/hooks/screen.ts +10 -3
  119. package/plugins/ruflo-metaharness/ruvector.db +0 -0
  120. package/plugins/ruflo-metaharness/scripts/smoke.sh +4 -4
@@ -1 +1 @@
1
- 3.42.0
1
+ 3.56.9
@@ -1,14 +1,14 @@
1
1
  {
2
2
  "manifest": {
3
- "version": "3.56.8",
3
+ "version": "3.56.10",
4
4
  "files": {
5
5
  "auto-memory-hook.mjs": "631fac9d518835e2852a406888316bbbddddb49e7ed8e406d2e971f46833df9a",
6
- "hook-handler.cjs": "0f855c667b8d2d04cb4fe91b14b351d14c3dfb30516dcd9d7ae156acbe0a689d",
7
- "intelligence.cjs": "f486be296eb2f0518f34b8f4453d9d0a988845e4aec6d18d8d47725aca6dd38b",
6
+ "hook-handler.cjs": "bc12b3cae92d5a7c8deef699fa00d0ef07b57625c18dbeac4387f15725b8e09f",
7
+ "intelligence.cjs": "a95ac9ee2759c340b14f9ff656765bcb05e0cefe2f018116bd26b3ba9987f3e4",
8
8
  "statusline.cjs": "d72417ecb38634011a19f095a30e07b9929e6f97663520172ece9be7491b2eaa",
9
9
  "router.cjs": "16648b3c0d73b6681ac2d8d13def6a68286c57f5a11004de5b19a0fa088c833d"
10
10
  }
11
11
  },
12
- "signature": "ld9RNoF9lnGKjlUpofQLwCZgtE9Y5QiNNkYZLGsSQhZGp51JhBnHymH9YWpFC3F1HBslxRnea6U2TVczJKK5Bg==",
12
+ "signature": "hZ0iy15VIKpZOjC2iG2fPsV68csqXrMhn2mifCIvzsjngaB/wmP0P4iMF38nAo57MeANEM6wW1nhK7YvteUXCg==",
13
13
  "algorithm": "ed25519"
14
14
  }
@@ -682,7 +682,8 @@ const handlers = {
682
682
  const dangerous = ['rm -rf /', 'format c:', 'del /s /q c:\\', ':(){:|:&};:'];
683
683
  // Join literal quote fragments and normalize root paths without shell evaluation.
684
684
  function hasRootDelete(command, depth = 0) {
685
- let word = '', quote = '', started = false, redirect = false
685
+ // mask has one letter per character of word: 'u' unquoted, 'q' quoted or escaped (only unquoted braces expand).
686
+ let word = '', mask = '', quote = '', started = false, redirect = false
686
687
  let ansiNul = false // a NUL inside $'...' ends that string, as in bash
687
688
  let inRm = false, optionsEnded = false, recursive = false, force = false, root = false
688
689
  const isRoot = (operand) => {
@@ -712,23 +713,92 @@ const handlers = {
712
713
  if (c === 'c' && at + 1 < s.length) return [String.fromCharCode(s.charCodeAt(at + 1) & 31), at + 1]
713
714
  return ['\\' + c, at]
714
715
  }
715
- const finishWord = () => {
716
- if (!started) return false
717
- // Literal shell strings (e.g. sh -c 'rm -rf /') also carried the old guard.
718
- // Bound rescanning to four levels; beyond that retain its conservative check.
719
- if (/[\s;&|()\x60]/.test(word)) {
720
- if (depth < 4 ? hasRootDelete(word, depth + 1) : word.includes('rm -rf /')) return true
716
+ // Past the rescan bound (or the brace budget), a string is judged by its tokens: an rm and a possible root operand
717
+ // anywhere in it deny. Quote characters are dropped first, so r"m" is still rm; a path token with braces may expand to /.
718
+ const looseRootDelete = (text) => {
719
+ const tokens = text.replace(/["'\\]/g, '').split(/[\s;&|()\x60$]+/)
720
+ return tokens.some(t => t === 'rm' || t.endsWith('/rm')) && tokens.some(t => isRoot(t) || (t.includes('/') && /[{}]/.test(t)))
721
+ }
722
+ // Bash brace expansion of one word ({a,b} lists, nested or glued to text), and nothing else: no {1..3} sequences, and
723
+ // nothing inside parameter braces after a dollar sign. Only unquoted braces and commas count. Iterative and bounded:
724
+ // past BRACE_WORDS words or BRACE_CHARS characters of work in this call it returns undefined.
725
+ const BRACE_WORDS = 4096
726
+ let braceChars = 1_000_000
727
+ // The leftmost expandable group: an unquoted {...} with an unquoted comma at its own level, outside any dollar-brace parameter. One pass.
728
+ const firstGroup = (text, marks) => {
729
+ const stack = []
730
+ let dollars = 0
731
+ let best
732
+ for (let k = 0; k < text.length; k++) {
733
+ if (marks[k] !== 'u') continue
734
+ const c = text[k]
735
+ if (c === '{') {
736
+ const dollar = k > 0 && text[k - 1] === '$' && marks[k - 1] === 'u'
737
+ if (dollar) dollars++
738
+ stack.push({ open: k, commas: [], dollar: dollar || dollars > 0 })
739
+ } else if (c === ',' && stack.length > 0) stack[stack.length - 1].commas.push(k)
740
+ else if (c === '}' && stack.length > 0) {
741
+ const top = stack.pop()
742
+ if (top.dollar && text[top.open - 1] === '$' && marks[top.open - 1] === 'u') dollars--
743
+ if (!top.dollar && top.commas.length > 0 && (best === undefined || top.open < best[0])) best = [top.open, top.commas, k]
744
+ }
745
+ }
746
+ return best
747
+ }
748
+ const braceExpand = (text, marks) => {
749
+ const out = []
750
+ const todo = [[text, marks]]
751
+ while (todo.length > 0) {
752
+ const [t, m] = todo.pop()
753
+ if ((braceChars -= t.length) < 0) return undefined
754
+ const group = firstGroup(t, m)
755
+ if (group === undefined) {
756
+ out.push(t)
757
+ if (out.length > BRACE_WORDS) return undefined
758
+ continue
759
+ }
760
+ const [open, commas, close] = group
761
+ const cuts = [open, ...commas, close]
762
+ // Pushed in reverse, so the words come out in bash's left-to-right order.
763
+ for (let c = cuts.length - 2; c >= 0; c--) {
764
+ const from = cuts[c] + 1, to = cuts[c + 1]
765
+ todo.push([t.slice(0, open) + t.slice(from, to) + t.slice(close + 1), m.slice(0, open) + m.slice(from, to) + m.slice(close + 1)])
766
+ }
767
+ if (todo.length > BRACE_WORDS) return undefined
721
768
  }
722
- if (!inRm) inRm = word === 'rm' || word.endsWith('/rm')
723
- else if (!optionsEnded && word === '--') optionsEnded = true
724
- else if (!optionsEnded && word.startsWith('-')) {
769
+ return out
770
+ }
771
+ const takeWord = (w) => {
772
+ if (!inRm) inRm = w === 'rm' || w.endsWith('/rm')
773
+ else if (!optionsEnded && w === '--') optionsEnded = true
774
+ else if (!optionsEnded && w.startsWith('-')) {
725
775
  // GNU getopt accepts any unambiguous long-option prefix: --r, --recur, --forc.
726
- recursive = recursive || (word.length > 2 && '--recursive'.startsWith(word)) || /^-[a-z]*r[a-z]*$/.test(word)
727
- force = force || (word.length > 2 && '--force'.startsWith(word)) || /^-[a-z]*f[a-z]*$/.test(word)
728
- } else root = root || isRoot(word)
729
- word = ''; started = false
776
+ recursive = recursive || (w.length > 2 && '--recursive'.startsWith(w)) || (/^-[a-z]*$/.test(w) && w.includes('r'))
777
+ force = force || (w.length > 2 && '--force'.startsWith(w)) || (/^-[a-z]*$/.test(w) && w.includes('f'))
778
+ } else root = root || isRoot(w)
730
779
  return inRm && recursive && force && root
731
780
  }
781
+ const finishWord = () => {
782
+ if (!started) return false
783
+ const text = word, marks = mask
784
+ word = ''; mask = ''; started = false
785
+ // Literal shell strings (e.g. sh -c 'rm -rf /', or sh -c '{rm,-rf,/}') also carried the old guard.
786
+ // Bound rescanning to four levels; beyond that judge the string by its tokens.
787
+ if (/[\s;&|()\x60{]/.test(text)) {
788
+ if (depth < 4 ? hasRootDelete(text, depth + 1) : looseRootDelete(text)) return true
789
+ }
790
+ // An unquoted {a,b} becomes separate words, as bash expands it: rm -rf {/*,} is rm -rf /*.
791
+ const words = text.includes('{') ? braceExpand(text, marks) : [text]
792
+ if (words === undefined) {
793
+ // Too many alternatives to check: one of them may be the root (an rm in progress fails closed), or the
794
+ // list itself may spell the rm: judge its tokens.
795
+ if (inRm) root = true
796
+ if (looseRootDelete(text.replace(/[{},]/g, ' '))) return true
797
+ return takeWord(text)
798
+ }
799
+ for (const w of words) if (w !== '' && takeWord(w)) return true
800
+ return false
801
+ }
732
802
  // A command substitution's body, from just after its opening backtick. Bash removes a
733
803
  // backslash only before $, a backtick or a backslash (and " inside double quotes) and
734
804
  // drops backslash-newline; any other backslash stays for the nested scan.
@@ -742,7 +812,7 @@ const handlers = {
742
812
  }
743
813
  body += command[j]
744
814
  }
745
- return [j, depth < 4 ? hasRootDelete(body, depth + 1) : body.includes('rm -rf /')]
815
+ return [j, depth < 4 ? hasRootDelete(body, depth + 1) : looseRootDelete(body)]
746
816
  }
747
817
  const finishCommand = () => {
748
818
  const denied = inRm && recursive && force && root
@@ -759,8 +829,8 @@ const handlers = {
759
829
  else if (char === '\\' && i + 1 < command.length) {
760
830
  const [text, last] = ansiEscape(command, i + 1)
761
831
  i = last
762
- if (!ansiNul) { const nul = text.indexOf('\0'); if (nul < 0) word += text; else { word += text.slice(0, nul); ansiNul = true } }
763
- } else if (!ansiNul) word += char
832
+ if (!ansiNul) { const nul = text.indexOf('\0'); const kept = nul < 0 ? text : text.slice(0, nul); word += kept; mask += 'q'.repeat(kept.length); if (nul >= 0) ansiNul = true }
833
+ } else if (!ansiNul) { word += char; mask += 'q' }
764
834
  continue
765
835
  }
766
836
  if (char === quote) quote = ''
@@ -770,20 +840,20 @@ const handlers = {
770
840
  // The substitution is gone from the word, as bash's empty output is (a quoted empty substitution glued to -rf leaves -rf).
771
841
  // A # right after it is still inside the word: a placeholder keeps a rescan (sh -c "...")
772
842
  // from reading it as the start of a comment.
773
- if (command[end + 1] === '#') word += '\u0001'
843
+ if (command[end + 1] === '#') { word += '\u0001'; mask += 'q' }
774
844
  i = end
775
845
  }
776
846
  else if (quote === '"' && char === '\\' && i + 1 < command.length &&
777
847
  (command[i + 1] === '"' || command[i + 1] === '\\' || command[i + 1] === '$' ||
778
848
  command.charCodeAt(i + 1) === 96 || command[i + 1] === '\n')) {
779
849
  const next = command[++i]
780
- if (next !== '\n') word += next
781
- } else word += char
850
+ if (next !== '\n') { word += next; mask += 'q' }
851
+ } else { word += char; mask += 'q' }
782
852
  continue
783
853
  }
784
854
  if (char === '\\' && i + 1 < command.length) {
785
855
  const next = command[++i]
786
- if (next !== '\n') { word += next; started = true }
856
+ if (next !== '\n') { word += next; mask += 'q'; started = true }
787
857
  } else if (char === '\x60' && command.indexOf('\x60', i + 1) > i) {
788
858
  // Command substitution: scan the body as its own command; the substitution
789
859
  // stays inside the enclosing word, so the rm being parsed keeps its state,
@@ -806,13 +876,29 @@ const handlers = {
806
876
  redirect = char === '<' || char === '>'
807
877
  if (!redirectionAmpersand && (char === '\n' || ';|&()'.includes(char)) && finishCommand()) return true
808
878
  } else {
809
- word += char; started = true
879
+ word += char; mask += 'u'; started = true
810
880
  }
811
881
  }
812
882
  return finishWord() || finishCommand()
813
883
  }
884
+
885
+ // A fork bomb's definition as bash reads it, whatever the spacing or the name: f(){ f|f& }, function f { f|f& },
886
+ // function f(){ f|f& }. Defining one is the danger: the call may come anywhere later.
887
+ function hasForkBomb(command) {
888
+ const s = command.replace(/\s+/g, '')
889
+ for (let brace = s.indexOf('{'); brace >= 0; brace = s.indexOf('{', brace + 1)) {
890
+ const end = s.startsWith('()', brace - 2) ? brace - 2 : brace
891
+ let start = end
892
+ while (start > 0 && end - start < 72 && /[a-z0-9_:.-]/.test(s[start - 1])) start--
893
+ const run = s.slice(start, end)
894
+ for (const name of run.startsWith('function') && run.length > 8 ? [run, run.slice(8)] : [run]) {
895
+ if (name && s.startsWith(name + '|' + name + '&}', brace + 1)) return true
896
+ }
897
+ }
898
+ return false
899
+ }
814
900
  for (const d of dangerous) {
815
- if (d === 'rm -rf /' ? hasRootDelete(cmd) : cmd.includes(d)) {
901
+ if (d === 'rm -rf /' ? hasRootDelete(cmd) : d === ':(){:|:&};:' ? hasForkBomb(cmd) : cmd.includes(d)) {
816
902
  console.error(`[BLOCKED] Dangerous command detected: ${d}`);
817
903
  // Claude Code PreToolUse: exit 2 blocks execution; exit 1 is non-blocking.
818
904
  process.exit(2);
@@ -739,27 +739,69 @@ function appendRecall(detailed, meta) {
739
739
  * getContext(prompt) — Called from route. Budget: <15ms.
740
740
  * Matches prompt to ranked entries, returns top-5 formatted context.
741
741
  */
742
+ /**
743
+ * Inflection variants of a prompt term (plural, -ed, -ing, -ion and their
744
+ * stems), so "migrations" finds an entry that says "migration" and "deploying"
745
+ * finds "deploy". Exact term matching alone lost what the old trigram overlap
746
+ * caught (#4136). The stem keeps at least 4 characters so short words stay exact.
747
+ */
748
+ function termVariants(word) {
749
+ const out = new Set([word]);
750
+ for (const suffix of ['s', 'es', 'ed', 'ing', 'ion', 'ions']) out.add(word + suffix);
751
+ for (const suffix of ['ions', 'ion', 'ing', 'ed', 'es', 's']) {
752
+ if (word.length - suffix.length >= 4 && word.endsWith(suffix)) {
753
+ const stem = word.slice(0, -suffix.length);
754
+ out.add(stem); out.add(stem + 'e'); out.add(stem + 's');
755
+ }
756
+ }
757
+ return [...out];
758
+ }
759
+
742
760
  function getContextDetailed(prompt) {
743
761
  if (!prompt) return null;
744
762
 
745
763
  const ranked = readJSON(RANKED_PATH);
746
764
  if (!ranked || !ranked.entries || ranked.entries.length === 0) return null;
747
765
 
748
- const promptWords = tokenize(prompt);
766
+ const promptWords = [...new Set(tokenize(prompt))];
749
767
  if (promptWords.length === 0) return null;
750
- const promptTrigrams = trigrams(promptWords);
751
768
 
752
- const ALPHA = 0.6; // content match weight
753
- const MIN_THRESHOLD = 0.05;
769
+ // Query coverage is independent of entry length: a short diagnostic prompt
770
+ // must still find its exact terms inside a long memory section (#4136).
771
+ // IDF downweights generic terms shared by many entries. Terms absent from
772
+ // every entry still count against coverage, so two incidental words cannot
773
+ // hide the rest of an unrelated prompt.
774
+ const candidates = ranked.entries.map(entry => ({ entry, words: new Set(entry.words || []) }));
775
+ const variants = promptWords.map(termVariants);
776
+ const hasTerm = (words, i) => variants[i].some(v => words.has(v));
777
+ const weights = promptWords.map((word, i) => {
778
+ const frequency = candidates.reduce((count, candidate) => count + Number(hasTerm(candidate.words, i)), 0);
779
+ return Math.log1p((candidates.length - frequency + 0.5) / (frequency + 0.5));
780
+ });
781
+ const totalWeight = weights.reduce((sum, weight) => sum + weight, 0);
782
+ if (totalWeight === 0) return null;
783
+
784
+ const MIN_COVERAGE = 0.15;
754
785
  const TOP_K = 5;
755
786
 
756
787
  // Score each entry
757
788
  const scored = [];
758
- for (const entry of ranked.entries) {
759
- const entryTrigrams = trigrams(entry.words || []);
760
- const contentMatch = jaccardSimilarity(promptTrigrams, entryTrigrams);
761
- const score = ALPHA * contentMatch + (1 - ALPHA) * (entry.pageRank || 0);
762
- if (score >= MIN_THRESHOLD) {
789
+ for (const { entry, words } of candidates) {
790
+ let matchedWeight = 0;
791
+ let matchedTerms = 0;
792
+ for (let i = 0; i < promptWords.length; i++) {
793
+ if (weights[i] > 0 && hasTerm(words, i)) {
794
+ matchedWeight += weights[i];
795
+ matchedTerms++;
796
+ }
797
+ }
798
+ // A single common word in a long prompt is not evidence of relevance;
799
+ // PageRank can order relevant entries but cannot make one relevant.
800
+ if (matchedTerms < Math.min(2, promptWords.length)) continue;
801
+ const coverage = matchedWeight / totalWeight;
802
+ if (coverage >= MIN_COVERAGE) {
803
+ const pageRank = Math.max(0, Math.min(1, Number(entry.pageRank) || 0));
804
+ const score = 0.9 * coverage + 0.1 * pageRank;
763
805
  scored.push({ ...entry, score });
764
806
  }
765
807
  }
File without changes
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: "Verification & Quality Assurance"
3
3
  description: |
4
- Comprehensive truth scoring, code quality verification, and automatic rollback system with 0.95 accuracy threshold for ensuring high-quality agent outputs and codebase reliability.
4
+ Check code quality and change risk with ruflo's shipped analysis commands (analyze diff, analyze complexity, analyze code), and run or extend ruflo's own CI regression guards. Use when assessing a change's risk, finding complexity hot spots, or adding a regression guard to ruflo. The truth-scoring and auto-rollback commands described later in this file are design notes, not shipped CLI commands.
5
5
  ---
6
6
 
7
7
  # Verification & Quality Assurance Skill
@@ -62,31 +62,34 @@ node plugins/ruflo-core/scripts/witness/history.mjs --history verification/macos
62
62
 
63
63
  ## Prerequisites
64
64
 
65
- - Ruflo installed (`npx ruflo@alpha`)
65
+ - Ruflo installed (`npx ruflo@latest`)
66
66
  - Git repository (for rollback features)
67
67
  - Node.js 18+ (for dashboard features)
68
68
  - `@noble/ed25519` (for the witness verifier — a single runtime dep, `npm i @noble/ed25519`)
69
69
 
70
70
  ## Quick Start
71
71
 
72
- ```bash
73
- # View current truth scores
74
- npx ruflo@alpha truth
72
+ These commands ship in the current CLI:
75
73
 
76
- # Run verification check
77
- npx ruflo@alpha verify check
74
+ ```bash
75
+ # Change risk since HEAD~1 (without --risk it also shows classification and reviewers)
76
+ npx ruflo@latest analyze diff HEAD~1 --risk
78
77
 
79
- # Verify specific file with custom threshold
80
- npx ruflo@alpha verify check --file src/app.js --threshold 0.98
78
+ # Complexity hot spots in a directory
79
+ npx ruflo@latest analyze complexity src/ --threshold 15
81
80
 
82
- # Rollback last failed verification
83
- npx ruflo@alpha verify rollback --last-good
81
+ # Size, functions and TODOs per file
82
+ npx ruflo@latest analyze code -p ./src
84
83
  ```
85
84
 
85
+ There is no truth-scoring, `verify check` or `verify rollback` command (`ruflo verify` only checks an installed ruflo package against its signed witness manifest). To verify agent output, run the project's own tests, linters and type checks, and roll back with git (`git restore <file>`, `git revert <commit>`). Inside the ruflo repo, run the guards in [Run the guards locally](#run-the-guards-locally).
86
+
86
87
  ---
87
88
 
88
89
  ## Complete Guide
89
90
 
91
+ > **Design notes, not shipped.** Everything below describes a planned truth-scoring, verification and rollback surface. `ruflo truth`, `ruflo verify check` and `ruflo verify rollback` are not commands in the current CLI, so do not run the examples below; use the commands in [Quick Start](#quick-start) instead.
92
+
90
93
  ### Truth Scoring System
91
94
 
92
95
  #### View Truth Metrics
package/README.md CHANGED
@@ -51,7 +51,7 @@ MCP exposes Ruflo tools to your client. The console and mods above are Claude Co
51
51
 
52
52
  <img src="docs/assets/readme/icons/terminal.svg" width="28" height="28" alt=""> **Prefer a plain terminal? Optional.** The console also runs on its own, with no Claude Code: `npx ruflo tui` (add `--plain` to print one frame for scripts). It shows Overview, Cost, Swarm, Events and Missions, plus an optional AI page. [Terminal UI guide](docs/USERGUIDE.md#terminal-ui-optional)
53
53
 
54
- <p align="center"><img src="docs/assets/ruflo-tui-compact.svg" width="640" alt="Looping recording of the optional ruflo terminal UI cycling through Overview, Cost, Swarm, Events and Missions in a sample project"></p>
54
+ <p align="center"><img src="docs/assets/ruflo-tui-compact.svg" width="640" alt="Looping recording of the optional ruflo terminal UI with its pictures: Overview in the Classic BBS look, the Orbital look with its moving globe, and the Swarm graph, in a sample project"></p>
55
55
 
56
56
  [Compare install paths](#quick-start) · [Open the console](#console-walkthrough) · [User guide](docs/USERGUIDE.md)
57
57
 
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "generation": 7,
4
- "generatedAt": "2026-10-12T17:29:00.623Z",
5
- "gitSha": "4f1b3e96",
4
+ "generatedAt": "2026-10-14T16:22:09.483Z",
5
+ "gitSha": "1882006a",
6
6
  "catalog": {
7
7
  "agents": 176,
8
8
  "tools": 423,
@@ -80,6 +80,8 @@ export declare function discoverTasks(sources: string[]): TaskInfo[];
80
80
  export declare function isTerminal(status: string): boolean;
81
81
  export declare function getProgress(tasks: TaskInfo[]): TaskProgress;
82
82
  export declare function calculateReward(iterations: number, durationMs: number): number;
83
+ /** Why the last tryLoadLearning() returned null (null when it succeeded or was never called). */
84
+ export declare function getLearningLoadError(): string | null;
83
85
  export declare function tryLoadLearning(): Promise<{
84
86
  initialize: () => Promise<boolean>;
85
87
  [key: string]: unknown;
@@ -9,7 +9,9 @@
9
9
  */
10
10
  import { randomUUID } from 'node:crypto';
11
11
  import { existsSync, readFileSync, mkdirSync, readdirSync } from 'node:fs';
12
- import { resolve, join } from 'node:path';
12
+ import { resolve, join, dirname } from 'node:path';
13
+ import { createRequire } from 'node:module';
14
+ import { pathToFileURL } from 'node:url';
13
15
  import { homedir } from 'node:os';
14
16
  import { writeFileAtomic } from './fs-secure.js';
15
17
  // ── Constants ─────────────────────────────────────────────────
@@ -288,17 +290,43 @@ export function calculateReward(iterations, durationMs) {
288
290
  return Math.round((iterFactor + timeFactor) * 100) / 100;
289
291
  }
290
292
  // ── Learning Integration ──────────────────────────────────────
293
+ let lastLearningLoadError = null;
294
+ /** Why the last tryLoadLearning() returned null (null when it succeeded or was never called). */
295
+ export function getLearningLoadError() {
296
+ return lastLearningLoadError;
297
+ }
298
+ /**
299
+ * agentic-flow's `exports` map does not expose dist/coordination/*, so a bare
300
+ * specifier import throws ERR_PACKAGE_PATH_NOT_EXPORTED (#4146). Resolve the
301
+ * package root through its exported entry point and import the file by URL.
302
+ */
303
+ function resolveAutopilotLearningUrl() {
304
+ const req = createRequire(import.meta.url);
305
+ let dir = dirname(req.resolve('agentic-flow'));
306
+ for (let i = 0; i < 6; i++) {
307
+ if (existsSync(join(dir, 'package.json')) && existsSync(join(dir, 'dist', 'coordination', 'autopilot-learning.js'))) {
308
+ return pathToFileURL(join(dir, 'dist', 'coordination', 'autopilot-learning.js')).href;
309
+ }
310
+ dir = dirname(dir);
311
+ }
312
+ throw new Error('agentic-flow does not ship dist/coordination/autopilot-learning.js');
313
+ }
291
314
  export async function tryLoadLearning() {
315
+ lastLearningLoadError = null;
292
316
  try {
293
- const modPath = 'agentic-flow/dist/coordination/autopilot-learning.js';
294
- const mod = await import(/* webpackIgnore: true */ modPath).catch(() => null);
295
- if (mod?.AutopilotLearning) {
296
- const instance = new mod.AutopilotLearning();
297
- if (await instance.initialize())
298
- return instance;
317
+ const mod = await import(/* webpackIgnore: true */ resolveAutopilotLearningUrl());
318
+ if (!mod?.AutopilotLearning) {
319
+ lastLearningLoadError = 'agentic-flow autopilot-learning module has no AutopilotLearning export';
320
+ return null;
299
321
  }
322
+ const instance = new mod.AutopilotLearning();
323
+ if (await instance.initialize())
324
+ return instance;
325
+ lastLearningLoadError = 'AutopilotLearning.initialize() returned false (AgentDB not initialized)';
326
+ }
327
+ catch (e) {
328
+ lastLearningLoadError = `Could not load agentic-flow autopilot learning: ${e instanceof Error ? e.message : String(e)}`;
300
329
  }
301
- catch { /* not available */ }
302
330
  return null;
303
331
  }
304
332
  //# sourceMappingURL=autopilot-state.js.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Resolve which (sub)command an invocation targets.
3
+ *
4
+ * The parser reports the matched command path (for example
5
+ * ['hooks', 'transfer', 'store', 'search']) and the remaining positional arguments.
6
+ * Resolution walks the whole path through each command's `subcommands`, so a command
7
+ * can sit at any depth. #4157: it used to stop two levels below the top-level command,
8
+ * so `hooks transfer store search` ran the `store` help instead of `search`.
9
+ */
10
+ import type { Command } from './types.js';
11
+ export declare function resolveTargetCommand(command: Command, commandPath: string[], positional: string[]): {
12
+ target: Command;
13
+ args: string[];
14
+ };
15
+ //# sourceMappingURL=command-resolution.d.ts.map
@@ -0,0 +1,25 @@
1
+ const matches = (sc, name) => sc.name === name || !!sc.aliases?.includes(name);
2
+ export function resolveTargetCommand(command, commandPath, positional) {
3
+ let target = command;
4
+ if (commandPath.length > 1 && command.subcommands) {
5
+ // The parser already removed the path from `positional`.
6
+ for (const name of commandPath.slice(1)) {
7
+ const next = target.subcommands?.find((sc) => matches(sc, name));
8
+ if (!next)
9
+ break;
10
+ target = next;
11
+ }
12
+ return { target, args: positional };
13
+ }
14
+ // Positional form: the path only named the top-level command.
15
+ let args = positional;
16
+ while (args.length > 0 && target.subcommands) {
17
+ const next = target.subcommands.find((sc) => matches(sc, args[0]));
18
+ if (!next)
19
+ break;
20
+ target = next;
21
+ args = args.slice(1);
22
+ }
23
+ return { target, args };
24
+ }
25
+ //# sourceMappingURL=command-resolution.js.map
@@ -736,15 +736,16 @@ const healthCommand = {
736
736
  output.writeln(output.bold('Agent Health'));
737
737
  output.writeln();
738
738
  // Overall summary with null checks
739
- const overall = result.overall ?? { healthy: 0, degraded: 0, unhealthy: 0, avgCpu: 0, avgMemory: 0 };
740
- const avgCpu = overall.avgCpu ?? 0;
741
- const avgMemory = overall.avgMemory ?? 0;
739
+ const overall = result.overall ?? { healthy: 0, degraded: 0, unhealthy: 0, unknown: 0 };
740
+ const avgCpu = overall.avgCpu ?? overall.cpu;
741
+ const avgMemory = overall.avgMemory ?? overall.memory;
742
742
  output.printBox([
743
743
  `Healthy: ${output.success(String(overall.healthy ?? 0))}`,
744
744
  `Degraded: ${output.warning(String(overall.degraded ?? 0))}`,
745
745
  `Unhealthy: ${output.error(String(overall.unhealthy ?? 0))}`,
746
- `Avg CPU: ${avgCpu.toFixed(1)}%`,
747
- `Avg Memory: ${(avgMemory * 100).toFixed(1)}%`
746
+ `Unknown: ${String(overall.unknown ?? 0)}`,
747
+ `Avg CPU: ${typeof avgCpu === 'number' ? `${avgCpu.toFixed(1)}%` : 'n/a'}`,
748
+ `Avg Memory: ${typeof avgMemory === 'number' ? `${(avgMemory * 100).toFixed(1)}%` : 'n/a'}`
748
749
  ].join(' | '), 'Overall Status');
749
750
  const healthAgents = result.agents ?? [];
750
751
  output.writeln();
@@ -753,14 +754,14 @@ const healthCommand = {
753
754
  { key: 'id', header: 'Agent ID', width: 18 },
754
755
  { key: 'type', header: 'Type', width: 12 },
755
756
  { key: 'health', header: 'Health', width: 10, format: formatHealthStatus },
756
- { key: 'cpu', header: 'CPU %', width: 8, align: 'right', format: (v) => `${Number(v ?? 0).toFixed(1)}%` },
757
+ { key: 'cpu', header: 'CPU %', width: 8, align: 'right', format: (v) => typeof v === 'number' ? `${v.toFixed(1)}%` : 'n/a' },
757
758
  { key: 'memory', header: 'Memory', width: 10, align: 'right', format: (v) => {
758
759
  const mem = v;
759
- if (!mem)
760
- return '0%';
760
+ if (!mem || !mem.limit)
761
+ return 'n/a';
761
762
  return `${(mem.used / mem.limit * 100).toFixed(0)}%`;
762
763
  } },
763
- { key: 'tasks', header: 'Tasks', width: 12, align: 'right', format: (v) => {
764
+ { key: 'tasks', header: 'Calls', width: 12, align: 'right', format: (v) => {
764
765
  const t = v;
765
766
  if (!t)
766
767
  return '0/0';
@@ -775,15 +776,14 @@ const healthCommand = {
775
776
  for (const agent of healthAgents) {
776
777
  output.writeln();
777
778
  output.writeln(output.highlight(agent.id));
778
- const uptime = agent.uptime ?? 0;
779
- const latency = agent.latency ?? { avg: 0, p99: 0 };
780
- const tasks = agent.tasks ?? { completed: 0, failed: 0, queued: 0 };
781
- const errors = agent.errors ?? { count: 0 };
779
+ const latency = agent.latency;
780
+ const tasks = agent.tasks;
781
+ const errors = agent.errors;
782
782
  output.printList([
783
- `Uptime: ${(uptime / 1000 / 60).toFixed(1)} min`,
784
- `Latency: avg ${(latency.avg ?? 0).toFixed(1)}ms, p99 ${(latency.p99 ?? 0).toFixed(1)}ms`,
785
- `Tasks: ${tasks.completed ?? 0} completed, ${tasks.failed ?? 0} failed, ${tasks.queued ?? 0} queued`,
786
- `Errors: ${errors.count ?? 0}${errors.lastError ? ` (${errors.lastError})` : ''}`
783
+ `Registry age: ${typeof agent.registryAgeMs === 'number' ? `${(agent.registryAgeMs / 1000 / 60).toFixed(1)} min` : 'n/a'}`,
784
+ `Latency: p50 ${typeof latency?.p50 === 'number' ? `${latency.p50.toFixed(1)}ms` : 'n/a'}, p95 ${typeof latency?.p95 === 'number' ? `${latency.p95.toFixed(1)}ms` : 'n/a'}`,
785
+ `Observed provider calls (last 20): ${tasks?.completed ?? 0} API-success, ${tasks?.failed ?? 0} error`,
786
+ `Errors: ${errors?.count ?? 0}${errors?.lastError ? ` (${errors.lastError})` : ''}`
787
787
  ]);
788
788
  }
789
789
  }