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 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
- (0, child_process_1.execSync)("npx ts-node src/extract-ir.ts .", { stdio: "pipe", cwd: path.resolve(__dirname, "..") });
199
- const irRaw = JSON.parse(fs.readFileSync(path.resolve(__dirname, "../ir.json"), "utf-8"));
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
- (0, child_process_1.execSync)("npx tsc --noEmit", { stdio: "pipe", cwd: path.resolve(__dirname, "..") });
211
- pass("零类型错误");
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
- const irPath = filePath || path.resolve(__dirname, "../ir.json");
50
- if (!fs.existsSync(irPath))
51
- return [];
52
- const raw = JSON.parse(fs.readFileSync(irPath, "utf-8"));
53
- return Array.isArray(raw) ? raw : (raw.functions || []);
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
@@ -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 { createLogger } from "./logger";
13
- // ── Load .env (in compiled ESM output, use import.meta.url; here we use __dirname) ──
14
- const envPath = path.resolve(__dirname, "..", ".env");
15
- if (fs.existsSync(envPath)) {
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 ${envPath}:
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 (!rejection.fixPath || rejection.fixPath.length === 0)
467
+ function attemptSSGRepair(actions, rejection, ir, protocols, namespaceInitialStates, depth = 0) {
468
+ if (depth > 5) {
469
+ console.error(`[修复] 递归深度超限 (${depth}),放弃确定性修复`);
405
470
  return null;
406
- // 找到被拦截函数在序列中的位置
407
- const blockedIdx = actions.findIndex(a => a.kind === "call" && a.function === rejection.blocked);
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 = ir.find((f) => f.name === fnName);
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
- ? `${fnName}_result` : undefined;
423
- const action = { kind: 'call', function: fnName, args };
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
- const repaired = [
430
- ...actions.slice(0, blockedIdx),
431
- ...repairActions,
432
- ...actions.slice(blockedIdx),
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
- const nested = attemptSSGRepair(repaired, recheck.rejection, ir, protocols, namespaceInitialStates);
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
- const irRaw = JSON.parse(fs.readFileSync("ir.json", "utf-8"));
452
- // Support both old (array) and new ({typeMap, functions}) formats
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 || preCheckErrors.length > 0) {
968
- const errorsFlat = [...preCheckErrors, ...seqResult.errors.flat()];
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
- : preCheckErrors.length > 0
974
- ? [{ svl: 1, violatedConstraint: "symbol_existence", actionIndex: 0, description: preCheckErrors.join("; ") }]
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 = preCheckErrors.length > 0
1008
- ? `精确错误:\n${preCheckErrors.map(e => ` - ${e}`).join("\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 动作的参数值来自结构化 {name, type, value},都是字面量,不做变量引用检查
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)</td><td>10 个真实项目、432 个序列、gold 标注</td><td>精确率 <strong>86.8%</strong> · 召回率 <strong>83.6%</strong> · F1 <strong>85.2%</strong></td></tr>
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 已生产可用、Python 规则开发中、C 为研究课题——能力边界清清楚楚。
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>F1 85.2%,主战场</td></tr>
532
- <tr><td>Python</td><td><span style="color:var(--amber);font-weight:700">⚠️ IR 就绪</span></td><td>代码提取可用,协议规则开发中(下一阶段优先)</td></tr>
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 版本感知已实现</td></tr>
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">检测器 + 防护规则</div></div>
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">19</div><div class="l">MCP 工具</div></div>
545
- <div class="stat"><div class="n">71</div><div class="l">单元测试全部通过</div></div>
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>当前仅 TS 生产可用;协议规则不可能覆盖所有场景——存在漏检的可能</td><td>诚实披露置信度;数据飞轮持续补规则;规则来自真实 CVE 案例</td></tr>
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 完成</td><td>信任引擎 ✅、策略引擎 ✅、PLSB 13/13 ✅、tRPC/Express 适配 ✅、PrintLab 案例 ✅</td></tr>
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>语言扩展 + 企业验证</td><td>Python 完整支持、企业 PoC × 2-3、Evolution 追踪、CI/CD 门禁集成</td></tr>
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>语言覆盖有限(当前仅 TS 生产可用)</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.0",
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/mcp-server.mjs test",
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": "npx ts-node src/extract-ir.ts .",
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": "npx ts-node src/check.ts",
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",