progmune-runtime 2.0.4 → 2.1.1

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.
Files changed (110) hide show
  1. package/.mcp.json +11 -0
  2. package/.progmune_allowlist +50 -0
  3. package/.test_report/test_report.md +87 -0
  4. package/Dockerfile +2 -10
  5. package/FAQ.md +167 -0
  6. package/README.md +108 -54
  7. package/demo-project/auth.ts +55 -0
  8. package/demo-project/tsconfig.json +8 -0
  9. package/dist/ab-stats.js +11 -0
  10. package/dist/acl-breakdown.js +13 -0
  11. package/dist/action-runtime.js +1 -0
  12. package/dist/all-sessions.js +11 -0
  13. package/dist/antibody-stats.js +11 -0
  14. package/dist/audit.js +222 -0
  15. package/dist/benchmark-count.js +7 -0
  16. package/dist/benchmark-full.js +17 -0
  17. package/dist/benchmark-pass-rate.js +54 -0
  18. package/dist/benchmark-report.js +67 -0
  19. package/dist/benchmark-save.js +62 -0
  20. package/dist/benchmark-status.js +15 -0
  21. package/dist/branch-ledger.js +393 -0
  22. package/dist/branch-tree-count.js +14 -0
  23. package/dist/check.js +506 -0
  24. package/dist/common-fixpath.js +12 -0
  25. package/dist/constraint-types.js +12 -0
  26. package/dist/deterministic-replay.js +283 -0
  27. package/dist/emitter.js +143 -12
  28. package/dist/exec-metrics.js +11 -0
  29. package/dist/execute.js +251 -0
  30. package/dist/extract-ir.js +592 -5
  31. package/dist/failure-collector.js +163 -0
  32. package/dist/failure-corpus.js +507 -35
  33. package/dist/failure-report.js +11 -0
  34. package/dist/failures.js +11 -0
  35. package/dist/fast-path-hits.js +13 -0
  36. package/dist/feedback.js +10 -3
  37. package/dist/file-lock.js +82 -0
  38. package/dist/find-session.js +10 -0
  39. package/dist/fingerprint-list.js +15 -0
  40. package/dist/gen-history-log.js +13 -0
  41. package/dist/generate.js +2 -1
  42. package/dist/generate_500.js +4 -2
  43. package/dist/genome.js +11 -0
  44. package/dist/heatmap-data.js +11 -0
  45. package/dist/heatmap.js +11 -0
  46. package/dist/immune-reporter.js +34 -22
  47. package/dist/ir-utils.js +18 -0
  48. package/dist/learned.js +11 -0
  49. package/dist/ledger-registry.js +252 -0
  50. package/dist/llm.js +46 -2
  51. package/dist/load-benchmarks.js +47 -0
  52. package/dist/main.js +2 -1
  53. package/dist/mcp-server.mjs +445 -43
  54. package/dist/memory-layer.js +54 -13
  55. package/dist/metrics.js +11 -0
  56. package/dist/obs-web.js +561 -0
  57. package/dist/p0_ssg_demo.js +255 -40
  58. package/dist/planner.js +996 -80
  59. package/dist/protocol-registry.js +112 -0
  60. package/dist/recent-session.js +12 -0
  61. package/dist/repair-proposal.js +363 -0
  62. package/dist/runtime-invariants.js +170 -0
  63. package/dist/runtime-types.js +117 -0
  64. package/dist/runtime.js +1 -0
  65. package/dist/search-planner.js +39 -9
  66. package/dist/semantic-snapshot.js +157 -0
  67. package/dist/semantic-trace.js +1497 -0
  68. package/dist/semantic-validator.js +3 -2
  69. package/dist/semantic_guard_test.js +2 -1
  70. package/dist/session-utils.js +19 -0
  71. package/dist/sessions.js +11 -0
  72. package/dist/ssg-validator.js +666 -20
  73. package/dist/svl-distribution.js +11 -0
  74. package/dist/terminal-status.js +11 -0
  75. package/dist/test_failure_corpus.js +3 -1
  76. package/dist/token-savings.js +11 -0
  77. package/dist/total-repairs.js +12 -0
  78. package/dist/unresolved-count.js +12 -0
  79. package/dist/utils.js +2 -0
  80. package/dist/valid-fingerprints.js +13 -0
  81. package/dist/validator.js +118 -60
  82. package/dist/verify-fps.js +11 -0
  83. package/dist/verify-ledgers.js +11 -0
  84. package/docs/whitepaper-style.css +77 -0
  85. package/docs/whitepaper-v2.1.md +609 -0
  86. package/docs/whitepaper-v2.2.md +1064 -0
  87. package/docs/whitepaper-v2.2.pdf +0 -0
  88. package/fly.toml +1 -1
  89. package/package.json +13 -4
  90. package/protocols.json +131 -11
  91. package/public/dashboard.html +119 -0
  92. package/server/hub.js +84 -12
  93. package/test/replay-golden/sess_1780063202050_mgeld.json +9 -0
  94. package/test/replay-golden/sess_1780064032560_gocld.json +354 -0
  95. package/test/replay-golden/sess_1780064413331_s2709.json +606 -0
  96. package/test/replay-golden/sess_1780064792710_y3avo.json +614 -0
  97. package/test/replay-golden.ts +84 -0
  98. package/test_benchmark.js +165 -0
  99. package/test_comprehensive.mjs +638 -0
  100. package/test_concurrency.js +129 -0
  101. package/test_ir_robustness.js +85 -0
  102. package/test_semantic_contracts.js +269 -0
  103. package/test_ssg_stress.js +156 -0
  104. package/test_svl3.js +58 -0
  105. package/tsconfig.json +1 -1
  106. package/.env.example +0 -3
  107. package/.progmune_memory/episodic.json +0 -186
  108. package/.progmune_memory/fingerprints.json +0 -7
  109. package/.progmune_memory/opt_in.json +0 -4
  110. package/immune_hub_data/2026-05-14.json +0 -50
package/dist/planner.js CHANGED
@@ -35,14 +35,16 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.plan = plan;
37
37
  const llm_1 = require("./llm");
38
+ const runtime_types_1 = require("./runtime-types");
38
39
  const action_runtime_1 = require("./action-runtime");
39
40
  const validator_1 = require("./validator");
40
41
  const semantic_validator_1 = require("./semantic-validator");
41
- const feedback_1 = require("./feedback");
42
42
  const utils_1 = require("./utils");
43
43
  const failure_corpus_1 = require("./failure-corpus");
44
44
  const memory_layer_1 = require("./memory-layer");
45
45
  const ssg_validator_1 = require("./ssg-validator");
46
+ const protocol_registry_1 = require("./protocol-registry");
47
+ const semantic_snapshot_1 = require("./semantic-snapshot");
46
48
  const fs = __importStar(require("fs"));
47
49
  function enrichActions(actions, ir) {
48
50
  return actions.map(a => {
@@ -88,160 +90,966 @@ function determineConstraintType(svl) {
88
90
  case "SVL-4": return "protocol";
89
91
  }
90
92
  }
91
- /** 加载 IR 中所有带 protocol 的函数为协议规则 */
93
+ /** 构建紧凑函数列表 包含能力元数据帮助 LLM 理解函数语义 */
94
+ function buildCompactFuncList(funcs, allFuncs) {
95
+ // Known string enums with example values
96
+ const ENUM_DEFAULTS = {
97
+ "SVL": '"SVL-4"', "RootCause": '"F01"', "BranchReason": '"repair_attempt"',
98
+ "RepairStrategy": '"insert"', "ConstraintType": '"protocol"',
99
+ };
100
+ return funcs.map((f) => {
101
+ const params = (f.params || []).map((p) => {
102
+ const t = (p.type || "any").replace(/\[\]$/, "");
103
+ const def = ENUM_DEFAULTS[t];
104
+ return def ? `${p.name}: ${def}` : `${p.name}: ${p.type}`;
105
+ }).join(",");
106
+ let line = `${f.name}(${params})->${f.returnType || "any"}`;
107
+ // Add capability metadata
108
+ const meta = [];
109
+ if (f.purpose)
110
+ meta.push(f.purpose.slice(0, 60));
111
+ if (f.produces && f.produces.length > 0)
112
+ meta.push(`→${f.produces.join(",")}`);
113
+ if (meta.length > 0)
114
+ line += ` // ${meta.join(" | ")}`;
115
+ return line;
116
+ }).join("\n");
117
+ }
118
+ /** Build capability chain hints from IR: producer→consumer relationships.
119
+ * e.g. "failureStats → formatFailureStats (FAILURE_STATS)" */
120
+ function buildChainHints(funcs) {
121
+ const chains = [];
122
+ for (const f of funcs) {
123
+ if (!f.produces)
124
+ continue;
125
+ for (const p of f.produces) {
126
+ const consumers = funcs.filter((x) => x.requires?.includes(p) && x.name !== f.name);
127
+ for (const c of consumers) {
128
+ chains.push(`${f.name}()→${c.name}() // ${p}`);
129
+ }
130
+ }
131
+ }
132
+ if (chains.length === 0)
133
+ return "";
134
+ return "\n推荐调用链(先调生产者,用 $变量名 传给消费者):\n" + chains.map(c => ` ${c}`).join("\n");
135
+ }
136
+ const SYSTEM_PROMPT = `你是程序合成助手。只输出 JSON 数组,不输出解释。
137
+
138
+ 格式:[{"f":"函数名","to":"变量名","a":[{"n":"参数名","t":"类型","v":值}]},{"r":"变量名"}]
139
+
140
+ 规则:
141
+ - 函数名从可用列表中选择,优先选注释中 purpose 匹配需求的函数
142
+ - 0参数函数直接用 "a":[]:{"f":"getAllSessions","to":"s","a":[]}
143
+ - 参数值规则(重要!):
144
+ - 字符串: "v":""(空串)或 "v":"SVL-4"(已知枚举值)
145
+ - 数字: "v":0 或 "v":1
146
+ - 布尔: "v":false
147
+ - 对象/数组: "v":{} as Type
148
+ - 上一个函数返回值: "v":"$变量名"($前缀引用)
149
+ - 返回值: {"r":"变量名"} — 必须返回,不能只调用不返回
150
+ - 链式调用:看到推荐调用链时,用 $变量名 把生产者输出传给消费者
151
+
152
+ 铁律:
153
+ - 函数签名中带引号的参数(如 "SVL-4")是字符串值,直接 v 中
154
+ - 字符串枚举(SVL等)用带引号的值,禁止 {} as Type
155
+ - 每个call的返回值用"to"命名变量,下一个call通过"$变量名"引用
156
+ - 最后一个action必须是{"r":"变量名"},不能以call结尾
157
+ - 如果你调了函数,必须return它的结果
158
+ - 只输出JSON`;
159
+ const RETRY_HINT = `输出格式:紧凑 JSON 数组 [{"f":"函数名","to":"变量名","a":[...]}]`;
160
+ /** 构建重试 prompt:精简但包含必要的 IR 语法提示 */
161
+ /** 解析 LLM 输出的紧凑 JSON 为 Action[]。
162
+ * 格式: [{"f":"fn","to":"var","a":[{"n":"p","t":"str","v":"x"}]}, {"r":"var"}, ...]
163
+ * f=function(→call), to=assignTo, a=args, r=return, if=condition
164
+ */
165
+ function parseActionJSON(text) {
166
+ const clean = text.replace(/```(?:json|javascript)?\s*/gi, '').replace(/```\s*/g, '').trim();
167
+ try {
168
+ const arr = JSON.parse(clean);
169
+ if (!Array.isArray(arr))
170
+ return null;
171
+ const actions = [];
172
+ for (const item of arr) {
173
+ if (!item || typeof item !== 'object')
174
+ return null;
175
+ if (item.r !== undefined) {
176
+ actions.push({ kind: "return", value: item.r });
177
+ }
178
+ else if (item.f) {
179
+ const a = {
180
+ kind: "call",
181
+ function: item.f,
182
+ args: (item.a || []).map((p) => ({
183
+ name: p.n || "arg",
184
+ type: p.t || "any",
185
+ value: p.v ?? null,
186
+ })),
187
+ };
188
+ if (item.to)
189
+ a.assignTo = item.to;
190
+ actions.push(a);
191
+ }
192
+ else if (item.if) {
193
+ const a = {
194
+ kind: "if",
195
+ condition: item.if,
196
+ thenActions: item.then ? parseActionJSON(JSON.stringify(item.then)) || [] : [],
197
+ elseActions: item.else ? parseActionJSON(JSON.stringify(item.else)) || [] : [],
198
+ };
199
+ actions.push(a);
200
+ }
201
+ else if (item.kind) {
202
+ // 完整 Action 格式(后向兼容)
203
+ actions.push(item);
204
+ }
205
+ else {
206
+ return null;
207
+ }
208
+ }
209
+ return actions.length > 0 ? actions : null;
210
+ }
211
+ catch {
212
+ return null;
213
+ }
214
+ }
215
+ /** 模糊函数名纠正:当 LLM 生成不存在的函数名时,用 Jaccard 相似度找最接近的 IR 函数 */
216
+ function correctFunctionNames(actions, ir) {
217
+ const corrections = [];
218
+ const corrected = actions.map((a, i) => {
219
+ if (a.kind !== "call" || !a.function)
220
+ return a;
221
+ // 跳过已知存在的函数和白名单
222
+ if (ir.some((f) => f.name === a.function))
223
+ return a;
224
+ if (["if", "for", "assign", "return"].includes(a.function))
225
+ return a;
226
+ // 用 Jaccard 相似度找最佳匹配
227
+ let bestMatch = "";
228
+ let bestScore = 0;
229
+ const target = a.function.toLowerCase();
230
+ for (const fn of ir) {
231
+ const score = (0, utils_1.jaccardSimilarity)(target, fn.name.toLowerCase());
232
+ if (score > bestScore) {
233
+ bestScore = score;
234
+ bestMatch = fn.name;
235
+ }
236
+ }
237
+ if (bestMatch && bestScore >= 0.3) {
238
+ corrections.push(`action[${i}]: "${a.function}" → "${bestMatch}" (相似度 ${bestScore.toFixed(2)})`);
239
+ return { ...a, function: bestMatch };
240
+ }
241
+ return a;
242
+ });
243
+ return { actions: corrected, corrections };
244
+ }
245
+ /** 参数签名预检:确保 call action 的参数数量与 IR 函数签名一致 */
246
+ function fixParameterCounts(actions, ir) {
247
+ const fixes = [];
248
+ const corrected = actions.map((a, i) => {
249
+ if (a.kind !== "call" || !a.function)
250
+ return a;
251
+ const def = ir.find((f) => f.name === a.function);
252
+ if (!def || !def.params)
253
+ return a;
254
+ const expected = def.params.length;
255
+ const actual = a.args ? a.args.length : 0;
256
+ if (actual === expected)
257
+ return a;
258
+ if (actual < expected) {
259
+ // 参数太少:填充缺失参数
260
+ const padded = [...(a.args || [])];
261
+ for (let j = actual; j < expected; j++) {
262
+ padded.push({ name: def.params[j].name, type: def.params[j].type || "any", value: "" });
263
+ }
264
+ fixes.push(`action[${i}] ${a.function}: 参数 ${actual}→${expected} (填充 ${expected - actual} 个缺失参数)`);
265
+ return { ...a, args: padded };
266
+ }
267
+ else {
268
+ // 参数太多:截断多余参数
269
+ fixes.push(`action[${i}] ${a.function}: 参数 ${actual}→${expected} (截断 ${actual - expected} 个多余参数)`);
270
+ return { ...a, args: a.args.slice(0, expected) };
271
+ }
272
+ });
273
+ return { actions: corrected, fixes };
274
+ }
275
+ /** 构建协议链提示:为 LLM 显示协议状态机的合法调用顺序 */
276
+ function buildProtocolChainHint(protocols) {
277
+ if (protocols.length === 0)
278
+ return "";
279
+ // 按命名空间分组
280
+ const byNs = new Map();
281
+ for (const p of protocols) {
282
+ const ns = p.protocol.namespace || "_global";
283
+ if (!byNs.has(ns))
284
+ byNs.set(ns, []);
285
+ byNs.get(ns).push(p);
286
+ }
287
+ const lines = ["\n⚠️ 协议约束(必须严格遵循调用顺序):"];
288
+ for (const [ns, fns] of byNs) {
289
+ if (ns === "_global" || fns.length <= 1)
290
+ continue;
291
+ lines.push(` [${ns}] 合法调用链: ${fns.map(p => p.function).join(" → ")}`);
292
+ for (const p of fns) {
293
+ const pre = p.protocol.pre_states?.join(",") || "(无)";
294
+ const post = p.protocol.post_states?.join(",") || "(无)";
295
+ lines.push(` ${p.function}: 前置状态=[${pre}] → 后置状态=[${post}]`);
296
+ }
297
+ }
298
+ return lines.join("\n");
299
+ }
300
+ /** JSON schema pre-validation: structural checks before IR-aware validation.
301
+ * Catches malformed actions that parseActionJSON() accepted but are semantically invalid. */
302
+ function validateActionSchema(actions) {
303
+ const errors = [];
304
+ const assignedVars = new Set();
305
+ const validVarName = /^[a-zA-Z_]\w*$/;
306
+ for (let i = 0; i < actions.length; i++) {
307
+ const a = actions[i];
308
+ const pos = `action[${i}]`;
309
+ if (!a.kind) {
310
+ errors.push(`${pos}: missing "kind" field`);
311
+ continue;
312
+ }
313
+ switch (a.kind) {
314
+ case "call": {
315
+ if (!a.function || typeof a.function !== "string") {
316
+ errors.push(`${pos}: call action requires "function" (string)`);
317
+ }
318
+ if (!Array.isArray(a.args)) {
319
+ errors.push(`${pos}: call action requires "args" (array)`);
320
+ }
321
+ else {
322
+ for (let j = 0; j < a.args.length; j++) {
323
+ const arg = a.args[j];
324
+ if (!arg || typeof arg !== "object") {
325
+ errors.push(`${pos}: args[${j}] must be an object`);
326
+ }
327
+ else if (arg.name === undefined && arg.type === undefined && arg.value === undefined) {
328
+ errors.push(`${pos}: args[${j}] missing name/type/value`);
329
+ }
330
+ }
331
+ }
332
+ if (a.assignTo !== undefined) {
333
+ if (typeof a.assignTo !== "string" || !validVarName.test(a.assignTo)) {
334
+ errors.push(`${pos}: assignTo "${a.assignTo}" is not a valid variable name`);
335
+ }
336
+ else if (assignedVars.has(a.assignTo)) {
337
+ errors.push(`${pos}: duplicate assignTo "${a.assignTo}" (previously assigned at another action)`);
338
+ }
339
+ else {
340
+ assignedVars.add(a.assignTo);
341
+ }
342
+ }
343
+ break;
344
+ }
345
+ case "return": {
346
+ if (a.value === undefined) {
347
+ errors.push(`${pos}: return action requires "value"`);
348
+ }
349
+ if (i < actions.length - 1) {
350
+ errors.push(`${pos}: return should be the last action (actions after return are unreachable)`);
351
+ }
352
+ break;
353
+ }
354
+ case "assign": {
355
+ if (!a.target || typeof a.target !== "string" || !validVarName.test(a.target)) {
356
+ errors.push(`${pos}: assign requires valid "target" variable name`);
357
+ }
358
+ else if (assignedVars.has(a.target)) {
359
+ errors.push(`${pos}: duplicate assign target "${a.target}"`);
360
+ }
361
+ else {
362
+ assignedVars.add(a.target);
363
+ }
364
+ if (a.value === undefined) {
365
+ errors.push(`${pos}: assign action requires "value"`);
366
+ }
367
+ break;
368
+ }
369
+ case "if": {
370
+ if (!a.condition || typeof a.condition !== "string") {
371
+ errors.push(`${pos}: if action requires "condition" (string)`);
372
+ }
373
+ if (!Array.isArray(a.thenActions)) {
374
+ errors.push(`${pos}: if action requires "thenActions" (array)`);
375
+ }
376
+ else {
377
+ const thenCheck = validateActionSchema(a.thenActions);
378
+ errors.push(...thenCheck.errors.map(e => `${pos}.thenActions: ${e}`));
379
+ }
380
+ if (a.elseActions && !Array.isArray(a.elseActions)) {
381
+ errors.push(`${pos}: elseActions must be an array if present`);
382
+ }
383
+ else if (a.elseActions) {
384
+ const elseCheck = validateActionSchema(a.elseActions);
385
+ errors.push(...elseCheck.errors.map(e => `${pos}.elseActions: ${e}`));
386
+ }
387
+ break;
388
+ }
389
+ case "for": {
390
+ if (!a.variable || typeof a.variable !== "string" || !validVarName.test(a.variable)) {
391
+ errors.push(`${pos}: for action requires valid "variable" name`);
392
+ }
393
+ if (!a.iterable || typeof a.iterable !== "string") {
394
+ errors.push(`${pos}: for action requires "iterable" (string)`);
395
+ }
396
+ if (!Array.isArray(a.bodyActions)) {
397
+ errors.push(`${pos}: for action requires "bodyActions" (array)`);
398
+ }
399
+ else {
400
+ const bodyCheck = validateActionSchema(a.bodyActions);
401
+ errors.push(...bodyCheck.errors.map(e => `${pos}.bodyActions: ${e}`));
402
+ }
403
+ break;
404
+ }
405
+ default: {
406
+ errors.push(`${pos}: unknown action kind "${a.kind}"`);
407
+ }
408
+ }
409
+ }
410
+ return { valid: errors.length === 0, errors };
411
+ }
412
+ /** 构建重试 prompt:精简但包含必要的 IR 语法提示 */
413
+ /** 加载 IR 中所有带 protocol 的函数为协议规则,同时从 protocols.json 加载命名空间初始状态 */
92
414
  function loadProtocols(ir) {
93
- return ir
415
+ const irProtocols = ir
94
416
  .filter((f) => f.protocol)
95
417
  .map((f) => ({ function: f.name, protocol: f.protocol }));
418
+ // Phase 6C: use ProtocolRegistry for nsInit (single source of truth)
419
+ const namespaceInitialStates = (0, protocol_registry_1.getNsInit)();
420
+ // Parse protocol rules from JSON (rules themselves still need the JSON for definitions)
421
+ let jsonProtocols = [];
422
+ try {
423
+ const protoDef = JSON.parse(fs.readFileSync("protocols.json", "utf-8"));
424
+ jsonProtocols = (0, ssg_validator_1.parseProtocolsFromJSON)(protoDef);
425
+ }
426
+ catch { }
427
+ // Merge: IR @protocol takes priority, but inherits JSON namespace
428
+ const merged = new Map();
429
+ for (const p of jsonProtocols)
430
+ merged.set(p.function, p);
431
+ for (const p of irProtocols) {
432
+ const existing = merged.get(p.function);
433
+ if (existing && existing.protocol.namespace && !p.protocol.namespace) {
434
+ p.protocol.namespace = existing.protocol.namespace;
435
+ }
436
+ merged.set(p.function, p);
437
+ }
438
+ return { protocols: [...merged.values()], namespaceInitialStates };
96
439
  }
97
- /** 验证动作序列的协议合法性 */
98
- function validateProtocol(actions, protocols, initialState) {
99
- const ssv = new ssg_validator_1.StateMachineValidator(protocols, initialState);
440
+ /** 验证动作序列的协议合法性,使用 Semantic Ledger (Phase 3) 纯函数 */
441
+ function validateProtocolWithTransitions(actions, protocols, namespaceInitialStates) {
442
+ const rules = new Map();
443
+ for (const p of protocols)
444
+ rules.set(p.function, p.protocol);
445
+ const ruleHash = (0, ssg_validator_1.hashRules)(rules);
446
+ const ctx = {
447
+ ledger: [],
448
+ currentState: (0, ssg_validator_1.rebuildState)([], namespaceInitialStates),
449
+ };
450
+ const transitions = [];
100
451
  for (let i = 0; i < actions.length; i++) {
101
452
  const a = actions[i];
102
453
  if (a.kind === "call" && a.function) {
103
- const result = ssv.apply(a.function);
104
- if (!result.valid) {
105
- return { valid: false, error: result.error, index: i };
454
+ const { valid, transition, rejection } = (0, ssg_validator_1.validateTransition)(ctx, a.function, i, rules, namespaceInitialStates, ruleHash);
455
+ transitions.push(transition);
456
+ if (!valid) {
457
+ const trace = transitions.map(t => ({
458
+ function: t.function,
459
+ statesBefore: t.statesBefore,
460
+ statesAfter: t.statesAfter,
461
+ }));
462
+ return { valid: false, rejection: rejection, index: i, trace, transitions, ruleHash };
106
463
  }
464
+ // Incremental update — O(1) per step
465
+ ctx.ledger = transitions;
466
+ ctx.currentState = transition.statesAfter;
467
+ }
468
+ }
469
+ // Invariant check on full ledger
470
+ const consistency = (0, ssg_validator_1.checkLedgerConsistency)(transitions, namespaceInitialStates);
471
+ if (!consistency.consistent) {
472
+ console.error(`[Invariant] Ledger consistency violations: ${consistency.violations.length}`);
473
+ for (const v of consistency.violations) {
474
+ console.error(` [${v.invariant}] index=${v.index}: ${v.detail}`);
107
475
  }
108
476
  }
109
- return { valid: true };
477
+ return { valid: true, transitions, ledgerConsistent: consistency.consistent, ruleHash };
110
478
  }
479
+ /** SSG 确定性修复:当协议违规有已知修复路径时,自动插入缺失函数 */
480
+ function attemptSSGRepair(actions, rejection, ir, protocols, namespaceInitialStates) {
481
+ if (!rejection.fixPath || rejection.fixPath.length === 0)
482
+ return null;
483
+ // 找到被拦截函数在序列中的位置
484
+ const blockedIdx = actions.findIndex(a => a.kind === "call" && a.function === rejection.blocked);
485
+ if (blockedIdx === -1)
486
+ return null;
487
+ // 为修复路径中的每个函数创建合成 Action
488
+ const repairActions = [];
489
+ for (const fnName of rejection.fixPath) {
490
+ const def = ir.find((f) => f.name === fnName);
491
+ if (!def)
492
+ return null;
493
+ const args = (def.params || []).map((p, i) => ({
494
+ name: p.name || `p${i}`,
495
+ type: p.type || 'any',
496
+ value: "",
497
+ }));
498
+ const assignTo = def.returnType && def.returnType !== 'void' && def.returnType !== 'undefined'
499
+ ? `${fnName}_result` : undefined;
500
+ const action = { kind: 'call', function: fnName, args };
501
+ if (assignTo)
502
+ action.assignTo = assignTo;
503
+ repairActions.push(action);
504
+ }
505
+ // 在被拦截函数前插入修复函数
506
+ const repaired = [
507
+ ...actions.slice(0, blockedIdx),
508
+ ...repairActions,
509
+ ...actions.slice(blockedIdx),
510
+ ];
511
+ // 重新验证
512
+ const recheck = validateProtocolWithTransitions(repaired, protocols, namespaceInitialStates);
513
+ if (recheck.valid) {
514
+ console.error(`🔧 SSG 确定性修复: 自动插入 ${rejection.fixPath.join(' → ')} 以解决协议违规`);
515
+ return repaired;
516
+ }
517
+ // 单步修复不够,尝试递归修复
518
+ if (recheck.rejection && recheck.rejection.fixPath && recheck.rejection.fixPath.length > 0) {
519
+ const nested = attemptSSGRepair(repaired, recheck.rejection, ir, protocols, namespaceInitialStates);
520
+ if (nested)
521
+ return nested;
522
+ }
523
+ return null;
524
+ }
525
+ /** @requires INTENT @produces ACTION_PLAN */
111
526
  async function plan(userIntent) {
112
527
  (0, llm_1.resetCallCount)();
113
- const ir = JSON.parse(fs.readFileSync("ir.json", "utf-8"));
528
+ const irRaw = JSON.parse(fs.readFileSync("ir.json", "utf-8"));
529
+ // Support both old (array) and new ({typeMap, functions}) formats
530
+ const ir = Array.isArray(irRaw) ? irRaw : (irRaw.functions || []);
531
+ // Helper: wrap actions into PlanResult
532
+ let repairMetrics = { applied: false, count: 0, branchIds: [] };
533
+ const wrapResult = (actions, repair) => ({
534
+ actions,
535
+ sessionId: session?.sessionId || "",
536
+ ruleHash: session?.ruleHash,
537
+ repairApplied: repair?.applied ?? repairMetrics.applied,
538
+ repairCount: repair?.count ?? repairMetrics.count,
539
+ repairBranchIds: repair?.branchIds ?? repairMetrics.branchIds,
540
+ });
541
+ // 初始化执行会话和快照(需在抗体快速通道前创建,以便记录 antibody hits)
542
+ const sessionId = (0, runtime_types_1.generateSessionId)();
543
+ const session = {
544
+ sessionId,
545
+ intent: userIntent,
546
+ attempts: [],
547
+ resolved: false,
548
+ startedAt: Date.now(),
549
+ };
550
+ const snapshot = (0, semantic_snapshot_1.createSnapshot)(ir, userIntent);
551
+ const snapshotId = (0, semantic_snapshot_1.saveSnapshot)(snapshot);
114
552
  // 语义模板快速通道
115
553
  const cachedTemplate = (0, memory_layer_1.findSemanticTemplate)(userIntent);
116
554
  if (cachedTemplate && cachedTemplate.successRate >= 0.8 && cachedTemplate.useCount >= 2) {
117
- console.log("⚡ 命中语义模板,直接复用已验证序列");
555
+ console.error("⚡ 命中语义模板,直接复用已验证序列");
118
556
  (0, memory_layer_1.recordEpisode)({ intent: userIntent, actions: cachedTemplate.actionSequence, success: true });
119
- return cachedTemplate.actionSequence;
557
+ return wrapResult(cachedTemplate.actionSequence);
558
+ }
559
+ // 提前加载协议(后续多处使用)
560
+ const { protocols, namespaceInitialStates } = loadProtocols(ir);
561
+ // 抗体免疫快速通道:查询高置信度抗体(ACL-3+),匹配则约束或跳过 LLM
562
+ const antibodies = (0, failure_corpus_1.queryAntibodies)(userIntent, "ACL-3");
563
+ let antibodyHint = "";
564
+ if (antibodies.length > 0) {
565
+ const top = antibodies[0];
566
+ const aclLabel = top.antibodyLevel;
567
+ console.error(`🛡️ 命中抗体: ${aclLabel} | 模式: ${top.signature} | 相似度: ${top._score.toFixed(2)}`);
568
+ console.error(` 修复路径: ${top.fixPath.join(" → ")}`);
569
+ // ACL-4: 全局稳定抗体 → 直接构建动作序列,跳过 LLM
570
+ if (aclLabel === "ACL-4" && top.fixPath.length > 0) {
571
+ const antibodyActions = top.fixPath.map((fnName) => {
572
+ const def = ir.find((f) => f.name === fnName);
573
+ const args = (def?.params || []).map((p, i) => ({
574
+ name: p.name || `p${i}`,
575
+ type: p.type || 'any',
576
+ value: "",
577
+ }));
578
+ const action = { kind: 'call', function: fnName, args };
579
+ if (def?.returnType && def.returnType !== 'void' && def.returnType !== 'undefined') {
580
+ action.assignTo = `${fnName}_result`;
581
+ }
582
+ return action;
583
+ });
584
+ const antibodyHit = {
585
+ level: aclLabel,
586
+ signature: top.signature,
587
+ fixPath: top.fixPath,
588
+ similarityScore: top._score,
589
+ action: "fast_path",
590
+ llmCallsSaved: 1,
591
+ estimatedTokensSaved: Math.ceil((0, llm_1.estimateTokens)(SYSTEM_PROMPT + userIntent) * 1.2),
592
+ };
593
+ // 验证抗体序列
594
+ const antibodyRuleHash = (() => {
595
+ const rules = new Map();
596
+ for (const p of protocols)
597
+ rules.set(p.function, p.protocol);
598
+ return (0, ssg_validator_1.hashRules)(rules);
599
+ })();
600
+ if (protocols.length > 0) {
601
+ const validation = validateProtocolWithTransitions(antibodyActions, protocols, namespaceInitialStates);
602
+ if (validation.valid) {
603
+ console.error(`⚡ ACL-4 抗体快速通道: 0 LLM 调用,节省 ~${Math.ceil((0, llm_1.estimateTokens)(SYSTEM_PROMPT + userIntent) * 1.2)} tokens (est.)`);
604
+ const antibodyAttempt = {
605
+ id: (0, runtime_types_1.generateAttemptId)(),
606
+ sessionId: session.sessionId,
607
+ attemptNumber: 1,
608
+ inputIntent: userIntent,
609
+ plannerSeed: (0, runtime_types_1.generatePlannerSeed)("antibody-acl4", "immune"),
610
+ constraintSnapshotId: snapshotId,
611
+ generatedActions: antibodyActions,
612
+ transitions: validation.transitions,
613
+ violations: [],
614
+ outcome: "success",
615
+ timestamp: Date.now(),
616
+ llmCallCount: 0,
617
+ durationMs: 0,
618
+ antibodyHit,
619
+ ruleHash: validation.ruleHash,
620
+ };
621
+ session.attempts.push(antibodyAttempt);
622
+ session.successfulAttempt = antibodyAttempt;
623
+ session.ruleHash = validation.ruleHash;
624
+ session.resolved = true;
625
+ session.snapshotId = snapshotId;
626
+ session.endedAt = Date.now();
627
+ (0, failure_corpus_1.recordSession)(session);
628
+ (0, memory_layer_1.recordEpisode)({ intent: userIntent, actions: antibodyActions, success: true });
629
+ return wrapResult(antibodyActions);
630
+ }
631
+ }
632
+ else {
633
+ // 无协议规则,直接信任抗体
634
+ console.error(`⚡ ACL-4 抗体快速通道: 0 LLM 调用(无协议约束),节省 ~${Math.ceil((0, llm_1.estimateTokens)(SYSTEM_PROMPT + userIntent) * 1.2)} tokens (est.)`);
635
+ const antibodyAttempt = {
636
+ id: (0, runtime_types_1.generateAttemptId)(),
637
+ sessionId: session.sessionId,
638
+ attemptNumber: 1,
639
+ inputIntent: userIntent,
640
+ plannerSeed: (0, runtime_types_1.generatePlannerSeed)("antibody-acl4", "immune"),
641
+ constraintSnapshotId: snapshotId,
642
+ generatedActions: antibodyActions,
643
+ transitions: [],
644
+ violations: [],
645
+ outcome: "success",
646
+ timestamp: Date.now(),
647
+ llmCallCount: 0,
648
+ durationMs: 0,
649
+ antibodyHit,
650
+ ruleHash: antibodyRuleHash,
651
+ };
652
+ session.attempts.push(antibodyAttempt);
653
+ session.successfulAttempt = antibodyAttempt;
654
+ session.ruleHash = antibodyRuleHash;
655
+ session.resolved = true;
656
+ session.snapshotId = snapshotId;
657
+ session.endedAt = Date.now();
658
+ (0, failure_corpus_1.recordSession)(session);
659
+ (0, memory_layer_1.recordEpisode)({ intent: userIntent, actions: antibodyActions, success: true });
660
+ return wrapResult(antibodyActions);
661
+ }
662
+ }
663
+ // ACL-3: 注入修复路径作为提示约束
664
+ antibodyHint = `\n已知正确调用顺序: ${top.fixPath.join(" → ")}。请遵循此顺序。`;
665
+ console.error(`💉 ACL-3 抗体注入提示: ${top.fixPath.join(" → ")}`);
120
666
  }
121
667
  const keywords = (0, utils_1.extractKeywords)(userIntent);
668
+ const intentLower = userIntent.toLowerCase();
122
669
  const scored = ir.map((f) => {
123
670
  let score = 0;
671
+ // Name match (existing)
124
672
  for (const kw of keywords) {
125
673
  score += (0, utils_1.jaccardSimilarity)(f.name.toLowerCase(), kw);
126
674
  if (f.name.toLowerCase().includes(kw))
127
675
  score += 0.5;
128
676
  }
677
+ // Capability Graph: purpose match
678
+ if (f.purpose) {
679
+ const purposeLower = f.purpose.toLowerCase();
680
+ for (const kw of keywords) {
681
+ if (purposeLower.includes(kw))
682
+ score += 1.0; // strong signal
683
+ }
684
+ // Full intent overlap with purpose
685
+ const intentWords = intentLower.split(/[\s,,]+/);
686
+ for (const w of intentWords) {
687
+ if (w.length > 2 && purposeLower.includes(w))
688
+ score += 0.3;
689
+ }
690
+ }
691
+ // Capability Graph: requires/produces capability matching
692
+ if (f.produces) {
693
+ for (const p of f.produces) {
694
+ if (intentLower.includes(p.toLowerCase().replace(/_/g, " ")))
695
+ score += 1.5;
696
+ }
697
+ }
698
+ if (f.requires) {
699
+ for (const r of f.requires) {
700
+ if (intentLower.includes(r.toLowerCase().replace(/_/g, " ")))
701
+ score += 0.5;
702
+ }
703
+ }
704
+ // Capability Graph: tag match
705
+ if (f.tags) {
706
+ for (const tag of f.tags) {
707
+ if (intentLower.includes(tag.toLowerCase()))
708
+ score += 0.8;
709
+ }
710
+ }
129
711
  return { ...f, score };
130
712
  });
131
713
  scored.sort((a, b) => b.score - a.score);
132
714
  const topFuncs = scored.slice(0, 15);
133
- const funcList = topFuncs.map((f) => {
134
- const rate = (0, feedback_1.getFunctionSuccessRate)(f.name);
135
- const star = rate > 0.8 ? "⭐" : rate > 0.5 ? "👍" : "⚠️";
136
- const params = f.params.map((p) => `${p.name}: ${p.type}`).join(", ");
137
- return `${star} ${f.name}(${params}) [${f.params.length}个参数] -> ${f.returnType} (成功率: ${(rate * 100).toFixed(0)}%)`;
138
- }).join("\n");
139
- const matchFunc = userIntent.match(/(?:实现|implement|编写|创建)\s*(\w+)\s*(?:函数|function)?/i);
715
+ const compactFuncList = buildCompactFuncList(topFuncs, ir);
716
+ const chainHints = buildChainHints(topFuncs);
717
+ // Known string-enum types: tell LLM these are strings, not objects
718
+ const STRING_ENUMS = {
719
+ "SVL": '"SVL-1"|"SVL-2"|"SVL-3"|"SVL-4"',
720
+ "RootCause": '"F01"|"F02"|...|"F10"',
721
+ "BranchReason": '"root"|"repair_attempt"|"alternative"',
722
+ "RepairStrategy": '"insert"|"replace"|"reorder"',
723
+ };
724
+ const typeHints = Object.keys(STRING_ENUMS).length > 0
725
+ ? `\n类型速查:${Object.entries(STRING_ENUMS).map(([k, v]) => `${k}=${v}`).join(",")}。这些类型传字符串值。`
726
+ : "";
727
+ const userIntentPart = userIntent.match(/(?:实现|implement|编写|创建)\s*(\w+)\s*(?:函数|function)?/i);
140
728
  const forbiddenFuncs = [];
141
- if (matchFunc) {
142
- const targetName = matchFunc[1];
729
+ if (userIntentPart) {
730
+ const targetName = userIntentPart[1];
143
731
  if (ir.find((f) => f.name.toLowerCase() === targetName.toLowerCase())) {
144
732
  forbiddenFuncs.push(targetName);
145
733
  }
146
734
  }
147
- const exampleCode = `assign("query_key", "user:123")
148
- callAssign("cache_get", "cached_data", "query_key")
149
- ifElse("cached_data", () => {
150
- output("cached_data")
151
- }, () => {
152
- callAssign("query_data", "fresh_data", "query_key")
153
- call("cache_set", "query_key", "fresh_data")
154
- output("fresh_data")
155
- })`;
156
- const basePrompt = `你能使用的函数:
157
- ${funcList}
735
+ const protocolChainHint = buildProtocolChainHint(protocols);
736
+ const userPrompt = `可用函数:
737
+ ${compactFuncList}${protocolChainHint}${chainHints}${typeHints}
158
738
 
159
- 绝对禁止调用列表外函数。
739
+ 需求:${userIntent}${antibodyHint}
160
740
 
161
- 示例(缓存查询,注意 assign 先于条件):
162
- ${exampleCode}
163
-
164
- 全局函数及用法规则:
165
- - 声明变量:assign("变量名", "值") callAssign("函数", "变量名", ...)
166
- - 条件分支:ifElse("变量名", () => { ... }, () => { ... })
167
- - 简单分支:ifBlock("变量名", () => { ... })
168
- - 调用:call("函数", "arg1", ...)
169
- - 返回:output("值或变量名")
170
-
171
- 铁律:
172
- 1. 必须先 assign 或 callAssign 再使用变量。
173
- 2. 参数数量必须与函数声明一致。
174
- 3. 条件括号内只能是已声明的变量名。
175
-
176
- 需求:
177
- ${userIntent}
178
-
179
- 只输出代码。`;
741
+ ${RETRY_HINT}
742
+ 只输出 JSON。`;
743
+ const estimatedTokens = (0, llm_1.estimateTokens)(SYSTEM_PROMPT + userPrompt);
744
+ console.error(`💰 估算 prompt token: ${estimatedTokens}`);
745
+ // ── 执行持久化:检查是否有未完成的 checkpoint ──
746
+ const cp = (0, failure_corpus_1.loadCheckpoint)(userIntent);
747
+ let startRetry = 0;
180
748
  let finalActions = [];
181
- let currentPrompt = basePrompt;
182
- // 加载协议规则,设定初始状态(例如未认证场景)
183
- const protocols = loadProtocols(ir);
184
- for (let r = 0; r < 3; r++) {
749
+ let currentPrompt = userPrompt;
750
+ let useSystem = true;
751
+ if (cp) {
752
+ console.error(`📌 恢复 checkpoint: 已完成 ${cp.attemptIndex} 次尝试,从第 ${cp.attemptIndex + 1} 次继续`);
753
+ startRetry = cp.attemptIndex;
754
+ currentPrompt = cp.currentPrompt;
755
+ useSystem = cp.useSystem;
756
+ // 从 checkpoint 恢复已有的 session.attempts
757
+ if (cp.sessionAttempts) {
758
+ session.attempts = cp.sessionAttempts;
759
+ }
760
+ }
761
+ const sessionRuleHash = (() => {
762
+ const rules = new Map();
763
+ for (const p of protocols)
764
+ rules.set(p.function, p.protocol);
765
+ return (0, ssg_validator_1.hashRules)(rules);
766
+ })();
767
+ session.ruleHash = sessionRuleHash;
768
+ function getMaskedFuncList() {
769
+ if (protocols.length === 0)
770
+ return compactFuncList;
771
+ const rules = new Map();
772
+ for (const p of protocols)
773
+ rules.set(p.function, p.protocol);
774
+ const ctx = { ledger: [], currentState: (0, ssg_validator_1.rebuildState)([], namespaceInitialStates) };
775
+ const legalFuncs = topFuncs.filter((f) => {
776
+ const proto = protocols.find((p) => p.function === f.name);
777
+ if (!proto)
778
+ return true;
779
+ const { valid } = (0, ssg_validator_1.validateTransition)(ctx, f.name, 0, rules, namespaceInitialStates);
780
+ return valid;
781
+ });
782
+ if (legalFuncs.length === topFuncs.length)
783
+ return compactFuncList;
784
+ return buildCompactFuncList(legalFuncs, ir);
785
+ }
786
+ const maxRetries = 3;
787
+ for (let r = startRetry; r < maxRetries; r++) {
185
788
  let text;
186
789
  try {
187
- text = await (0, llm_1.generate)(currentPrompt);
790
+ text = useSystem
791
+ ? await (0, llm_1.chat)(SYSTEM_PROMPT, currentPrompt)
792
+ : await (0, llm_1.generate)(`你是程序合成助手。\n\n${currentPrompt}`);
188
793
  }
189
794
  catch (e) {
190
795
  continue;
191
796
  }
192
797
  if (!text)
193
798
  continue;
194
- text = text.replace(/```javascript\s*/gi, '').replace(/```\s*/g, '').trim();
195
- console.log("📝 LLM 生成的代码:\n", text);
196
- const rawActions = (0, action_runtime_1.executeActionCode)(text);
197
- if (!rawActions || rawActions.length === 0) {
198
- console.log("⚠️ 代码执行失败,重试...");
199
- currentPrompt = basePrompt + "\n上一次代码无效,请严格模仿示例。";
799
+ text = text.replace(/```(?:json|javascript)?\s*/gi, '').replace(/```\s*/g, '').trim();
800
+ console.error("📝 LLM 输出:\n", text);
801
+ // 优先尝试 JSON 解析,失败则回退到 DSL 执行
802
+ let rawActions = parseActionJSON(text);
803
+ if (!rawActions || !Array.isArray(rawActions)) {
804
+ console.error("⚠️ JSON 解析失败,尝试 DSL 回退...");
805
+ rawActions = (0, action_runtime_1.executeActionCode)(text);
806
+ }
807
+ if (!rawActions || !Array.isArray(rawActions) || rawActions.length === 0) {
808
+ console.error("⚠️ 解析失败,重试...");
809
+ currentPrompt = `可用函数:\n${compactFuncList}${protocolChainHint}\n\n需求:${userIntent}\n\n上一次输出无效。请严格输出 JSON 数组。\n${RETRY_HINT}\n只输出 JSON。`;
810
+ useSystem = false;
200
811
  continue;
201
812
  }
813
+ // 🔧 SVL-1 修复: 模糊函数名纠正 — 把 LLM 编造的函数名映射到真实 IR 函数
814
+ const nameCorrection = correctFunctionNames(rawActions, ir);
815
+ if (nameCorrection.corrections.length > 0) {
816
+ console.error("🔧 [SVL-1 自动修复] 函数名纠正:");
817
+ for (const c of nameCorrection.corrections)
818
+ console.error(` ${c}`);
819
+ rawActions = nameCorrection.actions;
820
+ }
821
+ // 🔧 SVL-2 修复: 参数签名预检 — 自动调整 args 数量匹配 IR 签名
822
+ const paramFix = fixParameterCounts(rawActions, ir);
823
+ if (paramFix.fixes.length > 0) {
824
+ console.error("🔧 [SVL-2 自动修复] 参数数量修正:");
825
+ for (const f of paramFix.fixes)
826
+ console.error(` ${f}`);
827
+ rawActions = paramFix.actions;
828
+ }
829
+ // P2: JSON schema pre-validation — catch structural errors early
830
+ const schemaCheck = validateActionSchema(rawActions);
831
+ if (!schemaCheck.valid) {
832
+ console.error("⚠️ JSON schema 校验失败:", schemaCheck.errors.join("; "));
833
+ currentPrompt = `可用函数:\n${compactFuncList}${protocolChainHint}\n\n需求:${userIntent}\n\n输出格式错误:${schemaCheck.errors.join(";")}。请修正 JSON 结构。\n${RETRY_HINT}\n只输出 JSON。`;
834
+ useSystem = false;
835
+ const schemaViolation = {
836
+ svl: 1,
837
+ violatedConstraint: "schema",
838
+ actionIndex: 0,
839
+ description: schemaCheck.errors.join("; "),
840
+ };
841
+ const schemaAttempt = {
842
+ id: (0, runtime_types_1.generateAttemptId)(),
843
+ sessionId: session.sessionId,
844
+ attemptNumber: r + 1,
845
+ inputIntent: userIntent,
846
+ plannerSeed: (0, runtime_types_1.generatePlannerSeed)(currentPrompt, process.env.LLM_MODEL || "deepseek-chat"),
847
+ constraintSnapshotId: snapshotId,
848
+ generatedActions: rawActions,
849
+ transitions: [],
850
+ violations: [schemaViolation],
851
+ outcome: "constraint_violation",
852
+ timestamp: Date.now(),
853
+ llmCallCount: 0,
854
+ durationMs: 0,
855
+ ruleHash: sessionRuleHash,
856
+ };
857
+ session.attempts.push(schemaAttempt);
858
+ (0, memory_layer_1.recordEpisode)({ intent: userIntent, actions: rawActions, success: false, svlViolated: "SVL-1" });
859
+ (0, failure_corpus_1.saveCheckpoint)(userIntent, { attemptIndex: r + 1, sessionAttempts: session.attempts, currentPrompt, useSystem });
860
+ continue;
861
+ }
862
+ // 解析 $变量名 引用为实际变量名
863
+ rawActions = rawActions.map(a => {
864
+ if (a.kind === "call" && a.args) {
865
+ a.args = a.args.map(arg => {
866
+ if (typeof arg.value === 'string' && arg.value.startsWith('$')) {
867
+ return { ...arg, value: arg.value.slice(1) };
868
+ }
869
+ return arg;
870
+ });
871
+ }
872
+ return a;
873
+ });
202
874
  const enriched = enrichActions(rawActions, ir);
203
- const filtered = enriched.filter(a => !forbiddenFuncs.includes(a.function || ''));
875
+ const filtered = enriched.filter(a => !forbiddenFuncs.includes(a.kind === "call" ? a.function : ''));
876
+ let ssgTransitions = [];
204
877
  // 1) 基础序列校验
205
878
  const seqResult = (0, validator_1.validateActionSequence)(filtered);
206
879
  if (!seqResult.valid) {
207
880
  const errorsFlat = seqResult.errors.flat();
208
- console.log("⚠️ 序列校验失败:", errorsFlat.join(", "));
209
- const svl = determineSVL(errorsFlat);
881
+ console.error("⚠️ 序列校验失败:", errorsFlat.join(", "));
882
+ // Use structured violations directly from validator
883
+ const violations = seqResult.violations.length > 0
884
+ ? seqResult.violations
885
+ : [{
886
+ svl: 1,
887
+ violatedConstraint: "symbol_existence",
888
+ actionIndex: 0,
889
+ description: errorsFlat.join("; "),
890
+ }];
891
+ const primarySvl = `SVL-${violations[0].svl}`;
892
+ const attempt = {
893
+ id: (0, runtime_types_1.generateAttemptId)(),
894
+ sessionId: session.sessionId,
895
+ attemptNumber: r + 1,
896
+ inputIntent: userIntent,
897
+ plannerSeed: (0, runtime_types_1.generatePlannerSeed)(currentPrompt, process.env.LLM_MODEL || "deepseek-chat"),
898
+ constraintSnapshotId: snapshotId,
899
+ generatedActions: filtered,
900
+ transitions: [],
901
+ violations,
902
+ outcome: "constraint_violation",
903
+ timestamp: Date.now(),
904
+ llmCallCount: 0,
905
+ durationMs: 0,
906
+ ruleHash: sessionRuleHash,
907
+ };
908
+ session.attempts.push(attempt);
210
909
  (0, failure_corpus_1.recordFailure)({
211
910
  intent: userIntent,
212
911
  projectFunctions: ir.map((f) => f.name),
213
- violatedSVL: svl,
214
- constraintType: determineConstraintType(svl),
912
+ violatedSVL: primarySvl,
913
+ constraintType: violations[0].violatedConstraint,
215
914
  actionSequence: filtered,
216
915
  errorDetail: errorsFlat.join("; "),
916
+ ssgMissingFunctions: violations[0].missingStates,
917
+ plannerAttempt: r + 1,
918
+ plannerRetryTotal: maxRetries,
217
919
  });
218
- (0, memory_layer_1.recordEpisode)({ intent: userIntent, actions: filtered, success: false, svlViolated: svl });
219
- currentPrompt = basePrompt + `\n错误:${errorsFlat.join(";")}。请修正。`;
920
+ (0, memory_layer_1.recordEpisode)({ intent: userIntent, actions: filtered, success: false, svlViolated: primarySvl });
921
+ currentPrompt = `可用函数:\n${compactFuncList}${protocolChainHint}\n\n需求:${userIntent}\n\n错误:${errorsFlat.join(";")}。请修正。\n${RETRY_HINT}\n只输出 JSON。`;
922
+ useSystem = false;
923
+ (0, failure_corpus_1.saveCheckpoint)(userIntent, { attemptIndex: r + 1, sessionAttempts: session.attempts, currentPrompt, useSystem });
220
924
  continue;
221
925
  }
222
926
  // 2) 协议状态机校验 (SSG)
223
927
  if (protocols.length > 0) {
224
- const protoResult = validateProtocol(filtered, protocols, "UNAUTHENTICATED");
225
- if (!protoResult.valid) {
226
- console.log("🛡️ SSG 协议违规:", protoResult.error);
928
+ const protoResult = validateProtocolWithTransitions(filtered, protocols, namespaceInitialStates);
929
+ if (!protoResult.valid && protoResult.rejection) {
930
+ const rej = protoResult.rejection;
931
+ const explain = (0, ssg_validator_1.explainRejection)(rej);
932
+ console.error(explain);
933
+ const violation = {
934
+ svl: 4,
935
+ violatedConstraint: "protocol",
936
+ actionIndex: protoResult.index || 0,
937
+ currentStates: rej.currentState,
938
+ requiredStates: rej.requiredState,
939
+ missingStates: rej.missingFunctions,
940
+ fixPath: rej.fixPath,
941
+ namespace: rej.namespace,
942
+ description: JSON.stringify((0, ssg_validator_1.rejectionToJSON)(rej)),
943
+ };
944
+ const attempt = {
945
+ id: (0, runtime_types_1.generateAttemptId)(),
946
+ sessionId: session.sessionId,
947
+ attemptNumber: r + 1,
948
+ inputIntent: userIntent,
949
+ plannerSeed: (0, runtime_types_1.generatePlannerSeed)(currentPrompt, process.env.LLM_MODEL || "deepseek-chat"),
950
+ constraintSnapshotId: snapshotId,
951
+ generatedActions: filtered,
952
+ transitions: protoResult.transitions,
953
+ violations: [violation],
954
+ outcome: "constraint_violation",
955
+ timestamp: Date.now(),
956
+ llmCallCount: 0,
957
+ durationMs: 0,
958
+ ruleHash: sessionRuleHash,
959
+ };
960
+ session.attempts.push(attempt);
227
961
  (0, failure_corpus_1.recordFailure)({
228
962
  intent: userIntent,
229
963
  projectFunctions: ir.map((f) => f.name),
230
964
  violatedSVL: "SVL-4",
231
965
  constraintType: "protocol",
232
966
  actionSequence: filtered,
233
- errorDetail: protoResult.error,
967
+ errorDetail: JSON.stringify((0, ssg_validator_1.rejectionToJSON)(rej)),
968
+ ssgState: rej.currentState,
969
+ ssgTrace: protoResult.trace,
970
+ ssgFixPath: rej.fixPath,
971
+ ssgMissingFunctions: rej.missingFunctions,
972
+ plannerAttempt: r + 1,
973
+ plannerRetryTotal: maxRetries,
234
974
  });
235
975
  (0, memory_layer_1.recordEpisode)({ intent: userIntent, actions: filtered, success: false, svlViolated: "SVL-4" });
236
- currentPrompt = basePrompt + `\n协议错误:${protoResult.error}。请按照正确的业务顺序重新生成,确保先通过认证再签发令牌。`;
237
- continue;
976
+ // 尝试确定性修复:用 SSG fixPath 自动插入缺失函数
977
+ const repaired = attemptSSGRepair(filtered, rej, ir, protocols, namespaceInitialStates);
978
+ if (repaired) {
979
+ console.error("🔧 SSG 修复成功,跳过 LLM 重试");
980
+ // Phase 6: Repair → Branch — 保留原始序列作为证据
981
+ const { createRootBranch, createBranch } = require("./branch-ledger");
982
+ const rootBranch = createRootBranch(filtered);
983
+ rootBranch.outcome = "violation";
984
+ const repairBranch = createBranch(rootBranch, "repair_attempt", repaired);
985
+ repairBranch.outcome = "success";
986
+ session.branchTree = [rootBranch, repairBranch];
987
+ session.rootBranchId = rootBranch.id;
988
+ repairMetrics = {
989
+ applied: true,
990
+ count: rej.fixPath?.length || 0,
991
+ branchIds: [rootBranch.id, repairBranch.id],
992
+ };
993
+ // Phase 7: Record repair event for analytics
994
+ try {
995
+ const repairDir = ".progmune_corpus/repairs";
996
+ if (!fs.existsSync(repairDir))
997
+ fs.mkdirSync(repairDir, { recursive: true });
998
+ const repairRecord = {
999
+ sessionId: session.sessionId,
1000
+ timestamp: Date.now(),
1001
+ violation: "SVL-4",
1002
+ constraint: "protocol",
1003
+ blockedFunction: rej.blocked,
1004
+ namespace: rej.namespace,
1005
+ missingStates: rej.missingFunctions,
1006
+ fixPath: rej.fixPath,
1007
+ originalPlan: filtered.map((a) => a.kind === "call" ? a.function : a.kind),
1008
+ repairPlan: repaired.map((a) => a.kind === "call" ? a.function : a.kind),
1009
+ success: true,
1010
+ };
1011
+ fs.writeFileSync(`${repairDir}/repair_${session.sessionId}.json`, JSON.stringify(repairRecord, null, 2), "utf-8");
1012
+ }
1013
+ catch { }
1014
+ finalActions = repaired;
1015
+ break;
1016
+ }
1017
+ const maskedFuncList = getMaskedFuncList();
1018
+ currentPrompt = `当前协议状态只允许以下函数:\n${maskedFuncList}${protocolChainHint}\n\n需求:${userIntent}\n\n协议违规:${explain.replace(/\n/g, ';')}。请修正。\n${RETRY_HINT}\n只输出 JSON。`;
1019
+ useSystem = false;
1020
+ (0, failure_corpus_1.saveCheckpoint)(userIntent, { attemptIndex: r + 1, sessionAttempts: session.attempts, currentPrompt, useSystem });
238
1021
  continue;
239
1022
  }
1023
+ // SSG passed — capture transitions
1024
+ ssgTransitions = protoResult.transitions;
240
1025
  }
241
1026
  // 3) 语义合约校验
242
1027
  const semResult = (0, semantic_validator_1.checkSemantic)(userIntent, filtered);
243
1028
  if (!semResult.valid) {
244
- console.log("⚠️ 语义校验失败:", semResult.errors.join(", "));
1029
+ console.error("⚠️ 语义校验失败:", semResult.errors.join(", "));
1030
+ const violation = {
1031
+ svl: 4,
1032
+ violatedConstraint: "semantic_contract",
1033
+ actionIndex: 0,
1034
+ description: semResult.errors.join("; "),
1035
+ };
1036
+ const attempt = {
1037
+ id: (0, runtime_types_1.generateAttemptId)(),
1038
+ sessionId: session.sessionId,
1039
+ attemptNumber: r + 1,
1040
+ inputIntent: userIntent,
1041
+ plannerSeed: (0, runtime_types_1.generatePlannerSeed)(currentPrompt, process.env.LLM_MODEL || "deepseek-chat"),
1042
+ constraintSnapshotId: snapshotId,
1043
+ generatedActions: filtered,
1044
+ transitions: ssgTransitions,
1045
+ violations: [violation],
1046
+ outcome: "constraint_violation",
1047
+ timestamp: Date.now(),
1048
+ llmCallCount: 0,
1049
+ durationMs: 0,
1050
+ ruleHash: sessionRuleHash,
1051
+ };
1052
+ session.attempts.push(attempt);
245
1053
  (0, failure_corpus_1.recordFailure)({
246
1054
  intent: userIntent,
247
1055
  projectFunctions: ir.map((f) => f.name),
@@ -249,19 +1057,127 @@ ${userIntent}
249
1057
  constraintType: "protocol",
250
1058
  actionSequence: filtered,
251
1059
  errorDetail: semResult.errors.join("; "),
1060
+ plannerAttempt: r + 1,
1061
+ plannerRetryTotal: maxRetries,
252
1062
  });
253
1063
  (0, memory_layer_1.recordEpisode)({ intent: userIntent, actions: filtered, success: false, svlViolated: "SVL-4" });
254
- currentPrompt = basePrompt + `\n错误:${semResult.errors.join(";")}。请修正。`;
1064
+ currentPrompt = `可用函数:\n${compactFuncList}${protocolChainHint}\n\n需求:${userIntent}\n\n语义错误:${semResult.errors.join(";")}。请修正。\n${RETRY_HINT}\n只输出 JSON。`;
1065
+ useSystem = false;
1066
+ (0, failure_corpus_1.saveCheckpoint)(userIntent, { attemptIndex: r + 1, sessionAttempts: session.attempts, currentPrompt, useSystem });
255
1067
  continue;
256
1068
  }
1069
+ // 校验通过:构建成功 Attempt
1070
+ const successAntibodyHit = antibodyHint
1071
+ ? {
1072
+ level: antibodies[0]?.antibodyLevel || "ACL-3",
1073
+ signature: antibodies[0]?.signature || "",
1074
+ fixPath: antibodies[0]?.fixPath || [],
1075
+ similarityScore: antibodies[0]?._score || 0,
1076
+ action: "injected_hint",
1077
+ llmCallsSaved: 0,
1078
+ estimatedTokensSaved: 0,
1079
+ }
1080
+ : undefined;
1081
+ const successAttempt = {
1082
+ id: (0, runtime_types_1.generateAttemptId)(),
1083
+ sessionId: session.sessionId,
1084
+ attemptNumber: r + 1,
1085
+ inputIntent: userIntent,
1086
+ plannerSeed: (0, runtime_types_1.generatePlannerSeed)(currentPrompt, process.env.LLM_MODEL || "deepseek-chat"),
1087
+ constraintSnapshotId: snapshotId,
1088
+ generatedActions: filtered,
1089
+ transitions: ssgTransitions,
1090
+ violations: [],
1091
+ outcome: "success",
1092
+ timestamp: Date.now(),
1093
+ llmCallCount: 1,
1094
+ durationMs: 0,
1095
+ antibodyHit: successAntibodyHit,
1096
+ ruleHash: sessionRuleHash,
1097
+ };
1098
+ session.attempts.push(successAttempt);
1099
+ session.successfulAttempt = successAttempt;
257
1100
  finalActions = filtered;
258
1101
  break;
259
1102
  }
260
1103
  if (finalActions.length > 0) {
261
1104
  (0, memory_layer_1.recordEpisode)({ intent: userIntent, actions: finalActions, success: true });
1105
+ session.resolved = true;
1106
+ session.snapshotId = snapshotId;
1107
+ session.endedAt = Date.now();
1108
+ (0, failure_corpus_1.recordSession)(session); // Phase 5 will update recordSession to accept ExecutionSession
1109
+ (0, failure_corpus_1.clearCheckpoint)(userIntent);
262
1110
  }
263
1111
  else {
1112
+ // LLM 3 次重试失败,尝试本地规则回退
1113
+ console.error("[降级] LLM 规划失败,尝试本地规则回退");
1114
+ const fallback = generateFallbackPlan(userIntent, ir);
1115
+ if (fallback.length > 0) {
1116
+ console.error(`[降级] 本地规则生成了 ${fallback.length} 个动作`);
1117
+ (0, memory_layer_1.recordEpisode)({ intent: userIntent, actions: fallback, success: true });
1118
+ const fallbackAttempt = {
1119
+ id: (0, runtime_types_1.generateAttemptId)(),
1120
+ sessionId: session.sessionId,
1121
+ attemptNumber: session.attempts.length + 1,
1122
+ inputIntent: userIntent,
1123
+ plannerSeed: (0, runtime_types_1.generatePlannerSeed)("fallback", "local-rule"),
1124
+ constraintSnapshotId: snapshotId,
1125
+ generatedActions: fallback,
1126
+ transitions: [],
1127
+ violations: [],
1128
+ outcome: "success",
1129
+ timestamp: Date.now(),
1130
+ llmCallCount: 0,
1131
+ durationMs: 0,
1132
+ ruleHash: sessionRuleHash,
1133
+ };
1134
+ session.attempts.push(fallbackAttempt);
1135
+ session.successfulAttempt = fallbackAttempt;
1136
+ session.resolved = true;
1137
+ session.snapshotId = snapshotId;
1138
+ session.endedAt = Date.now();
1139
+ (0, failure_corpus_1.recordSession)(session);
1140
+ (0, failure_corpus_1.clearCheckpoint)(userIntent);
1141
+ return wrapResult(fallback);
1142
+ }
264
1143
  (0, memory_layer_1.recordEpisode)({ intent: userIntent, actions: [], success: false });
1144
+ session.resolved = false;
1145
+ session.snapshotId = snapshotId;
1146
+ session.endedAt = Date.now();
1147
+ (0, failure_corpus_1.recordSession)(session);
1148
+ (0, failure_corpus_1.clearCheckpoint)(userIntent);
1149
+ }
1150
+ return wrapResult(finalActions);
1151
+ }
1152
+ /** 本地规则回退:当 LLM 不可用时,根据意图关键词生成简单动作序列 */
1153
+ function generateFallbackPlan(intent, ir) {
1154
+ const intentLower = intent.toLowerCase();
1155
+ const actions = [];
1156
+ const keywords = intentLower.split(/[\s,,、]+/).filter(k => k.length > 1);
1157
+ const matchedFuncs = [];
1158
+ for (const kw of keywords) {
1159
+ for (const fn of ir) {
1160
+ if (fn.name.toLowerCase().includes(kw) && !matchedFuncs.find((f) => f.name === fn.name)) {
1161
+ matchedFuncs.push(fn);
1162
+ }
1163
+ }
1164
+ }
1165
+ if (matchedFuncs.length === 0)
1166
+ return [];
1167
+ for (const fn of matchedFuncs) {
1168
+ const args = (fn.params || []).map((p, i) => ({
1169
+ name: p.name || `p${i}`,
1170
+ type: p.type || 'any',
1171
+ value: `{{${p.name || `p${i}`}}}`
1172
+ }));
1173
+ const assignTo = fn.returnType && fn.returnType !== 'void' && fn.returnType !== 'undefined'
1174
+ ? `${fn.name}_result` : undefined;
1175
+ if (assignTo) {
1176
+ actions.push({ kind: 'call', function: fn.name, args, assignTo });
1177
+ }
1178
+ else {
1179
+ actions.push({ kind: 'call', function: fn.name, args });
1180
+ }
265
1181
  }
266
- return finalActions;
1182
+ return actions;
267
1183
  }