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,193 @@
1
+ "use strict";
2
+ /**
3
+ * Precision Labeling Tool
4
+ *
5
+ * Interactive CLI for hand-labeling call sequences as "clean" or "violation".
6
+ * This is the human-in-the-loop step that makes precision measurement possible.
7
+ *
8
+ * Usage:
9
+ * npx ts-node src/precision-label.ts <repoPath>
10
+ * npx ts-node src/precision-label.ts . --max 50
11
+ *
12
+ * Output: .progmune_labels.json in the repo root
13
+ */
14
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ var desc = Object.getOwnPropertyDescriptor(m, k);
17
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
18
+ desc = { enumerable: true, get: function() { return m[k]; } };
19
+ }
20
+ Object.defineProperty(o, k2, desc);
21
+ }) : (function(o, m, k, k2) {
22
+ if (k2 === undefined) k2 = k;
23
+ o[k2] = m[k];
24
+ }));
25
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
26
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
27
+ }) : function(o, v) {
28
+ o["default"] = v;
29
+ });
30
+ var __importStar = (this && this.__importStar) || (function () {
31
+ var ownKeys = function(o) {
32
+ ownKeys = Object.getOwnPropertyNames || function (o) {
33
+ var ar = [];
34
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
35
+ return ar;
36
+ };
37
+ return ownKeys(o);
38
+ };
39
+ return function (mod) {
40
+ if (mod && mod.__esModule) return mod;
41
+ var result = {};
42
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
43
+ __setModuleDefault(result, mod);
44
+ return result;
45
+ };
46
+ })();
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ const fs = __importStar(require("fs"));
49
+ const path = __importStar(require("path"));
50
+ const readline = __importStar(require("readline"));
51
+ const extract_ir_1 = require("./extract-ir");
52
+ function loadLabels(repoPath) {
53
+ const labelPath = path.join(repoPath, ".progmune_labels.json");
54
+ if (fs.existsSync(labelPath)) {
55
+ return JSON.parse(fs.readFileSync(labelPath, "utf-8"));
56
+ }
57
+ return { repo: repoPath, labeledAt: "", total: 0, labels: {}, sequences: {} };
58
+ }
59
+ function saveLabels(repoPath, data) {
60
+ data.labeledAt = new Date().toISOString();
61
+ data.total = Object.keys(data.labels).length;
62
+ const labelPath = path.join(repoPath, ".progmune_labels.json");
63
+ fs.writeFileSync(labelPath, JSON.stringify(data, null, 2), "utf-8");
64
+ }
65
+ function extractSequences(repoPath) {
66
+ const ir = (0, extract_ir_1.extractIR)(repoPath);
67
+ const seqs = [];
68
+ // Strategy 1: Functions with protocol annotations
69
+ for (const fn of ir) {
70
+ if (fn.protocol) {
71
+ const calls = [];
72
+ if (fn.protocol.pre_states)
73
+ calls.push(...fn.protocol.pre_states);
74
+ calls.push(fn.name);
75
+ if (fn.protocol.post_states)
76
+ calls.push(...fn.protocol.post_states);
77
+ if (calls.length >= 2) {
78
+ seqs.push({ index: seqs.length, calls, source: `protocol:${fn.name}` });
79
+ }
80
+ }
81
+ }
82
+ // Strategy 2: Group functions by file, extract call pairs
83
+ if (seqs.length < 20) {
84
+ const byFile = {};
85
+ for (const fn of ir) {
86
+ const file = fn.file || "unknown";
87
+ if (!byFile[file])
88
+ byFile[file] = [];
89
+ byFile[file].push(fn.name);
90
+ }
91
+ for (const [file, fns] of Object.entries(byFile)) {
92
+ if (fns.length >= 2) {
93
+ seqs.push({ index: seqs.length, calls: fns.slice(0, 8), source: `file:${path.basename(file)}` });
94
+ }
95
+ }
96
+ }
97
+ // Strategy 3: Extract call adjacency pairs from function bodies
98
+ if (seqs.length < 20) {
99
+ for (const fn of ir) {
100
+ if (fn.calls && fn.calls.length >= 2) {
101
+ const relevantCalls = fn.calls.filter((c) => ir.some((f) => f.name === c));
102
+ if (relevantCalls.length >= 2) {
103
+ seqs.push({
104
+ index: seqs.length,
105
+ calls: relevantCalls.slice(0, 8),
106
+ source: `calls:${fn.name}`,
107
+ });
108
+ }
109
+ }
110
+ }
111
+ }
112
+ return seqs;
113
+ }
114
+ // ═══════════════════════════════════════════════════════════════
115
+ // Interactive CLI
116
+ // ═══════════════════════════════════════════════════════════════
117
+ const C = {
118
+ reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
119
+ green: "\x1b[32m", red: "\x1b[31m", yellow: "\x1b[33m", cyan: "\x1b[36m",
120
+ };
121
+ async function main() {
122
+ const args = process.argv.slice(2);
123
+ const repoPath = path.resolve(args.find(a => !a.startsWith("--")) || ".");
124
+ const maxSeq = parseInt(args.includes("--max") ? args[args.indexOf("--max") + 1] : "50", 10);
125
+ if (!fs.existsSync(repoPath)) {
126
+ console.error(`❌ Path not found: ${repoPath}`);
127
+ process.exit(1);
128
+ }
129
+ console.error(`\n${C.bold}${C.cyan}Precision Labeling Tool${C.reset}`);
130
+ console.error(` Repo: ${repoPath}`);
131
+ console.error(` Extracting sequences...`);
132
+ // 1. Extract sequences
133
+ const sequences = extractSequences(repoPath).slice(0, maxSeq);
134
+ if (sequences.length === 0) {
135
+ console.error(` ❌ No call sequences found. Add protocol annotations (@protocol) or function calls with 2+ steps.`);
136
+ process.exit(1);
137
+ }
138
+ console.error(` Found: ${sequences.length} sequences to label`);
139
+ console.error(` Commands: ${C.green}c${C.reset}=clean ${C.red}v${C.reset}=violation ${C.yellow}s${C.reset}=skip q=quit\n`);
140
+ // 2. Load existing labels
141
+ const data = loadLabels(repoPath);
142
+ const alreadyLabeled = Object.keys(data.labels).length;
143
+ if (alreadyLabeled > 0) {
144
+ console.error(` ${C.yellow}Resuming: ${alreadyLabeled} already labeled${C.reset}\n`);
145
+ }
146
+ // 3. Interactive labeling loop
147
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
148
+ const ask = (q) => new Promise(resolve => rl.question(q, resolve));
149
+ let labeled = 0;
150
+ let skipped = 0;
151
+ for (const seq of sequences) {
152
+ // Skip already labeled
153
+ if (data.labels[seq.index] !== undefined)
154
+ continue;
155
+ // Show sequence
156
+ console.error(`${C.bold}[${seq.index + 1}/${sequences.length}]${C.reset} ${C.dim}${seq.source}${C.reset}`);
157
+ console.error(` ${seq.calls.map((c, i) => `${i > 0 ? "→ " : ""}${C.cyan}${c}${C.reset}`).join(" ")}`);
158
+ const answer = await ask(` ${C.green}[c]lean${C.reset} ${C.red}[v]iolation${C.reset} ${C.yellow}[s]kip${C.reset} [q]uit: `);
159
+ if (answer === "q") {
160
+ console.error(`\n Quit. Saving ${labeled} new labels...`);
161
+ break;
162
+ }
163
+ if (answer === "s") {
164
+ skipped++;
165
+ console.error(` ${C.yellow}↻ skipped${C.reset}\n`);
166
+ continue;
167
+ }
168
+ if (answer === "c") {
169
+ data.labels[seq.index] = "clean";
170
+ data.sequences[seq.index] = seq.calls;
171
+ labeled++;
172
+ console.error(` ${C.green}✓ clean${C.reset}\n`);
173
+ }
174
+ else if (answer === "v") {
175
+ data.labels[seq.index] = "violation";
176
+ data.sequences[seq.index] = seq.calls;
177
+ labeled++;
178
+ console.error(` ${C.red}✗ violation${C.reset}\n`);
179
+ }
180
+ else {
181
+ console.error(` ${C.yellow}? unknown — use c/v/s/q${C.reset}\n`);
182
+ }
183
+ }
184
+ rl.close();
185
+ // 4. Save
186
+ saveLabels(repoPath, data);
187
+ console.error(`\n${C.bold}Saved:${C.reset} ${labeled} new + ${alreadyLabeled} existing labels → ${path.join(repoPath, ".progmune_labels.json")}`);
188
+ console.error(` Clean: ${Object.values(data.labels).filter(l => l === "clean").length}`);
189
+ console.error(` Violation: ${Object.values(data.labels).filter(l => l === "violation").length}`);
190
+ console.error(` Skipped: ${skipped}`);
191
+ console.error(`\n Next: npx ts-node src/precision-report.ts ${repoPath}\n`);
192
+ }
193
+ main().catch(e => { console.error(e); process.exit(1); });
@@ -0,0 +1,149 @@
1
+ "use strict";
2
+ /**
3
+ * Precision Report Generator for C/C++ projects
4
+ *
5
+ * Reads labeled data and runs SSG precision measurement.
6
+ *
7
+ * Usage:
8
+ * npx ts-node src/precision-report-c.ts benchmarks/curl
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ const fs = __importStar(require("fs"));
45
+ const path = __importStar(require("path"));
46
+ const ssg_precision_1 = require("./ssg-precision");
47
+ const C = {
48
+ reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
49
+ green: "\x1b[32m", red: "\x1b[31m", yellow: "\x1b[33m", cyan: "\x1b[36m",
50
+ };
51
+ function main() {
52
+ const args = process.argv.slice(2);
53
+ const repoPath = path.resolve(args.find(a => !a.startsWith("--")) || ".");
54
+ // Labels stored in benchmarks/ directory, not inside the repo
55
+ const labelFile = path.join(path.dirname(repoPath), `${path.basename(repoPath)}-labels.json`);
56
+ if (!fs.existsSync(labelFile)) {
57
+ // Fallback: try inside the repo directory
58
+ const altFile = path.join(repoPath, `${path.basename(repoPath)}-labels.json`);
59
+ if (fs.existsSync(altFile))
60
+ return altFile;
61
+ }
62
+ if (!fs.existsSync(labelFile)) {
63
+ console.error(`❌ Labels not found: ${labelFile}`);
64
+ console.error(` Run: npx ts-node src/precision-label-c.ts ${repoPath}`);
65
+ process.exit(1);
66
+ }
67
+ const data = JSON.parse(fs.readFileSync(labelFile, "utf-8"));
68
+ const labels = data.labels || {};
69
+ const sequences = data.sequences || {};
70
+ const labeledIndices = Object.keys(labels).map(Number);
71
+ if (labeledIndices.length === 0) {
72
+ console.error("❌ No labeled sequences found.");
73
+ process.exit(1);
74
+ }
75
+ // Build clean sequences for rule discovery
76
+ const cleanSeqs = [];
77
+ for (const idx of labeledIndices) {
78
+ if (labels[idx] === "clean" && sequences[idx]) {
79
+ cleanSeqs.push(sequences[idx]);
80
+ }
81
+ }
82
+ if (cleanSeqs.length === 0) {
83
+ console.error("❌ No clean sequences for rule discovery. Label some sequences as 'clean' first.");
84
+ process.exit(1);
85
+ }
86
+ // Discover SSG rules
87
+ const { rules, nsInit } = (0, ssg_precision_1.discoverRulesFromSequences)(cleanSeqs);
88
+ // Validate all labeled sequences
89
+ let tp = 0, fp = 0, tn = 0, fn = 0;
90
+ const details = [];
91
+ for (const idx of labeledIndices) {
92
+ const expected = labels[idx];
93
+ const calls = sequences[idx] || [];
94
+ const result = (0, ssg_precision_1.validateSequenceWithSSG)(calls, rules, nsInit);
95
+ const detected = result.valid ? "clean" : "violation";
96
+ if (expected === "violation" && detected === "violation")
97
+ tp++;
98
+ else if (expected === "clean" && detected === "violation")
99
+ fp++;
100
+ else if (expected === "clean" && detected === "clean")
101
+ tn++;
102
+ else if (expected === "violation" && detected === "clean")
103
+ fn++;
104
+ if (expected !== detected) {
105
+ details.push({
106
+ index: idx,
107
+ expected, detected,
108
+ reason: result.reason || "",
109
+ calls: calls.slice(0, 6),
110
+ });
111
+ }
112
+ }
113
+ const total = tp + fp + tn + fn;
114
+ const precision = tp + fp > 0 ? tp / (tp + fp) : 0;
115
+ const recall = tp + fn > 0 ? tp / (tp + fn) : 0;
116
+ const f1 = precision + recall > 0 ? 2 * precision * recall / (precision + recall) : 0;
117
+ // Print report
118
+ const pct = (v) => `${(v * 100).toFixed(0)}%`;
119
+ const color = (v) => v >= 0.7 ? C.green : v >= 0.5 ? C.yellow : C.red;
120
+ console.log(`\n${C.bold}${C.cyan}╔══════════════════════════════════════════════╗${C.reset}`);
121
+ console.log(`${C.bold}${C.cyan}║${C.reset} ${C.bold}SSG Precision Report — curl${C.reset} ${C.bold}${C.cyan}║${C.reset}`);
122
+ console.log(`${C.bold}${C.cyan}╚══════════════════════════════════════════════╝${C.reset}`);
123
+ console.log("");
124
+ console.log(` Samples: ${total}`);
125
+ console.log(` Rules: ${rules.size} discovered from ${cleanSeqs.length} clean sequences`);
126
+ console.log(` TP: ${C.green}${tp}${C.reset} FP: ${C.red}${fp}${C.reset} TN: ${C.green}${tn}${C.reset} FN: ${C.red}${fn}${C.reset}`);
127
+ console.log("");
128
+ console.log(` Precision: ${color(precision)}${pct(precision)}${C.reset}`);
129
+ console.log(` Recall: ${color(recall)}${pct(recall)}${C.reset}`);
130
+ console.log(` F1: ${color(f1)}${pct(f1)}${C.reset}`);
131
+ console.log("");
132
+ if (details.length > 0) {
133
+ console.log(` ${C.yellow}Mismatches (${details.length}):${C.reset}`);
134
+ for (const d of details.slice(0, 10)) {
135
+ const icon = d.expected === "violation" ? `${C.red}FN${C.reset}` : `${C.yellow}FP${C.reset}`;
136
+ console.log(` ${icon} [${d.index}] expected ${d.expected}, got ${d.detected}`);
137
+ console.log(` ${C.dim}${d.calls.join(" → ")}${C.reset}`);
138
+ }
139
+ console.log("");
140
+ }
141
+ const rating = f1 >= 0.85 ? `${C.green}EXCELLENT${C.reset}` : f1 >= 0.7 ? `${C.yellow}GOOD${C.reset}` : f1 >= 0.5 ? `${C.yellow}FAIR${C.reset}` : `${C.red}NEEDS IMPROVEMENT${C.reset}`;
142
+ console.log(` Rating: ${rating}`);
143
+ // Save
144
+ const report = { repo: "curl", total, tp, fp, tn, fn, precision, recall, f1, rulesDiscovered: rules.size, timestamp: new Date().toISOString(), details };
145
+ const reportPath = path.join(path.dirname(repoPath), `${path.basename(repoPath)}-precision.json`);
146
+ fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
147
+ console.log(`\n📁 Saved: ${reportPath}\n`);
148
+ }
149
+ main();
@@ -0,0 +1,246 @@
1
+ "use strict";
2
+ /**
3
+ * Precision Report Generator
4
+ *
5
+ * Takes hand-labeled ground truth + SSG validation results,
6
+ * generates TP/FP/TN/FN matrix with precision/recall/F1.
7
+ *
8
+ * Usage:
9
+ * npx ts-node src/precision-report.ts <repoPath>
10
+ * npx ts-node src/precision-report.ts . --output precision.md
11
+ */
12
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
+ if (k2 === undefined) k2 = k;
14
+ var desc = Object.getOwnPropertyDescriptor(m, k);
15
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
16
+ desc = { enumerable: true, get: function() { return m[k]; } };
17
+ }
18
+ Object.defineProperty(o, k2, desc);
19
+ }) : (function(o, m, k, k2) {
20
+ if (k2 === undefined) k2 = k;
21
+ o[k2] = m[k];
22
+ }));
23
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
24
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
25
+ }) : function(o, v) {
26
+ o["default"] = v;
27
+ });
28
+ var __importStar = (this && this.__importStar) || (function () {
29
+ var ownKeys = function(o) {
30
+ ownKeys = Object.getOwnPropertyNames || function (o) {
31
+ var ar = [];
32
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
33
+ return ar;
34
+ };
35
+ return ownKeys(o);
36
+ };
37
+ return function (mod) {
38
+ if (mod && mod.__esModule) return mod;
39
+ var result = {};
40
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
41
+ __setModuleDefault(result, mod);
42
+ return result;
43
+ };
44
+ })();
45
+ Object.defineProperty(exports, "__esModule", { value: true });
46
+ exports.runPrecision = runPrecision;
47
+ exports.formatPrecisionTerminal = formatPrecisionTerminal;
48
+ exports.formatPrecisionMarkdown = formatPrecisionMarkdown;
49
+ exports.formatMultiRepoTable = formatMultiRepoTable;
50
+ const fs = __importStar(require("fs"));
51
+ const path = __importStar(require("path"));
52
+ const ssg_precision_1 = require("./ssg-precision");
53
+ function loadLabels(repoPath) {
54
+ const labelPath = path.join(repoPath, ".progmune_labels.json");
55
+ if (!fs.existsSync(labelPath)) {
56
+ throw new Error(`No labels found. Run 'npx ts-node src/precision-label.ts ${repoPath}' first.`);
57
+ }
58
+ const data = JSON.parse(fs.readFileSync(labelPath, "utf-8"));
59
+ return { labels: data.labels || {}, sequences: data.sequences || {} };
60
+ }
61
+ function runPrecision(repoPath) {
62
+ const { labels, sequences } = loadLabels(repoPath);
63
+ // Get all labeled sequences
64
+ const labeledIndices = Object.keys(labels).map(Number);
65
+ if (labeledIndices.length === 0) {
66
+ throw new Error("No labeled sequences found.");
67
+ }
68
+ // Build clean-labeled sequences for rule discovery
69
+ const cleanSeqs = [];
70
+ for (const idx of labeledIndices) {
71
+ if (labels[idx] === "clean" && sequences[idx]) {
72
+ cleanSeqs.push(sequences[idx]);
73
+ }
74
+ }
75
+ // Discover rules from clean sequences
76
+ const { rules, nsInit } = (0, ssg_precision_1.discoverRulesFromSequences)(cleanSeqs);
77
+ // Validate all labeled sequences against discovered rules
78
+ const matrix = {
79
+ repo: path.basename(repoPath),
80
+ total: labeledIndices.length,
81
+ truePositive: 0,
82
+ falsePositive: 0,
83
+ trueNegative: 0,
84
+ falseNegative: 0,
85
+ precision: 0,
86
+ recall: 0,
87
+ f1: 0,
88
+ details: [],
89
+ };
90
+ for (const idx of labeledIndices) {
91
+ const expected = labels[idx];
92
+ const calls = sequences[idx] || [];
93
+ if (rules.size === 0) {
94
+ // No rules discovered — all sequences pass (can't detect anything)
95
+ const detected = "clean";
96
+ matrix.details.push({ index: idx, calls, expected, detected, correct: expected === detected });
97
+ if (expected === "clean" && detected === "clean")
98
+ matrix.trueNegative++;
99
+ else if (expected === "violation" && detected === "clean")
100
+ matrix.falseNegative++;
101
+ continue;
102
+ }
103
+ const result = (0, ssg_precision_1.validateSequenceWithSSG)(calls, rules, nsInit);
104
+ const detected = result.valid ? "clean" : "violation";
105
+ matrix.details.push({ index: idx, calls, expected, detected, correct: expected === detected });
106
+ if (expected === "violation" && detected === "violation")
107
+ matrix.truePositive++;
108
+ else if (expected === "clean" && detected === "violation")
109
+ matrix.falsePositive++;
110
+ else if (expected === "clean" && detected === "clean")
111
+ matrix.trueNegative++;
112
+ else if (expected === "violation" && detected === "clean")
113
+ matrix.falseNegative++;
114
+ }
115
+ // Compute metrics
116
+ const tp = matrix.truePositive;
117
+ const fp = matrix.falsePositive;
118
+ const fn = matrix.falseNegative;
119
+ matrix.precision = tp + fp > 0 ? tp / (tp + fp) : 0;
120
+ matrix.recall = tp + fn > 0 ? tp / (tp + fn) : 0;
121
+ matrix.f1 = matrix.precision + matrix.recall > 0
122
+ ? 2 * (matrix.precision * matrix.recall) / (matrix.precision + matrix.recall)
123
+ : 0;
124
+ return matrix;
125
+ }
126
+ // ═══════════════════════════════════════════════════════════════
127
+ // Formatters
128
+ // ═══════════════════════════════════════════════════════════════
129
+ const C = {
130
+ reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m",
131
+ green: "\x1b[32m", red: "\x1b[31m", yellow: "\x1b[33m", cyan: "\x1b[36m",
132
+ };
133
+ function formatPrecisionTerminal(m) {
134
+ const pct = (v) => `${(v * 100).toFixed(0)}%`;
135
+ const color = (v, threshold = 0.7) => v >= threshold ? C.green : v >= 0.5 ? C.yellow : C.red;
136
+ const lines = [];
137
+ lines.push("");
138
+ lines.push(`${C.bold}${C.cyan}╔══════════════════════════════════════════════╗${C.reset}`);
139
+ lines.push(`${C.bold}${C.cyan}║${C.reset} ${C.bold}SSG Precision Report — ${m.repo}${C.reset}${" ".repeat(Math.max(0, 25 - m.repo.length))}${C.bold}${C.cyan}║${C.reset}`);
140
+ lines.push(`${C.bold}${C.cyan}╚══════════════════════════════════════════════╝${C.reset}`);
141
+ lines.push("");
142
+ lines.push(` Samples: ${m.total}`);
143
+ lines.push(` TP: ${C.green}${m.truePositive}${C.reset} FP: ${C.red}${m.falsePositive}${C.reset} TN: ${C.green}${m.trueNegative}${C.reset} FN: ${C.red}${m.falseNegative}${C.reset}`);
144
+ lines.push("");
145
+ lines.push(` Precision: ${color(m.precision)}${pct(m.precision)}${C.reset}`);
146
+ lines.push(` Recall: ${color(m.recall)}${pct(m.recall)}${C.reset}`);
147
+ lines.push(` F1: ${color(m.f1)}${pct(m.f1)}${C.reset}`);
148
+ lines.push("");
149
+ // Mismatches
150
+ const mismatches = m.details.filter(d => !d.correct);
151
+ if (mismatches.length > 0) {
152
+ lines.push(` ${C.yellow}Mismatches (${mismatches.length}):${C.reset}`);
153
+ for (const d of mismatches.slice(0, 10)) {
154
+ const icon = d.expected === "violation" ? `${C.red}FN${C.reset}` : `${C.yellow}FP${C.reset}`;
155
+ lines.push(` ${icon} [${d.index}] expected ${d.expected}, got ${d.detected}`);
156
+ lines.push(` ${C.dim}${d.calls.join(" → ")}${C.reset}`);
157
+ }
158
+ lines.push("");
159
+ }
160
+ // Rating
161
+ const rating = m.f1 >= 0.85 ? `${C.green}EXCELLENT${C.reset}`
162
+ : m.f1 >= 0.7 ? `${C.yellow}GOOD${C.reset}`
163
+ : m.f1 >= 0.5 ? `${C.yellow}FAIR${C.reset}`
164
+ : `${C.red}NEEDS IMPROVEMENT${C.reset}`;
165
+ lines.push(` Rating: ${rating}`);
166
+ lines.push("");
167
+ return lines.join("\n");
168
+ }
169
+ function formatPrecisionMarkdown(m) {
170
+ const pct = (v) => `${(v * 100).toFixed(0)}%`;
171
+ const lines = [];
172
+ lines.push(`# SSG Precision Report — ${m.repo}`);
173
+ lines.push("");
174
+ lines.push(`| Metric | Value |`);
175
+ lines.push(`|--------|-------|`);
176
+ lines.push(`| Samples | ${m.total} |`);
177
+ lines.push(`| True Positive | ${m.truePositive} |`);
178
+ lines.push(`| False Positive | ${m.falsePositive} |`);
179
+ lines.push(`| True Negative | ${m.trueNegative} |`);
180
+ lines.push(`| False Negative | ${m.falseNegative} |`);
181
+ lines.push(`| **Precision** | **${pct(m.precision)}** |`);
182
+ lines.push(`| **Recall** | **${pct(m.recall)}** |`);
183
+ lines.push(`| **F1** | **${pct(m.f1)}** |`);
184
+ lines.push("");
185
+ const mismatches = m.details.filter(d => !d.correct);
186
+ if (mismatches.length > 0) {
187
+ lines.push("## Mismatches");
188
+ lines.push("");
189
+ lines.push(`| # | Expected | Detected | Sequence |`);
190
+ lines.push(`|---|----------|----------|----------|`);
191
+ for (const d of mismatches) {
192
+ lines.push(`| ${d.index} | ${d.expected} | ${d.detected} | \`${d.calls.join(" → ")}\` |`);
193
+ }
194
+ lines.push("");
195
+ }
196
+ const rating = m.f1 >= 0.85 ? "🟢 EXCELLENT"
197
+ : m.f1 >= 0.7 ? "🟡 GOOD"
198
+ : m.f1 >= 0.5 ? "🟠 FAIR"
199
+ : "🔴 NEEDS IMPROVEMENT";
200
+ lines.push(`**Rating:** ${rating}`);
201
+ lines.push("");
202
+ return lines.join("\n");
203
+ }
204
+ // ═══════════════════════════════════════════════════════════════
205
+ // Multi-Repo Aggregation
206
+ // ═══════════════════════════════════════════════════════════════
207
+ function formatMultiRepoTable(matrices) {
208
+ const pct = (v) => `${(v * 100).toFixed(0)}%`;
209
+ const lines = [];
210
+ lines.push("");
211
+ lines.push(`${C.bold}${C.cyan}Multi-Repo Precision Summary${C.reset}`);
212
+ lines.push("");
213
+ lines.push(` ${C.dim}Repo Samples TP FP TN FN Precision Recall F1 Rating${C.reset}`);
214
+ lines.push(` ${C.dim}──────────── ─────── ─── ─── ─── ─── ───────── ────── ───── ──────${C.reset}`);
215
+ for (const m of matrices) {
216
+ const rating = m.f1 >= 0.85 ? `${C.green}★★★★${C.reset}`
217
+ : m.f1 >= 0.7 ? `${C.yellow}★★★${C.reset}`
218
+ : m.f1 >= 0.5 ? `${C.yellow}★★${C.reset}`
219
+ : `${C.red}★${C.reset}`;
220
+ lines.push(` ${m.repo.padEnd(12)} ${String(m.total).padEnd(7)} ${String(m.truePositive).padEnd(3)} ${String(m.falsePositive).padEnd(3)} ${String(m.trueNegative).padEnd(3)} ${String(m.falseNegative).padEnd(3)} ${pct(m.precision).padEnd(9)} ${pct(m.recall).padEnd(6)} ${pct(m.f1).padEnd(5)} ${rating}`);
221
+ }
222
+ lines.push("");
223
+ return lines.join("\n");
224
+ }
225
+ // ═══════════════════════════════════════════════════════════════
226
+ // CLI
227
+ // ═══════════════════════════════════════════════════════════════
228
+ if (require.main === module) {
229
+ const args = process.argv.slice(2);
230
+ const repoPath = path.resolve(args.find(a => !a.startsWith("--")) || ".");
231
+ const outputIdx = args.indexOf("--output");
232
+ const outputPath = outputIdx >= 0 ? args[outputIdx + 1] : undefined;
233
+ try {
234
+ const matrix = runPrecision(repoPath);
235
+ console.log(formatPrecisionTerminal(matrix));
236
+ if (outputPath) {
237
+ const md = formatPrecisionMarkdown(matrix);
238
+ fs.writeFileSync(outputPath, md, "utf-8");
239
+ console.error(`📁 Report: ${outputPath}`);
240
+ }
241
+ }
242
+ catch (e) {
243
+ console.error(`\n${C.red}❌ ${e.message}${C.reset}\n`);
244
+ process.exit(1);
245
+ }
246
+ }