progmune-runtime 2.0.2 → 2.1.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 (107) hide show
  1. package/.mcp.json +11 -0
  2. package/.progmune_allowlist +50 -0
  3. package/.test_report/test_report.md +87 -0
  4. package/Dockerfile +2 -10
  5. package/FAQ.md +167 -0
  6. package/demo-project/auth.ts +55 -0
  7. package/demo-project/tsconfig.json +8 -0
  8. package/dist/ab-stats.js +11 -0
  9. package/dist/acl-breakdown.js +13 -0
  10. package/dist/all-sessions.js +11 -0
  11. package/dist/antibody-stats.js +11 -0
  12. package/dist/audit.js +218 -0
  13. package/dist/benchmark-count.js +7 -0
  14. package/dist/benchmark-full.js +17 -0
  15. package/dist/benchmark-pass-rate.js +54 -0
  16. package/dist/benchmark-report.js +67 -0
  17. package/dist/benchmark-save.js +62 -0
  18. package/dist/benchmark-status.js +15 -0
  19. package/dist/branch-ledger.js +365 -0
  20. package/dist/branch-tree-count.js +14 -0
  21. package/dist/check.js +506 -0
  22. package/dist/common-fixpath.js +12 -0
  23. package/dist/constraint-types.js +12 -0
  24. package/dist/deterministic-replay.js +277 -0
  25. package/dist/emitter.js +112 -12
  26. package/dist/exec-metrics.js +11 -0
  27. package/dist/execute.js +242 -0
  28. package/dist/extract-ir.js +550 -5
  29. package/dist/failure-collector.js +143 -0
  30. package/dist/failure-corpus.js +481 -35
  31. package/dist/failure-report.js +11 -0
  32. package/dist/failures.js +11 -0
  33. package/dist/fast-path-hits.js +13 -0
  34. package/dist/feedback.js +6 -3
  35. package/dist/file-lock.js +82 -0
  36. package/dist/find-session.js +10 -0
  37. package/dist/fingerprint-list.js +15 -0
  38. package/dist/gen-history-log.js +13 -0
  39. package/dist/generate.js +2 -1
  40. package/dist/generate_500.js +4 -2
  41. package/dist/genome.js +11 -0
  42. package/dist/heatmap-data.js +11 -0
  43. package/dist/heatmap.js +11 -0
  44. package/dist/immune-reporter.js +137 -0
  45. package/dist/learned.js +11 -0
  46. package/dist/ledger-registry.js +241 -0
  47. package/dist/llm.js +43 -2
  48. package/dist/load-benchmarks.js +47 -0
  49. package/dist/main.js +2 -1
  50. package/dist/mcp-server.mjs +446 -34
  51. package/dist/memory-layer.js +51 -13
  52. package/dist/metrics.js +11 -0
  53. package/dist/obs-web.js +561 -0
  54. package/dist/p0_ssg_demo.js +255 -40
  55. package/dist/planner.js +933 -65
  56. package/dist/protocol-registry.js +105 -0
  57. package/dist/recent-session.js +12 -0
  58. package/dist/repair-proposal.js +353 -0
  59. package/dist/report.js +32 -0
  60. package/dist/runtime-invariants.js +161 -0
  61. package/dist/runtime-types.js +117 -0
  62. package/dist/search-planner.js +38 -9
  63. package/dist/semantic-snapshot.js +155 -0
  64. package/dist/semantic-trace.js +1497 -0
  65. package/dist/semantic-validator.js +3 -2
  66. package/dist/semantic_guard_test.js +2 -1
  67. package/dist/sessions.js +11 -0
  68. package/dist/ssg-validator.js +658 -20
  69. package/dist/svl-distribution.js +11 -0
  70. package/dist/terminal-status.js +11 -0
  71. package/dist/test_failure_corpus.js +3 -1
  72. package/dist/token-savings.js +11 -0
  73. package/dist/total-repairs.js +12 -0
  74. package/dist/unresolved-count.js +12 -0
  75. package/dist/valid-fingerprints.js +13 -0
  76. package/dist/validator.js +116 -60
  77. package/dist/verify-fps.js +11 -0
  78. package/dist/verify-ledgers.js +11 -0
  79. package/docs/whitepaper-style.css +77 -0
  80. package/docs/whitepaper-v2.1.md +609 -0
  81. package/docs/whitepaper-v2.2.md +1064 -0
  82. package/docs/whitepaper-v2.2.pdf +0 -0
  83. package/fly.toml +1 -1
  84. package/package.json +13 -4
  85. package/protocols.json +136 -0
  86. package/public/dashboard.html +119 -0
  87. package/readme.md +829 -0
  88. package/server/hub.js +84 -12
  89. package/test/replay-golden/sess_1780063202050_mgeld.json +9 -0
  90. package/test/replay-golden/sess_1780064032560_gocld.json +354 -0
  91. package/test/replay-golden/sess_1780064413331_s2709.json +606 -0
  92. package/test/replay-golden/sess_1780064792710_y3avo.json +614 -0
  93. package/test/replay-golden.ts +84 -0
  94. package/test_benchmark.js +165 -0
  95. package/test_comprehensive.mjs +638 -0
  96. package/test_concurrency.js +129 -0
  97. package/test_ir_robustness.js +85 -0
  98. package/test_semantic_contracts.js +269 -0
  99. package/test_ssg_stress.js +156 -0
  100. package/test_svl3.js +58 -0
  101. package/tsconfig.json +1 -1
  102. package/.env.example +0 -3
  103. package/.progmune_memory/fingerprints.json +0 -7
  104. package/.progmune_memory/opt_in.json +0 -4
  105. package/README.md +0 -118
  106. package/dist/mcp-server.js +0 -55
  107. package/immune_hub_data/2026-05-14.json +0 -50
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.loadBenchmarks = loadBenchmarks;
37
+ /** Load benchmark tasks from bench/tasks.json
38
+ * @protocol pre_states=[] post_states=["BENCHMARKS_LOADED"]
39
+ */
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ function loadBenchmarks() {
43
+ const tasksPath = path.resolve(process.cwd(), "bench/tasks.json");
44
+ if (!fs.existsSync(tasksPath))
45
+ return [];
46
+ return JSON.parse(fs.readFileSync(tasksPath, "utf-8"));
47
+ }
package/dist/main.js CHANGED
@@ -75,7 +75,8 @@ async function main() {
75
75
  // 2. 获取正常动作序列
76
76
  const intent = "实现登录接口,验证密码后返回 JWT,并记录日志";
77
77
  console.log("\n🧠 正常规划...");
78
- const normalActions = await (0, planner_1.plan)(intent);
78
+ const result = await (0, planner_1.plan)(intent);
79
+ const normalActions = result.actions;
79
80
  console.log("🎯 正常动作:");
80
81
  console.log(JSON.stringify(normalActions, null, 2));
81
82
  // 3. 正常流程
@@ -10,15 +10,120 @@ import * as path from 'path';
10
10
  const require = createRequire(import.meta.url);
11
11
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
12
 
13
- // ========== 处理 opt-in 子命令 ==========
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
+ // 处理命令行子命令
14
29
  const args = process.argv.slice(2);
15
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)');
16
73
 
17
- if (args[0] === 'opt-in') {
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') {
18
124
  const command = args[1] || 'status';
19
125
  const dir = path.dirname(OPT_IN_FILE);
20
126
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
21
-
22
127
  if (command === 'enable') {
23
128
  fs.writeFileSync(OPT_IN_FILE, JSON.stringify({ enabled: true, timestamp: new Date().toISOString() }, null, 2));
24
129
  console.log('✅ 已开启免疫网络上报。感谢您为全球免疫网络做出贡献!');
@@ -28,7 +133,6 @@ if (args[0] === 'opt-in') {
28
133
  if (fs.existsSync(OPT_IN_FILE)) fs.unlinkSync(OPT_IN_FILE);
29
134
  console.log('⛔ 已关闭免疫网络上报。Progmune 将以完全离线模式运行。');
30
135
  } else {
31
- // 显示当前状态
32
136
  if (fs.existsSync(OPT_IN_FILE)) {
33
137
  console.log('免疫网络上报状态: 已开启');
34
138
  console.log('脱敏错误指纹将自动上报。');
@@ -43,63 +147,371 @@ if (args[0] === 'opt-in') {
43
147
  process.exit(0);
44
148
  }
45
149
 
46
- // ========== MCP 服务器部分 ==========
47
150
  const { plan } = require('./planner.js');
48
- const { extractIRPython } = require('./extract-ir-python.js');
49
- const { emitPython } = require('./python-emitter.js');
151
+ const { extractIR } = require('./extract-ir.js');
152
+ const { emitCode } = require('./emitter.js');
50
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
+ };
51
163
 
52
164
  async function main() {
53
- const server = new Server({
54
- name: "progmune",
55
- version: "2.0.0"
56
- }, { capabilities: { tools: {} } });
165
+ const server = new Server(
166
+ { name: "progmune", version: "2.2.0" },
167
+ { capabilities: { tools: {} } }
168
+ );
57
169
 
58
170
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
59
- tools: [{
60
- name: "progmune_generate",
61
- description: "生成类型安全的Python代码,仅使用项目中真实存在的函数。",
62
- inputSchema: {
63
- type: "object",
64
- properties: {
65
- intent: { type: "string", description: "编程意图" },
66
- projectPath: { type: "string", description: "项目根目录绝对路径" }
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'",
182
+ },
183
+ projectPath: {
184
+ type: "string",
185
+ description: "Absolute path to the project root (must contain TypeScript source files)",
186
+ },
187
+ },
188
+ required: ["intent", "projectPath"],
67
189
  },
68
- required: ["intent", "projectPath"]
69
- }
70
- }]
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",
206
+ },
207
+ },
208
+ required: ["projectPath"],
209
+ },
210
+ },
211
+ ],
71
212
  }));
72
213
 
73
214
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
74
215
  if (request.params.name === "progmune_generate") {
75
216
  const { intent, projectPath } = request.params.arguments;
76
217
 
77
- // 前置检查 LLM_API_KEY
78
218
  if (!process.env.LLM_API_KEY) {
79
- return { content: [{ type: "text", text: "❌ 未设置 LLM_API_KEY。请在终端执行:\nexport LLM_API_KEY='你的密钥'\n然后重启客户端。" }] };
219
+ return {
220
+ content: [
221
+ {
222
+ type: "text",
223
+ text: `❌ LLM_API_KEY not set.
224
+
225
+ Progmune needs an LLM API key. Configure via:
226
+
227
+ .env file (loaded automatically):
228
+ LLM_API_KEY=your-key
229
+ LLM_BASE_URL=https://api.deepseek.com/v1
230
+ LLM_MODEL=deepseek-chat
231
+
232
+ Or export: export LLM_API_KEY='your-key'
233
+
234
+ Supports DeepSeek and OpenAI-compatible APIs.
235
+ Get a key: https://platform.deepseek.com/api_keys`,
236
+ },
237
+ ],
238
+ };
80
239
  }
81
240
 
82
- // 前置检查免疫网络配置
83
241
  if (!fs.existsSync(OPT_IN_FILE)) {
84
- return { content: [{ type: "text", text: "⚠️ 请先完成免疫网络配置。\n\n运行以下命令开启(推荐):\n npx progmune-runtime opt-in enable\n\n或运行以下命令以离线模式使用:\n npx progmune-runtime opt-in disable\n\n配置完成后重启客户端即可使用。" }] };
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
+ }
251
+
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
+ }
270
+
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;
279
+ }
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
+ };
85
336
  }
86
337
 
87
- // 执行规划
88
- const fns = extractIRPython(projectPath);
89
- fs.writeFileSync("ir.json", JSON.stringify(fns, null, 2));
90
- const actions = await plan(intent);
91
338
  if (!actions || actions.length === 0) {
92
- return { content: [{ type: "text", text: "无法生成满足约束的代码。" }] };
339
+ reportFingerprints().catch(() => {});
340
+ return {
341
+ content: [
342
+ {
343
+ type: "text",
344
+ text: "Could not generate constraint-satisfying TypeScript code.",
345
+ },
346
+ ],
347
+ };
93
348
  }
94
- const code = emitPython(actions);
349
+
350
+ // Emit TypeScript code
351
+ const code = emitCode(actions);
95
352
  recordRun(intent, actions, true);
353
+ reportFingerprints().catch(() => {});
354
+
96
355
  return { content: [{ type: "text", text: code }] };
97
356
  }
98
- throw new Error("未知工具");
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
+ }
391
+
392
+ if (request.params.name === "progmune_check") {
393
+ const { projectPath } = request.params.arguments;
394
+
395
+ if (!projectPath || !fs.existsSync(projectPath)) {
396
+ return {
397
+ content: [{ type: "text", text: `❌ Invalid projectPath: ${projectPath}` }],
398
+ };
399
+ }
400
+
401
+ process.env.PROGMUNE_PROJECT_DIR = projectPath;
402
+
403
+ const lines = [];
404
+ const G = (s) => `✔ ${s}`;
405
+ const B = (s) => `✖ ${s}`;
406
+
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
+ }
414
+
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}`));
425
+ }
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;
456
+ 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`);
477
+ }
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}`);
99
486
  });
100
487
 
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
+ }
508
+
101
509
  const transport = new StdioServerTransport();
102
510
  await server.connect(transport);
511
+ log.success("Progmune MCP server ready (TypeScript)");
103
512
  }
104
513
 
105
- main().catch(console.error);
514
+ main().catch((e) => {
515
+ console.error("[Progmune] Fatal:", e);
516
+ process.exit(1);
517
+ });