progmune-runtime 2.1.5 → 3.2.0

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 (356) hide show
  1. package/README.md +108 -468
  2. package/dist/ablation-study.js +144 -0
  3. package/dist/ablation-study.test.js +18 -0
  4. package/dist/action-runtime.js +3 -1
  5. package/dist/active-learning.js +211 -0
  6. package/dist/analytics.js +139 -0
  7. package/dist/asset-factory.js +309 -0
  8. package/dist/asset-growth.js +244 -0
  9. package/dist/asset-promotion.js +382 -0
  10. package/dist/asset-quality.js +550 -0
  11. package/dist/audit/business-translator.js +285 -0
  12. package/dist/audit/cli.js +66 -0
  13. package/dist/audit/formatters/html.js +379 -0
  14. package/dist/audit/formatters/json.js +11 -0
  15. package/dist/audit/formatters/markdown.js +192 -0
  16. package/dist/audit/formatters/terminal.js +189 -0
  17. package/dist/audit/index.js +25 -0
  18. package/dist/audit/report-builder.js +318 -0
  19. package/dist/audit/types.js +8 -0
  20. package/dist/audit.js +3 -3
  21. package/dist/auto-benchmark-generator.js +137 -0
  22. package/dist/auto-benchmark-generator.test.js +45 -0
  23. package/dist/auto-protocol-synthesizer.js +362 -0
  24. package/dist/auto-protocol-synthesizer.test.js +82 -0
  25. package/dist/autonomous-patch.js +175 -0
  26. package/dist/autonomous-patch.test.js +128 -0
  27. package/dist/badge/badge-server.js +98 -0
  28. package/dist/behavior-miner.js +442 -0
  29. package/dist/belief-layer.js +475 -0
  30. package/dist/benchmark-count.js +5 -0
  31. package/dist/benchmark-generator.js +211 -0
  32. package/dist/benchmark-harness.js +201 -0
  33. package/dist/benchmark-pass-rate.js +7 -0
  34. package/dist/benchmark-report.js +8 -3
  35. package/dist/benchmark-save.js +14 -1
  36. package/dist/bootstrap-validation.js +197 -0
  37. package/dist/bootstrap-validation.test.js +51 -0
  38. package/dist/branch-ledger.js +1 -1
  39. package/dist/capability-gap.js +130 -0
  40. package/dist/certify-html.js +351 -0
  41. package/dist/certify.js +326 -0
  42. package/dist/check.js +4 -4
  43. package/dist/compliance-miner.js +447 -0
  44. package/dist/continuous-benchmark.js +194 -0
  45. package/dist/continuous-benchmark.test.js +116 -0
  46. package/dist/corpus-stats.js +173 -0
  47. package/dist/counterfactual-engine.js +288 -0
  48. package/dist/coverage-dashboard.js +109 -0
  49. package/dist/coverage-system.test.js +205 -0
  50. package/dist/cross-repo-precision.js +352 -0
  51. package/dist/cve-benchmark.js +180 -0
  52. package/dist/cve-benchmark.test.js +28 -0
  53. package/dist/cve-collector.js +73 -0
  54. package/dist/data-quality.js +141 -0
  55. package/dist/decision-engine.js +388 -0
  56. package/dist/derive-metadata.js +250 -0
  57. package/dist/difficulty-active.test.js +198 -0
  58. package/dist/difficulty-map.js +244 -0
  59. package/dist/discovery-analytics.js +125 -0
  60. package/dist/discovery-model.js +149 -0
  61. package/dist/discovery-optimize.test.js +199 -0
  62. package/dist/discovery-trace.js +276 -0
  63. package/dist/discovery-trace.test.js +97 -0
  64. package/dist/emitter.js +83 -1
  65. package/dist/enterprise-dashboard.js +405 -0
  66. package/dist/eval-hardening.js +297 -0
  67. package/dist/eval-hardening.test.js +85 -0
  68. package/dist/evaluation-campaign.js +359 -0
  69. package/dist/evaluation-campaign.test.js +181 -0
  70. package/dist/evidence-growth.js +143 -0
  71. package/dist/evidence-repository.js +209 -0
  72. package/dist/evidence-system.js +441 -0
  73. package/dist/execute.js +15 -7
  74. package/dist/experimental/software-physics.js +291 -0
  75. package/dist/experimental/state-inference.js +516 -0
  76. package/dist/experimental/unsupervised-physics.js +230 -0
  77. package/dist/extract-ir-python.js +54 -7
  78. package/dist/extract-ir.js +376 -12
  79. package/dist/failure-collector.js +2 -2
  80. package/dist/failure-corpus.js +322 -9
  81. package/dist/feedback.js +16 -5
  82. package/dist/feedback.test.js +49 -0
  83. package/dist/file-lock.js +1 -1
  84. package/dist/flywheel-batch.js +292 -0
  85. package/dist/frameworks/express-cli.js +237 -0
  86. package/dist/frameworks/express-detector.js +445 -0
  87. package/dist/frameworks/express-detector.test.js +206 -0
  88. package/dist/frameworks/index.js +30 -0
  89. package/dist/frameworks/nestjs-detector.js +302 -0
  90. package/dist/frameworks/trpc-detector.js +161 -0
  91. package/dist/frameworks/version-awareness.js +179 -0
  92. package/dist/function-synonyms.js +164 -0
  93. package/dist/function-synonyms.test.js +68 -0
  94. package/dist/generalization.test.js +352 -0
  95. package/dist/goal-annotator.js +113 -0
  96. package/dist/goal-planner.js +563 -0
  97. package/dist/gold-cve.js +164 -0
  98. package/dist/gold-cve.test.js +104 -0
  99. package/dist/gold-quality.js +206 -0
  100. package/dist/gold-tiers.js +241 -0
  101. package/dist/governance-dashboard.js +327 -0
  102. package/dist/graph-viz.js +240 -0
  103. package/dist/guided-frontier.js +195 -0
  104. package/dist/hierarchical-planner.js +148 -0
  105. package/dist/identifier-parser.js +260 -0
  106. package/dist/immune-metrics.js +93 -0
  107. package/dist/immune-receiver.js +158 -0
  108. package/dist/immune-reporter.js +1 -1
  109. package/dist/improvement-orchestrator.js +206 -0
  110. package/dist/inject-p0-vocabulary.js +300 -0
  111. package/dist/intent-parser.js +218 -0
  112. package/dist/invariant-algebra.js +476 -0
  113. package/dist/invariant-calculus.js +533 -0
  114. package/dist/ir-utils.js +70 -0
  115. package/dist/ir-utils.test.js +50 -0
  116. package/dist/knowledge-api.js +312 -0
  117. package/dist/knowledge-evolution.js +452 -0
  118. package/dist/knowledge-explorer.js +506 -0
  119. package/dist/knowledge-flywheel.js +274 -0
  120. package/dist/knowledge-governance.js +338 -0
  121. package/dist/knowledge-governance.test.js +150 -0
  122. package/dist/knowledge-graph.js +181 -0
  123. package/dist/knowledge-guided-synth.js +246 -0
  124. package/dist/knowledge-loop.test.js +77 -0
  125. package/dist/knowledge-object.js +316 -0
  126. package/dist/knowledge-package.js +98 -0
  127. package/dist/kpi-dashboard.js +561 -0
  128. package/dist/l3-cross-function.js +280 -0
  129. package/dist/learning-ranker.js +148 -0
  130. package/dist/learning-ranker.test.js +291 -0
  131. package/dist/ledger/accountability.js +322 -0
  132. package/dist/ledger/chain-builder.js +185 -0
  133. package/dist/ledger/cli.js +222 -0
  134. package/dist/ledger/index.js +13 -0
  135. package/dist/ledger/signatures.js +193 -0
  136. package/dist/ledger/types.js +9 -0
  137. package/dist/llm.js +74 -3
  138. package/dist/load-benchmarks.js +8 -3
  139. package/dist/logger.js +66 -0
  140. package/dist/logger.test.js +37 -0
  141. package/dist/logistic-reward.js +339 -0
  142. package/dist/logistic-reward.test.js +180 -0
  143. package/dist/macro-graph.js +193 -0
  144. package/dist/macro-repair.js +183 -0
  145. package/dist/mcp-server.mjs +1202 -483
  146. package/dist/memory-layer.js +42 -5
  147. package/dist/multi-repo-precision.js +422 -0
  148. package/dist/name-free-protocol.js +425 -0
  149. package/dist/name-free-protocol.test.js +170 -0
  150. package/dist/name-scrambling.js +138 -0
  151. package/dist/name-scrambling.test.js +16 -0
  152. package/dist/p3-observability.test.js +281 -0
  153. package/dist/p5-orchestrator.test.js +225 -0
  154. package/dist/pairwise-preference.js +294 -0
  155. package/dist/pairwise-preference.test.js +140 -0
  156. package/dist/planner-constraints.js +104 -0
  157. package/dist/planner-prompts.js +155 -0
  158. package/dist/planner-telemetry.js +415 -0
  159. package/dist/planner-trace.js +214 -0
  160. package/dist/planner.js +162 -167
  161. package/dist/plsb/artifact.js +116 -0
  162. package/dist/plsb/cli.js +71 -0
  163. package/dist/plsb/index.js +19 -0
  164. package/dist/plsb/leaderboard.js +249 -0
  165. package/dist/plsb/report-md.js +156 -0
  166. package/dist/plsb/schema.js +179 -0
  167. package/dist/plsb-benchmark.js +284 -0
  168. package/dist/plsb-benchmark.test.js +119 -0
  169. package/dist/policy/cli.js +134 -0
  170. package/dist/policy/engine.js +333 -0
  171. package/dist/policy/index.js +12 -0
  172. package/dist/policy/types.js +59 -0
  173. package/dist/policy-miner.js +505 -0
  174. package/dist/precision-analyze.js +229 -0
  175. package/dist/precision-benchmark.js +147 -0
  176. package/dist/precision-label-c.js +134 -0
  177. package/dist/precision-label.js +193 -0
  178. package/dist/precision-report-c.js +149 -0
  179. package/dist/precision-report.js +246 -0
  180. package/dist/progmune-status.js +108 -0
  181. package/dist/proof-engine.js +479 -0
  182. package/dist/proof-provenance.js +315 -0
  183. package/dist/protocol-coverage.js +294 -0
  184. package/dist/protocol-detector.js +1189 -0
  185. package/dist/protocol-embedding-expanded.js +297 -0
  186. package/dist/protocol-embedding-expanded.test.js +97 -0
  187. package/dist/protocol-embedding.js +195 -0
  188. package/dist/protocol-embedding.test.js +82 -0
  189. package/dist/protocol-extractor-v2.js +354 -0
  190. package/dist/protocol-extractor-v2.test.js +140 -0
  191. package/dist/protocol-extractor.js +310 -0
  192. package/dist/protocol-extractor.test.js +113 -0
  193. package/dist/protocol-foundation.js +322 -0
  194. package/dist/protocol-foundation.test.js +163 -0
  195. package/dist/protocol-frontier.js +243 -0
  196. package/dist/protocol-frontier.test.js +92 -0
  197. package/dist/protocol-gap-analyzer.js +228 -0
  198. package/dist/protocol-gap-analyzer.test.js +49 -0
  199. package/dist/protocol-invariants.js +276 -0
  200. package/dist/protocol-invariants.test.js +111 -0
  201. package/dist/protocol-knowledge.js +464 -0
  202. package/dist/protocol-miner.js +343 -0
  203. package/dist/protocol-mining.js +207 -0
  204. package/dist/protocol-mining.test.js +37 -0
  205. package/dist/protocol-registry.js +1 -1
  206. package/dist/protocol-security-benchmark.js +222 -0
  207. package/dist/protocol-vulnerability.js +257 -0
  208. package/dist/protocol-vulnerability.test.js +60 -0
  209. package/dist/python-benchmark.js +120 -0
  210. package/dist/python-emitter.js +163 -45
  211. package/dist/python-protocol-extractor.js +187 -0
  212. package/dist/python-protocol-extractor.test.js +116 -0
  213. package/dist/realworld-benchmark.js +646 -0
  214. package/dist/realworld-benchmark.test.js +36 -0
  215. package/dist/repair-arch.test.js +411 -0
  216. package/dist/repair-evolution.test.js +454 -0
  217. package/dist/repair-executor.js +719 -0
  218. package/dist/repair-proposal.js +4 -4
  219. package/dist/repair-ranker.js +141 -0
  220. package/dist/repair-strategies.js +419 -0
  221. package/dist/repair-taxonomy.js +234 -0
  222. package/dist/repair-types.js +12 -0
  223. package/dist/repo-evaluator.js +250 -0
  224. package/dist/repo-evaluator.test.js +128 -0
  225. package/dist/resource-abstraction.js +242 -0
  226. package/dist/resource-detector.js +211 -0
  227. package/dist/result.test.js +43 -0
  228. package/dist/reward-system.js +411 -0
  229. package/dist/reward-system.test.js +175 -0
  230. package/dist/risk-model.js +215 -0
  231. package/dist/rule-miner.js +234 -7
  232. package/dist/rule-specificity.js +254 -0
  233. package/dist/runtime-types.js +27 -0
  234. package/dist/scaffold.js +208 -0
  235. package/dist/scale-collector.test.js +101 -0
  236. package/dist/scale-trajectory-collector.js +128 -0
  237. package/dist/sdk.js +250 -0
  238. package/dist/search-planner.js +4 -41
  239. package/dist/semantic-snapshot.js +1 -1
  240. package/dist/semantic-topology.js +121 -0
  241. package/dist/semantic-trace.js +310 -317
  242. package/dist/sequence-extractor.js +343 -0
  243. package/dist/skill-library.js +245 -0
  244. package/dist/skill-planner.test.js +189 -0
  245. package/dist/software-physics.js +291 -0
  246. package/dist/software-physics.test.js +81 -0
  247. package/dist/ssg-precision.js +478 -0
  248. package/dist/ssg-validator.js +71 -21
  249. package/dist/state-inference-doubleblind.test.js +160 -0
  250. package/dist/state-inference.js +516 -0
  251. package/dist/state-inference.test.js +115 -0
  252. package/dist/state-machine-fingerprint.js +345 -0
  253. package/dist/state-machine-fingerprint.test.js +120 -0
  254. package/dist/state-miner.js +386 -0
  255. package/dist/state-name-inference.js +213 -0
  256. package/dist/state-name-inference.test.js +69 -0
  257. package/dist/strategy-planner.js +326 -59
  258. package/dist/strategy-planner.test.js +135 -0
  259. package/dist/telemetry-analytics.test.js +402 -0
  260. package/dist/terminal-format.js +68 -0
  261. package/dist/terminal-format.test.js +83 -0
  262. package/dist/topology-factory.js +196 -0
  263. package/dist/topology-representation.js +242 -0
  264. package/dist/topology-representation.test.js +27 -0
  265. package/dist/trajectory-augmentation.js +254 -0
  266. package/dist/trajectory-augmentation.test.js +63 -0
  267. package/dist/trajectory-corpus.js +440 -0
  268. package/dist/trajectory-corpus.test.js +32 -0
  269. package/dist/trajectory-feedback.test.js +116 -0
  270. package/dist/transition-synthesizer.js +286 -0
  271. package/dist/transition-synthesizer.test.js +123 -0
  272. package/dist/trust/api-semantic-mapper.js +809 -0
  273. package/dist/trust/call-graph-propagator.js +225 -0
  274. package/dist/trust/cli.js +122 -0
  275. package/dist/trust/compliance-scorer.js +283 -0
  276. package/dist/trust/confidence-calculator.js +261 -0
  277. package/dist/trust/engine.js +1145 -0
  278. package/dist/trust/explainability.js +85 -0
  279. package/dist/trust/formatters/ci.js +42 -0
  280. package/dist/trust/formatters/json.js +11 -0
  281. package/dist/trust/formatters/terminal.js +152 -0
  282. package/dist/trust/index.js +39 -0
  283. package/dist/trust/phase1-verify.js +171 -0
  284. package/dist/trust/protocol-domain-validator.js +697 -0
  285. package/dist/trust/score-calculator.js +282 -0
  286. package/dist/trust/ssg-bridge.js +641 -0
  287. package/dist/trust/ssg-bridge.test.js +269 -0
  288. package/dist/trust/types.js +67 -0
  289. package/dist/trust/violation-trace.js +335 -0
  290. package/dist/trust-api.js +179 -0
  291. package/dist/trust-calibration.js +279 -0
  292. package/dist/unknown-protocol-discovery.js +339 -0
  293. package/dist/unknown-protocol-discovery.test.js +102 -0
  294. package/dist/unsupervised-physics.js +230 -0
  295. package/dist/unsupervised-physics.test.js +95 -0
  296. package/dist/utils.test.js +37 -0
  297. package/dist/validator.js +187 -10
  298. package/dist/verification-intelligence.js +475 -0
  299. package/dist/verify-api.js +432 -0
  300. package/dist/vi-impact-report.js +293 -0
  301. package/dist/wl-fingerprint.js +162 -0
  302. package/dist/wl-fingerprint.test.js +130 -0
  303. package/dist/zeroshot-strategy.js +139 -0
  304. package/docs/Progmune_/346/212/225/350/265/204/344/272/272/347/231/275/347/232/256/344/271/246_v2.0.html +576 -0
  305. package/docs/Progmune_/351/241/271/347/233/256/345/205/250/350/247/243.html +710 -0
  306. package/package.json +74 -7
  307. package/protocols.json +1956 -50
  308. package/.dockerignore +0 -14
  309. package/.mcp.json +0 -11
  310. package/.progmune_allowlist +0 -50
  311. package/.test_report/test_report.md +0 -87
  312. package/Dockerfile +0 -9
  313. package/FAQ.md +0 -167
  314. package/WHITEPAPER.md +0 -540
  315. package/demo-project/auth.ts +0 -55
  316. package/demo-project/tsconfig.json +0 -8
  317. package/dist/acl-breakdown.js +0 -13
  318. package/dist/all-sessions.js +0 -11
  319. package/dist/antibody-stats.js +0 -11
  320. package/dist/branch-tree-count.js +0 -14
  321. package/dist/common-fixpath.js +0 -12
  322. package/dist/constraint-types.js +0 -12
  323. package/dist/exec-metrics.js +0 -11
  324. package/dist/failure-report.js +0 -11
  325. package/dist/fast-path-hits.js +0 -13
  326. package/dist/fingerprint-list.js +0 -15
  327. package/dist/gen-history-log.js +0 -13
  328. package/dist/heatmap-data.js +0 -11
  329. package/dist/recent-session.js +0 -12
  330. package/dist/svl-distribution.js +0 -11
  331. package/dist/terminal-status.js +0 -11
  332. package/dist/token-savings.js +0 -11
  333. package/dist/total-repairs.js +0 -12
  334. package/dist/unresolved-count.js +0 -12
  335. package/dist/valid-fingerprints.js +0 -13
  336. package/dist/verify-ledgers.js +0 -11
  337. package/docs/whitepaper-style.css +0 -77
  338. package/docs/whitepaper-v2.1.md +0 -609
  339. package/docs/whitepaper-v2.2.md +0 -1064
  340. package/docs/whitepaper-v2.2.pdf +0 -0
  341. package/fly.toml +0 -31
  342. package/public/dashboard.html +0 -119
  343. package/server/hub.js +0 -116
  344. package/test/replay-golden/sess_1780063202050_mgeld.json +0 -9
  345. package/test/replay-golden/sess_1780064032560_gocld.json +0 -354
  346. package/test/replay-golden/sess_1780064413331_s2709.json +0 -606
  347. package/test/replay-golden/sess_1780064792710_y3avo.json +0 -614
  348. package/test/replay-golden.ts +0 -84
  349. package/test_benchmark.js +0 -165
  350. package/test_comprehensive.mjs +0 -638
  351. package/test_concurrency.js +0 -129
  352. package/test_ir_robustness.js +0 -85
  353. package/test_semantic_contracts.js +0 -269
  354. package/test_ssg_stress.js +0 -156
  355. package/test_svl3.js +0 -58
  356. package/tsconfig.json +0 -17
@@ -13,73 +13,178 @@ exports.selectCapabilityChains = selectCapabilityChains;
13
13
  exports.formatChainHint = formatChainHint;
14
14
  const utils_1 = require("./utils");
15
15
  const feedback_1 = require("./feedback");
16
+ const semantic_topology_1 = require("./semantic-topology");
17
+ const planner_constraints_1 = require("./planner-constraints");
18
+ const failure_corpus_1 = require("./failure-corpus");
16
19
  /** Build a capability graph from IR functions. */
17
20
  function buildCapabilityGraph(ir) {
21
+ const SKIP_FILES = new Set(["src/strategy-planner.ts", "src/planner.ts"]);
22
+ const SKIP_TAGS = new Set(); // P8.3: Python IR now supported
18
23
  const graph = new Map();
19
24
  for (const f of ir) {
20
25
  if (!f.exported)
21
26
  continue;
27
+ if (SKIP_FILES.has(f.file))
28
+ continue;
29
+ if ((f.tags || []).some(t => SKIP_TAGS.has(t)))
30
+ continue; // skip language-specific
22
31
  graph.set(f.name, {
23
32
  name: f.name,
24
33
  purpose: f.purpose || "",
25
34
  tags: f.tags || [],
26
35
  requires: f.requires || [],
27
36
  produces: f.produces || [],
37
+ useWhen: f.useWhen || [],
28
38
  score: 0,
39
+ hasExplicitMeta: !f._requiresDerived && !f._producesDerived
40
+ && (f.requires || []).length > 0 && (f.produces || []).length > 0,
29
41
  });
30
42
  }
31
43
  return graph;
32
44
  }
33
- /** Score a capability node against an intent. */
45
+ /** Score a capability node against an intent.
46
+ * Returns 0 for irrelevant nodes (no keyword match at all). */
34
47
  function scoreNode(node, intentLower, keywords) {
35
48
  let score = 0;
49
+ let hasMatch = false;
50
+ // ── Primary verb detection: first keyword is the main action ──
51
+ const primaryVerb = keywords.length > 0 ? keywords[0] : "";
52
+ const nameWords = node.name.replace(/([A-Z])/g, " $1").toLowerCase().split(/[\s_]+/).filter(w => w.length > 1);
53
+ const nameLower = node.name.toLowerCase();
36
54
  // Name match
37
55
  for (const kw of keywords) {
38
- if (node.name.toLowerCase().includes(kw))
56
+ // Substring match
57
+ if (nameLower.includes(kw)) {
39
58
  score += 1;
40
- score += (0, utils_1.jaccardSimilarity)(node.name.toLowerCase(), kw);
59
+ hasMatch = true;
60
+ // Exact word match
61
+ if (nameWords.includes(kw)) {
62
+ // Primary verb (first keyword) that STARTS the function name → decisive bonus
63
+ // e.g., "extract" matches "extractIR" → +5, beats generic "validate" matches
64
+ if (kw === primaryVerb && nameLower.startsWith(kw))
65
+ score += 5;
66
+ else
67
+ score += 2;
68
+ }
69
+ }
70
+ const js = (0, utils_1.jaccardSimilarity)(nameLower, kw);
71
+ if (js > 0.2) {
72
+ score += js;
73
+ hasMatch = true;
74
+ }
41
75
  }
42
- // Purpose match
76
+ // Purpose match — semantic bridge when function names differ from intent
43
77
  const purposeLower = node.purpose.toLowerCase();
78
+ const intentWords = intentLower.split(/[\s,,]+/).filter(w => w.length > 2);
44
79
  for (const kw of keywords) {
45
- if (purposeLower.includes(kw))
80
+ if (purposeLower.includes(kw)) {
46
81
  score += 2;
82
+ hasMatch = true;
83
+ }
84
+ }
85
+ // Jaccard purpose similarity: "send receipt" ↔ "dispatch a message to recipient"
86
+ if (purposeLower.length > 0) {
87
+ const purposeWords = purposeLower.split(/[\s,,]+/).filter(w => w.length > 2);
88
+ if (purposeWords.length > 0 && intentWords.length > 0) {
89
+ const shared = intentWords.filter(w => purposeWords.includes(w)).length;
90
+ const union = new Set([...intentWords, ...purposeWords]).size;
91
+ const purposeJaccard = shared / union;
92
+ if (purposeJaccard > 0.1) {
93
+ score += purposeJaccard * 5;
94
+ hasMatch = true;
95
+ }
96
+ }
47
97
  }
48
98
  // Tag match
49
99
  for (const tag of node.tags) {
50
- if (intentLower.includes(tag.toLowerCase()))
100
+ if (intentLower.includes(tag.toLowerCase())) {
51
101
  score += 1.5;
102
+ hasMatch = true;
103
+ }
52
104
  }
53
105
  // Semantic word overlap in purpose
54
- const intentWords = intentLower.split(/[\s,,]+/);
55
106
  for (const w of intentWords) {
56
- if (w.length > 2 && purposeLower.includes(w))
107
+ if (w.length > 2 && purposeLower.includes(w)) {
57
108
  score += 0.5;
109
+ hasMatch = true;
110
+ }
58
111
  }
59
- // Dynamic Credit: multiply by actual success rate
112
+ // useWhen scenario match
113
+ if (node.useWhen) {
114
+ for (const scenario of node.useWhen) {
115
+ const scenarioWords = scenario.toLowerCase().split(/[\s,]+/);
116
+ const matchCount = scenarioWords.filter((w) => w.length > 3 && intentLower.includes(w)).length;
117
+ if (matchCount >= 2) {
118
+ score += 3.0;
119
+ hasMatch = true;
120
+ }
121
+ else if (matchCount === 1) {
122
+ score += 1.0;
123
+ hasMatch = true;
124
+ }
125
+ }
126
+ }
127
+ // Require at least one match to be relevant
128
+ if (!hasMatch)
129
+ return 0;
130
+ // Bonus: explicit annotations are more reliable than derived ones
131
+ if (node.hasExplicitMeta)
132
+ score *= 1.3;
133
+ // L3: Dynamic Credit — multiply by actual success rate
60
134
  const successRate = (0, feedback_1.getFailureAdjustedCredit)(node.name);
61
135
  const creditFactor = 0.3 + successRate * 0.7;
62
- return score * creditFactor;
136
+ score *= creditFactor;
137
+ // Rule → Planner: apply mined rule constraints
138
+ const constraints = (0, planner_constraints_1.getConstraints)();
139
+ const { multiplier } = (0, planner_constraints_1.applyConstraints)(node.name, node.purpose, constraints);
140
+ return score * multiplier;
63
141
  }
64
- /** Find all capability nodes that produce a given capability label. */
65
- function findProducers(graph, capability) {
66
- const producers = [];
142
+ /** Pick the highest-scoring node from a list. */
143
+ function topByScore(nodes) {
144
+ return nodes.sort((a, b) => b.score - a.score)[0];
145
+ }
146
+ /** Shared logic for finding nodes that produce or consume a capability label.
147
+ * Falls back to topology similarity if no direct data-flow match.
148
+ *
149
+ * @param field - which side of the data-flow edge to match on
150
+ * @param isProducer - when true the field value acts as the "produce" side
151
+ * of capabilityMatch; when false it acts as the "require" side. */
152
+ function findRelated(graph, capability, allNodes, field, isProducer) {
153
+ const results = [];
67
154
  for (const node of graph.values()) {
68
- if (node.produces.some(p => p === capability || capability.includes(p) || p.includes(capability))) {
69
- producers.push(node);
155
+ if (node[field].some(v => v === capability || capability.includes(v) || v.includes(capability))) {
156
+ results.push(node);
70
157
  }
71
158
  }
72
- return producers;
73
- }
74
- /** Find all capability nodes that require a given capability label. */
75
- function findConsumers(graph, capability) {
76
- const consumers = [];
77
- for (const node of graph.values()) {
78
- if (node.requires.some(r => r === capability || capability.includes(r) || r.includes(capability))) {
79
- consumers.push(node);
159
+ // Topology fallback: find semantically related nodes via capabilityMatch
160
+ if (results.length === 0) {
161
+ try {
162
+ const topo = (0, semantic_topology_1.getTopology)();
163
+ for (const node of allNodes) {
164
+ if (node[field].length > 0) {
165
+ for (const v of node[field]) {
166
+ const matched = isProducer
167
+ ? topo.capabilityMatch(v, capability)
168
+ : topo.capabilityMatch(capability, v);
169
+ if (matched && !results.includes(node)) {
170
+ results.push(node);
171
+ break;
172
+ }
173
+ }
174
+ }
175
+ }
80
176
  }
177
+ catch { /* topology fallback unavailable — non-critical */ }
81
178
  }
82
- return consumers;
179
+ return results;
180
+ }
181
+ /** Find all capability nodes that produce a given capability label. */
182
+ function findProducers(graph, capability, allNodes) {
183
+ return findRelated(graph, capability, allNodes, "produces", true);
184
+ }
185
+ /** Find all capability nodes that require a given capability label. */
186
+ function findConsumers(graph, capability, allNodes) {
187
+ return findRelated(graph, capability, allNodes, "requires", false);
83
188
  }
84
189
  /**
85
190
  * Select the best capability chain for an intent.
@@ -90,58 +195,202 @@ function findConsumers(graph, capability) {
90
195
  * 3. For each producer, trace forward: producer → consumer → consumer...
91
196
  * 4. Score each chain and return top N
92
197
  */
93
- function selectCapabilityChains(intent, ir, maxChains = 5) {
198
+ function selectCapabilityChains(intent, ir, maxChains = 5, llmSeeds) {
199
+ // Guard: null/undefined/empty intent
200
+ if (!intent || typeof intent !== "string" || !intent.trim())
201
+ return { chains: [], needsLLM: false };
202
+ // Ensure semantic topology is built (cached after first build)
203
+ const startTopo = Date.now();
204
+ (0, semantic_topology_1.getTopology)(ir);
205
+ const topoMs = Date.now() - startTopo;
94
206
  const intentLower = intent.toLowerCase();
95
207
  const keywords = (0, utils_1.extractKeywords)(intent);
96
208
  const graph = buildCapabilityGraph(ir);
97
209
  // Score all nodes
210
+ const startScore = Date.now();
98
211
  for (const node of graph.values()) {
99
212
  node.score = scoreNode(node, intentLower, keywords);
100
213
  }
101
- // Find seed nodes: highest-scoring nodes that produce something
102
- const seeds = [...graph.values()]
103
- .filter(n => n.produces.length > 0 && n.score > 0)
104
- .sort((a, b) => b.score - a.score)
105
- .slice(0, 10);
214
+ // Dynamic threshold: tighten for large IR to prevent score dilution
215
+ let dynamicThreshold = graph.size > 500 ? 2.0 : graph.size > 200 ? 1.5 : 1.0;
216
+ // Fallback: if no seeds found, halve threshold
217
+ let seeds = [...graph.values()]
218
+ .filter(n => n.score > dynamicThreshold && (n.produces.length > 0 || n.score > dynamicThreshold + 2))
219
+ .sort((a, b) => b.score - a.score);
220
+ if (seeds.length === 0 && dynamicThreshold > 0.3) {
221
+ dynamicThreshold *= 0.5;
222
+ seeds = [...graph.values()]
223
+ .filter(n => n.score > dynamicThreshold && (n.produces.length > 0 || n.score > dynamicThreshold + 1))
224
+ .sort((a, b) => b.score - a.score);
225
+ }
226
+ // Best-effort fallback: prefer nodes with edges AND some score
227
+ if (seeds.length === 0) {
228
+ seeds = [...graph.values()]
229
+ .filter(n => n.score > 0 && (n.produces.length > 0 || n.requires.length > 0))
230
+ .sort((a, b) => b.score - a.score)
231
+ .slice(0, 5);
232
+ }
233
+ // Ultimate fallback: just take any exported nodes with score > 0
234
+ if (seeds.length === 0) {
235
+ seeds = [...graph.values()].filter(n => n.score > 0).slice(0, 3);
236
+ }
237
+ seeds = seeds.slice(0, graph.size > 1000 ? 10 : graph.size > 500 ? 20 : 15);
238
+ // ── Capability Ranking: preload edge confidence from session history ──
239
+ const edgeConfMap = new Map();
240
+ for (const ec of (0, failure_corpus_1.getEdgeConfidence)()) {
241
+ edgeConfMap.set(`${ec.producer}→${ec.consumer}`, ec.confidence);
242
+ }
243
+ // ── LLM seed injection: bridge the semantic gap ──
244
+ if (llmSeeds && llmSeeds.length > 0) {
245
+ const seedNames = new Set(seeds.map(n => n.name));
246
+ const maxScore = seeds.length > 0 ? Math.max(...seeds.map(n => n.score)) : 1;
247
+ for (const fnName of llmSeeds) {
248
+ if (seedNames.has(fnName))
249
+ continue;
250
+ const node = graph.get(fnName);
251
+ if (node) {
252
+ node.score = Math.max(node.score, maxScore);
253
+ seeds.unshift(node);
254
+ seedNames.add(fnName); // keep seedNames in sync for edge expansion
255
+ }
256
+ }
257
+ // Edge Confidence expansion: high-conf downstream from seeds
258
+ let edgeAdded = 0;
259
+ for (const [edgeKey, conf] of edgeConfMap) {
260
+ if (conf < 0.9)
261
+ continue;
262
+ const [prod, cons] = edgeKey.split("→");
263
+ if (!seedNames.has(prod))
264
+ continue;
265
+ if (seedNames.has(cons))
266
+ continue;
267
+ const consumerNode = graph.get(cons);
268
+ if (consumerNode) {
269
+ consumerNode.score = Math.max(consumerNode.score, maxScore * 0.8);
270
+ seeds.push(consumerNode);
271
+ edgeAdded++;
272
+ }
273
+ }
274
+ if (edgeAdded > 0)
275
+ console.error(`[EdgeExp] ${edgeAdded}高置信边扩展种子 (+${edgeAdded} nodes)`);
276
+ }
277
+ const allNodes = [...graph.values()];
106
278
  const chains = [];
279
+ const BEAM_WIDTH = graph.size > 500 ? 3 : 5;
280
+ const MAX_CHAIN_LEN = parseInt(process.env.PROGMUNE_MAX_CHAIN_LEN || "5", 10);
281
+ const SCORE_FLOOR = graph.size > 1000 ? 0.2 : 0.1; // always filter noise
107
282
  for (const seed of seeds) {
108
- // Build chain: seed → consumer → consumer...
109
- const chain = [seed];
110
- const visited = new Set([seed.name]);
111
- let totalScore = seed.score;
112
- // Forward trace: for each produce of the last node, find consumers
113
- let current = seed;
114
- let extended = true;
115
- while (extended && chain.length < 8) {
116
- extended = false;
117
- for (const p of current.produces) {
118
- const consumers = findConsumers(graph, p).filter(c => !visited.has(c.name));
119
- if (consumers.length > 0) {
120
- // Pick best-scoring consumer
121
- const bestConsumer = consumers.sort((a, b) => b.score - a.score)[0];
122
- chain.push(bestConsumer);
123
- visited.add(bestConsumer.name);
124
- totalScore += bestConsumer.score;
125
- current = bestConsumer;
126
- extended = true;
127
- break;
283
+ let beam = [{
284
+ chain: [seed],
285
+ visited: new Set([seed.name]),
286
+ totalScore: seed.score,
287
+ leapDecay: 1.0,
288
+ deadEnd: false,
289
+ }];
290
+ let bestEntry = null;
291
+ for (let depth = 0; depth < MAX_CHAIN_LEN && beam.length > 0; depth++) {
292
+ const nextBeam = [];
293
+ for (const entry of beam) {
294
+ if (entry.deadEnd || entry.chain.length >= MAX_CHAIN_LEN) {
295
+ // Dead-end or max length: finalize this branch
296
+ if (!bestEntry || (entry.totalScore / entry.chain.length) > (bestEntry.totalScore / bestEntry.chain.length)) {
297
+ bestEntry = entry;
298
+ }
299
+ continue;
300
+ }
301
+ const current = entry.chain[entry.chain.length - 1];
302
+ let expanded = false;
303
+ // Strategy 1: direct data flow (produces → requires)
304
+ for (const p of current.produces) {
305
+ const consumers = findConsumers(graph, p, allNodes)
306
+ .filter(c => !entry.visited.has(c.name) && c.score >= SCORE_FLOOR);
307
+ for (const consumer of consumers) {
308
+ const newVisited = new Set(entry.visited);
309
+ newVisited.add(consumer.name);
310
+ // Capability Ranking: edge confidence × consumer score
311
+ const edgeKey = `${current.name}→${consumer.name}`;
312
+ const edgeConf = edgeConfMap.get(edgeKey) ?? 0.5; // Laplace prior for unseen edges
313
+ const rankedScore = consumer.score * (0.5 + edgeConf * 0.5);
314
+ nextBeam.push({
315
+ chain: [...entry.chain, consumer],
316
+ visited: newVisited,
317
+ totalScore: entry.totalScore + rankedScore,
318
+ leapDecay: 1.0,
319
+ deadEnd: consumer.score === 0 && consumer.produces.length === 0,
320
+ });
321
+ expanded = true;
322
+ }
323
+ }
324
+ // Strategy 2: semantic leap — topology similarity
325
+ if (!expanded) {
326
+ try {
327
+ const topo = (0, semantic_topology_1.getTopology)();
328
+ const similar = topo.findSimilar(current.name, 10)
329
+ .filter(s => !entry.visited.has(s.name) && s.similarity > 0.25);
330
+ for (const s of similar.slice(0, 3)) {
331
+ const bestMatch = graph.get(s.name);
332
+ if (bestMatch && bestMatch.score >= SCORE_FLOOR) {
333
+ const newVisited = new Set(entry.visited);
334
+ newVisited.add(bestMatch.name);
335
+ nextBeam.push({
336
+ chain: [...entry.chain, bestMatch],
337
+ visited: newVisited,
338
+ totalScore: entry.totalScore + bestMatch.score * entry.leapDecay,
339
+ leapDecay: entry.leapDecay * 0.7, // decay 30% per leap
340
+ deadEnd: false,
341
+ });
342
+ expanded = true;
343
+ }
344
+ }
345
+ }
346
+ catch { /* semantic leap unavailable — non-critical */ }
347
+ }
348
+ // Dead-end: no consumers found → finalize this branch
349
+ if (!expanded) {
350
+ if (!bestEntry || (entry.totalScore / entry.chain.length) > (bestEntry.totalScore / bestEntry.chain.length)) {
351
+ bestEntry = entry;
352
+ }
128
353
  }
129
354
  }
355
+ // Beam pruning: keep top BEAM_WIDTH by average score
356
+ nextBeam.sort((a, b) => (b.totalScore / b.chain.length) - (a.totalScore / a.chain.length));
357
+ beam = nextBeam.slice(0, BEAM_WIDTH);
130
358
  }
359
+ // Any remaining beam entries compete for best
360
+ for (const entry of beam) {
361
+ if (!bestEntry || (entry.totalScore / entry.chain.length) > (bestEntry.totalScore / bestEntry.chain.length)) {
362
+ bestEntry = entry;
363
+ }
364
+ }
365
+ if (!bestEntry || bestEntry.chain.length < 1)
366
+ continue;
367
+ const chain = bestEntry.chain;
368
+ let totalScore = bestEntry.totalScore;
369
+ const visited = bestEntry.visited;
131
370
  // Backward trace: does seed need something? Find producers.
132
371
  if (seed.requires.length > 0) {
133
- const producers = findProducers(graph, seed.requires[0])
372
+ const producers = findProducers(graph, seed.requires[0], allNodes)
134
373
  .filter(p => !visited.has(p.name));
135
374
  if (producers.length > 0) {
136
- const bestProducer = producers.sort((a, b) => b.score - a.score)[0];
375
+ const bestProducer = topByScore(producers);
137
376
  chain.unshift(bestProducer);
138
377
  totalScore += bestProducer.score;
139
378
  }
140
379
  }
380
+ // Hybrid: supplement graph chain with high-score keyword matches the graph missed
381
+ const missed = allNodes
382
+ .filter(n => !visited.has(n.name) && n.score >= SCORE_FLOOR)
383
+ .sort((a, b) => b.score - a.score)
384
+ .slice(0, 2); // at most 2 supplements
385
+ for (const m of missed) {
386
+ chain.push(m);
387
+ visited.add(m.name);
388
+ totalScore += m.score * 0.5; // half weight — graph-discovered nodes are preferred
389
+ }
141
390
  if (chain.length >= 1) {
142
391
  chains.push({
143
392
  nodes: chain,
144
- score: totalScore / chain.length, // average score
393
+ score: totalScore / chain.length,
145
394
  explanation: chain.map(n => n.name).join(" → "),
146
395
  });
147
396
  }
@@ -157,16 +406,34 @@ function selectCapabilityChains(intent, ir, maxChains = 5) {
157
406
  unique.push(c);
158
407
  }
159
408
  }
160
- return unique.slice(0, maxChains);
409
+ const scoreMs = Date.now() - startScore;
410
+ // Detect semantic gap: very few seeds passed threshold → LLM could help
411
+ const needsLLM = seeds.length === 0 || (seeds.length <= 2 && graph.size > 50);
412
+ if (graph.size > 200) {
413
+ console.error(`[Strategy] ${graph.size} nodes | topo=${topoMs}ms score=${scoreMs}ms | ${unique.length} chains | needsLLM=${needsLLM}`);
414
+ }
415
+ return { chains: unique.slice(0, maxChains), needsLLM };
161
416
  }
162
- /** Format chains as a hint for the LLM prompt. */
417
+ /** Format chains as a routing hint (not full path).
418
+ * Only shows top 2 high-confidence next steps per entry,
419
+ * so the LLM gets guidance without being forced into long chains. */
163
420
  function formatChainHint(chains) {
164
421
  if (chains.length === 0)
165
422
  return "";
166
- const lines = ["\n推荐能力链 (Strategy Layer):"];
167
- for (let i = 0; i < Math.min(chains.length, 3); i++) {
168
- const c = chains[i];
169
- lines.push(` ${i + 1}. ${c.explanation} (★${c.score.toFixed(1)})`);
423
+ const lines = ["\n建议的下一步调用 (已从历史成功链中验证):"];
424
+ const shown = new Set();
425
+ for (const c of chains.slice(0, 3)) {
426
+ const pairs = [];
427
+ for (let i = 0; i < c.nodes.length - 1 && pairs.length < 2; i++) {
428
+ const key = `${c.nodes[i].name}→${c.nodes[i + 1].name}`;
429
+ if (!shown.has(key)) {
430
+ shown.add(key);
431
+ pairs.push(`${c.nodes[i].name} → ${c.nodes[i + 1].name}`);
432
+ }
433
+ }
434
+ if (pairs.length > 0) {
435
+ lines.push(` ${pairs.join(" | ")} (★${c.score.toFixed(1)})`);
436
+ }
170
437
  }
171
438
  return lines.join("\n");
172
439
  }
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * Unit tests for strategy-planner pure functions.
5
+ * Tests the graph-building and chain-selection logic in isolation.
6
+ */
7
+ const vitest_1 = require("vitest");
8
+ const strategy_planner_1 = require("./strategy-planner");
9
+ // Minimal mock IR with functions covering auth and data domains
10
+ const mockIR = [
11
+ {
12
+ name: "validatePassword",
13
+ purpose: "Validate a user's password against stored hash",
14
+ tags: ["auth", "security"],
15
+ requires: ["password", "hash"],
16
+ produces: ["validation_result"],
17
+ useWhen: ["user login", "password reset"],
18
+ file: "src/auth.ts",
19
+ exported: true,
20
+ params: [
21
+ { name: "password", type: "string" },
22
+ { name: "hash", type: "string" },
23
+ ],
24
+ returnType: "boolean",
25
+ },
26
+ {
27
+ name: "generateJWT",
28
+ purpose: "Generate a JWT token for authenticated user",
29
+ tags: ["auth", "token"],
30
+ requires: ["validation_result"],
31
+ produces: ["jwt_token"],
32
+ useWhen: ["after successful login"],
33
+ file: "src/auth.ts",
34
+ exported: true,
35
+ params: [{ name: "userId", type: "string" }],
36
+ returnType: "string",
37
+ },
38
+ {
39
+ name: "fetchUser",
40
+ purpose: "Fetch user data from the database",
41
+ tags: ["data", "user"],
42
+ requires: ["userId"],
43
+ produces: ["user_data"],
44
+ useWhen: ["user profile", "dashboard"],
45
+ file: "src/data.ts",
46
+ exported: true,
47
+ params: [{ name: "userId", type: "string" }],
48
+ returnType: "object",
49
+ },
50
+ {
51
+ name: "saveToDatabase",
52
+ purpose: "Save record to persistent storage",
53
+ tags: ["data", "storage"],
54
+ requires: ["record"],
55
+ produces: ["saved_id"],
56
+ useWhen: ["data persistence"],
57
+ file: "src/data.ts",
58
+ exported: true,
59
+ params: [{ name: "record", type: "object" }],
60
+ returnType: "string",
61
+ },
62
+ // Non-exported function — should be skipped
63
+ {
64
+ name: "internalHelper",
65
+ purpose: "Internal helper — not exported",
66
+ tags: [],
67
+ requires: [],
68
+ produces: [],
69
+ file: "src/internal.ts",
70
+ exported: false,
71
+ params: [],
72
+ returnType: "void",
73
+ },
74
+ ];
75
+ (0, vitest_1.describe)("selectCapabilityChains", () => {
76
+ (0, vitest_1.it)("returns chains for an auth intent", () => {
77
+ const { chains } = (0, strategy_planner_1.selectCapabilityChains)("authenticate user", mockIR, 3);
78
+ (0, vitest_1.expect)(chains.length).toBeGreaterThan(0);
79
+ // Should find at least validatePassword or generateJWT
80
+ const names = chains.flatMap(c => c.nodes.map(n => n.name));
81
+ (0, vitest_1.expect)(names.some(n => n.includes("validate") || n.includes("JWT"))).toBe(true);
82
+ });
83
+ (0, vitest_1.it)("returns chains for a data intent", () => {
84
+ const { chains } = (0, strategy_planner_1.selectCapabilityChains)("fetch user data", mockIR, 3);
85
+ (0, vitest_1.expect)(chains.length).toBeGreaterThan(0);
86
+ const names = chains.flatMap(c => c.nodes.map(n => n.name));
87
+ (0, vitest_1.expect)(names.some(n => n.includes("fetchUser"))).toBe(true);
88
+ });
89
+ (0, vitest_1.it)("respects maxChains limit", () => {
90
+ const { chains } = (0, strategy_planner_1.selectCapabilityChains)("data", mockIR, 1);
91
+ (0, vitest_1.expect)(chains.length).toBeLessThanOrEqual(1);
92
+ });
93
+ (0, vitest_1.it)("skips planner internals (self-referential guard)", () => {
94
+ const selfIR = [{
95
+ name: "selectCapabilityChains",
96
+ purpose: "planner logic",
97
+ tags: [],
98
+ requires: [],
99
+ produces: [],
100
+ file: "src/strategy-planner.ts",
101
+ exported: true,
102
+ params: [],
103
+ returnType: "any",
104
+ }];
105
+ const { chains } = (0, strategy_planner_1.selectCapabilityChains)("planning", selfIR, 3);
106
+ // strategy-planner.ts and planner.ts should be filtered out
107
+ const names = chains.flatMap(c => c.nodes.map(n => n.name));
108
+ (0, vitest_1.expect)(names).not.toContain("selectCapabilityChains");
109
+ });
110
+ });
111
+ (0, vitest_1.describe)("selectCapabilityChains — edge cases", () => {
112
+ (0, vitest_1.it)("returns empty array for null intent", () => {
113
+ (0, vitest_1.expect)((0, strategy_planner_1.selectCapabilityChains)(null, mockIR)).toEqual({ chains: [], needsLLM: false });
114
+ });
115
+ (0, vitest_1.it)("returns empty array for undefined intent", () => {
116
+ (0, vitest_1.expect)((0, strategy_planner_1.selectCapabilityChains)(undefined, mockIR)).toEqual({ chains: [], needsLLM: false });
117
+ });
118
+ (0, vitest_1.it)("returns empty array for empty string intent", () => {
119
+ (0, vitest_1.expect)((0, strategy_planner_1.selectCapabilityChains)("", mockIR)).toEqual({ chains: [], needsLLM: false });
120
+ });
121
+ (0, vitest_1.it)("returns empty array for whitespace-only intent", () => {
122
+ (0, vitest_1.expect)((0, strategy_planner_1.selectCapabilityChains)(" ", mockIR)).toEqual({ chains: [], needsLLM: false });
123
+ });
124
+ });
125
+ (0, vitest_1.describe)("formatChainHint", () => {
126
+ (0, vitest_1.it)("returns empty string for empty chains", () => {
127
+ (0, vitest_1.expect)((0, strategy_planner_1.formatChainHint)([])).toBe("");
128
+ });
129
+ (0, vitest_1.it)("formats a single chain", () => {
130
+ const { chains } = (0, strategy_planner_1.selectCapabilityChains)("authenticate user", mockIR, 1);
131
+ const hint = (0, strategy_planner_1.formatChainHint)(chains);
132
+ (0, vitest_1.expect)(hint).toContain("建议的下一步调用");
133
+ (0, vitest_1.expect)(hint).toContain("★");
134
+ });
135
+ });