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.
- package/.mcp.json +11 -0
- package/.progmune_allowlist +50 -0
- package/.test_report/test_report.md +87 -0
- package/Dockerfile +2 -10
- package/FAQ.md +167 -0
- package/demo-project/auth.ts +55 -0
- package/demo-project/tsconfig.json +8 -0
- package/dist/ab-stats.js +11 -0
- package/dist/acl-breakdown.js +13 -0
- package/dist/all-sessions.js +11 -0
- package/dist/antibody-stats.js +11 -0
- package/dist/audit.js +218 -0
- package/dist/benchmark-count.js +7 -0
- package/dist/benchmark-full.js +17 -0
- package/dist/benchmark-pass-rate.js +54 -0
- package/dist/benchmark-report.js +67 -0
- package/dist/benchmark-save.js +62 -0
- package/dist/benchmark-status.js +15 -0
- package/dist/branch-ledger.js +365 -0
- package/dist/branch-tree-count.js +14 -0
- package/dist/check.js +506 -0
- package/dist/common-fixpath.js +12 -0
- package/dist/constraint-types.js +12 -0
- package/dist/deterministic-replay.js +277 -0
- package/dist/emitter.js +112 -12
- package/dist/exec-metrics.js +11 -0
- package/dist/execute.js +242 -0
- package/dist/extract-ir.js +550 -5
- package/dist/failure-collector.js +143 -0
- package/dist/failure-corpus.js +481 -35
- package/dist/failure-report.js +11 -0
- package/dist/failures.js +11 -0
- package/dist/fast-path-hits.js +13 -0
- package/dist/feedback.js +6 -3
- package/dist/file-lock.js +82 -0
- package/dist/find-session.js +10 -0
- package/dist/fingerprint-list.js +15 -0
- package/dist/gen-history-log.js +13 -0
- package/dist/generate.js +2 -1
- package/dist/generate_500.js +4 -2
- package/dist/genome.js +11 -0
- package/dist/heatmap-data.js +11 -0
- package/dist/heatmap.js +11 -0
- package/dist/immune-reporter.js +137 -0
- package/dist/learned.js +11 -0
- package/dist/ledger-registry.js +241 -0
- package/dist/llm.js +43 -2
- package/dist/load-benchmarks.js +47 -0
- package/dist/main.js +2 -1
- package/dist/mcp-server.mjs +446 -34
- package/dist/memory-layer.js +51 -13
- package/dist/metrics.js +11 -0
- package/dist/obs-web.js +561 -0
- package/dist/p0_ssg_demo.js +255 -40
- package/dist/planner.js +933 -65
- package/dist/protocol-registry.js +105 -0
- package/dist/recent-session.js +12 -0
- package/dist/repair-proposal.js +353 -0
- package/dist/report.js +32 -0
- package/dist/runtime-invariants.js +161 -0
- package/dist/runtime-types.js +117 -0
- package/dist/search-planner.js +38 -9
- package/dist/semantic-snapshot.js +155 -0
- package/dist/semantic-trace.js +1497 -0
- package/dist/semantic-validator.js +3 -2
- package/dist/semantic_guard_test.js +2 -1
- package/dist/sessions.js +11 -0
- package/dist/ssg-validator.js +658 -20
- package/dist/svl-distribution.js +11 -0
- package/dist/terminal-status.js +11 -0
- package/dist/test_failure_corpus.js +3 -1
- package/dist/token-savings.js +11 -0
- package/dist/total-repairs.js +12 -0
- package/dist/unresolved-count.js +12 -0
- package/dist/valid-fingerprints.js +13 -0
- package/dist/validator.js +116 -60
- package/dist/verify-fps.js +11 -0
- package/dist/verify-ledgers.js +11 -0
- package/docs/whitepaper-style.css +77 -0
- package/docs/whitepaper-v2.1.md +609 -0
- package/docs/whitepaper-v2.2.md +1064 -0
- package/docs/whitepaper-v2.2.pdf +0 -0
- package/fly.toml +1 -1
- package/package.json +13 -4
- package/protocols.json +136 -0
- package/public/dashboard.html +119 -0
- package/readme.md +829 -0
- package/server/hub.js +84 -12
- package/test/replay-golden/sess_1780063202050_mgeld.json +9 -0
- package/test/replay-golden/sess_1780064032560_gocld.json +354 -0
- package/test/replay-golden/sess_1780064413331_s2709.json +606 -0
- package/test/replay-golden/sess_1780064792710_y3avo.json +614 -0
- package/test/replay-golden.ts +84 -0
- package/test_benchmark.js +165 -0
- package/test_comprehensive.mjs +638 -0
- package/test_concurrency.js +129 -0
- package/test_ir_robustness.js +85 -0
- package/test_semantic_contracts.js +269 -0
- package/test_ssg_stress.js +156 -0
- package/test_svl3.js +58 -0
- package/tsconfig.json +1 -1
- package/.env.example +0 -3
- package/.progmune_memory/fingerprints.json +0 -7
- package/.progmune_memory/opt_in.json +0 -4
- package/README.md +0 -118
- package/dist/mcp-server.js +0 -55
- 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
|
|
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. 正常流程
|
package/dist/mcp-server.mjs
CHANGED
|
@@ -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
|
-
//
|
|
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
|
-
|
|
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 {
|
|
49
|
-
const {
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
514
|
+
main().catch((e) => {
|
|
515
|
+
console.error("[Progmune] Fatal:", e);
|
|
516
|
+
process.exit(1);
|
|
517
|
+
});
|