progmune-runtime 3.3.0 → 3.3.2
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/dist/check.js +17 -4
- package/dist/ir-utils.js +19 -5
- package/dist/mcp-server.mjs +18 -11
- package/dist/planner.js +129 -25
- package/dist/validator.js +11 -1
- package/docs/Progmune_/351/241/271/347/233/256/345/205/250/350/247/243.html +17 -12
- package/package.json +4 -4
package/dist/check.js
CHANGED
|
@@ -193,10 +193,14 @@ if (cliArg === "--verify") {
|
|
|
193
193
|
}
|
|
194
194
|
}
|
|
195
195
|
// ── 1. IR 提取 ──
|
|
196
|
+
// 目标项目:PROGMUNE_PROJECT_DIR > CWD。使用编译产物提取器
|
|
197
|
+
// (安装态下包内没有 src/ 也没有 ts-node)。
|
|
196
198
|
step("1/6 IR 提取");
|
|
197
199
|
try {
|
|
198
|
-
|
|
199
|
-
const
|
|
200
|
+
const projectDir = path.resolve(process.env.PROGMUNE_PROJECT_DIR || process.cwd());
|
|
201
|
+
const extractor = path.resolve(__dirname, "extract-ir.js");
|
|
202
|
+
(0, child_process_1.execSync)(`node "${extractor}" "${projectDir}"`, { stdio: "pipe" });
|
|
203
|
+
const irRaw = JSON.parse(fs.readFileSync(path.resolve(projectDir, "ir.json"), "utf-8"));
|
|
200
204
|
const ir = Array.isArray(irRaw) ? irRaw : (irRaw.functions || []);
|
|
201
205
|
const externalCount = ir.filter((f) => f.external).length;
|
|
202
206
|
pass(`IR 提取完成: ${ir.length} 个函数 (${externalCount} 外部)`);
|
|
@@ -207,8 +211,17 @@ catch (e) {
|
|
|
207
211
|
// ── 2. TypeScript 编译 ──
|
|
208
212
|
step("2/6 TypeScript 类型检查");
|
|
209
213
|
try {
|
|
210
|
-
|
|
211
|
-
|
|
214
|
+
const projectDir = path.resolve(process.env.PROGMUNE_PROJECT_DIR || process.cwd());
|
|
215
|
+
const tsconfig = path.resolve(projectDir, "tsconfig.json");
|
|
216
|
+
if (!fs.existsSync(tsconfig)) {
|
|
217
|
+
warn("跳过(项目无 tsconfig.json)");
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
// 用包内 typescript 依赖的 tsc(安装态可用),而非全局 npx
|
|
221
|
+
const tscBin = path.resolve(__dirname, "..", "node_modules", "typescript", "bin", "tsc");
|
|
222
|
+
(0, child_process_1.execSync)(`node "${tscBin}" --noEmit -p "${tsconfig}"`, { stdio: "pipe" });
|
|
223
|
+
pass("零类型错误");
|
|
224
|
+
}
|
|
212
225
|
}
|
|
213
226
|
catch (e) {
|
|
214
227
|
const stderr = e.stderr?.toString() || e.stdout?.toString() || "";
|
package/dist/ir-utils.js
CHANGED
|
@@ -46,11 +46,25 @@ const path = __importStar(require("path"));
|
|
|
46
46
|
* is a bare array or an object with a `functions` key.
|
|
47
47
|
*/
|
|
48
48
|
function loadIR(filePath) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
49
|
+
// Resolution order: explicit path → PROGMUNE_PROJECT_DIR → CWD →
|
|
50
|
+
// package directory (legacy fallback). The package dir must be LAST —
|
|
51
|
+
// in an installed-package setup the project's ir.json lives in the
|
|
52
|
+
// consuming project, not inside node_modules/progmune-runtime.
|
|
53
|
+
const candidates = [];
|
|
54
|
+
if (filePath)
|
|
55
|
+
candidates.push(filePath);
|
|
56
|
+
const projectDir = process.env.PROGMUNE_PROJECT_DIR;
|
|
57
|
+
if (projectDir)
|
|
58
|
+
candidates.push(path.resolve(projectDir, "ir.json"));
|
|
59
|
+
candidates.push(path.resolve(process.cwd(), "ir.json"));
|
|
60
|
+
candidates.push(path.resolve(__dirname, "../ir.json"));
|
|
61
|
+
for (const irPath of candidates) {
|
|
62
|
+
if (fs.existsSync(irPath)) {
|
|
63
|
+
const raw = JSON.parse(fs.readFileSync(irPath, "utf-8"));
|
|
64
|
+
return Array.isArray(raw) ? raw : (raw.functions || []);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return [];
|
|
54
68
|
}
|
|
55
69
|
/** Count exported functions in an IR function list.
|
|
56
70
|
* @requires IR_FUNCTIONS @produces EXPORT_COUNT
|
package/dist/mcp-server.mjs
CHANGED
|
@@ -9,10 +9,17 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
9
9
|
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
10
10
|
import * as fs from "fs";
|
|
11
11
|
import * as path from "path";
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
import { fileURLToPath } from "url";
|
|
13
|
+
import { createLogger } from "./logger.js";
|
|
14
|
+
// ── ESM-compatible __dirname (the compiled output is an .mjs module) ──
|
|
15
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
// ── Load .env(包目录 + 项目目录,后者优先) ──
|
|
17
|
+
for (const envPath of [
|
|
18
|
+
path.resolve(process.env.PROGMUNE_PROJECT_DIR || process.cwd(), ".env"),
|
|
19
|
+
path.resolve(__dirname, "..", ".env"),
|
|
20
|
+
]) {
|
|
21
|
+
if (!fs.existsSync(envPath))
|
|
22
|
+
continue;
|
|
16
23
|
const envContent = fs.readFileSync(envPath, "utf-8");
|
|
17
24
|
for (const line of envContent.split("\n")) {
|
|
18
25
|
const trimmed = line.trim();
|
|
@@ -27,12 +34,12 @@ if (fs.existsSync(envPath)) {
|
|
|
27
34
|
process.env[key] = value;
|
|
28
35
|
}
|
|
29
36
|
}
|
|
30
|
-
import { plan } from "./planner";
|
|
31
|
-
import { extractIR } from "./extract-ir";
|
|
32
|
-
import { extractIRPython, isPythonProject } from "./extract-ir-python";
|
|
33
|
-
import { emitCode } from "./emitter";
|
|
34
|
-
import { recordRun } from "./feedback";
|
|
35
|
-
import { reportFingerprints } from "./immune-reporter";
|
|
37
|
+
import { plan } from "./planner.js";
|
|
38
|
+
import { extractIR } from "./extract-ir.js";
|
|
39
|
+
import { extractIRPython, isPythonProject } from "./extract-ir-python.js";
|
|
40
|
+
import { emitCode } from "./emitter.js";
|
|
41
|
+
import { recordRun } from "./feedback.js";
|
|
42
|
+
import { reportFingerprints } from "./immune-reporter.js";
|
|
36
43
|
const OPT_IN_FILE = path.resolve(__dirname, "..", ".progmune_memory", "opt_in.json");
|
|
37
44
|
// ── Structured logging (stderr, not stdout JSON-RPC) ──
|
|
38
45
|
const log = createLogger("progmune");
|
|
@@ -332,7 +339,7 @@ async function main() {
|
|
|
332
339
|
|
|
333
340
|
Progmune needs an LLM API key to generate code. Configure via:
|
|
334
341
|
|
|
335
|
-
【.env file】Add to ${
|
|
342
|
+
【.env file】Add to ${path.resolve(process.env.PROGMUNE_PROJECT_DIR || process.cwd(), ".env")}:
|
|
336
343
|
LLM_API_KEY=your-key
|
|
337
344
|
LLM_BASE_URL=https://api.deepseek.com/v1
|
|
338
345
|
LLM_MODEL=deepseek-chat
|
package/dist/planner.js
CHANGED
|
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.validateProtocolWithTransitions = validateProtocolWithTransitions;
|
|
36
37
|
exports.plan = plan;
|
|
37
38
|
const llm_1 = require("./llm");
|
|
38
39
|
const runtime_types_1 = require("./runtime-types");
|
|
@@ -49,6 +50,7 @@ const semantic_snapshot_1 = require("./semantic-snapshot");
|
|
|
49
50
|
const strategy_planner_1 = require("./strategy-planner");
|
|
50
51
|
const semantic_topology_1 = require("./semantic-topology");
|
|
51
52
|
const planner_prompts_1 = require("./planner-prompts");
|
|
53
|
+
const ir_utils_1 = require("./ir-utils");
|
|
52
54
|
const fs = __importStar(require("fs"));
|
|
53
55
|
function enrichActions(actions, ir) {
|
|
54
56
|
return actions.map(a => {
|
|
@@ -389,6 +391,68 @@ function validateProtocolWithTransitions(actions, protocols, namespaceInitialSta
|
|
|
389
391
|
ctx.currentState = transition.statesAfter;
|
|
390
392
|
}
|
|
391
393
|
}
|
|
394
|
+
// End-of-sequence check: held resources must be released (resource leak).
|
|
395
|
+
// A state S is resource-holding when some rule REQUIRES S and INVALIDATES S
|
|
396
|
+
// (acquire/release semantics — e.g. FILE_OPEN set by open_file, released by
|
|
397
|
+
// close_file). Only RESOURCE-LIFECYCLE namespaces apply: session/auth flows
|
|
398
|
+
// legitimately END with an active session (SESSION_ACTIVE is not a leak).
|
|
399
|
+
const RESOURCE_NS = /^(file|db|database|connection|conn|socket|stream|resource|io)/i;
|
|
400
|
+
const heldStates = [];
|
|
401
|
+
for (const p of protocols) {
|
|
402
|
+
const ann = p.protocol;
|
|
403
|
+
if (!ann)
|
|
404
|
+
continue;
|
|
405
|
+
const ns = ann.namespace || "";
|
|
406
|
+
if (!RESOURCE_NS.test(ns))
|
|
407
|
+
continue;
|
|
408
|
+
const inv = ann.invalidate || [];
|
|
409
|
+
const pre = ann.pre_states || [];
|
|
410
|
+
for (const s of inv) {
|
|
411
|
+
if (pre.includes(s))
|
|
412
|
+
heldStates.push({ state: s, releaseFn: p.function, namespace: ns });
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
// 只检查"本序列中获取"的持有状态——继承自命名空间初始状态的
|
|
416
|
+
// (如 db 初始即 DB_CONNECTED)不算泄漏。
|
|
417
|
+
const acquiredStates = new Set();
|
|
418
|
+
for (const t of transitions) {
|
|
419
|
+
for (const ns of Object.keys(t.statesAfter || {})) {
|
|
420
|
+
const after = t.statesAfter[ns] || [];
|
|
421
|
+
const before = t.statesBefore?.[ns] || [];
|
|
422
|
+
for (const s of after) {
|
|
423
|
+
if (!before.includes(s))
|
|
424
|
+
acquiredStates.add(`${ns}::${s}`);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
for (const hs of heldStates) {
|
|
429
|
+
const cur = ctx.currentState[hs.namespace] || [];
|
|
430
|
+
if (!acquiredStates.has(`${hs.namespace}::${hs.state}`))
|
|
431
|
+
continue;
|
|
432
|
+
if (cur.includes(hs.state)) {
|
|
433
|
+
const trace = transitions.map(t => ({
|
|
434
|
+
function: t.function,
|
|
435
|
+
statesBefore: t.statesBefore,
|
|
436
|
+
statesAfter: t.statesAfter,
|
|
437
|
+
}));
|
|
438
|
+
return {
|
|
439
|
+
valid: false,
|
|
440
|
+
rejection: {
|
|
441
|
+
blocked: "(end-of-sequence)",
|
|
442
|
+
currentState: cur,
|
|
443
|
+
requiredState: [],
|
|
444
|
+
missingFunctions: [hs.releaseFn],
|
|
445
|
+
fixPath: [hs.releaseFn],
|
|
446
|
+
namespace: hs.namespace,
|
|
447
|
+
endState: true,
|
|
448
|
+
},
|
|
449
|
+
index: actions.length,
|
|
450
|
+
trace,
|
|
451
|
+
transitions,
|
|
452
|
+
ruleHash,
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
}
|
|
392
456
|
// Invariant check on full ledger
|
|
393
457
|
const consistency = (0, ssg_validator_1.checkLedgerConsistency)(transitions, namespaceInitialStates);
|
|
394
458
|
if (!consistency.consistent) {
|
|
@@ -400,37 +464,54 @@ function validateProtocolWithTransitions(actions, protocols, namespaceInitialSta
|
|
|
400
464
|
return { valid: true, transitions, ledgerConsistent: consistency.consistent, ruleHash };
|
|
401
465
|
}
|
|
402
466
|
/** SSG 确定性修复:当协议违规有已知修复路径时,自动插入缺失函数 */
|
|
403
|
-
function attemptSSGRepair(actions, rejection, ir, protocols, namespaceInitialStates) {
|
|
404
|
-
if (
|
|
467
|
+
function attemptSSGRepair(actions, rejection, ir, protocols, namespaceInitialStates, depth = 0) {
|
|
468
|
+
if (depth > 5) {
|
|
469
|
+
console.error(`[修复] 递归深度超限 (${depth}),放弃确定性修复`);
|
|
405
470
|
return null;
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
if (blockedIdx === -1)
|
|
471
|
+
}
|
|
472
|
+
if (!rejection.fixPath || rejection.fixPath.length === 0)
|
|
409
473
|
return null;
|
|
474
|
+
// 名称归一化:内置规则可能是下划线风格(generate_jwt),项目 IR 是
|
|
475
|
+
// camelCase(generateJwt)——修复动作必须使用 IR 中的真实函数名。
|
|
476
|
+
const normalizeName = (n) => n.replace(/[_-]/g, "").toLowerCase();
|
|
477
|
+
const resolveIR = (fnName) => ir.find((f) => f.name === fnName)
|
|
478
|
+
|| ir.find((f) => normalizeName(f.name) === normalizeName(fnName));
|
|
410
479
|
// 为修复路径中的每个函数创建合成 Action
|
|
411
480
|
const repairActions = [];
|
|
412
481
|
for (const fnName of rejection.fixPath) {
|
|
413
|
-
const def =
|
|
482
|
+
const def = resolveIR(fnName);
|
|
414
483
|
if (!def)
|
|
415
484
|
return null;
|
|
485
|
+
const realName = def.name;
|
|
416
486
|
const args = (def.params || []).map((p, i) => ({
|
|
417
487
|
name: p.name || `p${i}`,
|
|
418
488
|
type: p.type || 'any',
|
|
419
489
|
value: "",
|
|
420
490
|
}));
|
|
421
491
|
const assignTo = def.returnType && def.returnType !== 'void' && def.returnType !== 'undefined'
|
|
422
|
-
? `${
|
|
423
|
-
const action = { kind: 'call', function:
|
|
492
|
+
? `${realName}_result` : undefined;
|
|
493
|
+
const action = { kind: 'call', function: realName, args };
|
|
424
494
|
if (assignTo)
|
|
425
495
|
action.assignTo = assignTo;
|
|
426
496
|
repairActions.push(action);
|
|
427
497
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
...repairActions
|
|
432
|
-
|
|
433
|
-
|
|
498
|
+
let repaired;
|
|
499
|
+
if (rejection.endState) {
|
|
500
|
+
// 末尾状态违规(资源未释放):释放函数追加到序列末尾
|
|
501
|
+
repaired = [...actions, ...repairActions];
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
// 找到被拦截函数在序列中的位置
|
|
505
|
+
const blockedIdx = actions.findIndex(a => a.kind === "call" && a.function === rejection.blocked);
|
|
506
|
+
if (blockedIdx === -1)
|
|
507
|
+
return null;
|
|
508
|
+
// 在被拦截函数前插入修复函数
|
|
509
|
+
repaired = [
|
|
510
|
+
...actions.slice(0, blockedIdx),
|
|
511
|
+
...repairActions,
|
|
512
|
+
...actions.slice(blockedIdx),
|
|
513
|
+
];
|
|
514
|
+
}
|
|
434
515
|
// 重新验证
|
|
435
516
|
const recheck = validateProtocolWithTransitions(repaired, protocols, namespaceInitialStates);
|
|
436
517
|
if (recheck.valid) {
|
|
@@ -439,7 +520,8 @@ function attemptSSGRepair(actions, rejection, ir, protocols, namespaceInitialSta
|
|
|
439
520
|
}
|
|
440
521
|
// 单步修复不够,尝试递归修复
|
|
441
522
|
if (recheck.rejection && recheck.rejection.fixPath && recheck.rejection.fixPath.length > 0) {
|
|
442
|
-
|
|
523
|
+
console.error(`[修复] 重验仍失败 (blocked=${recheck.rejection.blocked}, fixPath=${recheck.rejection.fixPath.join(" → ")}),递归深度 ${depth + 1}`);
|
|
524
|
+
const nested = attemptSSGRepair(repaired, recheck.rejection, ir, protocols, namespaceInitialStates, depth + 1);
|
|
443
525
|
if (nested)
|
|
444
526
|
return nested;
|
|
445
527
|
}
|
|
@@ -448,9 +530,8 @@ function attemptSSGRepair(actions, rejection, ir, protocols, namespaceInitialSta
|
|
|
448
530
|
/** @requires INTENT @produces ACTION_PLAN */
|
|
449
531
|
async function plan(userIntent, llmSeeds) {
|
|
450
532
|
(0, llm_1.resetCallCount)();
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
const ir = Array.isArray(irRaw) ? irRaw : (irRaw.functions || []);
|
|
533
|
+
// IR 读取走 loadIR 的解析顺序(显式路径 → PROGMUNE_PROJECT_DIR → CWD → 包目录回退)
|
|
534
|
+
const ir = (0, ir_utils_1.loadIR)();
|
|
454
535
|
// P1: Build Semantic Topology (once per plan call, cached)
|
|
455
536
|
try {
|
|
456
537
|
(0, semantic_topology_1.rebuildTopology)(ir);
|
|
@@ -458,7 +539,7 @@ async function plan(userIntent, llmSeeds) {
|
|
|
458
539
|
catch { /* topology rebuild — optional */ }
|
|
459
540
|
// Helper: wrap actions into PlanResult
|
|
460
541
|
let repairMetrics = { applied: false, count: 0, branchIds: [] };
|
|
461
|
-
const wrapResult = (actions, repair, degraded = false) => ({
|
|
542
|
+
const wrapResult = (actions, repair, degraded = false, blocked, blockedReason) => ({
|
|
462
543
|
actions,
|
|
463
544
|
sessionId: session?.sessionId || "",
|
|
464
545
|
ruleHash: session?.ruleHash,
|
|
@@ -466,6 +547,8 @@ async function plan(userIntent, llmSeeds) {
|
|
|
466
547
|
repairApplied: repair?.applied ?? repairMetrics.applied,
|
|
467
548
|
repairCount: repair?.count ?? repairMetrics.count,
|
|
468
549
|
repairBranchIds: repair?.branchIds ?? repairMetrics.branchIds,
|
|
550
|
+
blocked,
|
|
551
|
+
blockedReason,
|
|
469
552
|
});
|
|
470
553
|
// 初始化执行会话和快照(需在抗体快速通道前创建,以便记录 antibody hits)
|
|
471
554
|
const sessionId = (0, runtime_types_1.generateSessionId)();
|
|
@@ -963,15 +1046,19 @@ ${planner_prompts_1.RETRY_HINT}
|
|
|
963
1046
|
}
|
|
964
1047
|
}
|
|
965
1048
|
// 1) 基础序列校验
|
|
1049
|
+
// 预检查的协议违规("需要先调用 X")不属于符号/类型错误——
|
|
1050
|
+
// 它们由下方 SSG 块以 SVL-4 处理(含确定性修复)。
|
|
1051
|
+
// 只有符号/类型类错误走本回退分支,避免把 SVL-4 误标为 SVL-1。
|
|
1052
|
+
const preCheckSymbolErrors = preCheckErrors.filter(e => e.includes("函数不存在") || e.includes("参数数量"));
|
|
966
1053
|
const seqResult = (0, validator_1.validateActionSequence)(filtered);
|
|
967
|
-
if (!seqResult.valid ||
|
|
968
|
-
const errorsFlat = [...
|
|
1054
|
+
if (!seqResult.valid || preCheckSymbolErrors.length > 0) {
|
|
1055
|
+
const errorsFlat = [...preCheckSymbolErrors, ...seqResult.errors.flat()];
|
|
969
1056
|
console.error("⚠️ 序列校验失败:", errorsFlat.join(", "));
|
|
970
1057
|
// Use structured violations directly from validator
|
|
971
1058
|
const violations = seqResult.violations.length > 0
|
|
972
1059
|
? seqResult.violations
|
|
973
|
-
:
|
|
974
|
-
? [{ svl: 1, violatedConstraint: "symbol_existence", actionIndex: 0, description:
|
|
1060
|
+
: preCheckSymbolErrors.length > 0
|
|
1061
|
+
? [{ svl: 1, violatedConstraint: "symbol_existence", actionIndex: 0, description: preCheckSymbolErrors.join("; ") }]
|
|
975
1062
|
: [{ svl: 1, violatedConstraint: "symbol_existence", actionIndex: 0, description: errorsFlat.join("; ") }];
|
|
976
1063
|
const primarySvl = `SVL-${violations[0].svl}`;
|
|
977
1064
|
const attempt = {
|
|
@@ -1004,8 +1091,8 @@ ${planner_prompts_1.RETRY_HINT}
|
|
|
1004
1091
|
});
|
|
1005
1092
|
(0, memory_layer_1.recordEpisode)({ intent: userIntent, actions: filtered, success: false, svlViolated: primarySvl });
|
|
1006
1093
|
// Build targeted retry prompt based on pre-check results
|
|
1007
|
-
const specificErrors =
|
|
1008
|
-
? `精确错误:\n${
|
|
1094
|
+
const specificErrors = preCheckSymbolErrors.length > 0
|
|
1095
|
+
? `精确错误:\n${preCheckSymbolErrors.map(e => ` - ${e}`).join("\n")}`
|
|
1009
1096
|
: `错误:${errorsFlat.join(";")}`;
|
|
1010
1097
|
currentPrompt = `可用函数:\n${compactFuncList}${protocolChainHint}\n\n需求:${userIntent}${antibodyHint}\n\n${specificErrors}\n请修正上述问题。\n${planner_prompts_1.RETRY_HINT}\n只输出 JSON。`;
|
|
1011
1098
|
useSystem = false;
|
|
@@ -1017,6 +1104,13 @@ ${planner_prompts_1.RETRY_HINT}
|
|
|
1017
1104
|
const protoResult = validateProtocolWithTransitions(filtered, protocols, namespaceInitialStates);
|
|
1018
1105
|
if (!protoResult.valid && protoResult.rejection) {
|
|
1019
1106
|
const rej = protoResult.rejection;
|
|
1107
|
+
// P3:fixPath / missingFunctions 归一化到 IR 真实函数名——
|
|
1108
|
+
// 内置规则是下划线风格(generate_jwt),项目 IR 是 camelCase
|
|
1109
|
+
// (generateJwt)。提示与记录使用项目里真实存在的名字。
|
|
1110
|
+
const normIRName = (n) => ir.find((f) => f.name === n)
|
|
1111
|
+
|| ir.find((f) => f.name.replace(/[_-]/g, "").toLowerCase() === n.replace(/[_-]/g, "").toLowerCase());
|
|
1112
|
+
rej.fixPath = (rej.fixPath || []).map(n => normIRName(n)?.name || n);
|
|
1113
|
+
rej.missingFunctions = (rej.missingFunctions || []).map(n => normIRName(n)?.name || n);
|
|
1020
1114
|
const explain = (0, ssg_validator_1.explainRejection)(rej);
|
|
1021
1115
|
console.error(explain);
|
|
1022
1116
|
const violation = {
|
|
@@ -1290,6 +1384,16 @@ ${planner_prompts_1.RETRY_HINT}
|
|
|
1290
1384
|
session.endedAt = Date.now();
|
|
1291
1385
|
(0, failure_corpus_1.recordSession)(session);
|
|
1292
1386
|
(0, failure_corpus_1.clearCheckpoint)(userIntent);
|
|
1387
|
+
// 显式失败信号:所有尝试(含本地回退)都被约束拦截——
|
|
1388
|
+
// 调用方必须能区分"无事可做"与"被拦截"。
|
|
1389
|
+
const lastViolation = session.attempts.length > 0
|
|
1390
|
+
? session.attempts[session.attempts.length - 1].violations[0]
|
|
1391
|
+
: undefined;
|
|
1392
|
+
const reason = lastViolation
|
|
1393
|
+
? `所有生成尝试均被 SVL-${lastViolation.svl} 拦截: ${lastViolation.violatedConstraint}${lastViolation.fixPath?.length ? `(修复路径: ${lastViolation.fixPath.join(" → ")})` : ""}`
|
|
1394
|
+
: "所有生成尝试均被约束拦截,本地回退亦失败";
|
|
1395
|
+
console.error(`[拦截] ${reason}`);
|
|
1396
|
+
return wrapResult([], undefined, true, true, reason);
|
|
1293
1397
|
}
|
|
1294
1398
|
return wrapResult(finalActions);
|
|
1295
1399
|
}
|
package/dist/validator.js
CHANGED
|
@@ -86,7 +86,17 @@ function checkVariableFlow(actions) {
|
|
|
86
86
|
};
|
|
87
87
|
const processAction = (action) => {
|
|
88
88
|
if (action.kind === "call") {
|
|
89
|
-
// call
|
|
89
|
+
// call 参数值若是变量引用($ 前缀,或 planner 剥离 $ 后的裸标识符),
|
|
90
|
+
// 必须已声明——白皮书 SVL-3:"变量先声明后使用"。
|
|
91
|
+
for (const arg of action.args || []) {
|
|
92
|
+
const val = arg?.value;
|
|
93
|
+
if (typeof val !== "string")
|
|
94
|
+
continue;
|
|
95
|
+
const bare = val.startsWith("$") ? val.slice(1) : val;
|
|
96
|
+
if (bare && !isLiteral(bare) && /^[a-zA-Z_]\w*$/.test(bare) && !declared.has(bare)) {
|
|
97
|
+
errors.push(`变量 '${bare}' 在使用前未声明 (call 参数 '${arg.name || "?"}')`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
90
100
|
if (action.assignTo) {
|
|
91
101
|
declared.set(action.assignTo, "any");
|
|
92
102
|
}
|
|
@@ -465,6 +465,8 @@ AI 生成: create_session(...) ← 当前状态 UNAUTHENTICATED
|
|
|
465
465
|
<ul>
|
|
466
466
|
<li><strong>22 个协议检测器</strong>:TLS 握手、SSH 连接、HTTP 请求、支付处理、会话管理、文件上传、注册流程等</li>
|
|
467
467
|
<li><strong>26 条防护规则(safeguard)</strong>:触发式检查——"如果出现了 X,就必须有 Y"。例:注册函数必须调用安全哈希(bcrypt/argon2);管理操作必须检查角色;tRPC mutation 必须有 <code>.input()</code> 校验 schema</li>
|
|
468
|
+
<li><strong>15 条源码级检测规则(Python,2026-08 上线)</strong>:SQL 注入(f-string/%/.format/拼接,参数化写法正确放行)、SSRF(限定接收者的 HTTP 抓取 + request 污点追踪)、路径穿越(文件 sink + 污点)、XSS(跨文件:模板 <code>{{ var|safe }}</code> × 污点上下文绑定)、SSTI、XXE(解析器配置 + 污点双信号)、命令注入(仅动态命令参数)、反序列化、CSRF 双形态(<code>@csrf_exempt</code> / GET 状态变更)、eval 代码执行、硬编码 JWT 密钥(含跨模块常量)、cookie 授权、可预测重置令牌</li>
|
|
469
|
+
<li><strong>提取器标记架构</strong>:IR 提取器做源码级分析(污点追踪、import 解析、限定调用链、模板扫描、跨模块常量表)→ 合成标记 → 规则消费。零管道改动、完全可审计,已同步移植到 TS(ts-morph)提取器</li>
|
|
468
470
|
</ul>
|
|
469
471
|
|
|
470
472
|
<h3>5.4 API 语义映射:让检查认识"陌生函数"</h3>
|
|
@@ -501,12 +503,15 @@ AI 生成: create_session(...) ← 当前状态 UNAUTHENTICATED
|
|
|
501
503
|
<h3>5.7 基准测试:用数据证明能力</h3>
|
|
502
504
|
<table>
|
|
503
505
|
<tr><th>基准</th><th>方法</th><th>结果</th></tr>
|
|
504
|
-
<tr><td><strong>盲测基准</strong>(TypeScript
|
|
506
|
+
<tr><td><strong>盲测基准</strong>(TypeScript,100 项目)</td><td>90 个合成风格变体 + 10 个模型变体项目、795 条 gold finding、严格定位匹配</td><td>精确率 <strong>100%</strong> · 召回率 <strong>98.5%</strong>(有效口径 100%)· 事实性误报 <strong>0</strong></td></tr>
|
|
507
|
+
<tr><td><strong>盲测基准</strong>(Python,90 项目)</td><td>90 个合成风格变体、729 条 gold finding</td><td>精确率 <strong>100%</strong> · 召回率 <strong>100%</strong> · 事实性误报 <strong>0</strong></td></tr>
|
|
508
|
+
<tr><td><strong>真实应用验证</strong>(PyGoat)</td><td>OWASP 故意脆弱 Django 应用、232 条检测逐条人工核实</td><td>标记精确率 <strong>100%</strong>(67 真阳性 / 0 误报);覆盖 14 个漏洞类别</td></tr>
|
|
509
|
+
<tr><td><strong>良构应用</strong>(django/fastapi realworld、django-unicorn)</td><td>176 条检测</td><td>0 条误报真阳性;3 条框架内部边界 FP(已定性归档)</td></tr>
|
|
505
510
|
<tr><td><strong>黄金基准</strong>(C 语言)</td><td>curl/libssh/nginx/openssl 等真实 CVE 案例</td><td>F1 16.5% —— <strong>研究阶段</strong>,诚实披露</td></tr>
|
|
506
511
|
<tr><td><strong>PLSB v1.0</strong></td><td>13 类协议安全弱点、39 个手工验证缺陷案例</td><td>覆盖 <strong>13/13 全类别</strong>(业界唯一)</td></tr>
|
|
507
512
|
</table>
|
|
508
513
|
<div class="plain">
|
|
509
|
-
<strong>为什么披露 C 语言的低分?</strong>因为"诚实"是产品原则。一个只报告好消息的基准没有参考价值。TS
|
|
514
|
+
<strong>为什么披露 C 语言的低分?</strong>因为"诚实"是产品原则。一个只报告好消息的基准没有参考价值。TS 与 Python 已生产可用、C 为研究课题——能力边界清清楚楚。所有基准均有 gold 标注文件入库,可复现。
|
|
510
515
|
</div>
|
|
511
516
|
</section>
|
|
512
517
|
|
|
@@ -528,21 +533,21 @@ AI 生成: create_session(...) ← 当前状态 UNAUTHENTICATED
|
|
|
528
533
|
<h3>6.2 语言与框架覆盖(诚实版)</h3>
|
|
529
534
|
<table>
|
|
530
535
|
<tr><th>能力</th><th>状态</th><th>说明</th></tr>
|
|
531
|
-
<tr><td>TypeScript / JavaScript</td><td><span style="color:var(--green);font-weight:700">✅ 生产</span></td><td
|
|
532
|
-
<tr><td>Python</td><td><span style="color:var(--
|
|
536
|
+
<tr><td>TypeScript / JavaScript</td><td><span style="color:var(--green);font-weight:700">✅ 生产</span></td><td>盲测 100 项目:精确率 100%、召回率 98.5%</td></tr>
|
|
537
|
+
<tr><td>Python</td><td><span style="color:var(--green);font-weight:700">✅ 生产</span></td><td>盲测 90 项目:精确率/召回率双 100%;15 条源码级检测规则;PyGoat 真实验证 67 TP / 0 FP</td></tr>
|
|
533
538
|
<tr><td>C</td><td><span style="color:var(--amber);font-weight:700">⚠️ 研究</span></td><td>F1 16.5%,瓶颈已定位(规则覆盖),L3 实验已终止</td></tr>
|
|
534
539
|
<tr><td>Go / Java</td><td><span style="color:var(--ink-3)">❌ 未支持</span></td><td>路线图内</td></tr>
|
|
535
|
-
<tr><td>框架适配</td><td><span style="color:var(--amber);font-weight:700">2/13</span></td><td>Express ✅、tRPC ✅、NestJS 部分;Next.js
|
|
540
|
+
<tr><td>框架适配</td><td><span style="color:var(--amber);font-weight:700">2/13</span></td><td>Express ✅、tRPC ✅、NestJS 部分;Next.js 版本感知已实现;Django/FastAPI 经 Python 源码级检测间接覆盖</td></tr>
|
|
536
541
|
</table>
|
|
537
542
|
|
|
538
543
|
<h3>6.3 知识库规模</h3>
|
|
539
544
|
<div class="stats">
|
|
540
545
|
<div class="stat"><div class="n">148</div><div class="l">协议规则</div></div>
|
|
541
546
|
<div class="stat"><div class="n">21</div><div class="l">协议命名空间全覆盖</div></div>
|
|
542
|
-
<div class="stat"><div class="n">22+26</div><div class="l">检测器 +
|
|
547
|
+
<div class="stat"><div class="n">22+26+15</div><div class="l">检测器 + 防护 + 源码级规则</div></div>
|
|
543
548
|
<div class="stat"><div class="n">39</div><div class="l">黄金缺陷案例(人工验证)</div></div>
|
|
544
|
-
<div class="stat"><div class="n">
|
|
545
|
-
<div class="stat"><div class="n">
|
|
549
|
+
<div class="stat"><div class="n">190</div><div class="l">盲测项目(TS 100 + Python 90)</div></div>
|
|
550
|
+
<div class="stat"><div class="n">4</div><div class="l">真实应用验证仓库</div></div>
|
|
546
551
|
</div>
|
|
547
552
|
</section>
|
|
548
553
|
|
|
@@ -655,7 +660,7 @@ AI 生成: create_session(...) ← 当前状态 UNAUTHENTICATED
|
|
|
655
660
|
<tr><th>风险</th><th>等级</th><th>内容</th><th>缓解措施</th></tr>
|
|
656
661
|
<tr><td><strong>市场教育成本</strong></td><td><span style="color:var(--red);font-weight:700">高</span></td><td>多数企业尚未意识到"AI 代码需要验证"这个问题本身</td><td>公开缺陷基准库、从受监管行业切入、开源社区自然教育</td></tr>
|
|
657
662
|
<tr><td><strong>大厂竞争</strong></td><td><span style="color:var(--amber);font-weight:700">中</span></td><td>GitHub/Microsoft 可能内置类似功能</td><td>聚焦"验证层"差异化;大厂内置方案通常浅层;开源先发</td></tr>
|
|
658
|
-
<tr><td><strong>验证的边界性</strong></td><td><span style="color:var(--amber);font-weight:700">中</span></td><td
|
|
663
|
+
<tr><td><strong>验证的边界性</strong></td><td><span style="color:var(--amber);font-weight:700">中</span></td><td>TS 与 Python 已生产可用;协议规则不可能覆盖所有场景——存在漏检的可能</td><td>诚实披露置信度;数据飞轮持续补规则;规则来自真实 CVE 案例</td></tr>
|
|
659
664
|
<tr><td><strong>LLM 依赖</strong></td><td><span style="color:var(--green);font-weight:700">低</span></td><td>代码生成环节依赖底层模型能力</td><td>架构上模型无关;验证器本身不依赖任何 LLM;模型越强产品越有价值</td></tr>
|
|
660
665
|
<tr><td><strong>商业化节奏</strong></td><td><span style="color:var(--amber);font-weight:700">中</span></td><td>开源到付费的转化路径需要时间验证;尚无企业 PoC</td><td>GitLab/Databricks 模式已成熟;优先做 2-3 个企业 PoC 验证 PMF</td></tr>
|
|
661
666
|
<tr><td><strong>核心假说未验证</strong></td><td><span style="color:var(--amber);font-weight:700">中</span></td><td>"每个新代码库让所有验证更强"的知识网络假说尚无实证</td><td>以 Phase 2 的 PoC 数据为准;假说不成立也不影响单点验证的产品价值</td></tr>
|
|
@@ -672,9 +677,9 @@ AI 生成: create_session(...) ← 当前状态 UNAUTHENTICATED
|
|
|
672
677
|
|
|
673
678
|
<table>
|
|
674
679
|
<tr><th>阶段</th><th>时间</th><th>目标</th><th>关键交付</th></tr>
|
|
675
|
-
<tr><td><strong>当前</strong></td><td>2026-08</td><td>Trust Foundation
|
|
680
|
+
<tr><td><strong>当前</strong></td><td>2026-08</td><td>Trust Foundation + 语言扩展完成</td><td>信任引擎 ✅、策略引擎 ✅、PLSB 13/13 ✅、tRPC/Express 适配 ✅、PrintLab 案例 ✅、<strong>Python 生产级 ✅(盲测双 100%、PyGoat 真实验证 0 误报)✅、15 条源码级检测规则 ✅、TS/Python 盲测 precision 100% ✅</strong></td></tr>
|
|
676
681
|
<tr><td><strong>Phase 1 收官</strong></td><td>2026 Q3</td><td>可信度校准</td><td>已知风险项目上的 Score 校准、真实项目验收、Trust Report PDF</td></tr>
|
|
677
|
-
<tr><td><strong>Phase 2</strong></td><td>2026 Q4</td><td
|
|
682
|
+
<tr><td><strong>Phase 2</strong></td><td>2026 Q4</td><td>企业验证</td><td>企业 PoC × 2-3、Evolution 追踪、CI/CD 门禁集成、良构应用框架内部 FP 收尾(类属性建模)</td></tr>
|
|
678
683
|
<tr><td><strong>Phase 3</strong></td><td>2027+</td><td>平台化</td><td>Go/Java、SaaS 仪表盘、公开基准排行榜、全球免疫网络、协议模板市场</td></tr>
|
|
679
684
|
</table>
|
|
680
685
|
</section>
|
|
@@ -694,7 +699,7 @@ AI 生成: create_session(...) ← 当前状态 UNAUTHENTICATED
|
|
|
694
699
|
<h3>为什么需要谨慎</h3>
|
|
695
700
|
<ol>
|
|
696
701
|
<li>企业 PoC 为零——PMF 未验证,商业化路径尚需数据</li>
|
|
697
|
-
<li
|
|
702
|
+
<li>语言覆盖有限(TS/Python 生产可用,Go/Java 未支持)</li>
|
|
698
703
|
<li>"信任"类产品教育成本高,销售周期长</li>
|
|
699
704
|
<li>大厂随时可能跟进(但架构先发 + 数据积累构成时间壁垒)</li>
|
|
700
705
|
</ol>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "progmune-runtime",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.2",
|
|
4
4
|
"description": "Progmune — AI Trust Decision Engine. Verify AI-generated code before it reaches production. Outputs APPROVED / NEEDS_REVIEW / BLOCKED with evidence.",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist/",
|
|
@@ -16,15 +16,15 @@
|
|
|
16
16
|
"prepare": "npm run build",
|
|
17
17
|
"build": "tsc -p tsconfig.json && tsc -p tsconfig.mcp.json && mv dist-mcp/mcp-server.js dist/mcp-server.mjs && rm -rf dist-mcp",
|
|
18
18
|
"start": "node dist/mcp-server.mjs",
|
|
19
|
-
"test": "node dist/
|
|
19
|
+
"test": "node dist/check.js",
|
|
20
20
|
"test:unit": "vitest run --exclude '**/stress/**' --exclude '**/soak/**' --exclude '**/chaos/**'",
|
|
21
21
|
"test:watch": "vitest",
|
|
22
22
|
"test:coverage": "vitest run --coverage",
|
|
23
|
-
"ir": "
|
|
23
|
+
"ir": "node dist/extract-ir.js .",
|
|
24
24
|
"guard": "node -e \"require('./dist/validator.js'); console.log('✅ 校验器已加载')\"",
|
|
25
25
|
"obs": "npx ts-node src/semantic-trace.ts",
|
|
26
26
|
"obs-web": "npx ts-node src/obs-web.ts",
|
|
27
|
-
"check": "
|
|
27
|
+
"check": "node dist/check.js",
|
|
28
28
|
"audit": "npx ts-node src/audit.ts",
|
|
29
29
|
"governance": "npx ts-node src/audit/cli.ts --all",
|
|
30
30
|
"governance:json": "npx ts-node src/audit/cli.ts --all --json",
|