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
@@ -1,517 +1,1236 @@
1
- #!/usr/bin/env node
2
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js';
5
- import { createRequire } from 'module';
6
- import { fileURLToPath } from 'url';
7
- import * as fs from 'fs';
8
- import * as path from 'path';
9
-
10
- const require = createRequire(import.meta.url);
11
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
-
13
- // ── Load .env ──
14
- const ENV_FILE = path.resolve(__dirname, "..", ".env");
15
- if (fs.existsSync(ENV_FILE)) {
16
- const envContent = fs.readFileSync(ENV_FILE, "utf-8");
17
- for (const line of envContent.split("\n")) {
18
- const trimmed = line.trim();
19
- if (!trimmed || trimmed.startsWith("#")) continue;
20
- const eqIdx = trimmed.indexOf("=");
21
- if (eqIdx === -1) continue;
22
- const key = trimmed.slice(0, eqIdx).trim();
23
- const value = trimmed.slice(eqIdx + 1).trim();
24
- if (!process.env[key]) process.env[key] = value;
25
- }
26
- }
27
-
28
- // 处理命令行子命令
29
- const args = process.argv.slice(2);
30
- const OPT_IN_FILE = path.resolve(__dirname, '../.progmune_memory/opt_in.json');
31
- if (args[0] === 'test') {
32
- console.log('🧪 Progmune Runtime 自测试\n');
33
- const { validateActionSequence } = require('./validator.js');
34
- const { StateMachineValidator } = require('./ssg-validator.js');
35
- const { recordFailure, getAllFailures } = require('./failure-corpus.js');
36
- const { recordEpisode, findSemanticTemplate } = require('./memory-layer.js');
37
-
38
- let passed = 0, total = 0;
39
- function assert(cond, name) { total++; if (cond) { passed++; console.log(' ✅ ' + name); } else { console.log(' ❌ ' + name); } }
40
-
41
- // SVL-1
42
- const ir = [{ name: 'test_fn', params: [], returnType: 'void' }];
43
- require('fs').writeFileSync('ir.json', JSON.stringify(ir));
44
- assert(validateActionSequence([{ kind: 'call', function: 'test_fn', args: [] }]).valid, 'SVL-1: 存在函数通过');
45
- assert(!validateActionSequence([{ kind: 'call', function: 'no_exist', args: [] }]).valid, 'SVL-1: 不存在函数拦截');
46
-
47
- // SVL-2
48
- const ir2 = [{ name: 'needs_args', params: [{ name: 'x', type: 'int' }], returnType: 'void' }];
49
- require('fs').writeFileSync('ir.json', JSON.stringify(ir2));
50
- Object.keys(require.cache).filter(k => k.includes('validator')).forEach(k => delete require.cache[k]);
51
- const { validateActionSequence: v2 } = require('./validator.js');
52
- assert(v2([{ kind: 'call', function: 'needs_args', args: [{ name: 'x', type: 'int', value: 1 }] }]).valid, 'SVL-2: 参数数量匹配通过');
53
- assert(!v2([{ kind: 'call', function: 'needs_args', args: [] }]).valid, 'SVL-2: 参数数量不匹配拦截');
54
-
55
- // SVL-3
56
- const ir3 = [{ name: 'get', params: [{ name: 'k', type: 'str' }], returnType: 'any' }];
57
- require('fs').writeFileSync('ir.json', JSON.stringify(ir3));
58
- Object.keys(require.cache).filter(k => k.includes('validator')).forEach(k => delete require.cache[k]);
59
- const { validateActionSequence: v3 } = require('./validator.js');
60
- assert(v3([{ kind: 'assign', target: 'x', value: '"val"' }, { kind: 'call', function: 'get', args: [{ name: 'k', type: 'str', value: 'x' }] }]).valid, 'SVL-3: 变量先声明后使用通过');
61
- assert(!v3([{ kind: 'assign', target: 'x', value: 'undef' }]).valid, 'SVL-3: 未声明变量拦截');
62
-
63
- // SVL-4
64
- const protocols = [
65
- { function: 'auth', protocol: { pre_states: ['INIT'], post_states: ['AUTHED'], invalidate: ['INIT'] } },
66
- { function: 'issue_token', protocol: { pre_states: ['AUTHED'], post_states: ['TOKEN_ISSUED'], invalidate: ['AUTHED'] } }
67
- ];
68
- const ssv = new StateMachineValidator(protocols, 'INIT');
69
- assert(ssv.apply('auth').valid, 'SVL-4: 合法状态跃迁通过');
70
- assert(ssv.apply('issue_token').valid, 'SVL-4: 认证后签发令牌通过');
71
- const ssv2 = new StateMachineValidator(protocols, 'INIT');
72
- assert(!ssv2.apply('issue_token').valid, 'SVL-4: 非法跃迁拦截(无 auth)');
73
-
74
- // Failure Corpus
75
- recordFailure({ intent: 'test', projectFunctions: ['f'], violatedSVL: 'SVL-1', constraintType: 'symbol_existence', actionSequence: [], errorDetail: 'test' });
76
- assert(getAllFailures().length > 0, 'Failure Corpus: 记录成功');
77
-
78
- // Memory
79
- recordEpisode({ intent: 'test memory', actions: [], success: true });
80
- assert(findSemanticTemplate('test memory') !== undefined || true, '记忆系统: 工作正常');
81
-
82
- console.log(`\n📊 结果: ${passed}/${total} 通过 (${(passed/total*100).toFixed(0)}%)`);
83
- process.exit(passed === total ? 0 : 1);
84
- } else if (args[0] === 'setup') {
85
- console.log('⚙️ Progmune Runtime 配置向导\n');
86
- const key = args[1] || process.env.LLM_API_KEY || '';
87
- if (key) {
88
- const envContent = `# Progmune Runtime 配置
89
- LLM_API_KEY=${key}
90
- LLM_BASE_URL=${process.env.LLM_BASE_URL || 'https://api.deepseek.com/v1'}
91
- LLM_MODEL=${process.env.LLM_MODEL || 'deepseek-chat'}
92
- PROGMUNE_HUB=${process.env.PROGMUNE_HUB || 'http://localhost:8080/report'}
93
- `;
94
- fs.writeFileSync(ENV_FILE, envContent);
95
- console.log('✅ 配置已保存到 .env 文件');
96
- console.log(' 请确保在 MCP 客户端配置中设置 env 字段指向这些变量。');
97
- console.log('\n Claude Code 配置示例:');
98
- console.log(' {');
99
- console.log(' "mcpServers": {');
100
- console.log(' "progmune": {');
101
- console.log(' "command": "npx",');
102
- console.log(' "args": ["progmune-runtime"],');
103
- console.log(' "env": {');
104
- console.log(' "LLM_API_KEY": "' + key.slice(0, 8) + '...",');
105
- console.log(' "LLM_BASE_URL": "' + (process.env.LLM_BASE_URL || 'https://api.deepseek.com/v1') + '"');
106
- console.log(' }');
107
- console.log(' }');
108
- console.log(' }');
109
- console.log(' }');
110
- } else {
111
- console.log('用法: npx progmune-runtime setup <API密钥>');
112
- console.log('');
113
- console.log('示例:');
114
- console.log(' npx progmune-runtime setup sk-your-key-here');
115
- console.log('');
116
- console.log('环境变量说明:');
117
- console.log(' LLM_API_KEY 必要 DeepSeek 或 OpenAI API 密钥');
118
- console.log(' LLM_BASE_URL 可选 API 地址 (默认: https://api.deepseek.com/v1)');
119
- console.log(' LLM_MODEL 可选 模型名 (默认: deepseek-chat)');
120
- console.log(' PROGMUNE_HUB 可选 免疫汇聚服务器地址');
121
- }
122
- process.exit(0);
123
- } else if (args[0] === 'opt-in') {
124
- const command = args[1] || 'status';
125
- const dir = path.dirname(OPT_IN_FILE);
126
- if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
127
- if (command === 'enable') {
128
- fs.writeFileSync(OPT_IN_FILE, JSON.stringify({ enabled: true, timestamp: new Date().toISOString() }, null, 2));
129
- console.log('✅ 已开启免疫网络上报。感谢您为全球免疫网络做出贡献!');
130
- console.log('每次代码生成后,脱敏错误指纹将自动上报到中央服务器。');
131
- console.log('您可以通过 "npx progmune-runtime opt-in disable" 随时关闭。');
132
- } else if (command === 'disable') {
133
- if (fs.existsSync(OPT_IN_FILE)) fs.unlinkSync(OPT_IN_FILE);
134
- console.log('⛔ 已关闭免疫网络上报。Progmune 将以完全离线模式运行。');
135
- } else {
136
- if (fs.existsSync(OPT_IN_FILE)) {
137
- console.log('免疫网络上报状态: 已开启');
138
- console.log('脱敏错误指纹将自动上报。');
139
- } else {
140
- console.log('免疫网络上报状态: 未配置');
141
- console.log('请运行以下命令开启(推荐):');
142
- console.log(' npx progmune-runtime opt-in enable');
143
- console.log('或运行以下命令明确关闭:');
144
- console.log(' npx progmune-runtime opt-in disable');
1
+ /**
2
+ * Progmune MCP Server (TypeScript)
3
+ *
4
+ * Exposes Progmune Runtime as an MCP server for Claude Code.
5
+ * Tools: progmune_generate (TS code gen), progmune_status (health), progmune_check (immune audit)
6
+ */
7
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
8
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
9
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
10
+ import * as fs from "fs";
11
+ import * as path from "path";
12
+ import { createLogger } from "./logger";
13
+ // ── Load .env (in compiled ESM output, use import.meta.url; here we use __dirname) ──
14
+ const envPath = path.resolve(__dirname, "..", ".env");
15
+ if (fs.existsSync(envPath)) {
16
+ const envContent = fs.readFileSync(envPath, "utf-8");
17
+ for (const line of envContent.split("\n")) {
18
+ const trimmed = line.trim();
19
+ if (!trimmed || trimmed.startsWith("#"))
20
+ continue;
21
+ const eqIdx = trimmed.indexOf("=");
22
+ if (eqIdx === -1)
23
+ continue;
24
+ const key = trimmed.slice(0, eqIdx).trim();
25
+ const value = trimmed.slice(eqIdx + 1).trim();
26
+ if (!process.env[key])
27
+ process.env[key] = value;
145
28
  }
146
- }
147
- process.exit(0);
148
29
  }
149
-
150
- const { plan } = require('./planner.js');
151
- const { extractIR } = require('./extract-ir.js');
152
- const { emitCode } = require('./emitter.js');
153
- const { recordRun } = require('./feedback.js');
154
- const { reportFingerprints } = require('./immune-reporter.js');
155
-
156
- // 结构化日志工具:所有日志输出到 stderr,不干扰 MCP stdout JSON-RPC
157
- const log = {
158
- info: (msg) => console.error(`[Progmune] ${msg}`),
159
- warn: (msg) => console.error(`[Progmune] ⚠️ ${msg}`),
160
- error: (msg) => console.error(`[Progmune] ❌ ${msg}`),
161
- success: (msg) => console.error(`[Progmune] ✅ ${msg}`),
162
- };
163
-
30
+ import { plan } from "./planner";
31
+ import { extractIR } from "./extract-ir";
32
+ import { extractIRPython, isPythonProject } from "./extract-ir-python";
33
+ import { emitCode } from "./emitter";
34
+ import { recordRun } from "./feedback";
35
+ import { reportFingerprints } from "./immune-reporter";
36
+ const OPT_IN_FILE = path.resolve(__dirname, "..", ".progmune_memory", "opt_in.json");
37
+ // ── Structured logging (stderr, not stdout JSON-RPC) ──
38
+ const log = createLogger("progmune");
164
39
  async function main() {
165
- const server = new Server(
166
- { name: "progmune", version: "2.2.0" },
167
- { capabilities: { tools: {} } }
168
- );
169
-
170
- server.setRequestHandler(ListToolsRequestSchema, async () => ({
171
- tools: [
172
- {
173
- name: "progmune_generate",
174
- description:
175
- "Generate type-safe TypeScript code using only functions that actually exist in the project. Multi-layer validation: symbol existence, type arity, variable flow, SSG protocol constraints.",
176
- inputSchema: {
177
- type: "object",
178
- properties: {
179
- intent: {
180
- type: "string",
181
- description: "Natural-language programming intent, e.g. 'authenticate a user and issue a JWT token'",
40
+ const server = new Server({ name: "progmune", version: "2.2.0" }, { capabilities: { tools: {} } });
41
+ // ── Tool listing ──
42
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
43
+ tools: [
44
+ {
45
+ name: "progmune_generate",
46
+ description: "Generate type-safe TypeScript code using only functions that actually exist in the project. The generated code passes Progmune's multi-layer validation (symbol existence, type arity, variable flow, SSG protocol constraints).",
47
+ inputSchema: {
48
+ type: "object",
49
+ properties: {
50
+ intent: {
51
+ type: "string",
52
+ description: "Natural-language programming intent, e.g. 'authenticate a user and issue a JWT token'",
53
+ },
54
+ projectPath: {
55
+ type: "string",
56
+ description: "Absolute path to the project root directory (must contain TypeScript source files)",
57
+ },
58
+ },
59
+ required: ["intent", "projectPath"],
60
+ },
182
61
  },
183
- projectPath: {
184
- type: "string",
185
- description: "Absolute path to the project root (must contain TypeScript source files)",
62
+ {
63
+ name: "progmune_status",
64
+ description: "View Progmune Runtime health: LLM config, immune network stats, failure patterns, memory state, antibody hit rate.",
65
+ inputSchema: {
66
+ type: "object",
67
+ properties: {},
68
+ required: [],
69
+ },
186
70
  },
187
- },
188
- required: ["intent", "projectPath"],
189
- },
190
- },
191
- {
192
- name: "progmune_status",
193
- description: "View Progmune Runtime health: LLM config, immune network stats, failure patterns, memory state.",
194
- inputSchema: { type: "object", properties: {}, required: [] },
195
- },
196
- {
197
- name: "progmune_check",
198
- description:
199
- "Run the immune audit pipeline: IR extraction, SSG protocol validation, ledger invariants (Invariant-0 + Invariant-1 + Replay), failure genome, antibody efficacy.",
200
- inputSchema: {
201
- type: "object",
202
- properties: {
203
- projectPath: {
204
- type: "string",
205
- description: "Absolute path to the project root",
71
+ {
72
+ name: "progmune_check",
73
+ description: "Run the full 7-step immune audit pipeline on the project: IR extraction, TypeScript type-checking, SSG protocol validation, ledger invariants (Invariant-0 + Invariant-1 + Replay), failure genome, antibody efficacy, and namespace state snapshots.",
74
+ inputSchema: {
75
+ type: "object",
76
+ properties: {
77
+ projectPath: {
78
+ type: "string",
79
+ description: "Absolute path to the project root directory",
80
+ },
81
+ },
82
+ required: ["projectPath"],
83
+ },
84
+ },
85
+ {
86
+ name: "progmune_execute",
87
+ description: "Execute the full Progmune pipeline: intent → IR extraction → Planner (LLM + immune constraints) → validated TypeScript code with @progmune-generated marker → file written → fingerprint registered. This is the PRIMARY code generation tool. Use this instead of writing files directly.",
88
+ inputSchema: {
89
+ type: "object",
90
+ properties: {
91
+ intent: { type: "string", description: "Natural-language programming intent" },
92
+ filePath: { type: "string", description: "Relative or absolute path to write the generated file (e.g. 'src/my-module.ts')" },
93
+ projectPath: { type: "string", description: "Absolute path to project root" },
94
+ },
95
+ required: ["intent", "filePath", "projectPath"],
96
+ },
97
+ },
98
+ {
99
+ name: "progmune_audit",
100
+ description: "Audit source files for @progmune-generated markers. Returns Progmune coverage rate.",
101
+ inputSchema: {
102
+ type: "object",
103
+ properties: {
104
+ directory: { type: "string", description: "Directory to audit (e.g. 'src/')" },
105
+ },
106
+ required: [],
107
+ },
108
+ },
109
+ {
110
+ name: "progmune_init",
111
+ description: "Ensure CLAUDE.md includes Progmune usage instructions so AI assistants prefer progmune_generate.",
112
+ inputSchema: {
113
+ type: "object",
114
+ properties: {
115
+ projectPath: { type: "string", description: "Absolute path to project root" },
116
+ },
117
+ required: ["projectPath"],
118
+ },
119
+ },
120
+ {
121
+ name: "progmune_repair",
122
+ description: "When immune violations exist, run the full repair loop: check consistency → generate repair proposals → create repair branches → deterministic replay. Returns proposals for human approval. NEVER auto-writes to ledger.",
123
+ inputSchema: {
124
+ type: "object",
125
+ properties: {
126
+ sessionId: { type: "string", description: "Session ID to repair (from progmune_check or corpus)" },
127
+ projectPath: { type: "string", description: "Absolute path to project root" },
128
+ },
129
+ required: ["sessionId"],
130
+ },
131
+ },
132
+ {
133
+ name: "progmune_accept",
134
+ description: "Accept a repair proposal and merge it into the session ledger. After this, the session will have the fix applied.",
135
+ inputSchema: {
136
+ type: "object",
137
+ properties: {
138
+ sessionId: { type: "string", description: "Session ID" },
139
+ proposalId: { type: "string", description: "Repair proposal ID (from progmune_repair)" },
140
+ projectPath: { type: "string", description: "Absolute path to project root" },
141
+ },
142
+ required: ["sessionId", "proposalId"],
143
+ },
144
+ },
145
+ // P8.2: Zero-shot protocol discovery & repair
146
+ {
147
+ name: "progmune_discover",
148
+ description: "Discover protocol state machines from a codebase using name-free topology analysis. Extracts call sequences, clusters by graph fingerprint, and returns discovered protocols with confidence scores.",
149
+ inputSchema: {
150
+ type: "object",
151
+ properties: {
152
+ projectPath: { type: "string", description: "Absolute path to project root" },
153
+ repoName: { type: "string", description: "Repository name for identification (e.g., 'redis', 'postgresql')" },
154
+ },
155
+ required: ["projectPath"],
156
+ },
157
+ },
158
+ {
159
+ name: "progmune_zeroshot",
160
+ description: "Zero-shot repair: attempt to fix a broken action sequence using discovered protocol knowledge, without any hand-written rules for the target library. Returns Top-3 repair candidates.",
161
+ inputSchema: {
162
+ type: "object",
163
+ properties: {
164
+ brokenSequence: { type: "array", items: { type: "string" }, description: "The broken (incomplete) action sequence" },
165
+ expectedPattern: { type: "string", description: "Expected protocol pattern (e.g., 'acquire_use_release', 'transaction')" },
166
+ projectPath: { type: "string", description: "Absolute path to project root" },
167
+ },
168
+ required: ["brokenSequence"],
169
+ },
170
+ },
171
+ {
172
+ name: "progmune_scaffold",
173
+ description: "Generate complete project files from templates (Express API, CLI tool, static site). Uses LLM to parameterize templates with project context. Complements progmune_generate which generates function-call-level code. Use this for architecture-level code: servers, pages, CLI entry points.",
174
+ inputSchema: {
175
+ type: "object",
176
+ properties: {
177
+ intent: { type: "string", description: "Natural-language description of what to build" },
178
+ scaffoldType: { type: "string", enum: ["express-api", "cli-tool", "static-site"], description: "Template type: 'express-api' (REST server with SQLite), 'cli-tool' (CLI with arg parsing), 'static-site' (single-file HTML page)" },
179
+ filePath: { type: "string", description: "Relative or absolute path to write the generated file" },
180
+ projectPath: { type: "string", description: "Absolute path to project root" },
181
+ },
182
+ required: ["intent", "scaffoldType", "projectPath"],
183
+ },
184
+ },
185
+ {
186
+ name: "progmune_governance_report",
187
+ description: "Generate an AI Code Governance Report for the project. Aggregates session integrity, SSV ledger verification, PLSB benchmark coverage, provenance fingerprint audit, and antibody efficacy into a structured report with PASS/WARN/FAIL verdict. Use this to prove the safety of AI-generated code.",
188
+ inputSchema: {
189
+ type: "object",
190
+ properties: {
191
+ projectPath: { type: "string", description: "Absolute path to project root" },
192
+ sessionId: { type: "string", description: "Optional: specific session ID for per-session governance certificate" },
193
+ format: { type: "string", enum: ["terminal", "json", "markdown"], description: "Output format" },
194
+ fast: { type: "boolean", description: "Skip expensive PLSB benchmark for faster response" },
195
+ },
196
+ required: [],
197
+ },
198
+ },
199
+ {
200
+ name: "progmune_provenance",
201
+ description: "Build the end-to-end provenance chain for a session. Traces every generation, validation, repair, and deployment event with cryptographic hashes — proving the full lifecycle of AI-generated code.",
202
+ inputSchema: {
203
+ type: "object",
204
+ properties: {
205
+ sessionId: { type: "string", description: "Session ID to trace" },
206
+ format: { type: "string", enum: ["terminal", "json"], description: "Output format" },
207
+ },
208
+ required: ["sessionId"],
209
+ },
210
+ },
211
+ {
212
+ name: "progmune_accountability",
213
+ description: "Build the AI Code Supply Chain accountability ledger for a session. Maps every action to a responsible actor (human, LLM, validator, reviewer, deployer) with identity, role, and cryptographic signatures. Detects custody gaps where actor identity cannot be verified.",
214
+ inputSchema: {
215
+ type: "object",
216
+ properties: {
217
+ sessionId: { type: "string", description: "Session ID to trace" },
218
+ author: { type: "string", description: "Email of human who initiated generation" },
219
+ reviewer: { type: "string", description: "Email of human reviewer" },
220
+ approver: { type: "string", description: "Email of human who approved deployment" },
221
+ deployer: { type: "string", description: "CI/CD system ID that deployed" },
222
+ format: { type: "string", enum: ["terminal", "json"], description: "Output format" },
223
+ },
224
+ required: ["sessionId"],
225
+ },
226
+ },
227
+ {
228
+ name: "progmune_policy_check",
229
+ description: "Run the Policy Engine against a file. Evaluates 6 rules (confidence, provenance, PLSB coverage, human review, fingerprint, violations) and returns ALLOW, WARN, or BLOCK. This is the deploy gate — use it in CI/CD to block deployment of unverified AI-generated code.",
230
+ inputSchema: {
231
+ type: "object",
232
+ properties: {
233
+ filePath: { type: "string", description: "Absolute path to the file to check" },
234
+ author: { type: "string", description: "Email of human who initiated generation" },
235
+ reviewer: { type: "string", description: "Email of human reviewer" },
236
+ format: { type: "string", enum: ["terminal", "json"], description: "Output format" },
237
+ },
238
+ required: ["filePath"],
239
+ },
206
240
  },
207
- },
208
- required: ["projectPath"],
209
- },
210
- },
211
- ],
212
- }));
213
-
214
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
215
- if (request.params.name === "progmune_generate") {
216
- const { intent, projectPath } = request.params.arguments;
217
-
218
- if (!process.env.LLM_API_KEY) {
219
- return {
220
- content: [
221
241
  {
222
- type: "text",
223
- text: `❌ LLM_API_KEY not set.
242
+ name: "progmune_plsb",
243
+ description: "Query the Protocol Lifecycle Security Benchmark (PLSB). Get taxonomy coverage, recall/precision metrics, and per-category detection status. Use this to check if protocol vulnerability categories are covered.",
244
+ inputSchema: {
245
+ type: "object",
246
+ properties: {
247
+ format: { type: "string", enum: ["summary", "json", "markdown"], description: "Output format" },
248
+ category: { type: "string", description: "Optional: filter by PLS-ID (e.g., PLS-001)" },
249
+ },
250
+ required: [],
251
+ },
252
+ },
253
+ {
254
+ name: "progmune_certify",
255
+ description: "Issue an AI Code Certificate for a file. Verifies: @progmune-generated marker, session integrity, ledger consistency, fingerprint verification, and PLSB coverage. Returns a human-readable certificate suitable for audit and compliance.",
256
+ inputSchema: {
257
+ type: "object",
258
+ properties: {
259
+ filePath: { type: "string", description: "Absolute path to the file to certify" },
260
+ format: { type: "string", enum: ["terminal", "json"], description: "Output format" },
261
+ },
262
+ required: ["filePath"],
263
+ },
264
+ },
265
+ {
266
+ name: "progmune_trust_check",
267
+ description: "Run the AI Trust Decision Engine on a project. Returns a weighted Trust Score (0-100) with APPROVED/NEEDS_REVIEW/BLOCKED decision, Coverage Confidence (quantified % with ±margin, replacing qualitative HIGH/MEDIUM/LOW labels), Violation Traces (step-by-step protocol state reasoning chains with expected vs actual state comparisons), and complete evidence trail. This is the primary CI/CD gating tool — use it to decide whether AI-generated code is safe to deploy. Dimensions: Policy Compliance (35%), Protocol Safety (30%), Verification Coverage (20%), Governance Integrity (15%). Critical violations auto-BLOCK. Use --json for machine-readable output with coverageConfidence and violationTraces fields.",
268
+ inputSchema: {
269
+ type: "object",
270
+ properties: {
271
+ projectPath: {
272
+ type: "string",
273
+ description: "Absolute path to the project root to evaluate",
274
+ },
275
+ commit: {
276
+ type: "string",
277
+ description: "Git commit SHA to evaluate (for audit trail)",
278
+ },
279
+ branch: {
280
+ type: "string",
281
+ description: "Git branch name",
282
+ },
283
+ policy: {
284
+ type: "string",
285
+ description: "Policy name or path to .progmune-policy.json",
286
+ },
287
+ format: {
288
+ type: "string",
289
+ enum: ["terminal", "json"],
290
+ description: "Output format (default: terminal)",
291
+ },
292
+ },
293
+ required: ["projectPath"],
294
+ },
295
+ },
296
+ {
297
+ name: "progmune_score",
298
+ description: "Score a function's protocol compliance on a continuous 0-1 scale across 6 dimensions (auth, tls, payment, data_integrity, resource, session). Unlike progmune_check which returns binary pass/fail, progmune_score returns per-dimension compliance scores, identifies the weakest dimension, and suggests specific missing calls. Use this BEFORE generating code — pass the intended function calls and purpose to see what protocols are missing. No ML. Pure rule-based scoring using existing SSG protocol definitions.",
299
+ inputSchema: {
300
+ type: "object",
301
+ properties: {
302
+ calls: {
303
+ type: "array",
304
+ items: { type: "string" },
305
+ description: "Array of function call names in execution order (e.g. ['getOrder', 'processRefund', 'sendEmail'])",
306
+ },
307
+ purpose: {
308
+ type: "string",
309
+ description: "Optional function purpose to trigger dimension relevance (e.g. '退款处理', '用户注册', '文件上传')",
310
+ },
311
+ functionName: {
312
+ type: "string",
313
+ description: "Optional function name for context",
314
+ },
315
+ },
316
+ required: ["calls"],
317
+ },
318
+ },
319
+ ],
320
+ }));
321
+ // ── Tool handler ──
322
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
323
+ if (request.params.name === "progmune_generate") {
324
+ const { intent, projectPath } = request.params.arguments;
325
+ // Guard: LLM API key
326
+ if (!process.env.LLM_API_KEY) {
327
+ return {
328
+ content: [
329
+ {
330
+ type: "text",
331
+ text: `❌ LLM_API_KEY not set.
224
332
 
225
- Progmune needs an LLM API key. Configure via:
333
+ Progmune needs an LLM API key to generate code. Configure via:
226
334
 
227
- .env file (loaded automatically):
335
+ 【.env file】Add to ${envPath}:
228
336
  LLM_API_KEY=your-key
229
337
  LLM_BASE_URL=https://api.deepseek.com/v1
230
338
  LLM_MODEL=deepseek-chat
231
339
 
232
- Or export: export LLM_API_KEY='your-key'
340
+ 【Environment】export LLM_API_KEY='your-key'
233
341
 
234
342
  Supports DeepSeek and OpenAI-compatible APIs.
235
343
  Get a key: https://platform.deepseek.com/api_keys`,
236
- },
237
- ],
238
- };
239
- }
344
+ },
345
+ ],
346
+ };
347
+ }
348
+ // Guard: opt-in
349
+ if (!fs.existsSync(OPT_IN_FILE)) {
350
+ return {
351
+ content: [
352
+ {
353
+ type: "text",
354
+ text: `⚠️ Immune network not configured.
240
355
 
241
- if (!fs.existsSync(OPT_IN_FILE)) {
242
- return {
243
- content: [
244
- {
245
- type: "text",
246
- text: "⚠️ Immune network not configured.\n\nEnable (recommended):\n npx progmune-runtime opt-in enable\n\nOr disable for offline mode:\n npx progmune-runtime opt-in disable\n\nThen restart Claude Code.",
247
- },
248
- ],
249
- };
250
- }
356
+ Enable (recommended):
357
+ npx progmune-runtime opt-in enable
251
358
 
252
- // Validate projectPath
253
- if (!projectPath || typeof projectPath !== "string") {
254
- return {
255
- content: [
256
- { type: "text", text: "❌ projectPath must be a non-empty string (absolute path to the project root)." },
257
- ],
258
- };
259
- }
260
- if (!fs.existsSync(projectPath)) {
261
- return {
262
- content: [{ type: "text", text: `❌ Path not found: ${projectPath}` }],
263
- };
264
- }
265
- if (!fs.statSync(projectPath).isDirectory()) {
266
- return {
267
- content: [{ type: "text", text: `❌ Not a directory: ${projectPath}` }],
268
- };
269
- }
359
+ Or disable for offline mode:
360
+ npx progmune-runtime opt-in disable
270
361
 
271
- // Check for TypeScript source files
272
- const hasTsFiles = (() => {
273
- try {
274
- return fs.readdirSync(projectPath).some(
275
- (f) => f.endsWith(".ts") || f.endsWith(".tsx")
276
- );
277
- } catch {
278
- return false;
362
+ Then restart Claude Code.`,
363
+ },
364
+ ],
365
+ };
366
+ }
367
+ // Validate projectPath
368
+ if (!projectPath || typeof projectPath !== "string") {
369
+ return {
370
+ content: [
371
+ {
372
+ type: "text",
373
+ text: "❌ projectPath must be a non-empty string (absolute path to the project root).",
374
+ },
375
+ ],
376
+ };
377
+ }
378
+ if (!fs.existsSync(projectPath)) {
379
+ return {
380
+ content: [
381
+ {
382
+ type: "text",
383
+ text: `❌ Path not found: ${projectPath}`,
384
+ },
385
+ ],
386
+ };
387
+ }
388
+ if (!fs.statSync(projectPath).isDirectory()) {
389
+ return {
390
+ content: [
391
+ {
392
+ type: "text",
393
+ text: `❌ Not a directory: ${projectPath}`,
394
+ },
395
+ ],
396
+ };
397
+ }
398
+ // Check for TypeScript files
399
+ const hasTsFiles = (() => {
400
+ try {
401
+ return fs.readdirSync(projectPath).some((f) => f.endsWith(".ts"));
402
+ }
403
+ catch {
404
+ return false;
405
+ }
406
+ })();
407
+ if (!hasTsFiles) {
408
+ log.warn(`No .ts files found in project root, IR may be empty`);
409
+ }
410
+ // IR extraction — auto-detect language
411
+ const isPython = isPythonProject(projectPath);
412
+ let ir;
413
+ try {
414
+ ir = isPython ? extractIRPython(projectPath) : extractIR(projectPath);
415
+ }
416
+ catch (e) {
417
+ return {
418
+ content: [
419
+ {
420
+ type: "text",
421
+ text: `❌ IR extraction failed: ${e.message}\n\nMake sure the project has a tsconfig.json or .ts source files.`,
422
+ },
423
+ ],
424
+ };
425
+ }
426
+ if (ir.length === 0) {
427
+ log.warn(`No functions extracted from ${projectPath}`);
428
+ }
429
+ // Cross-validate protocols.json with IR
430
+ const protocolsFile = path.resolve(__dirname, "..", "protocols.json");
431
+ if (fs.existsSync(protocolsFile)) {
432
+ const protocols = JSON.parse(fs.readFileSync(protocolsFile, "utf-8"));
433
+ for (const funcName of Object.keys(protocols)) {
434
+ if (!ir.find((f) => f.name === funcName)) {
435
+ log.warn(`protocols.json defines "${funcName}" but IR has no such function — protocol ignored`);
436
+ }
437
+ }
438
+ }
439
+ // Write ir.json (planning reads from disk)
440
+ fs.writeFileSync("ir.json", JSON.stringify(ir, null, 2));
441
+ // Set project path for memory isolation
442
+ process.env.PROGMUNE_PROJECT_DIR = projectPath;
443
+ // Plan
444
+ let planResult;
445
+ try {
446
+ planResult = await plan(intent);
447
+ }
448
+ catch (e) {
449
+ log.error(`Planning failed: ${e.message}`);
450
+ reportFingerprints().catch(() => { });
451
+ return {
452
+ content: [
453
+ {
454
+ type: "text",
455
+ text: `❌ Planning failed: ${e.message}`,
456
+ },
457
+ ],
458
+ };
459
+ }
460
+ const actions = planResult?.actions || [];
461
+ if (actions.length === 0) {
462
+ reportFingerprints().catch(() => { });
463
+ return {
464
+ content: [
465
+ {
466
+ type: "text",
467
+ text: "Could not generate constraint-satisfying TypeScript code.",
468
+ },
469
+ ],
470
+ };
471
+ }
472
+ // Emit TypeScript code with generation marker
473
+ let protocolRuleCount = 0;
474
+ if (fs.existsSync(protocolsFile)) {
475
+ try {
476
+ protocolRuleCount = Object.keys(JSON.parse(fs.readFileSync(protocolsFile, "utf-8")).rules || {}).length;
477
+ }
478
+ catch { /* protocol parse — non-critical */ }
479
+ }
480
+ const code = emitCode(actions, {
481
+ sessionId: planResult.sessionId,
482
+ ruleHash: planResult.ruleHash,
483
+ irFunctionCount: ir.length,
484
+ protocolRuleCount,
485
+ });
486
+ recordRun(intent, actions, true);
487
+ // Report fingerprints async (don't block response)
488
+ reportFingerprints().catch(() => { });
489
+ return { content: [{ type: "text", text: code }] };
279
490
  }
280
- })();
281
- if (!hasTsFiles) {
282
- log.warn(`No .ts/.tsx files found in project root, IR may be empty`);
283
- }
284
-
285
- // IR extraction (TypeScript via ts-morph)
286
- let ir;
287
- try {
288
- ir = extractIR(projectPath);
289
- } catch (e) {
290
- return {
291
- content: [
292
- {
293
- type: "text",
294
- text: `❌ IR extraction failed: ${e.message}\n\nEnsure the project has a tsconfig.json or .ts source files.`,
295
- },
296
- ],
297
- };
298
- }
299
-
300
- if (ir.length === 0) {
301
- log.warn(`No functions extracted from ${projectPath}`);
302
- }
303
-
304
- // Cross-validate protocols.json with IR
305
- const protocolsFile = path.resolve(__dirname, "..", "protocols.json");
306
- if (fs.existsSync(protocolsFile)) {
307
- try {
308
- const protocols = JSON.parse(fs.readFileSync(protocolsFile, "utf-8"));
309
- const rules = protocols.rules || {};
310
- for (const funcName of Object.keys(rules)) {
311
- if (!ir.find((f) => f.name === funcName)) {
312
- log.warn(
313
- `protocols.json defines "${funcName}" but IR has no such function — protocol ignored`
314
- );
315
- }
316
- }
317
- } catch {}
318
- }
319
-
320
- // Write ir.json for planner
321
- fs.writeFileSync("ir.json", JSON.stringify(ir, null, 2));
322
-
323
- // Set project path for memory isolation
324
- process.env.PROGMUNE_PROJECT_DIR = projectPath;
325
-
326
- // Plan
327
- let actions;
328
- try {
329
- actions = await plan(intent);
330
- } catch (e) {
331
- log.error(`Planning failed: ${e.message}`);
332
- reportFingerprints().catch(() => {});
333
- return {
334
- content: [{ type: "text", text: `❌ Planning failed: ${e.message}` }],
335
- };
336
- }
337
-
338
- if (!actions || actions.length === 0) {
339
- reportFingerprints().catch(() => {});
340
- return {
341
- content: [
342
- {
343
- type: "text",
344
- text: "Could not generate constraint-satisfying TypeScript code.",
345
- },
346
- ],
347
- };
348
- }
349
-
350
- // Emit TypeScript code
351
- const code = emitCode(actions);
352
- recordRun(intent, actions, true);
353
- reportFingerprints().catch(() => {});
354
-
355
- return { content: [{ type: "text", text: code }] };
356
- }
357
-
358
- if (request.params.name === "progmune_status") {
359
- const { getAllFailures, getTopFailurePatterns } = require("./failure-corpus.js");
360
- const { getRecentEpisodes } = require("./memory-layer.js");
361
- const { callCount } = require("./llm.js");
362
- const failures = getAllFailures();
363
- const patterns = getTopFailurePatterns(5);
364
- const episodes = getRecentEpisodes(5);
365
- const hubEndpoint = process.env.PROGMUNE_HUB || "not configured";
366
- const hubReachable = await checkHubReachable(hubEndpoint);
367
-
368
- const status = {
369
- version: "2.2.0",
370
- llm: {
371
- model: process.env.LLM_MODEL || "deepseek-chat",
372
- baseUrl: process.env.LLM_BASE_URL || "https://api.deepseek.com/v1",
373
- callCount: callCount || 0,
374
- apiKeySet: !!process.env.LLM_API_KEY,
375
- },
376
- immuneNetwork: {
377
- optIn: fs.existsSync(OPT_IN_FILE),
378
- hub: hubEndpoint,
379
- hubReachable,
380
- totalFailures: failures.length,
381
- topPatterns: patterns,
382
- },
383
- memory: {
384
- recentEpisodes: episodes.length,
385
- },
386
- };
387
- return {
388
- content: [{ type: "text", text: JSON.stringify(status, null, 2) }],
389
- };
390
- }
491
+ if (request.params.name === "progmune_status") {
492
+ const { getAllFailures, getTopFailurePatterns } = require("./failure-corpus");
493
+ const { getRecentEpisodes } = require("./memory-layer");
494
+ const { callCount } = require("./llm");
495
+ const failures = getAllFailures();
496
+ const patterns = getTopFailurePatterns(5);
497
+ const episodes = getRecentEpisodes(5);
498
+ const hubEndpoint = process.env.PROGMUNE_HUB || "未配置";
499
+ let hubReachable = false;
500
+ if (hubEndpoint && hubEndpoint !== "未配置") {
501
+ try {
502
+ const http = require("http");
503
+ const https = require("https");
504
+ const transport = hubEndpoint.startsWith("https") ? https : http;
505
+ hubReachable = await new Promise((resolve) => {
506
+ const req = transport.get(hubEndpoint, (res) => {
507
+ resolve(res.statusCode === 200);
508
+ });
509
+ req.on("error", () => resolve(false));
510
+ req.setTimeout(3000, () => {
511
+ req.destroy();
512
+ resolve(false);
513
+ });
514
+ });
515
+ }
516
+ catch {
517
+ hubReachable = false;
518
+ }
519
+ }
520
+ // P7.3: Ranker status (heuristic vs learning)
521
+ let rankerStatus = { type: "heuristic", modelWeight: 0, modelSamples: 0, uptimeSeconds: 0 };
522
+ try {
523
+ const { getRankerStatus } = require("./counterfactual-engine");
524
+ rankerStatus = getRankerStatus();
525
+ }
526
+ catch { }
527
+ const status = {
528
+ version: "3.2.0",
529
+ llm: {
530
+ model: process.env.LLM_MODEL || "deepseek-chat",
531
+ baseUrl: process.env.LLM_BASE_URL || "https://api.deepseek.com/v1",
532
+ callCount: callCount || 0,
533
+ apiKeySet: !!process.env.LLM_API_KEY,
534
+ },
535
+ immuneNetwork: {
536
+ optIn: fs.existsSync(OPT_IN_FILE),
537
+ hub: hubEndpoint,
538
+ hubReachable,
539
+ totalFailures: failures.length,
540
+ topPatterns: patterns,
541
+ },
542
+ memory: {
543
+ recentEpisodes: episodes.length,
544
+ },
545
+ ranker: rankerStatus,
546
+ };
547
+ return {
548
+ content: [{ type: "text", text: JSON.stringify(status, null, 2) }],
549
+ };
550
+ }
551
+ if (request.params.name === "progmune_check") {
552
+ const { projectPath } = request.params.arguments;
553
+ if (!projectPath || !fs.existsSync(projectPath)) {
554
+ return {
555
+ content: [{ type: "text", text: `❌ Invalid projectPath: ${projectPath}` }],
556
+ };
557
+ }
558
+ process.env.PROGMUNE_PROJECT_DIR = projectPath;
559
+ const results = [];
560
+ const pass = (msg) => results.push(`✔ ${msg}`);
561
+ const fail = (msg) => results.push(`✖ ${msg}`);
562
+ const warn = (msg) => results.push(`! ${msg}`);
563
+ // 1. IR extraction — auto-detect language
564
+ try {
565
+ const py = isPythonProject(projectPath);
566
+ const ir = py ? extractIRPython(projectPath) : extractIR(projectPath);
567
+ pass(`IR (${py ? "Python" : "TypeScript"}): ${ir.length} functions extracted`);
568
+ }
569
+ catch (e) {
570
+ fail(`IR extraction: ${e.message}`);
571
+ }
572
+ // 2. SSG protocol validation (basic)
573
+ const protoPath = path.resolve(projectPath, "protocols.json");
574
+ if (fs.existsSync(protoPath)) {
575
+ try {
576
+ const protoDef = JSON.parse(fs.readFileSync(protoPath, "utf-8"));
577
+ const { parseProtocolsFromJSON } = require("./ssg-validator");
578
+ const protocols = parseProtocolsFromJSON(protoDef);
579
+ pass(`SSG: ${protocols.length} protocol rules loaded`);
580
+ }
581
+ catch (e) {
582
+ fail(`SSG: ${e.message}`);
583
+ }
584
+ }
585
+ else {
586
+ warn("SSG: no protocols.json found");
587
+ }
588
+ // 3. Failure corpus
589
+ try {
590
+ const { getFailureGenome, getAntibodyStats } = require("./failure-corpus");
591
+ const genome = getFailureGenome();
592
+ const ab = getAntibodyStats();
593
+ results.push(` Failures: ${genome.totalFailures} total | SVL-1:${genome.bySVL["SVL-1"]} SVL-2:${genome.bySVL["SVL-2"]} SVL-3:${genome.bySVL["SVL-3"]} SVL-4:${genome.bySVL["SVL-4"]}`);
594
+ results.push(` Antibodies: ${ab.totalHits} hits | ${ab.fastPathHits} fast-path | ${ab.totalLLMCallsSaved} LLM calls saved | ${ab.totalTokensSaved} tokens saved`);
595
+ }
596
+ catch (e) {
597
+ fail(`Failure corpus: ${e.message}`);
598
+ }
599
+ // 4. Ledger invariants
600
+ try {
601
+ const { checkLedgerConsistency, rebuildState } = require("./ssg-validator");
602
+ const sessionsDir = path.join(projectPath, ".progmune_corpus", "sessions");
603
+ if (fs.existsSync(sessionsDir)) {
604
+ let checked = 0;
605
+ let consistent = 0;
606
+ const nsInit = new Map();
607
+ nsInit.set("_global", "UNAUTHENTICATED");
608
+ for (const file of fs.readdirSync(sessionsDir)) {
609
+ if (!file.endsWith(".json"))
610
+ continue;
611
+ const session = JSON.parse(fs.readFileSync(path.join(sessionsDir, file), "utf-8"));
612
+ for (const attempt of session.attempts || []) {
613
+ const ts = attempt.transitions || [];
614
+ if (ts.length === 0)
615
+ continue;
616
+ const result = checkLedgerConsistency(ts, nsInit);
617
+ checked++;
618
+ if (result.consistent)
619
+ consistent++;
620
+ }
621
+ }
622
+ if (checked > 0) {
623
+ const status = consistent === checked ? "all clean" : `${checked - consistent} violations`;
624
+ results.push(` Ledger: ${checked} ledgers checked, ${status}`);
625
+ }
626
+ else {
627
+ results.push(` Ledger: no ledgers to check`);
628
+ }
629
+ }
630
+ else {
631
+ results.push(` Ledger: no session data`);
632
+ }
633
+ }
634
+ catch (e) {
635
+ fail(`Ledger invariants: ${e.message}`);
636
+ }
637
+ return {
638
+ content: [{ type: "text", text: results.join("\n") }],
639
+ };
640
+ }
641
+ if (request.params.name === "progmune_execute") {
642
+ const { intent, filePath, projectPath } = request.params.arguments;
643
+ if (!intent || !filePath) {
644
+ return { content: [{ type: "text", text: "❌ intent and filePath are required." }] };
645
+ }
646
+ const { execute } = require("./execute");
647
+ const result = await execute(intent, projectPath || process.cwd(), filePath);
648
+ if (result.success) {
649
+ const marker = `// @progmune-generated session=${result.sessionId}`;
650
+ let extra = "";
651
+ if (result.degraded) {
652
+ extra = `\n⚠️ LLM generation exhausted — used rule-based fallback. Code quality may be low. Review the output carefully.`;
653
+ }
654
+ if (result.repairApplied) {
655
+ extra += `\n🔧 SSG Repair: ${result.repairCount} fix(es) applied, branches: ${result.repairBranchIds.map((id) => id.slice(0, 12)).join(", ")}`;
656
+ }
657
+ return { content: [{ type: "text", text: `✅ Generated and written to ${result.filePath}${extra}
391
658
 
392
- if (request.params.name === "progmune_check") {
393
- const { projectPath } = request.params.arguments;
659
+ ${marker}
660
+ Session: ${result.sessionId}
661
+ IR: ${result.irFunctionCount} functions, ${result.protocolRuleCount} protocol rules
662
+ Hash: ${result.hash}
394
663
 
395
- if (!projectPath || !fs.existsSync(projectPath)) {
396
- return {
397
- content: [{ type: "text", text: `❌ Invalid projectPath: ${projectPath}` }],
398
- };
399
- }
664
+ Code:
665
+ ${result.code}` }] };
666
+ }
667
+ return { content: [{ type: "text", text: `❌ Execution failed: ${result.error}` }] };
668
+ }
669
+ if (request.params.name === "progmune_audit") {
670
+ const { directory } = (request.params.arguments || {});
671
+ const targetDir = directory || process.cwd();
672
+ const { auditDirectory, formatAuditResult } = require("./audit");
673
+ const result = auditDirectory(targetDir);
674
+ const text = formatAuditResult(result);
675
+ if (result.warning) {
676
+ return { content: [{ type: "text", text: `⚠️ ${result.warning}\n\n${text}` }] };
677
+ }
678
+ return { content: [{ type: "text", text }] };
679
+ }
680
+ if (request.params.name === "progmune_init") {
681
+ const { projectPath } = request.params.arguments;
682
+ const results = [];
683
+ // 1. Install pre-commit hook
684
+ const hookPath = path.resolve(projectPath, ".git/hooks/pre-commit");
685
+ const guardScript = path.resolve(__dirname, "..", "bin/progmune-guard.sh");
686
+ if (fs.existsSync(guardScript)) {
687
+ const hookContent = `#!/bin/bash\n# Progmune Guard — installed by progmune_init\n"${guardScript}"\n`;
688
+ const existingHook = fs.existsSync(hookPath) ? fs.readFileSync(hookPath, "utf-8") : "";
689
+ if (!existingHook.includes("progmune-guard.sh")) {
690
+ fs.writeFileSync(hookPath, (existingHook ? existingHook + "\n" : "") + hookContent, "utf-8");
691
+ try {
692
+ fs.chmodSync(hookPath, "755");
693
+ }
694
+ catch { /* chmod best-effort */ }
695
+ results.push("✅ Pre-commit hook installed (.git/hooks/pre-commit)");
696
+ }
697
+ else {
698
+ results.push("✅ Pre-commit hook already installed");
699
+ }
700
+ }
701
+ else {
702
+ results.push("⚠️ Guard script not found — skipping hook installation");
703
+ }
704
+ // 2. Create allowlist if missing
705
+ const allowlistPath = path.resolve(projectPath, ".progmune_allowlist");
706
+ if (!fs.existsSync(allowlistPath)) {
707
+ const defaultAllowlist = `# Progmune Allowlist\n\\.json$\n\\.md$\n\\.sh$\ntest-.*\ndist/\nnode_modules/\n`;
708
+ fs.writeFileSync(allowlistPath, defaultAllowlist, "utf-8");
709
+ results.push("✅ .progmune_allowlist created");
710
+ }
711
+ // 3. Ensure CLAUDE.md has Progmune instructions
712
+ const claudeMdPath = path.resolve(projectPath, "CLAUDE.md");
713
+ const PROGMUNE_INSTRUCTIONS = `
400
714
 
401
- process.env.PROGMUNE_PROJECT_DIR = projectPath;
715
+ ## Progmune — Program Immune System
402
716
 
403
- const lines = [];
404
- const G = (s) => `✔ ${s}`;
405
- const B = (s) => `✖ ${s}`;
717
+ This project uses [Progmune](https://github.com/shenlian19831109/progmune-runtime) for constraint-guided code generation. When writing new TypeScript code, prefer the \`progmune_generate\` MCP tool over direct file writes.
406
718
 
407
- // 1. IR extraction
408
- try {
409
- const ir = extractIR(projectPath);
410
- lines.push(G(`IR: ${ir.length} functions extracted`));
411
- } catch (e) {
412
- lines.push(B(`IR: ${e.message}`));
413
- }
719
+ ### Available MCP Tools
720
+ - \`progmune_generate\` — generate TypeScript code constrained by the project's IR and protocol rules
721
+ - \`progmune_check\` run the full 7-step immune audit pipeline
722
+ - \`progmune_audit\` scan files for @progmune-generated markers and report coverage
723
+ - \`progmune_status\` view LLM config, immune network state, failure patterns
724
+ - \`progmune_init\` — ensure CLAUDE.md has Progmune instructions
414
725
 
415
- // 2. SSG protocols
416
- const protoPath = path.resolve(projectPath, "protocols.json");
417
- if (fs.existsSync(protoPath)) {
418
- try {
419
- const protoDef = JSON.parse(fs.readFileSync(protoPath, "utf-8"));
420
- const { parseProtocolsFromJSON } = require("./ssg-validator.js");
421
- const protocols = parseProtocolsFromJSON(protoDef);
422
- lines.push(G(`SSG: ${protocols.length} protocol rules loaded`));
423
- } catch (e) {
424
- lines.push(B(`SSG: ${e.message}`));
726
+ ### How to verify Progmune is working
727
+ - Generated files have \`// @progmune-generated session=...\` header
728
+ - Run \`npm run check\` to audit immune status
729
+ - Run \`progmune_audit\` to see coverage rate
730
+ `;
731
+ let updated = false;
732
+ if (fs.existsSync(claudeMdPath)) {
733
+ const existing = fs.readFileSync(claudeMdPath, "utf-8");
734
+ if (!existing.includes("progmune_generate")) {
735
+ fs.writeFileSync(claudeMdPath, existing + PROGMUNE_INSTRUCTIONS, "utf-8");
736
+ updated = true;
737
+ }
738
+ }
739
+ else {
740
+ fs.writeFileSync(claudeMdPath, `# ${path.basename(projectPath)}\n${PROGMUNE_INSTRUCTIONS}`, "utf-8");
741
+ updated = true;
742
+ }
743
+ results.push(updated
744
+ ? `✅ CLAUDE.md ${fs.existsSync(claudeMdPath) ? "updated" : "created"} with Progmune instructions.`
745
+ : `✅ CLAUDE.md already has Progmune instructions.`);
746
+ return { content: [{ type: "text", text: results.join("\n") }] };
425
747
  }
426
- } else {
427
- lines.push(`! SSG: no protocols.json`);
428
- }
429
-
430
- // 3. Failure genome + antibodies
431
- try {
432
- const { getFailureGenome, getAntibodyStats } = require("./failure-corpus.js");
433
- const genome = getFailureGenome();
434
- const ab = getAntibodyStats();
435
- lines.push(
436
- ` Failures: ${genome.totalFailures} | SVL-1:${genome.bySVL["SVL-1"]} SVL-2:${genome.bySVL["SVL-2"]} SVL-3:${genome.bySVL["SVL-3"]} SVL-4:${genome.bySVL["SVL-4"]}`
437
- );
438
- lines.push(
439
- ` Antibodies: ${ab.totalHits} hits | ${ab.fastPathHits} fast-path | ${ab.totalLLMCallsSaved} LLM saved | ${ab.totalTokensSaved} tokens saved`
440
- );
441
- } catch (e) {
442
- lines.push(B(`Failure corpus: ${e.message}`));
443
- }
444
-
445
- // 4. Ledger invariants
446
- try {
447
- const { checkLedgerConsistency } = require("./ssg-validator.js");
448
- const sessionsDir = path.join(projectPath, ".progmune_corpus", "sessions");
449
- if (fs.existsSync(sessionsDir)) {
450
- let checked = 0,
451
- consistent = 0;
452
- const nsInit = new Map();
453
- nsInit.set("_global", "UNAUTHENTICATED");
454
- for (const file of fs.readdirSync(sessionsDir)) {
455
- if (!file.endsWith(".json")) continue;
748
+ if (request.params.name === "progmune_repair") {
749
+ const { sessionId, projectPath } = (request.params.arguments || {});
750
+ if (!sessionId) {
751
+ return { content: [{ type: "text", text: "❌ sessionId is required." }] };
752
+ }
753
+ const targetDir = projectPath || process.cwd();
754
+ process.env.PROGMUNE_PROJECT_DIR = targetDir;
456
755
  try {
457
- const session = JSON.parse(
458
- fs.readFileSync(path.join(sessionsDir, file), "utf-8")
459
- );
460
- for (const attempt of session.attempts || []) {
461
- const ts = attempt.transitions || [];
462
- if (ts.length === 0) continue;
463
- const result = checkLedgerConsistency(ts, nsInit);
464
- checked++;
465
- if (result.consistent) consistent++;
466
- }
467
- } catch {}
468
- }
469
- if (checked > 0) {
470
- const ok = consistent === checked ? "all clean" : `${checked - consistent} violations`;
471
- lines.push(` Ledger: ${checked} ledgers checked, ${ok}`);
472
- } else {
473
- lines.push(` Ledger: no ledgers found`);
474
- }
475
- } else {
476
- lines.push(` Ledger: no session data`);
756
+ const { getAllSessions } = require("./failure-corpus");
757
+ const { checkLedgerConsistency } = require("./ssg-validator");
758
+ const { generateRepairSummary } = require("./repair-proposal");
759
+ const { createRootBranch, createBranch, wrapAsBranch, buildBranchMap, findRootBranch } = require("./branch-ledger");
760
+ const { replayLedger } = require("./deterministic-replay");
761
+ const sessions = getAllSessions();
762
+ const session = sessions.find((s) => s.sessionId === sessionId || s.sessionId.startsWith(sessionId));
763
+ if (!session) {
764
+ return { content: [{ type: "text", text: `❌ Session not found: ${sessionId}` }] };
765
+ }
766
+ // Collect transitions
767
+ let allTransitions = [];
768
+ for (const a of (session.attempts || [])) {
769
+ allTransitions = allTransitions.concat(a.transitions || []);
770
+ }
771
+ const nsInit = require("./protocol-registry").getNsInit();
772
+ const consistency = checkLedgerConsistency(allTransitions, nsInit);
773
+ if (consistency.consistent) {
774
+ return { content: [{ type: "text", text: `✅ Session ${sessionId} ledger is consistent. No repairs needed.` }] };
775
+ }
776
+ const irRaw = JSON.parse(require("fs").readFileSync("ir.json", "utf-8"));
777
+ const ir = Array.isArray(irRaw) ? irRaw : (irRaw.functions || []);
778
+ const protocols = [];
779
+ const summary = generateRepairSummary(allTransitions, ir, protocols, nsInit);
780
+ let output = `🔧 Repair Analysis: ${sessionId}\n\n`;
781
+ output += `Violations: ${consistency.violations.length}\n`;
782
+ output += `Proposals: ${summary.proposals.length} (${summary.minimalFixSet.length} minimal fixes)\n\n`;
783
+ for (let i = 0; i < summary.minimalFixSet.length; i++) {
784
+ const p = summary.minimalFixSet[i];
785
+ output += `## Proposal ${i + 1}: ${p.strategy}\n`;
786
+ output += ` ID: ${p.id}\n`;
787
+ output += ` Index: ${p.violationIndex}\n`;
788
+ output += ` Confidence: ${(p.confidence * 100).toFixed(0)}%\n`;
789
+ output += ` Reason: ${p.reason}\n`;
790
+ output += ` Explanation: ${p.explanation}\n`;
791
+ if (p.insertBefore !== undefined) {
792
+ output += ` Insert before: action[${p.insertBefore}]\n`;
793
+ }
794
+ output += `\n`;
795
+ }
796
+ output += `\n── To accept a proposal ──\n`;
797
+ output += `progmune_accept(sessionId="${sessionId}", proposalId="<id>")\n`;
798
+ return { content: [{ type: "text", text: output }] };
799
+ }
800
+ catch (e) {
801
+ return { content: [{ type: "text", text: `❌ Repair analysis failed: ${e.message}` }] };
802
+ }
477
803
  }
478
- } catch (e) {
479
- lines.push(B(`Ledger: ${e.message}`));
480
- }
481
-
482
- return { content: [{ type: "text", text: lines.join("\n") }] };
483
- }
484
-
485
- throw new Error(`Unknown tool: ${request.params.name}`);
486
- });
804
+ if (request.params.name === "progmune_accept") {
805
+ const { sessionId, proposalId, projectPath } = (request.params.arguments || {});
806
+ if (!sessionId || !proposalId) {
807
+ return { content: [{ type: "text", text: "❌ sessionId and proposalId are required." }] };
808
+ }
809
+ const targetDir = projectPath || process.cwd();
810
+ process.env.PROGMUNE_PROJECT_DIR = targetDir;
811
+ try {
812
+ const { getAllSessions } = require("./failure-corpus");
813
+ const { checkLedgerConsistency } = require("./ssg-validator");
814
+ const { generateRepairSummary } = require("./repair-proposal");
815
+ const sessions = getAllSessions();
816
+ const session = sessions.find((s) => s.sessionId === sessionId || s.sessionId.startsWith(sessionId));
817
+ if (!session) {
818
+ return { content: [{ type: "text", text: `❌ Session not found: ${sessionId}` }] };
819
+ }
820
+ let allTransitions = [];
821
+ for (const a of (session.attempts || [])) {
822
+ allTransitions = allTransitions.concat(a.transitions || []);
823
+ }
824
+ const nsInit = require("./protocol-registry").getNsInit();
825
+ const irRaw = JSON.parse(require("fs").readFileSync("ir.json", "utf-8"));
826
+ const ir = Array.isArray(irRaw) ? irRaw : (irRaw.functions || []);
827
+ const summary = generateRepairSummary(allTransitions, ir, [], nsInit);
828
+ const proposal = summary.proposals.find((p) => p.id === proposalId);
829
+ if (!proposal) {
830
+ return { content: [{ type: "text", text: `❌ Proposal not found: ${proposalId}` }] };
831
+ }
832
+ // Apply the proposal as a branch (human-accepted)
833
+ const { wrapAsBranch, createBranch } = require("./branch-ledger");
834
+ const parentBranch = wrapAsBranch(allTransitions);
835
+ const { applyProposalAsBranch } = require("./repair-proposal");
836
+ const repairBranch = applyProposalAsBranch(proposal, parentBranch, allTransitions, ir);
837
+ // Re-check consistency on the repaired branch
838
+ const repaired = checkLedgerConsistency(repairBranch.transitions, nsInit);
839
+ let output = `✅ Repair Accepted\n\n`;
840
+ output += `Session: ${sessionId}\n`;
841
+ output += `Proposal: ${proposal.id}\n`;
842
+ output += `Strategy: ${proposal.strategy}\n`;
843
+ output += `Repair Branch: ${repairBranch.id}\n`;
844
+ output += `Branch transitions: ${repairBranch.transitions.length}\n`;
845
+ output += `Re-check: ${repaired.consistent ? '✅ Consistent' : '⚠️ ' + repaired.violations.length + ' remaining violations'}\n\n`;
846
+ if (repaired.consistent) {
847
+ // Update fingerprint with repaired ledger
848
+ const { hashLedger } = require("./ssg-validator");
849
+ const { getFingerprint } = require("./ledger-registry");
850
+ const fp = getFingerprint(sessionId);
851
+ const newHash = hashLedger(repairBranch.transitions);
852
+ output += `Original hash: ${fp?.ledgerHash?.slice(0, 16) || 'unknown'}\n`;
853
+ output += `Repaired hash: ${newHash.slice(0, 16)}\n`;
854
+ output += `\n${'═'.repeat(48)}\n`;
855
+ output += `✅ Repair complete. The fix has been applied to the repair branch.\n`;
856
+ output += ` Original ledger is preserved (immutable).\n`;
857
+ output += ` Branch ${repairBranch.id} contains the fix.\n`;
858
+ }
859
+ return { content: [{ type: "text", text: output }] };
860
+ }
861
+ catch (e) {
862
+ return { content: [{ type: "text", text: `❌ Accept failed: ${e.message}` }] };
863
+ }
864
+ }
865
+ if (request.params.name === "progmune_discover") {
866
+ const { projectPath, repoName } = request.params.arguments;
867
+ process.env.PROGMUNE_PROJECT_DIR = projectPath;
868
+ const { buildKnownFingerprintLibrary, extractUnknownRepoSequences, discoverProtocolsFromSequences } = require("./unknown-protocol-discovery");
869
+ const { CROSS_REPO_SEQUENCES } = require("./experimental/unsupervised-physics");
870
+ const known = buildKnownFingerprintLibrary();
871
+ const name = repoName || path.basename(projectPath);
872
+ // Try to extract sequences from the trajectory corpus
873
+ const { loadTrajectories } = require("./failure-corpus");
874
+ const trajectories = loadTrajectories().filter((t) => t.trajectory.length >= 2);
875
+ const seqs = trajectories.length > 0
876
+ ? trajectories.map(t => t.trajectory)
877
+ : (CROSS_REPO_SEQUENCES[name] || []);
878
+ const discovered = discoverProtocolsFromSequences(seqs, name, known);
879
+ const result = {
880
+ repo: name,
881
+ discoveredCount: discovered.length,
882
+ protocols: discovered.map((p) => ({
883
+ name: p.name,
884
+ prototype: p.prototype,
885
+ states: p.fingerprint.stateCount,
886
+ transitions: p.fingerprint.transitions.length,
887
+ entryPoints: p.fingerprint.entryStates.length,
888
+ exitPoints: p.fingerprint.exitStates.length,
889
+ isDAG: p.fingerprint.isDAG,
890
+ closestKnown: p.closestKnown || "novel",
891
+ confidence: p.matchConfidence,
892
+ rules: p.rules.map((r) => ({
893
+ function: r.function,
894
+ pre_states: r.pre_states,
895
+ post_states: r.post_states,
896
+ invalidate: r.invalidate,
897
+ })),
898
+ })),
899
+ };
900
+ return {
901
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
902
+ };
903
+ }
904
+ if (request.params.name === "progmune_zeroshot") {
905
+ const { brokenSequence, expectedPattern, projectPath } = request.params.arguments;
906
+ if (projectPath)
907
+ process.env.PROGMUNE_PROJECT_DIR = projectPath;
908
+ const { buildKnownFingerprintLibrary, discoverProtocolsFromSequences, evaluateZeroShotRepair } = require("./unknown-protocol-discovery");
909
+ const { loadTrajectories } = require("./failure-corpus");
910
+ const known = buildKnownFingerprintLibrary();
911
+ const trajectories = loadTrajectories().filter((t) => t.trajectory.length >= 2);
912
+ const seqs = trajectories.length > 0
913
+ ? trajectories.map(t => t.trajectory)
914
+ : [brokenSequence];
915
+ const discovered = discoverProtocolsFromSequences(seqs, "zeroshot", known);
916
+ // Build defect cases from the broken sequence
917
+ const expected = expectedPattern === "transaction"
918
+ ? [...brokenSequence, "commit_tx"]
919
+ : expectedPattern === "acquire_use_release"
920
+ ? [...brokenSequence, "close_file"]
921
+ : brokenSequence;
922
+ const defectCases = [{
923
+ broken: brokenSequence,
924
+ expected,
925
+ description: `zeroshot: ${brokenSequence.join(" → ")} (pattern: ${expectedPattern || "auto"})`,
926
+ }];
927
+ const repairResult = evaluateZeroShotRepair(discovered, defectCases);
928
+ const result = {
929
+ brokenSequence,
930
+ expectedPattern: expectedPattern || "auto-detected",
931
+ discoveredProtocols: discovered.length,
932
+ repairSuccess: repairResult.success,
933
+ repairTotal: repairResult.total,
934
+ repairRate: repairResult.repairRate,
935
+ details: repairResult.details,
936
+ topMatches: discovered.slice(0, 3).map((p) => ({
937
+ name: p.name,
938
+ prototype: p.prototype,
939
+ closestKnown: p.closestKnown || "none",
940
+ confidence: p.matchConfidence,
941
+ })),
942
+ };
943
+ return {
944
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
945
+ };
946
+ }
947
+ if (request.params.name === "progmune_scaffold") {
948
+ const { intent, scaffoldType, filePath, projectPath } = request.params.arguments;
949
+ if (!intent || !scaffoldType || !projectPath) {
950
+ return { content: [{ type: "text", text: "❌ intent, scaffoldType, and projectPath are required." }] };
951
+ }
952
+ if (!["express-api", "cli-tool", "static-site"].includes(scaffoldType)) {
953
+ return { content: [{ type: "text", text: `❌ Unknown scaffold type: ${scaffoldType}. Available: express-api, cli-tool, static-site` }] };
954
+ }
955
+ const { scaffold } = require("./scaffold");
956
+ const result = await scaffold(scaffoldType, intent, projectPath, filePath);
957
+ if (result.success) {
958
+ const marker = result.filePath?.endsWith(".html")
959
+ ? `<!-- @progmune-scaffolded type=${result.scaffoldType} -->`
960
+ : `// @progmune-scaffolded type=${result.scaffoldType}`;
961
+ let extra = "";
962
+ if (result.filePath) {
963
+ extra = `\n📁 Written to: ${result.filePath}`;
964
+ }
965
+ return { content: [{ type: "text", text: `✅ Scaffold generated (${result.scaffoldType})${extra}
487
966
 
488
- async function checkHubReachable(hubUrl) {
489
- if (!hubUrl || hubUrl === "not configured") return false;
490
- try {
491
- const http = require("http");
492
- const https = require("https");
493
- const transport = hubUrl.startsWith("https") ? https : http;
494
- return new Promise((resolve) => {
495
- const req = transport.get(hubUrl, (res) => {
496
- resolve(res.statusCode === 200);
497
- });
498
- req.on("error", () => resolve(false));
499
- req.setTimeout(3000, () => {
500
- req.destroy();
501
- resolve(false);
502
- });
503
- });
504
- } catch {
505
- return false;
506
- }
507
- }
967
+ ${marker}
968
+ Code length: ${result.code.length} chars
508
969
 
509
- const transport = new StdioServerTransport();
510
- await server.connect(transport);
511
- log.success("Progmune MCP server ready (TypeScript)");
970
+ Preview (first 80 lines):
971
+ ${result.code.split("\n").slice(0, 80).join("\n")}` }] };
972
+ }
973
+ return { content: [{ type: "text", text: `❌ Scaffold failed: ${result.error}` }] };
974
+ }
975
+ if (request.params.name === "progmune_governance_report") {
976
+ const { projectPath, sessionId, format, fast } = request.params.arguments;
977
+ const targetDir = projectPath || process.cwd();
978
+ process.env.PROGMUNE_PROJECT_DIR = targetDir;
979
+ const { buildGovernanceReport, formatAsJSON, formatAsTerminal, formatAsMarkdown } = require("./audit");
980
+ const report = buildGovernanceReport(targetDir, { fast, sessionId });
981
+ let output;
982
+ switch (format) {
983
+ case "json":
984
+ output = formatAsJSON(report);
985
+ break;
986
+ case "markdown":
987
+ output = formatAsMarkdown(report);
988
+ break;
989
+ default: output = formatAsTerminal(report);
990
+ }
991
+ return { content: [{ type: "text", text: output }] };
992
+ }
993
+ if (request.params.name === "progmune_provenance") {
994
+ const { sessionId, format } = request.params.arguments;
995
+ if (!sessionId) {
996
+ return { content: [{ type: "text", text: "❌ sessionId is required." }] };
997
+ }
998
+ try {
999
+ const { buildProvenanceChain } = require("./ledger");
1000
+ const chain = buildProvenanceChain(sessionId);
1001
+ if (format === "json") {
1002
+ return { content: [{ type: "text", text: JSON.stringify(chain, null, 2) }] };
1003
+ }
1004
+ // Terminal format
1005
+ let output = `🔗 Provenance Chain: ${chain.sessionId}\n\n`;
1006
+ output += `Intent: ${chain.intent}\n`;
1007
+ output += `Integrity: ${chain.integrity.toUpperCase()}\n`;
1008
+ output += `Transitions: ${chain.totalTransitions} (${chain.validTransitions} valid, ${chain.invalidTransitions} invalid)\n`;
1009
+ output += `Repairs: ${chain.repairCount}\n`;
1010
+ output += `Ledger Hash: ${chain.finalLedgerHash}\n`;
1011
+ output += `Stored Hash: ${chain.storedFingerprintHash || "(none)"}\n\n`;
1012
+ for (let i = 0; i < chain.events.length; i++) {
1013
+ const e = chain.events[i];
1014
+ const icon = e.result === "passed" || e.result === "approved" ? "✅" : e.result === "failed" ? "❌" : "🔧";
1015
+ output += `[${String(i).padStart(2, "0")}] ${icon} ${e.step.padEnd(10)} | ${e.artifact.padEnd(25).slice(0, 25)} | ${e.hash}\n`;
1016
+ if (e.detail)
1017
+ output += ` ${e.detail.slice(0, 100)}\n`;
1018
+ }
1019
+ return { content: [{ type: "text", text: output }] };
1020
+ }
1021
+ catch (e) {
1022
+ return { content: [{ type: "text", text: `❌ Provenance failed: ${e.message}` }] };
1023
+ }
1024
+ }
1025
+ if (request.params.name === "progmune_plsb") {
1026
+ const { format, category } = request.params.arguments;
1027
+ try {
1028
+ const { generatePLSBArtifact, generatePLSBReportMarkdown } = require("./plsb");
1029
+ const ar = generatePLSBArtifact();
1030
+ if (format === "json") {
1031
+ return { content: [{ type: "text", text: JSON.stringify(ar, null, 2) }] };
1032
+ }
1033
+ if (format === "markdown") {
1034
+ const md = generatePLSBReportMarkdown();
1035
+ return { content: [{ type: "text", text: md }] };
1036
+ }
1037
+ // Summary format (default)
1038
+ const bm = ar.benchmark.metadata;
1039
+ let output = "PLSB v1.0 — Protocol Lifecycle Security Benchmark\n\n";
1040
+ output += `Entries: ${bm.total} (${bm.verified} verified)\n`;
1041
+ output += `Recall: ${(bm.recall * 100).toFixed(0)}%\n`;
1042
+ output += `Precision: ${(bm.precision * 100).toFixed(0)}%\n`;
1043
+ output += `Coverage: ${Object.keys(bm.byPLS).length}/${ar.benchmark.taxonomy.length} categories\n\n`;
1044
+ const filter = category ? ar.benchmark.taxonomy.filter((t) => t.id === category) : ar.benchmark.taxonomy;
1045
+ for (const t of filter) {
1046
+ const count = bm.byPLS[t.id] || 0;
1047
+ output += ` ${count > 0 ? "✅" : "⚠️"} ${t.id} ${t.name}: ${count} entries (${t.category})\n`;
1048
+ }
1049
+ return { content: [{ type: "text", text: output }] };
1050
+ }
1051
+ catch (e) {
1052
+ return { content: [{ type: "text", text: `❌ PLSB query failed: ${e.message}` }] };
1053
+ }
1054
+ }
1055
+ if (request.params.name === "progmune_policy_check") {
1056
+ const { filePath, author, reviewer, format } = request.params.arguments;
1057
+ try {
1058
+ const { certify } = require("./certify");
1059
+ const { buildAccountabilityChain } = require("./ledger");
1060
+ const { evaluatePolicy } = require("./policy");
1061
+ const cert = certify(filePath);
1062
+ let acct;
1063
+ try {
1064
+ const opts = {};
1065
+ if (author)
1066
+ opts.author = { id: author, name: author.split("@")[0], role: "developer" };
1067
+ if (reviewer)
1068
+ opts.reviewers = [{ id: reviewer, name: reviewer.split("@")[0], role: "reviewer" }];
1069
+ acct = buildAccountabilityChain(cert.sessionId, opts);
1070
+ }
1071
+ catch { /* no accountability */ }
1072
+ const ctx = {
1073
+ certificate: {
1074
+ validated: cert.validated,
1075
+ confidence: cert.confidence,
1076
+ provenanceIntact: cert.provenanceIntact,
1077
+ fingerprint: cert.fingerprint,
1078
+ violations: cert.violations,
1079
+ plsbCoverage: cert.plsbCoverage,
1080
+ plsbRecall: cert.plsbRecall,
1081
+ degraded: cert.degraded,
1082
+ sessionId: cert.sessionId,
1083
+ file: cert.file,
1084
+ },
1085
+ accountability: acct ? {
1086
+ humanEvents: acct.humanEvents,
1087
+ aiEvents: acct.aiEvents,
1088
+ automatedEvents: acct.automatedEvents,
1089
+ custodyGap: acct.custodyGap,
1090
+ } : undefined,
1091
+ };
1092
+ const result = evaluatePolicy(ctx);
1093
+ if (format === "json") {
1094
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1095
+ }
1096
+ const verdictEmoji = result.verdict === "ALLOW" ? "✅" : result.verdict === "WARN" ? "⚠️" : "❌";
1097
+ let output = `Policy Engine: ${verdictEmoji} ${result.verdict}\n\n`;
1098
+ output += `File: ${filePath}\n`;
1099
+ output += `${result.passed_rules}/${result.rules} rules passed\n\n`;
1100
+ if (result.violations.length > 0) {
1101
+ for (const v of result.violations) {
1102
+ output += ` [${v.rule.severity.toUpperCase()}] ${v.rule.type}: ${v.actual} → ${v.expected}\n`;
1103
+ if (v.detail)
1104
+ output += ` ${v.detail.slice(0, 100)}\n`;
1105
+ }
1106
+ output += "\n";
1107
+ }
1108
+ output += result.summary;
1109
+ return { content: [{ type: "text", text: output }] };
1110
+ }
1111
+ catch (e) {
1112
+ return { content: [{ type: "text", text: `❌ Policy check failed: ${e.message}` }] };
1113
+ }
1114
+ }
1115
+ if (request.params.name === "progmune_accountability") {
1116
+ const { sessionId, author, reviewer, approver, deployer, format } = request.params.arguments;
1117
+ if (!sessionId) {
1118
+ return { content: [{ type: "text", text: "❌ sessionId is required." }] };
1119
+ }
1120
+ try {
1121
+ const { buildAccountabilityChain, verifyAccountabilityChain } = require("./ledger");
1122
+ const opts = {};
1123
+ if (author)
1124
+ opts.author = { id: author, name: author.split("@")[0], role: "developer" };
1125
+ if (reviewer)
1126
+ opts.reviewers = [{ id: reviewer, name: reviewer.split("@")[0], role: "reviewer" }];
1127
+ if (approver)
1128
+ opts.approver = { id: approver, name: approver.split("@")[0], role: "security_engineer" };
1129
+ if (deployer)
1130
+ opts.deployer = { id: deployer, name: deployer };
1131
+ const chain = buildAccountabilityChain(sessionId, opts);
1132
+ if (format === "json") {
1133
+ return { content: [{ type: "text", text: JSON.stringify(chain, null, 2) }] };
1134
+ }
1135
+ // Terminal format
1136
+ const actors = `${chain.humanEvents} human · ${chain.aiEvents} AI · ${chain.automatedEvents} automated`;
1137
+ const custody = chain.custodyGap ? "⚠️ GAPS" : "✅ VERIFIED";
1138
+ let output = `🔗 Accountability Ledger: ${chain.sessionId}\n\n`;
1139
+ output += `Intent: ${chain.intent}\n`;
1140
+ output += `Integrity: ${chain.integrity.toUpperCase()}\n`;
1141
+ output += `Custody: ${custody}\n`;
1142
+ output += `Actors: ${actors}\n`;
1143
+ output += `Chain: ${chain.chainHash}\n\n`;
1144
+ for (let i = 0; i < chain.events.length; i++) {
1145
+ const e = chain.events[i];
1146
+ const typeIcon = e.actorType === "human" ? "👤" : e.actorType === "llm" ? "🤖"
1147
+ : e.actorType === "reviewer" ? "✅" : e.actorType === "deployer" ? "🚀" : "⚙";
1148
+ const resultIcon = e.result === "passed" || e.result === "approved" ? "✅" : e.result === "failed" ? "❌" : "🔧";
1149
+ output += `[${String(i).padStart(2, "0")}] ${typeIcon} ${e.actorLabel.padEnd(30).slice(0, 30)} ${resultIcon} ${e.action}\n`;
1150
+ output += ` hash: ${e.hash} ← ${e.prevHash ? e.prevHash.slice(0, 12) : "genesis"}\n`;
1151
+ if (i < chain.events.length - 1)
1152
+ output += ` │\n`;
1153
+ }
1154
+ if (chain.custodyGap) {
1155
+ output += `\n⚠️ Custody gaps detected. Use --author, --reviewer, --approver to identify human actors.\n`;
1156
+ }
1157
+ return { content: [{ type: "text", text: output }] };
1158
+ }
1159
+ catch (e) {
1160
+ return { content: [{ type: "text", text: `❌ Accountability chain failed: ${e.message}` }] };
1161
+ }
1162
+ }
1163
+ if (request.params.name === "progmune_certify") {
1164
+ const { filePath, format } = request.params.arguments;
1165
+ if (!filePath) {
1166
+ return { content: [{ type: "text", text: "❌ filePath is required." }] };
1167
+ }
1168
+ try {
1169
+ const { certify, formatCertificate } = require("./certify");
1170
+ const cert = certify(filePath);
1171
+ if (format === "json") {
1172
+ return { content: [{ type: "text", text: JSON.stringify(cert, null, 2) }] };
1173
+ }
1174
+ return { content: [{ type: "text", text: formatCertificate(cert) }] };
1175
+ }
1176
+ catch (e) {
1177
+ return { content: [{ type: "text", text: `❌ Certification failed: ${e.message}` }] };
1178
+ }
1179
+ }
1180
+ if (request.params.name === "progmune_trust_check") {
1181
+ const { projectPath, commit, branch, policy, format } = request.params.arguments;
1182
+ const targetDir = projectPath || process.cwd();
1183
+ process.env.PROGMUNE_PROJECT_DIR = targetDir;
1184
+ try {
1185
+ const { evaluateTrust } = require("./trust");
1186
+ const decision = evaluateTrust({
1187
+ projectPath: targetDir,
1188
+ projectName: path.basename(targetDir),
1189
+ commit: commit || "unknown",
1190
+ branch,
1191
+ policyName: policy,
1192
+ });
1193
+ if (format === "json") {
1194
+ const { formatTrustJSON } = require("./trust/formatters/json");
1195
+ return { content: [{ type: "text", text: formatTrustJSON(decision) }] };
1196
+ }
1197
+ const { formatTrustTerminal } = require("./trust/formatters/terminal");
1198
+ return { content: [{ type: "text", text: formatTrustTerminal(decision) }] };
1199
+ }
1200
+ catch (e) {
1201
+ return { content: [{ type: "text", text: `❌ Trust check failed: ${e.message}` }] };
1202
+ }
1203
+ }
1204
+ if (request.params.name === "progmune_score") {
1205
+ const { calls, purpose, functionName } = request.params.arguments;
1206
+ if (!calls || !Array.isArray(calls) || calls.length === 0) {
1207
+ return { content: [{ type: "text", text: "❌ calls is required and must be a non-empty array of function names." }] };
1208
+ }
1209
+ try {
1210
+ const { scoreCompliance, scoreFunction, formatComplianceReport } = require("./trust/compliance-scorer");
1211
+ const result = functionName
1212
+ ? scoreFunction(functionName, calls, purpose)
1213
+ : scoreCompliance(calls, purpose);
1214
+ const report = formatComplianceReport(result);
1215
+ const json = JSON.stringify(result, null, 2);
1216
+ return {
1217
+ content: [
1218
+ { type: "text", text: report },
1219
+ { type: "text", text: `\n── JSON ──\n${json}` },
1220
+ ],
1221
+ };
1222
+ }
1223
+ catch (e) {
1224
+ return { content: [{ type: "text", text: `❌ Compliance scoring failed: ${e.message}` }] };
1225
+ }
1226
+ }
1227
+ throw new Error(`Unknown tool: ${request.params.name}`);
1228
+ });
1229
+ const transport = new StdioServerTransport();
1230
+ await server.connect(transport);
1231
+ log.info("✅ Progmune MCP server ready (TypeScript)");
512
1232
  }
513
-
514
1233
  main().catch((e) => {
515
- console.error("[Progmune] Fatal:", e);
516
- process.exit(1);
1234
+ console.error("[Progmune] Fatal:", e);
1235
+ process.exit(1);
517
1236
  });