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
@@ -0,0 +1,516 @@
1
+ "use strict";
2
+ /**
3
+ * P8.1: State Inference Engine — From raw call sequences to state machines
4
+ *
5
+ * Core insight: a protocol STATE is defined by what calls produce it,
6
+ * consume it, and invalidate it — NOT by function names.
7
+ *
8
+ * This engine takes raw call sequences (strings) and infers state machines
9
+ * using purely structural co-occurrence analysis. Zero dependency on
10
+ * function names, keywords, or pre-written protocol rules.
11
+ *
12
+ * Pipeline:
13
+ * Raw call sequences
14
+ * → Assign structural roles (producer/consumer/invalidator)
15
+ * → Group functions into states by structural equivalence
16
+ * → Build state transition graph (opaque S0, S1, S2...)
17
+ * → Extract state machine fingerprint
18
+ *
19
+ * The decisive test: after scrambling all function names to F_001, F_002,
20
+ * the inferred state machine must be identical to the original.
21
+ */
22
+ Object.defineProperty(exports, "__esModule", { value: true });
23
+ exports.inferStateMachine = inferStateMachine;
24
+ exports.extractStateFingerprint = extractStateFingerprint;
25
+ exports.stateFingerprintToVector = stateFingerprintToVector;
26
+ exports.stateFingerprintSimilarity = stateFingerprintSimilarity;
27
+ exports.printInferredStateMachine = printInferredStateMachine;
28
+ function assignRoles(fnIndices, sequences) {
29
+ const N = fnIndices.size;
30
+ const roles = Array.from({ length: N }, (_, i) => ({
31
+ index: i,
32
+ asFirst: 0,
33
+ asLast: 0,
34
+ asMiddle: 0,
35
+ totalOccurrences: 0,
36
+ predecessors: new Set(),
37
+ successors: new Set(),
38
+ role: "isolated",
39
+ }));
40
+ for (const seq of sequences) {
41
+ if (seq.length === 0)
42
+ continue;
43
+ for (let pos = 0; pos < seq.length; pos++) {
44
+ const idx = fnIndices.get(seq[pos]);
45
+ if (idx === undefined)
46
+ continue;
47
+ roles[idx].totalOccurrences++;
48
+ if (pos === 0)
49
+ roles[idx].asFirst++;
50
+ if (pos === seq.length - 1)
51
+ roles[idx].asLast++;
52
+ if (pos > 0 && pos < seq.length - 1)
53
+ roles[idx].asMiddle++;
54
+ if (pos > 0) {
55
+ const prevIdx = fnIndices.get(seq[pos - 1]);
56
+ if (prevIdx !== undefined)
57
+ roles[idx].predecessors.add(prevIdx);
58
+ }
59
+ if (pos < seq.length - 1) {
60
+ const nextIdx = fnIndices.get(seq[pos + 1]);
61
+ if (nextIdx !== undefined)
62
+ roles[idx].successors.add(nextIdx);
63
+ }
64
+ }
65
+ }
66
+ // Classify: entry (mostly first), exit (mostly last), bridge (both), isolated (neither)
67
+ for (const r of roles) {
68
+ const total = Math.max(1, r.totalOccurrences);
69
+ const firstRate = r.asFirst / total;
70
+ const lastRate = r.asLast / total;
71
+ if (firstRate > 0.5 && r.successors.size > 0)
72
+ r.role = "entry";
73
+ else if (lastRate > 0.5 && r.predecessors.size > 0)
74
+ r.role = "exit";
75
+ else if (r.predecessors.size > 0 || r.successors.size > 0)
76
+ r.role = "bridge";
77
+ else
78
+ r.role = "isolated";
79
+ }
80
+ return roles;
81
+ }
82
+ /**
83
+ * Group functions that share the same role and similar neighbor patterns
84
+ * into opaque states (S0, S1, S2...).
85
+ */
86
+ function groupIntoStates(roles) {
87
+ const N = roles.length;
88
+ // Strategy: functions with the same role AND overlapping predecessor/successor
89
+ // sets likely belong to the same protocol state.
90
+ const groups = [];
91
+ const assigned = new Set();
92
+ // Pass 1: Group by role first
93
+ for (const roleType of ["entry", "bridge", "exit", "isolated"]) {
94
+ const candidates = roles
95
+ .map((r, i) => ({ ...r, index: i }))
96
+ .filter(r => r.role === roleType && !assigned.has(r.index));
97
+ // Within the same role, group by neighbor overlap
98
+ const grouped = [];
99
+ const used = new Set();
100
+ for (const c of candidates) {
101
+ if (used.has(c.index))
102
+ continue;
103
+ const cluster = [c.index];
104
+ used.add(c.index);
105
+ // Find other candidates that share ≥50% neighbor overlap
106
+ for (const other of candidates) {
107
+ if (used.has(other.index))
108
+ continue;
109
+ const predOverlap = intersectSize(c.predecessors, other.predecessors);
110
+ const succOverlap = intersectSize(c.successors, other.successors);
111
+ const total = Math.max(1, Math.max(c.predecessors.size, other.predecessors.size) +
112
+ Math.max(c.successors.size, other.successors.size));
113
+ if ((predOverlap + succOverlap) / total > 0.3) {
114
+ cluster.push(other.index);
115
+ used.add(other.index);
116
+ }
117
+ }
118
+ grouped.push(cluster);
119
+ }
120
+ for (const cluster of grouped) {
121
+ const stateId = `S${groups.length}`;
122
+ const predStates = new Set();
123
+ const succStates = new Set();
124
+ for (const fnIdx of cluster) {
125
+ assigned.add(fnIdx);
126
+ for (const p of roles[fnIdx].predecessors)
127
+ predStates.add(`fn_${p}`);
128
+ for (const s of roles[fnIdx].successors)
129
+ succStates.add(`fn_${s}`);
130
+ }
131
+ groups.push({
132
+ id: stateId,
133
+ members: cluster,
134
+ predecessorStates: predStates,
135
+ successorStates: succStates,
136
+ });
137
+ }
138
+ }
139
+ return groups;
140
+ }
141
+ function intersectSize(a, b) {
142
+ let count = 0;
143
+ for (const x of a)
144
+ if (b.has(x))
145
+ count++;
146
+ return count;
147
+ }
148
+ // ═══════════════════════════════════════════════════════════════
149
+ // Step 3: Build state transition graph
150
+ // ═══════════════════════════════════════════════════════════════
151
+ function buildStateTransitions(groups, roles) {
152
+ const S = groups.length;
153
+ const matrix = Array.from({ length: S }, () => new Array(S).fill(0));
154
+ // For each pair of states, count how many functions in state A
155
+ // are directly followed by functions in state B in any sequence
156
+ const fnToState = new Map();
157
+ for (let s = 0; s < S; s++) {
158
+ for (const fnIdx of groups[s].members) {
159
+ fnToState.set(fnIdx, s);
160
+ }
161
+ }
162
+ // Count transitions between states by analyzing function-to-function edges
163
+ for (let si = 0; si < S; si++) {
164
+ for (const fnIdx of groups[si].members) {
165
+ for (const succFn of roles[fnIdx].successors) {
166
+ const sj = fnToState.get(succFn);
167
+ if (sj !== undefined) {
168
+ matrix[si][sj]++;
169
+ }
170
+ }
171
+ }
172
+ }
173
+ return matrix;
174
+ }
175
+ // ═══════════════════════════════════════════════════════════════
176
+ // Step 4: Extract fingerprint
177
+ // ═══════════════════════════════════════════════════════════════
178
+ function computeStateGraphStats(matrix, groups) {
179
+ const S = matrix.length;
180
+ if (S === 0)
181
+ return { isDAG: true, diameter: 0, sccCount: 0, avgBranching: 0, density: 0 };
182
+ // DAG check: Kahn's algorithm
183
+ const inDeg = new Array(S).fill(0);
184
+ for (let i = 0; i < S; i++)
185
+ for (let j = 0; j < S; j++)
186
+ if (matrix[i][j] > 0)
187
+ inDeg[j]++;
188
+ const q = [];
189
+ for (let i = 0; i < S; i++)
190
+ if (inDeg[i] === 0)
191
+ q.push(i);
192
+ let visited = 0;
193
+ while (q.length > 0) {
194
+ const n = q.shift();
195
+ visited++;
196
+ for (let j = 0; j < S; j++) {
197
+ if (matrix[n][j] > 0 && --inDeg[j] === 0)
198
+ q.push(j);
199
+ }
200
+ }
201
+ const isDAG = visited === S;
202
+ // Diameter: BFS from each node
203
+ let diameter = 0;
204
+ for (let start = 0; start < S; start++) {
205
+ const dist = new Array(S).fill(-1);
206
+ const bq = [start];
207
+ dist[start] = 0;
208
+ while (bq.length > 0) {
209
+ const n = bq.shift();
210
+ for (let j = 0; j < S; j++) {
211
+ if (matrix[n][j] > 0 && dist[j] === -1) {
212
+ dist[j] = dist[n] + 1;
213
+ diameter = Math.max(diameter, dist[j]);
214
+ bq.push(j);
215
+ }
216
+ }
217
+ }
218
+ }
219
+ // SCC count: Kosaraju simplification (undirected component count as proxy)
220
+ const undirected = new Set();
221
+ for (let i = 0; i < S; i++)
222
+ for (let j = 0; j < S; j++)
223
+ if (matrix[i][j] > 0 || matrix[j][i] > 0)
224
+ undirected.add(`${Math.min(i, j)},${Math.max(i, j)}`);
225
+ const comps = new Map();
226
+ let compId = 0;
227
+ for (const edge of undirected) {
228
+ const [a, b] = edge.split(",").map(Number);
229
+ const ca = comps.get(a), cb = comps.get(b);
230
+ if (ca === undefined && cb === undefined) {
231
+ comps.set(a, compId);
232
+ comps.set(b, compId);
233
+ compId++;
234
+ }
235
+ else if (ca !== undefined && cb === undefined) {
236
+ comps.set(b, ca);
237
+ }
238
+ else if (cb !== undefined && ca === undefined) {
239
+ comps.set(a, cb);
240
+ }
241
+ // both defined: merge would be needed but keep simple
242
+ }
243
+ const sccCount = compId > 0 ? compId : S;
244
+ // Branching factor
245
+ let totalOut = 0, nonZeroOut = 0;
246
+ for (let i = 0; i < S; i++) {
247
+ let out = 0;
248
+ for (let j = 0; j < S; j++)
249
+ if (matrix[i][j] > 0)
250
+ out++;
251
+ totalOut += out;
252
+ if (out > 0)
253
+ nonZeroOut++;
254
+ }
255
+ const avgBranching = nonZeroOut > 0 ? totalOut / nonZeroOut : 0;
256
+ // Density
257
+ const maxEdges = S * (S - 1);
258
+ const edgeCount = undirected.size;
259
+ const density = maxEdges > 0 ? edgeCount / maxEdges : 0;
260
+ return { isDAG, diameter, sccCount, avgBranching, density };
261
+ }
262
+ // ═══════════════════════════════════════════════════════════════
263
+ // Public API
264
+ // ═══════════════════════════════════════════════════════════════
265
+ /**
266
+ * Infer a state machine from raw call sequences.
267
+ *
268
+ * ZERO dependency on function names. The same sequences with scrambled
269
+ * names produce structurally identical state machines.
270
+ *
271
+ * @param sequences Array of call sequences (e.g., [["open","read","close"],...])
272
+ * @returns InferredStateMachine with opaque state nodes
273
+ */
274
+ function inferStateMachine(sequences) {
275
+ if (sequences.length === 0) {
276
+ return {
277
+ fnCount: 0, stateCount: 0, states: [],
278
+ stateTransitions: [], isDAG: true, diameter: 0, sccCount: 0, avgBranching: 0,
279
+ };
280
+ }
281
+ // Build function index
282
+ const fnIndex = new Map();
283
+ for (const seq of sequences) {
284
+ for (const fn of seq) {
285
+ if (!fnIndex.has(fn))
286
+ fnIndex.set(fn, fnIndex.size);
287
+ }
288
+ }
289
+ // Step 1: Role assignment
290
+ const roles = assignRoles(fnIndex, sequences);
291
+ // Step 2: Group into states
292
+ const groups = groupIntoStates(roles);
293
+ // If no clear groups found, create a simple linear state machine
294
+ if (groups.length === 0) {
295
+ return buildFallbackStateMachine(fnIndex, sequences);
296
+ }
297
+ // Step 3: Build state transition matrix
298
+ const stateTransitions = buildStateTransitions(groups, roles);
299
+ // Step 4: Extract stats
300
+ const stats = computeStateGraphStats(stateTransitions, groups);
301
+ // Build InferredState objects
302
+ const states = groups.map((g, i) => {
303
+ let inDeg = 0, outDeg = 0;
304
+ for (let j = 0; j < stateTransitions.length; j++) {
305
+ if (stateTransitions[j][i] > 0)
306
+ inDeg++;
307
+ if (stateTransitions[i][j] > 0)
308
+ outDeg++;
309
+ }
310
+ return {
311
+ id: g.id,
312
+ members: g.members,
313
+ role: inDeg === 0 && outDeg > 0 ? "entry"
314
+ : outDeg === 0 && inDeg > 0 ? "exit"
315
+ : inDeg > 0 && outDeg > 0 ? "bridge"
316
+ : "isolated",
317
+ inDegree: inDeg,
318
+ outDegree: outDeg,
319
+ };
320
+ });
321
+ return {
322
+ fnCount: fnIndex.size,
323
+ stateCount: states.length,
324
+ states,
325
+ stateTransitions,
326
+ ...stats,
327
+ };
328
+ }
329
+ /** Fallback: build a simple linear chain when grouping fails. */
330
+ function buildFallbackStateMachine(fnIndex, sequences) {
331
+ // Find the longest sequence and use it as a template
332
+ const longest = sequences.reduce((a, b) => a.length >= b.length ? a : b, sequences[0] || []);
333
+ const S = Math.min(longest.length, 10);
334
+ const states = [];
335
+ const fnToState = new Map();
336
+ for (let i = 0; i < S; i++) {
337
+ const fnIdx = fnIndex.get(longest[i]);
338
+ fnToState.set(fnIdx, i);
339
+ states.push({
340
+ id: `F${i}`,
341
+ members: [fnIdx],
342
+ role: i === 0 ? "entry" : i === S - 1 ? "exit" : "bridge",
343
+ inDegree: i > 0 ? 1 : 0,
344
+ outDegree: i < S - 1 ? 1 : 0,
345
+ });
346
+ }
347
+ const matrix = Array.from({ length: S }, () => new Array(S).fill(0));
348
+ for (let i = 0; i < S - 1; i++)
349
+ matrix[i][i + 1] = 1;
350
+ return {
351
+ fnCount: fnIndex.size,
352
+ stateCount: S,
353
+ states,
354
+ stateTransitions: matrix,
355
+ isDAG: true,
356
+ diameter: S - 1,
357
+ sccCount: S,
358
+ avgBranching: S > 0 ? (S - 1) / S : 0,
359
+ };
360
+ }
361
+ /**
362
+ * Extract a fixed-size fingerprint vector from an inferred state machine.
363
+ * All features are in [0,1] where possible.
364
+ */
365
+ function extractStateFingerprint(sm) {
366
+ const S = sm.stateCount;
367
+ const entryCount = sm.states.filter(s => s.role === "entry").length;
368
+ const exitCount = sm.states.filter(s => s.role === "exit").length;
369
+ const bridgeCount = sm.states.filter(s => s.role === "bridge").length;
370
+ const isolatedCount = sm.states.filter(s => s.role === "isolated").length;
371
+ let totalTransitions = 0;
372
+ let selfLoopCount = 0;
373
+ for (let i = 0; i < S; i++) {
374
+ for (let j = 0; j < S; j++) {
375
+ if (sm.stateTransitions[i]?.[j] > 0) {
376
+ totalTransitions++;
377
+ if (i === j)
378
+ selfLoopCount++;
379
+ }
380
+ }
381
+ }
382
+ // Degree entropy: Shannon entropy of in/out degree distributions
383
+ const inDegs = new Array(S).fill(0);
384
+ const outDegs = new Array(S).fill(0);
385
+ for (let i = 0; i < S; i++) {
386
+ for (let j = 0; j < S; j++) {
387
+ if (sm.stateTransitions[i]?.[j] > 0) {
388
+ outDegs[i]++;
389
+ inDegs[j]++;
390
+ }
391
+ }
392
+ }
393
+ const entropy = (counts) => {
394
+ const total = counts.reduce((a, b) => a + b, 0);
395
+ if (total === 0)
396
+ return 0;
397
+ return -counts
398
+ .filter(c => c > 0)
399
+ .map(c => c / total)
400
+ .reduce((s, p) => s - p * Math.log2(p), 0);
401
+ };
402
+ // Path length variance: BFS from all entry points
403
+ const pathLengths = [];
404
+ for (const entry of sm.states.filter(s => s.role === "entry")) {
405
+ const entryIdx = sm.states.indexOf(entry);
406
+ const dist = new Array(S).fill(-1);
407
+ const q = [entryIdx];
408
+ dist[entryIdx] = 0;
409
+ while (q.length > 0) {
410
+ const n = q.shift();
411
+ for (let j = 0; j < S; j++) {
412
+ if (sm.stateTransitions[n]?.[j] > 0 && dist[j] === -1) {
413
+ dist[j] = dist[n] + 1;
414
+ q.push(j);
415
+ }
416
+ }
417
+ }
418
+ for (const d of dist)
419
+ if (d > 0)
420
+ pathLengths.push(d);
421
+ }
422
+ const avgPath = pathLengths.length > 0
423
+ ? pathLengths.reduce((a, b) => a + b, 0) / pathLengths.length : 0;
424
+ const pathVariance = pathLengths.length > 1
425
+ ? pathLengths.reduce((s, p) => s + (p - avgPath) ** 2, 0) / pathLengths.length : 0;
426
+ return {
427
+ stateCount: S,
428
+ fnCount: sm.fnCount,
429
+ transitionCount: totalTransitions,
430
+ entryCount,
431
+ exitCount,
432
+ bridgeCount,
433
+ isolatedCount,
434
+ entryRatio: S > 0 ? entryCount / S : 0,
435
+ exitRatio: S > 0 ? exitCount / S : 0,
436
+ isDAG: sm.isDAG,
437
+ diameter: sm.diameter,
438
+ sccCount: sm.sccCount,
439
+ avgBranching: Math.round(sm.avgBranching * 100) / 100,
440
+ density: Math.round((S > 1 ? totalTransitions / (S * (S - 1)) : 0) * 1000) / 1000,
441
+ selfLoopCount,
442
+ inDegreeEntropy: Math.round(entropy(inDegs) * 1000) / 1000,
443
+ outDegreeEntropy: Math.round(entropy(outDegs) * 1000) / 1000,
444
+ pathVariance: Math.round(pathVariance * 1000) / 1000,
445
+ };
446
+ }
447
+ /**
448
+ * Convert fingerprint to a 14-dim numeric vector for similarity comparison.
449
+ */
450
+ function stateFingerprintToVector(fp) {
451
+ return [
452
+ fp.stateCount,
453
+ fp.fnCount,
454
+ fp.transitionCount,
455
+ fp.entryCount,
456
+ fp.exitCount,
457
+ fp.bridgeCount,
458
+ fp.isolatedCount,
459
+ fp.entryRatio,
460
+ fp.exitRatio,
461
+ fp.isDAG ? 1 : 0,
462
+ fp.diameter,
463
+ fp.sccCount,
464
+ fp.avgBranching,
465
+ fp.density,
466
+ fp.selfLoopCount,
467
+ fp.inDegreeEntropy,
468
+ fp.outDegreeEntropy,
469
+ fp.pathVariance,
470
+ ];
471
+ }
472
+ /**
473
+ * Cosine similarity between two state fingerprints.
474
+ */
475
+ function stateFingerprintSimilarity(a, b) {
476
+ const va = stateFingerprintToVector(a);
477
+ const vb = stateFingerprintToVector(b);
478
+ let dot = 0, normA = 0, normB = 0;
479
+ for (let i = 0; i < va.length; i++) {
480
+ dot += va[i] * vb[i];
481
+ normA += va[i] * va[i];
482
+ normB += vb[i] * vb[i];
483
+ }
484
+ if (normA === 0 && normB === 0)
485
+ return 1;
486
+ if (normA === 0 || normB === 0)
487
+ return 0;
488
+ return dot / (Math.sqrt(normA) * Math.sqrt(normB));
489
+ }
490
+ // ═══════════════════════════════════════════════════════════════
491
+ // Reporting
492
+ // ═══════════════════════════════════════════════════════════════
493
+ function printInferredStateMachine(sm) {
494
+ console.log(`\n─── Inferred State Machine ───`);
495
+ console.log(` Functions: ${sm.fnCount}`);
496
+ console.log(` Inferred states: ${sm.stateCount}`);
497
+ console.log(` DAG: ${sm.isDAG}`);
498
+ console.log(` Diameter: ${sm.diameter}`);
499
+ console.log(` SCC count: ${sm.sccCount}`);
500
+ if (sm.states.length > 0) {
501
+ console.log(`\n States:`);
502
+ for (const s of sm.states) {
503
+ console.log(` ${s.id} (${s.role}): ${s.members.length} functions, in=${s.inDegree}, out=${s.outDegree}`);
504
+ }
505
+ }
506
+ if (sm.stateTransitions.length > 0 && sm.stateTransitions.length <= 10) {
507
+ console.log(`\n State transitions:`);
508
+ for (let i = 0; i < sm.stateTransitions.length; i++) {
509
+ for (let j = 0; j < sm.stateTransitions[i].length; j++) {
510
+ if (sm.stateTransitions[i][j] > 0) {
511
+ console.log(` S${i} → S${j} (${sm.stateTransitions[i][j]} edges)`);
512
+ }
513
+ }
514
+ }
515
+ }
516
+ }