progmune-runtime 3.3.7 → 3.4.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.
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ /**
3
+ * Phase 12: Agent 自监督层 (P3)
4
+ *
5
+ * 运行项目测试并提取失败信息 —— 失败注入下一次尝试的 prompt(失败→prompt 回路)。
6
+ * 设计文档 P3:编译/测试失败反馈注入重试。
7
+ *
8
+ * 自动探测顺序:
9
+ * 1. package.json 有 "test" script → npm test --silent
10
+ * 2. 存在 .py 文件 → python3 -m pytest -q
11
+ * 3. 都没有 → { ran: false }(调用方跳过该门)
12
+ */
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || (function () {
30
+ var ownKeys = function(o) {
31
+ ownKeys = Object.getOwnPropertyNames || function (o) {
32
+ var ar = [];
33
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34
+ return ar;
35
+ };
36
+ return ownKeys(o);
37
+ };
38
+ return function (mod) {
39
+ if (mod && mod.__esModule) return mod;
40
+ var result = {};
41
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
42
+ __setModuleDefault(result, mod);
43
+ return result;
44
+ };
45
+ })();
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.runProjectTests = runProjectTests;
48
+ const fs = __importStar(require("fs"));
49
+ const path = __importStar(require("path"));
50
+ const child_process_1 = require("child_process");
51
+ // ── Helpers ──
52
+ const FAILURE_PATTERN = /(FAIL|✕|×|failed|Error:|error TS|AssertionError|FAILED)/i;
53
+ function extractFailures(output) {
54
+ return output
55
+ .split("\n")
56
+ .map((l) => l.trim())
57
+ .filter((l) => l.length > 0 && FAILURE_PATTERN.test(l))
58
+ .slice(0, 10);
59
+ }
60
+ function runCommand(cwd, command, timeoutMs) {
61
+ try {
62
+ const output = (0, child_process_1.execSync)(command, {
63
+ cwd,
64
+ timeout: timeoutMs,
65
+ encoding: "utf-8",
66
+ stdio: "pipe",
67
+ });
68
+ return { pass: true, output };
69
+ }
70
+ catch (e) {
71
+ // 非零退出或超时 → 捕获输出
72
+ const output = `${e.stdout || ""}\n${e.stderr || ""}`;
73
+ return { pass: false, output, error: e?.message || String(e) };
74
+ }
75
+ }
76
+ // ── Main ──
77
+ /**
78
+ * 自动探测并运行项目测试。
79
+ *
80
+ * @requires PROJECT_PATH @produces TEST_RESULT
81
+ */
82
+ function runProjectTests(projectPath, timeoutMs = 60000) {
83
+ // 1) npm test
84
+ const pkgPath = path.join(projectPath, "package.json");
85
+ if (fs.existsSync(pkgPath)) {
86
+ try {
87
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
88
+ if (pkg.scripts?.test) {
89
+ const command = "npm test --silent";
90
+ const r = runCommand(projectPath, command, timeoutMs);
91
+ return {
92
+ ran: true,
93
+ pass: r.pass,
94
+ failures: extractFailures(r.output),
95
+ command,
96
+ error: r.error,
97
+ };
98
+ }
99
+ }
100
+ catch { /* package.json 解析失败 → 继续探测 */ }
101
+ }
102
+ // 2) pytest
103
+ const hasPy = listQuickly(projectPath, (e) => e.endsWith(".py"));
104
+ if (hasPy) {
105
+ const command = "python3 -m pytest -q";
106
+ const r = runCommand(projectPath, command, timeoutMs);
107
+ if (r.error && /no module named pytest/i.test(r.error + r.output)) {
108
+ return { ran: false, pass: true, failures: [], command, error: "pytest 未安装" };
109
+ }
110
+ return {
111
+ ran: true,
112
+ pass: r.pass,
113
+ failures: extractFailures(r.output),
114
+ command,
115
+ error: r.error,
116
+ };
117
+ }
118
+ return { ran: false, pass: true, failures: [], command: "(无测试脚本)" };
119
+ }
120
+ /** 浅层探测是否存在匹配文件(不递归依赖目录)。 */
121
+ function listQuickly(projectPath, match) {
122
+ const SKIP = new Set(["node_modules", "dist", "build", ".git", "__pycache__", "venv", ".venv"]);
123
+ const stack = [projectPath];
124
+ const seen = new Set();
125
+ while (stack.length > 0) {
126
+ const dir = stack.pop();
127
+ if (seen.has(dir))
128
+ continue;
129
+ seen.add(dir);
130
+ let entries;
131
+ try {
132
+ entries = fs.readdirSync(dir, { withFileTypes: true });
133
+ }
134
+ catch {
135
+ continue;
136
+ }
137
+ for (const e of entries) {
138
+ if (e.isFile() && match(e.name))
139
+ return true;
140
+ if (e.isDirectory() && !SKIP.has(e.name) && !e.name.startsWith("."))
141
+ stack.push(path.join(dir, e.name));
142
+ }
143
+ }
144
+ return false;
145
+ }
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ /**
3
+ * Phase 12: 自监督层测试 (P3)
4
+ *
5
+ * runProjectTests 的探测逻辑与失败提取。全部 mock,不跑真实测试。
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ const vitest_1 = require("vitest");
9
+ const child_process_1 = require("child_process");
10
+ const agent_supervision_1 = require("./agent-supervision");
11
+ vitest_1.vi.mock("child_process", () => ({
12
+ execSync: vitest_1.vi.fn(),
13
+ }));
14
+ const mockExecSync = vitest_1.vi.mocked(child_process_1.execSync);
15
+ (0, vitest_1.beforeEach)(() => {
16
+ vitest_1.vi.clearAllMocks();
17
+ });
18
+ (0, vitest_1.describe)("agent-supervision", () => {
19
+ (0, vitest_1.it)("package.json 有 test script → npm test,失败时提取失败行", () => {
20
+ mockExecSync.mockImplementation(() => {
21
+ const err = new Error("Command failed");
22
+ err.stdout = "FAIL src/auth.test.ts\nAssertionError: token 无效\n 12 passing\n 1 failing\n";
23
+ err.stderr = "";
24
+ throw err;
25
+ });
26
+ // 真实 npm 项目路径下才能探测到 package.json —— 用临时脚本验证探测逻辑
27
+ const fs = require("fs");
28
+ const os = require("os");
29
+ const path = require("path");
30
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pm-test-"));
31
+ fs.writeFileSync(path.join(dir, "package.json"), JSON.stringify({ scripts: { test: "vitest run" } }));
32
+ const r = (0, agent_supervision_1.runProjectTests)(dir, 5000);
33
+ (0, vitest_1.expect)(r.ran).toBe(true);
34
+ (0, vitest_1.expect)(r.pass).toBe(false);
35
+ (0, vitest_1.expect)(r.failures.length).toBeGreaterThan(0);
36
+ (0, vitest_1.expect)(r.failures.join(" ")).toContain("token 无效");
37
+ (0, vitest_1.expect)(r.command).toBe("npm test --silent");
38
+ });
39
+ (0, vitest_1.it)("测试通过时 pass=true 且 failures 为空", () => {
40
+ mockExecSync.mockReturnValue(" 12 passing (3s)");
41
+ const fs = require("fs");
42
+ const os = require("os");
43
+ const path = require("path");
44
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pm-test2-"));
45
+ fs.writeFileSync(path.join(dir, "package.json"), JSON.stringify({ scripts: { test: "vitest run" } }));
46
+ const r = (0, agent_supervision_1.runProjectTests)(dir, 5000);
47
+ (0, vitest_1.expect)(r.ran).toBe(true);
48
+ (0, vitest_1.expect)(r.pass).toBe(true);
49
+ (0, vitest_1.expect)(r.failures).toHaveLength(0);
50
+ });
51
+ (0, vitest_1.it)("无测试脚本且无 python 文件 → ran=false(调用方跳过该门)", () => {
52
+ const fs = require("fs");
53
+ const os = require("os");
54
+ const path = require("path");
55
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), "pm-test3-"));
56
+ const r = (0, agent_supervision_1.runProjectTests)(dir, 5000);
57
+ (0, vitest_1.expect)(r.ran).toBe(false);
58
+ (0, vitest_1.expect)(mockExecSync).not.toHaveBeenCalled();
59
+ });
60
+ });
package/dist/execute.js CHANGED
@@ -225,6 +225,10 @@ async function execute(intent, projectPath, filePath) {
225
225
  /** @requires FILE_PATH @produces COMPILE_RESULT */
226
226
  /** @requires FILE_PATH @produces COMPILE_RESULT */
227
227
  function verifyCompiles(filePath) {
228
+ // tsc 报错行以「相对 tsconfig 的文件路径」开头(如 `login_flow.ts(7,13):`),
229
+ // 而调用方可能传绝对路径——两种形态都要匹配,否则编译门会静默漏报。
230
+ const base = path.basename(filePath);
231
+ const isMatch = (l) => l.includes(filePath) || l.startsWith(base + "(") || l.startsWith(base + ":");
228
232
  try {
229
233
  const { execSync } = require("child_process");
230
234
  const result = execSync(`npx tsc --noEmit --project tsconfig.json --pretty false 2>&1`, {
@@ -233,13 +237,13 @@ function verifyCompiles(filePath) {
233
237
  stdio: "pipe",
234
238
  });
235
239
  // tsc exits 0, check if our file is mentioned in output anyway (unlikely but safe)
236
- const lines = result.split("\n").filter((l) => l.includes(filePath));
240
+ const lines = result.split("\n").filter(isMatch);
237
241
  return { pass: lines.length === 0, errors: lines };
238
242
  }
239
243
  catch (e) {
240
244
  // tsc exits non-zero — parse stderr/stdout for our file's errors
241
245
  const output = (e.stdout || "") + (e.stderr || "");
242
- const lines = output.split("\n").filter((l) => l.includes(filePath));
246
+ const lines = output.split("\n").filter(isMatch);
243
247
  return { pass: lines.length === 0, errors: lines };
244
248
  }
245
249
  }
@@ -121,8 +121,10 @@ function parseProtocolFromJSDoc(node) {
121
121
  const preMatch = text.match(/pre_states\s*=\s*\[([^\]]*)\]/);
122
122
  const postMatch = text.match(/post_states\s*=\s*\[([^\]]*)\]/);
123
123
  const invMatch = text.match(/invalidate\s*=\s*\[([^\]]*)\]/);
124
+ // 非规则注解(如文件头文档正文中的 "@protocol" 字样被 ts-morph 解析为 tag)
125
+ // → 跳过继续找下一个 @protocol tag,而不是直接放弃
124
126
  if (!preMatch || !postMatch)
125
- return undefined;
127
+ continue;
126
128
  const namespace = nsMatch ? nsMatch[1] : undefined;
127
129
  const pre_states = preMatch[1].split(',').map((s) => s.trim().replace(/["']/g, '')).filter(Boolean);
128
130
  const post_states = postMatch[1].split(',').map((s) => s.trim().replace(/["']/g, '')).filter(Boolean);
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+ /**
3
+ * Phase 12: Progmune 免疫巡逻 CLI — `progmune patrol`(形态 B 第一版)
4
+ *
5
+ * 扫描项目 → trust_check → 违规报告 + 建议补丁(绝不自动合并)。
6
+ * 支持 --watch 持续监听(文件变更 → 防抖 → 重新巡逻 → 刷新报告)。
7
+ *
8
+ * Usage:
9
+ * npx ts-node src/patrol-cli.ts --project <dir> [options]
10
+ * npm run patrol -- --project <dir> [options]
11
+ *
12
+ * Options:
13
+ * --project <dir> 目标项目目录(默认 CWD)
14
+ * --watch 持续监听模式(文件变更后自动重扫)
15
+ * --json JSON 输出(单次扫描)
16
+ * --help, -h
17
+ */
18
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
19
+ if (k2 === undefined) k2 = k;
20
+ var desc = Object.getOwnPropertyDescriptor(m, k);
21
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
22
+ desc = { enumerable: true, get: function() { return m[k]; } };
23
+ }
24
+ Object.defineProperty(o, k2, desc);
25
+ }) : (function(o, m, k, k2) {
26
+ if (k2 === undefined) k2 = k;
27
+ o[k2] = m[k];
28
+ }));
29
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
30
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
31
+ }) : function(o, v) {
32
+ o["default"] = v;
33
+ });
34
+ var __importStar = (this && this.__importStar) || (function () {
35
+ var ownKeys = function(o) {
36
+ ownKeys = Object.getOwnPropertyNames || function (o) {
37
+ var ar = [];
38
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
39
+ return ar;
40
+ };
41
+ return ownKeys(o);
42
+ };
43
+ return function (mod) {
44
+ if (mod && mod.__esModule) return mod;
45
+ var result = {};
46
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
47
+ __setModuleDefault(result, mod);
48
+ return result;
49
+ };
50
+ })();
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ const path = __importStar(require("path"));
53
+ const agent_patrol_1 = require("./agent-patrol");
54
+ const agent_perception_1 = require("./agent-perception");
55
+ // 在 chdir 到项目目录之前按启动 CWD 加载 .env——
56
+ // trust 引擎内部的 lazy require(语义映射 LLM 回退)发生在 chdir 之后,
57
+ // 若不预载,LLM_API_KEY 不可用,映射降级会导致漏报。
58
+ try {
59
+ require("dotenv/config");
60
+ }
61
+ catch { /* dotenv 可选 */ }
62
+ const args = process.argv.slice(2);
63
+ if (args.includes("--help") || args.includes("-h")) {
64
+ console.log(`
65
+ Progmune 免疫巡逻 (P4) — 监听/扫描 → trust_check → 报告 + 建议补丁(永不自动合并)
66
+
67
+ Usage:
68
+ npx ts-node src/patrol-cli.ts --project <dir> [options]
69
+ npm run patrol -- --project <dir> [options]
70
+
71
+ Options:
72
+ --project <dir> 目标项目目录(默认当前目录)
73
+ --watch 持续监听模式(源文件变更后自动重扫并刷新报告)
74
+ --json JSON 输出(单次扫描)
75
+ --help, -h 显示帮助
76
+
77
+ Example:
78
+ npm run patrol -- --project demo-patrol
79
+ npm run patrol -- --project demo-patrol --watch
80
+ `);
81
+ process.exit(0);
82
+ }
83
+ const getFlag = (name) => {
84
+ const idx = args.indexOf(`--${name}`);
85
+ return idx >= 0 && idx + 1 < args.length ? args[idx + 1] : undefined;
86
+ };
87
+ const projectPath = path.resolve(getFlag("project") || process.cwd());
88
+ const watch = args.includes("--watch");
89
+ const json = args.includes("--json");
90
+ async function scanOnce(label) {
91
+ try {
92
+ const report = await (0, agent_patrol_1.runPatrol)(projectPath);
93
+ const reportFile = (0, agent_patrol_1.writePatrolReport)(report, projectPath);
94
+ if (json && !watch) {
95
+ console.log(JSON.stringify(report, null, 2));
96
+ }
97
+ else {
98
+ console.log(`[${label}] ` + (0, agent_patrol_1.formatPatrolTerminal)(report).replace(/\n/g, "\n "));
99
+ console.log(` 报告: ${reportFile}`);
100
+ }
101
+ }
102
+ catch (e) {
103
+ console.error(`❌ 巡逻失败: ${e?.message || e}`);
104
+ }
105
+ }
106
+ async function main() {
107
+ process.chdir(projectPath);
108
+ console.log(`🛡️ Progmune 免疫巡逻 (P4) — 项目: ${projectPath}${watch ? "(持续监听)" : ""}`);
109
+ if (!watch) {
110
+ await scanOnce("扫描");
111
+ process.exit(0);
112
+ }
113
+ // 持续监听:RepoWatcher 防抖触发重扫
114
+ await scanOnce("首次");
115
+ let scanning = false;
116
+ const watcher = new agent_perception_1.RepoWatcher(projectPath, async (file) => {
117
+ if (scanning)
118
+ return; // 扫描期间的新变更合并进下一轮
119
+ scanning = true;
120
+ console.log(`🔍 检测到变更: ${file}`);
121
+ await scanOnce("重扫");
122
+ scanning = false;
123
+ }, 1500);
124
+ watcher.start();
125
+ console.log("👂 监听中(Ctrl+C 退出)…");
126
+ }
127
+ main().catch((e) => {
128
+ console.error(`❌ 巡逻 CLI 异常: ${e?.message || e}`);
129
+ process.exit(1);
130
+ });
@@ -40,6 +40,9 @@ exports.RETRY_HINT = `输出格式:紧凑 JSON 数组 [{"f":"函数名","to":"
40
40
  // ── Formatters ──
41
41
  /** Build a compact function list with parameter examples for LLM precision. */
42
42
  function buildCompactFuncList(funcs, allFuncs) {
43
+ // 语义 marker(__progmune_*,提取器注入供规则消费)不是真实可调用函数——
44
+ // 不出现在 LLM 可见函数列表中,防止被生成成真实调用导致编译失败
45
+ funcs = funcs.filter((f) => !String(f.name || "").startsWith("__progmune_"));
43
46
  // Example values for each type — helps LLM fill meaningful args
44
47
  function exampleValue(type, paramName) {
45
48
  const t = (type || "any").replace(/\[\]$/, "").toLowerCase();
package/dist/planner.js CHANGED
@@ -900,6 +900,8 @@ ${planner_prompts_1.RETRY_HINT}
900
900
  : await (0, llm_1.generate)(`你是程序合成助手。\n\n${currentPrompt}`);
901
901
  }
902
902
  catch (e) {
903
+ // 铁律:失败原因必须可见(不许静默绕过)——LLM 异常记录后继续重试/降级
904
+ console.error(`⚠️ LLM 调用失败 (attempt ${r + 1}/${maxRetries}): ${e?.message || e}`);
903
905
  continue;
904
906
  }
905
907
  if (!text)
@@ -704,12 +704,53 @@ async function collectProtocolViolations(ctx, callGraph) {
704
704
  let ssgMatchedCalls = 0;
705
705
  let ssgViolationCount = 0;
706
706
  try {
707
+ // ── P4.5 校准:TS/JS 项目在 ir.json 缺失时先提取 IR ──
708
+ // 序列必须来自「函数体内真实调用」而非「文件内函数声明名单」——
709
+ // 声明顺序 ≠ 执行顺序:正则扫描会把声明当调用(auth.ts 误报),
710
+ // 也会因调用数不足阈值漏掉单调用违规文件(bad_flow 漏报)。
711
+ if (!ctx.language || ctx.language === "typescript" || ctx.language === "javascript") {
712
+ try {
713
+ const fs = require("fs");
714
+ if (!fs.existsSync(path.join(ctx.projectPath, "ir.json"))) {
715
+ const { extractIR } = require("../extract-ir");
716
+ const ir = extractIR(ctx.projectPath);
717
+ fs.writeFileSync(path.join(ctx.projectPath, "ir.json"), JSON.stringify(ir, null, 2));
718
+ }
719
+ }
720
+ catch { /* best-effort — 回退正则扫描 */ }
721
+ }
707
722
  // ── Phase 1-5 Semantic Pipeline ──
708
723
  const callSequences = extractCallSequencesFromProject(ctx.projectPath, ctx.language);
709
724
  const flaggedCount = { value: 0 };
710
725
  const cleanCount = { value: 0 };
711
726
  // ── SSG State Machine: load protocol rules once ──
712
727
  protocolRulesData = (0, ssg_bridge_1.loadProtocolRules)(ctx.projectPath);
728
+ // ── P4.5: 合并项目 IR 注解协议(IR 优先,缺 namespace 继承内置 JSON) ──
729
+ // 内置 protocols.json 的规则是通用弱约束(如 generate_jwt pre=[]),
730
+ // 项目文件里的 @protocol 注解才是项目真实协议(如 pre=[PASSWORD_VERIFIED])。
731
+ // planner 已用此合并语义,trust 引擎需对齐,否则项目级前置约束不生效。
732
+ if (protocolRulesData) {
733
+ try {
734
+ const fs = require("fs");
735
+ const irPath = path.join(ctx.projectPath, "ir.json");
736
+ if (fs.existsSync(irPath)) {
737
+ const ir = JSON.parse(fs.readFileSync(irPath, "utf-8"));
738
+ if (Array.isArray(ir)) {
739
+ for (const f of ir) {
740
+ if (!f.protocol)
741
+ continue;
742
+ const protocol = { ...f.protocol };
743
+ const existing = protocolRulesData.rules.get(String(f.name));
744
+ if (existing?.namespace && !protocol.namespace) {
745
+ protocol.namespace = existing.namespace;
746
+ }
747
+ protocolRulesData.rules.set(String(f.name), protocol);
748
+ }
749
+ }
750
+ }
751
+ }
752
+ catch { /* best-effort */ }
753
+ }
713
754
  for (const seq of callSequences) {
714
755
  try {
715
756
  const semantic = await (0, api_semantic_mapper_1.mapSequenceToSemanticWithLLM)(seq.calls);
@@ -864,6 +905,46 @@ async function collectProtocolViolations(ctx, callGraph) {
864
905
  };
865
906
  }
866
907
  function extractCallSequencesFromProject(projectPath, language) {
908
+ // P4.5: 优先 IR 精确序列(每个函数体内的真实调用,从各自入口验证)
909
+ const irSequences = extractCallSequencesFromIR(projectPath);
910
+ if (irSequences.length > 0)
911
+ return irSequences;
912
+ // 回退:正则扫描(C 等无 IR 语言保持原行为)
913
+ return extractCallSequencesRegex(projectPath, language);
914
+ }
915
+ /**
916
+ * P4.5: 从 ir.json 构建 per-function 调用序列。
917
+ * 每个函数体内的 calls[] 是一条独立序列(从协议初始状态起步验证)——
918
+ * 函数声明名单不再是验证对象;语义 marker(__progmune_*)供规则消费,不作真实调用。
919
+ */
920
+ function extractCallSequencesFromIR(projectPath) {
921
+ try {
922
+ const fs = require("fs");
923
+ const irPath = path.join(projectPath, "ir.json");
924
+ if (!fs.existsSync(irPath))
925
+ return [];
926
+ const ir = JSON.parse(fs.readFileSync(irPath, "utf-8"));
927
+ if (!Array.isArray(ir))
928
+ return [];
929
+ const sequences = [];
930
+ for (const f of ir) {
931
+ const calls = (f.calls || []).filter((c) => typeof c === "string" && !c.startsWith("__progmune_"));
932
+ if (calls.length === 0)
933
+ continue;
934
+ sequences.push({
935
+ calls,
936
+ file: String(f.file || ""),
937
+ function: String(f.name || "unknown"),
938
+ });
939
+ }
940
+ return sequences;
941
+ }
942
+ catch {
943
+ return [];
944
+ }
945
+ }
946
+ /** 正则扫描回退路径(原实现):按文件扫调用样 token,≥4 才成序列。 */
947
+ function extractCallSequencesRegex(projectPath, language) {
867
948
  const sequences = [];
868
949
  try {
869
950
  const fs = require("fs");
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "progmune-runtime",
3
- "version": "3.3.7",
3
+ "version": "3.4.0",
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/",
7
7
  "protocols.json",
8
+ "CHANGELOG.md",
8
9
  "docs/Progmune_项目全解.html",
9
10
  "docs/Progmune_投资人白皮书_v2.0.html"
10
11
  ],
@@ -30,6 +31,8 @@
30
31
  "governance:json": "node dist/audit/cli.js --all --json",
31
32
  "governance:md": "node dist/audit/cli.js --all --markdown",
32
33
  "certify": "node dist/certify.js",
34
+ "agent": "node dist/agent-cli.js",
35
+ "patrol": "node dist/patrol-cli.js",
33
36
  "certify:html": "node dist/certify-html.js",
34
37
  "policy": "node dist/policy/cli.js check",
35
38
  "precision": "node dist/multi-repo-precision.js",