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.
- 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 +33 -22
- 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 +445 -43
- 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 +905 -80
- package/dist/protocol-registry.js +105 -0
- package/dist/recent-session.js +12 -0
- package/dist/repair-proposal.js +353 -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 +131 -11
- 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/episodic.json +0 -186
- package/.progmune_memory/fingerprints.json +0 -7
- package/.progmune_memory/opt_in.json +0 -4
- package/README.md +0 -118
- package/immune_hub_data/2026-05-14.json +0 -50
package/dist/mcp-server.mjs
CHANGED
|
@@ -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] === '
|
|
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 {
|
|
45
|
-
const {
|
|
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
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
66
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
514
|
+
main().catch((e) => {
|
|
515
|
+
console.error("[Progmune] Fatal:", e);
|
|
516
|
+
process.exit(1);
|
|
517
|
+
});
|
package/dist/memory-layer.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
71
|
-
|
|
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
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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.
|
|
173
|
+
console.error(`[语义记忆] 巩固完成,模板数量: ${templates.length}`);
|
|
136
174
|
}
|
|
137
175
|
function findSemanticTemplate(intent) {
|
|
138
176
|
const templates = loadSemantic();
|
package/dist/metrics.js
ADDED
|
@@ -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();
|