progmune-runtime 2.0.4 → 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 (106) 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 +33 -22
  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 +445 -43
  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 +905 -80
  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/runtime-invariants.js +161 -0
  60. package/dist/runtime-types.js +117 -0
  61. package/dist/search-planner.js +38 -9
  62. package/dist/semantic-snapshot.js +155 -0
  63. package/dist/semantic-trace.js +1497 -0
  64. package/dist/semantic-validator.js +3 -2
  65. package/dist/semantic_guard_test.js +2 -1
  66. package/dist/sessions.js +11 -0
  67. package/dist/ssg-validator.js +658 -20
  68. package/dist/svl-distribution.js +11 -0
  69. package/dist/terminal-status.js +11 -0
  70. package/dist/test_failure_corpus.js +3 -1
  71. package/dist/token-savings.js +11 -0
  72. package/dist/total-repairs.js +12 -0
  73. package/dist/unresolved-count.js +12 -0
  74. package/dist/valid-fingerprints.js +13 -0
  75. package/dist/validator.js +116 -60
  76. package/dist/verify-fps.js +11 -0
  77. package/dist/verify-ledgers.js +11 -0
  78. package/docs/whitepaper-style.css +77 -0
  79. package/docs/whitepaper-v2.1.md +609 -0
  80. package/docs/whitepaper-v2.2.md +1064 -0
  81. package/docs/whitepaper-v2.2.pdf +0 -0
  82. package/fly.toml +1 -1
  83. package/package.json +13 -4
  84. package/protocols.json +131 -11
  85. package/public/dashboard.html +119 -0
  86. package/readme.md +829 -0
  87. package/server/hub.js +84 -12
  88. package/test/replay-golden/sess_1780063202050_mgeld.json +9 -0
  89. package/test/replay-golden/sess_1780064032560_gocld.json +354 -0
  90. package/test/replay-golden/sess_1780064413331_s2709.json +606 -0
  91. package/test/replay-golden/sess_1780064792710_y3avo.json +614 -0
  92. package/test/replay-golden.ts +84 -0
  93. package/test_benchmark.js +165 -0
  94. package/test_comprehensive.mjs +638 -0
  95. package/test_concurrency.js +129 -0
  96. package/test_ir_robustness.js +85 -0
  97. package/test_semantic_contracts.js +269 -0
  98. package/test_ssg_stress.js +156 -0
  99. package/test_svl3.js +58 -0
  100. package/tsconfig.json +1 -1
  101. package/.env.example +0 -3
  102. package/.progmune_memory/episodic.json +0 -186
  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/immune_hub_data/2026-05-14.json +0 -50
@@ -10,10 +10,117 @@ 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
+ // ── 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
+
13
28
  // 处理命令行子命令
14
29
  const args = process.argv.slice(2);
15
30
  const OPT_IN_FILE = path.resolve(__dirname, '../.progmune_memory/opt_in.json');
16
- if (args[0] === 'opt-in') {
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') {
17
124
  const command = args[1] || 'status';
18
125
  const dir = path.dirname(OPT_IN_FILE);
19
126
  if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
@@ -41,43 +148,67 @@ if (args[0] === 'opt-in') {
41
148
  }
42
149
 
43
150
  const { plan } = require('./planner.js');
44
- const { extractIRPython } = require('./extract-ir-python.js');
45
- const { emitPython } = require('./python-emitter.js');
151
+ const { extractIR } = require('./extract-ir.js');
152
+ const { emitCode } = require('./emitter.js');
46
153
  const { recordRun } = require('./feedback.js');
154
+ const { reportFingerprints } = require('./immune-reporter.js');
47
155
 
48
- // 从独立的协议文件中加载协议,并注入到 IR
49
- function mergeProtocols(irArray) {
50
- const protocolsFile = path.resolve(__dirname, '../protocols.json');
51
- if (!fs.existsSync(protocolsFile)) return irArray;
52
-
53
- const protocols = JSON.parse(fs.readFileSync(protocolsFile, 'utf-8'));
54
- return irArray.map(fn => {
55
- if (protocols[fn.name]) {
56
- return { ...fn, protocol: protocols[fn.name] };
57
- }
58
- return fn;
59
- });
60
- }
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
+ };
61
163
 
62
164
  async function main() {
63
- const server = new Server({
64
- name: "progmune",
65
- version: "2.0.0"
66
- }, { capabilities: { tools: {} } });
165
+ const server = new Server(
166
+ { name: "progmune", version: "2.2.0" },
167
+ { capabilities: { tools: {} } }
168
+ );
67
169
 
68
170
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
69
- tools: [{
70
- name: "progmune_generate",
71
- description: "生成类型安全的Python代码,仅使用项目中真实存在的函数。",
72
- inputSchema: {
73
- type: "object",
74
- properties: {
75
- intent: { type: "string", description: "编程意图" },
76
- 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"],
77
189
  },
78
- required: ["intent", "projectPath"]
79
- }
80
- }]
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
+ ],
81
212
  }));
82
213
 
83
214
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
@@ -85,31 +216,302 @@ async function main() {
85
216
  const { intent, projectPath } = request.params.arguments;
86
217
 
87
218
  if (!process.env.LLM_API_KEY) {
88
- 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
+ };
89
239
  }
90
240
 
91
241
  if (!fs.existsSync(OPT_IN_FILE)) {
92
- 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
+ };
93
336
  }
94
337
 
95
- const fns = extractIRPython(projectPath);
96
- // 关键步骤:将协议信息合并到 IR 中
97
- const irWithProtocols = mergeProtocols(fns);
98
- fs.writeFileSync("ir.json", JSON.stringify(irWithProtocols, null, 2));
99
-
100
- const actions = await plan(intent);
101
338
  if (!actions || actions.length === 0) {
102
- 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
+ };
103
348
  }
104
- const code = emitPython(actions);
349
+
350
+ // Emit TypeScript code
351
+ const code = emitCode(actions);
105
352
  recordRun(intent, actions, true);
353
+ reportFingerprints().catch(() => {});
354
+
106
355
  return { content: [{ type: "text", text: code }] };
107
356
  }
108
- 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}`);
109
486
  });
110
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
+
111
509
  const transport = new StdioServerTransport();
112
510
  await server.connect(transport);
511
+ log.success("Progmune MCP server ready (TypeScript)");
113
512
  }
114
513
 
115
- main().catch(console.error);
514
+ main().catch((e) => {
515
+ console.error("[Progmune] Fatal:", e);
516
+ process.exit(1);
517
+ });
@@ -41,7 +41,17 @@ exports.consolidateSemantic = consolidateSemantic;
41
41
  exports.findSemanticTemplate = findSemanticTemplate;
42
42
  const fs = __importStar(require("fs"));
43
43
  const path = __importStar(require("path"));
44
- const MEMORY_DIR = path.resolve(__dirname, "../.progmune_memory");
44
+ const file_lock_1 = require("./file-lock");
45
+ // 基于项目路径的隔离记忆目录,可通过环境变量 PROGMUNE_MEMORY_DIR 自定义
46
+ // 优先使用 PROGMUNE_PROJECT_DIR(由 MCP 服务器在调用时设置),确保多项目隔离
47
+ const projectDir = process.env.PROGMUNE_PROJECT_DIR || process.cwd();
48
+ const MEMORY_DIR = process.env.PROGMUNE_MEMORY_DIR
49
+ || path.resolve(projectDir, ".progmune_memory");
50
+ function ensureDir(dir) {
51
+ if (!fs.existsSync(dir))
52
+ fs.mkdirSync(dir, { recursive: true });
53
+ }
54
+ // ========== 工作记忆 ==========
45
55
  class WorkMemory {
46
56
  constructor() {
47
57
  this.bindings = new Map();
@@ -56,19 +66,30 @@ class WorkMemory {
56
66
  exports.WorkMemory = WorkMemory;
57
67
  const EPISODIC_FILE = path.join(MEMORY_DIR, "episodic.json");
58
68
  const MAX_EPISODES = 50;
59
- function ensureDir(dir) {
60
- if (!fs.existsSync(dir))
61
- fs.mkdirSync(dir, { recursive: true });
62
- }
69
+ const EPISODE_TTL_MS = 7 * 24 * 60 * 60 * 1000; // 7 天
63
70
  function loadEpisodes() {
64
71
  ensureDir(MEMORY_DIR);
65
72
  if (!fs.existsSync(EPISODIC_FILE))
66
73
  return [];
67
- return JSON.parse(fs.readFileSync(EPISODIC_FILE, "utf-8"));
74
+ const all = JSON.parse(fs.readFileSync(EPISODIC_FILE, "utf-8"));
75
+ const cutoff = Date.now() - EPISODE_TTL_MS;
76
+ const fresh = all.filter(e => new Date(e.timestamp).getTime() > cutoff);
77
+ if (fresh.length < all.length) {
78
+ console.error(`[记忆衰减-读取] 过滤了 ${all.length - fresh.length} 条过期情景记忆`);
79
+ fs.writeFileSync(EPISODIC_FILE, JSON.stringify(fresh, null, 2));
80
+ }
81
+ return fresh;
68
82
  }
69
83
  function saveEpisodes(episodes) {
70
- ensureDir(MEMORY_DIR);
71
- fs.writeFileSync(EPISODIC_FILE, JSON.stringify(episodes.slice(0, MAX_EPISODES), null, 2));
84
+ (0, file_lock_1.withLock)("episodic.json", () => {
85
+ ensureDir(MEMORY_DIR);
86
+ const cutoff = Date.now() - EPISODE_TTL_MS;
87
+ const fresh = episodes.filter(e => new Date(e.timestamp).getTime() > cutoff);
88
+ if (fresh.length !== episodes.length) {
89
+ console.error(`[记忆衰减-写入] 清理了 ${episodes.length - fresh.length} 条过期情景记忆`);
90
+ }
91
+ fs.writeFileSync(EPISODIC_FILE, JSON.stringify(fresh.slice(0, MAX_EPISODES), null, 2));
92
+ });
72
93
  }
73
94
  function recordEpisode(episode) {
74
95
  const episodes = loadEpisodes();
@@ -78,8 +99,9 @@ function recordEpisode(episode) {
78
99
  timestamp: new Date().toISOString(),
79
100
  };
80
101
  episodes.unshift(newEpisode);
81
- if (episodes.length > MAX_EPISODES)
102
+ if (episodes.length > MAX_EPISODES) {
82
103
  episodes.length = MAX_EPISODES;
104
+ }
83
105
  saveEpisodes(episodes);
84
106
  }
85
107
  function getRecentEpisodes(limit = 10) {
@@ -89,15 +111,31 @@ function getSuccessfulEpisodes(limit = 10) {
89
111
  return loadEpisodes().filter(e => e.success).slice(0, limit);
90
112
  }
91
113
  const SEMANTIC_FILE = path.join(MEMORY_DIR, "semantic.json");
114
+ const SEMANTIC_TTL_MS = 30 * 24 * 60 * 60 * 1000; // 30 天
92
115
  function loadSemantic() {
93
116
  ensureDir(MEMORY_DIR);
94
117
  if (!fs.existsSync(SEMANTIC_FILE))
95
118
  return [];
96
- return JSON.parse(fs.readFileSync(SEMANTIC_FILE, "utf-8"));
119
+ const all = JSON.parse(fs.readFileSync(SEMANTIC_FILE, "utf-8"));
120
+ const cutoff = Date.now() - SEMANTIC_TTL_MS;
121
+ const fresh = all.filter(t => new Date(t.lastUsedAt).getTime() > cutoff);
122
+ if (fresh.length < all.length) {
123
+ console.error(`[记忆衰减-读取] 清理了 ${all.length - fresh.length} 个过期语义模板`);
124
+ fs.writeFileSync(SEMANTIC_FILE, JSON.stringify(fresh, null, 2));
125
+ }
126
+ return fresh;
97
127
  }
98
128
  function saveSemantic(templates) {
99
- ensureDir(MEMORY_DIR);
100
- fs.writeFileSync(SEMANTIC_FILE, JSON.stringify(templates, null, 2));
129
+ (0, file_lock_1.withLock)("semantic.json", () => {
130
+ ensureDir(MEMORY_DIR);
131
+ // TTL 衰减:清理超过 30 天未使用的语义模板
132
+ const cutoff = Date.now() - SEMANTIC_TTL_MS;
133
+ const fresh = templates.filter(t => new Date(t.lastUsedAt).getTime() > cutoff);
134
+ if (fresh.length !== templates.length) {
135
+ console.error(`[记忆衰减] 清理了 ${templates.length - fresh.length} 个过期语义模板`);
136
+ }
137
+ fs.writeFileSync(SEMANTIC_FILE, JSON.stringify(fresh, null, 2));
138
+ });
101
139
  }
102
140
  function consolidateSemantic(minOccurrences = 3) {
103
141
  const episodes = getSuccessfulEpisodes(MAX_EPISODES);
@@ -132,7 +170,7 @@ function consolidateSemantic(minOccurrences = 3) {
132
170
  }
133
171
  }
134
172
  saveSemantic(templates);
135
- console.log(`[语义记忆] 巩固完成,模板数量: ${templates.length}`);
173
+ console.error(`[语义记忆] 巩固完成,模板数量: ${templates.length}`);
136
174
  }
137
175
  function findSemanticTemplate(intent) {
138
176
  const templates = loadSemantic();
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.main = main;
4
+ // @progmune-generated session=sess_1780345721473_u5ik3 timestamp=2026-06-01T20:28:42.736Z ruleHash=9dec68bc2995e92a
5
+ // Generated with IR constraint: 387 functions, 17 protocol rules
6
+ const execute_1 = require("./execute");
7
+ function main() {
8
+ const metrics = (0, execute_1.getExecutionMetrics)();
9
+ return metrics;
10
+ }
11
+ main();