progmune-runtime 2.1.0 → 2.1.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.
@@ -48,6 +48,9 @@ exports.formatFailureStats = formatFailureStats;
48
48
  const fs = __importStar(require("fs"));
49
49
  const CORPUS_DIR = "failure-corpus";
50
50
  /** Classify a compile error string into a root cause. */
51
+ /** Classify a compile error into a root cause category. */
52
+ /** @requires ERROR_STRING @produces ROOT_CAUSE */
53
+ /** @requires ERROR_STRING @produces ROOT_CAUSE */
51
54
  function classifyError(error) {
52
55
  if (!error)
53
56
  return "F10";
@@ -68,6 +71,9 @@ function classifyError(error) {
68
71
  return "F10";
69
72
  }
70
73
  /** Classify a planning failure. */
74
+ /** Classify a planning failure into a root cause category. */
75
+ /** @requires ERROR_STRING @produces ROOT_CAUSE */
76
+ /** @requires ERROR_STRING @produces ROOT_CAUSE */
71
77
  function classifyPlanError(error) {
72
78
  if (!error)
73
79
  return "F10";
@@ -78,6 +84,8 @@ function classifyPlanError(error) {
78
84
  return "F07"; // most plan failures are parsing issues
79
85
  }
80
86
  /** Record a failure and save to disk. */
87
+ /** Record a generation failure to the failure corpus. */
88
+ /** @requires FAILURE_EVENT @produces FAILURE_ID */
81
89
  function recordFailure(record) {
82
90
  const id = `F-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
83
91
  const entry = {
@@ -94,6 +102,10 @@ function recordFailure(record) {
94
102
  return id;
95
103
  }
96
104
  /** Load all recorded failures. */
105
+ /** Load all recorded failures from the failure corpus. */
106
+ /** @requires FAILURE_CORPUS @produces FAILURE_LIST */
107
+ /** @requires FAILURE_CORPUS @produces FAILURE_LIST */
108
+ /** @requires FAILURE_CORPUS @produces FAILURE_LIST */
97
109
  function loadFailures() {
98
110
  if (!fs.existsSync(CORPUS_DIR))
99
111
  return [];
@@ -109,6 +121,10 @@ function loadFailures() {
109
121
  return failures.sort((a, b) => b.timestamp - a.timestamp);
110
122
  }
111
123
  /** Get failure statistics grouped by root cause. */
124
+ /** Get failure statistics grouped by root cause. */
125
+ /** @requires FAILURE_LIST @produces FAILURE_STATS */
126
+ /** @requires FAILURE_LIST @produces FAILURE_STATS */
127
+ /** @requires FAILURE_LIST @produces FAILURE_STATS */
112
128
  function failureStats() {
113
129
  const failures = loadFailures();
114
130
  const byRootCause = {};
@@ -123,6 +139,10 @@ function failureStats() {
123
139
  };
124
140
  }
125
141
  /** Format failure stats as readable text. */
142
+ /** Format failure statistics as a human-readable report. */
143
+ /** @requires FAILURE_STATS @produces FORMATTED_REPORT */
144
+ /** @requires FAILURE_STATS @produces FORMATTED_REPORT */
145
+ /** @requires FAILURE_STATS @produces FORMATTED_REPORT */
126
146
  function formatFailureStats() {
127
147
  const stats = failureStats();
128
148
  if (stats.total === 0)
@@ -66,6 +66,7 @@ function checkpointPath(intent) {
66
66
  const hash = Buffer.from(intent).toString("base64").replace(/[/+=]/g, "_").slice(0, 32);
67
67
  return path.join(CHECKPOINT_DIR, `ckpt_${hash}.json`);
68
68
  }
69
+ /** Save planner checkpoint for crash recovery. */
69
70
  function saveCheckpoint(intent, data) {
70
71
  ensureDir(CHECKPOINT_DIR);
71
72
  const cp = {
@@ -75,6 +76,7 @@ function saveCheckpoint(intent, data) {
75
76
  };
76
77
  fs.writeFileSync(checkpointPath(intent), JSON.stringify(cp, null, 2));
77
78
  }
79
+ /** Load a previously saved planner checkpoint. */
78
80
  function loadCheckpoint(intent) {
79
81
  try {
80
82
  const raw = fs.readFileSync(checkpointPath(intent), "utf-8");
@@ -84,12 +86,14 @@ function loadCheckpoint(intent) {
84
86
  return null;
85
87
  }
86
88
  }
89
+ /** Clear a saved planner checkpoint. */
87
90
  function clearCheckpoint(intent) {
88
91
  try {
89
92
  fs.unlinkSync(checkpointPath(intent));
90
93
  }
91
94
  catch { }
92
95
  }
96
+ /** Record a constraint violation to the failure corpus. */
93
97
  function recordFailure(record) {
94
98
  (0, file_lock_1.withLock)("failure-corpus", () => {
95
99
  ensureDir(CORPUS_DIR);
@@ -122,6 +126,8 @@ function recordFailure(record) {
122
126
  * 保存执行会话(含所有尝试、违规、状态转移)。
123
127
  * @protocol namespace=dev_pipeline pre_states=["CODE_EMITTED"] post_states=["SESSION_RECORDED"] invalidate=["CODE_EMITTED"]
124
128
  */
129
+ /** @requires EXECUTION_DATA @produces SESSION_ID */
130
+ /** @requires EXECUTION_DATA @produces SESSION_ID */
125
131
  function recordSession(session) {
126
132
  ensureDir(SESSIONS_DIR);
127
133
  const sessionId = session.sessionId || `sess_${Date.now()}_${Math.random().toString(36).slice(2, 7)}`;
@@ -158,6 +164,7 @@ function recordSession(session) {
158
164
  fs.writeFileSync(path.join(SESSIONS_DIR, `${sessionId}.json`), JSON.stringify(fullSession, null, 2));
159
165
  return sessionId;
160
166
  }
167
+ /** @requires FAILURE_CORPUS @produces FAILURE_LIST */
161
168
  function getAllFailures() {
162
169
  const records = [];
163
170
  if (!fs.existsSync(CORPUS_DIR))
@@ -178,9 +185,11 @@ function getAllFailures() {
178
185
  }
179
186
  return records;
180
187
  }
188
+ /** @requires FAILURE_LIST @produces FILTERED_FAILURES */
181
189
  function getFailuresBySVL(level) {
182
190
  return getAllFailures().filter(r => r.violatedSVL === level);
183
191
  }
192
+ /** @requires FAILURE_LIST @produces FAILURE_PATTERNS */
184
193
  function getTopFailurePatterns(limit = 5) {
185
194
  const groups = new Map();
186
195
  for (const r of getAllFailures()) {
@@ -200,6 +209,9 @@ function getTopFailurePatterns(limit = 5) {
200
209
  /** Get failure genome statistics: total failures, SVL distribution, constraint types, top patterns.
201
210
  * @tags failure, statistics, genome, audit
202
211
  */
212
+ /** Get failure genome statistics: total failures by SVL, constraint type, and fix path. */
213
+ /** @requires FAILURE_DATA @produces FAILURE_GENOME */
214
+ /** @requires FAILURE_DATA @produces FAILURE_GENOME */
203
215
  function getFailureGenome() {
204
216
  const sessions = getAllSessions();
205
217
  const bySVL = { "SVL-1": 0, "SVL-2": 0, "SVL-3": 0, "SVL-4": 0 };
@@ -259,6 +271,10 @@ function getFailureGenome() {
259
271
  /** Load all execution sessions from the corpus directory.
260
272
  * @tags session, corpus, audit, history
261
273
  */
274
+ /** @requires SESSION_DATA @produces SESSION_LIST */
275
+ /** @requires SESSION_CORPUS @produces SESSION_LIST */
276
+ /** @requires SESSION_CORPUS @produces SESSION_LIST */
277
+ /** @requires SESSION_CORPUS @produces SESSION_LIST */
262
278
  function getAllSessions() {
263
279
  const sessions = [];
264
280
  if (!fs.existsSync(SESSIONS_DIR))
@@ -349,6 +365,8 @@ function computeACL(count, distinctIntents, resolvedRate) {
349
365
  return "ACL-2";
350
366
  return "ACL-1";
351
367
  }
368
+ /** Get antibody patterns learned from failure history. */
369
+ /** @requires FAILURE_HISTORY @produces LEARNED_PATTERNS */
352
370
  function getLearnedPatterns() {
353
371
  const sessions = getAllSessions();
354
372
  const agg = new Map();
@@ -407,6 +425,8 @@ function getLearnedPatterns() {
407
425
  return { failureToFix: patterns };
408
426
  }
409
427
  /** 查询匹配当前意图的高置信度抗体(ACL-3+),用于推理层免疫加速 */
428
+ /** Query antibody registry for matching repair patterns. */
429
+ /** @requires FAILURE_SIGNATURE @produces ANTIBODY_MATCH */
410
430
  function queryAntibodies(intent, minACL = "ACL-3") {
411
431
  const { failureToFix } = getLearnedPatterns();
412
432
  const aclRank = { "ACL-1": 1, "ACL-2": 2, "ACL-3": 3, "ACL-4": 4 };
@@ -431,6 +451,9 @@ function queryAntibodies(intent, minACL = "ACL-3") {
431
451
  .sort((a, b) => b._score - a._score);
432
452
  }
433
453
  /** 语义热力图:哪些协议/层最脆弱,约束如何聚类 */
454
+ /** Get semantic heatmap showing fragile protocols and SVL hotspots. */
455
+ /** @requires FAILURE_DATA @produces HEATMAP */
456
+ /** @requires FAILURE_HEATMAP @produces HEATMAP_DATA */
434
457
  function getSemanticHeatmap() {
435
458
  const sessions = getAllSessions();
436
459
  // Count total violations from sessions
@@ -493,6 +516,8 @@ function getSemanticHeatmap() {
493
516
  /** Get antibody efficacy statistics: hits by level, tokens saved, top signatures.
494
517
  * @tags antibody, immune, statistics, efficiency
495
518
  */
519
+ /** @requires ANTIBODY_DATA @produces ANTIBODY_STATS */
520
+ /** @requires ANTIBODY_DATA @produces ANTIBODY_STATS */
496
521
  function getAntibodyStats() {
497
522
  const sessions = getAllSessions();
498
523
  let totalHits = 0;
@@ -537,6 +562,7 @@ function getAntibodyStats() {
537
562
  .slice(0, 10);
538
563
  return { totalHits, fastPathHits, injectedHintHits, totalLLMCallsSaved, totalTokensSaved, byLevel, topSignatures };
539
564
  }
565
+ /** Generate candidate immune rules from failure patterns. */
540
566
  function generateCandidateRules() {
541
567
  const genome = getFailureGenome();
542
568
  const rules = [];
package/dist/feedback.js CHANGED
@@ -41,11 +41,13 @@ const fs = __importStar(require("fs"));
41
41
  const path = __importStar(require("path"));
42
42
  const file_lock_1 = require("./file-lock");
43
43
  const FEEDBACK_PATH = path.resolve(__dirname, "../feedback.json");
44
+ /** @requires CORPUS @produces FEEDBACK_DATA */
44
45
  function loadFeedback() {
45
46
  if (!fs.existsSync(FEEDBACK_PATH))
46
47
  return [];
47
48
  return JSON.parse(fs.readFileSync(FEEDBACK_PATH, "utf-8"));
48
49
  }
50
+ /** @requires FEEDBACK_EVENT @produces FEEDBACK_ID */
49
51
  function saveFeedback(record) {
50
52
  (0, file_lock_1.withLock)("feedback.json", () => {
51
53
  const data = loadFeedback();
@@ -53,6 +55,7 @@ function saveFeedback(record) {
53
55
  fs.writeFileSync(FEEDBACK_PATH, JSON.stringify(data, null, 2));
54
56
  });
55
57
  }
58
+ /** @requires FUNCTION_NAME @produces SUCCESS_RATE */
56
59
  function getFunctionSuccessRate(funcName) {
57
60
  const records = loadFeedback();
58
61
  const funcRecords = records.filter(r => r.functionName === funcName);
@@ -61,6 +64,7 @@ function getFunctionSuccessRate(funcName) {
61
64
  const successCount = funcRecords.filter(r => r.success).length;
62
65
  return successCount / funcRecords.length;
63
66
  }
67
+ /** @requires EXECUTION_DATA @produces RUN_ID */
64
68
  function recordRun(intent, actions, success, error) {
65
69
  for (const action of actions) {
66
70
  if (action.kind === "call") {
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.computeHealthScore = computeHealthScore;
4
+ exports.formatHealthLevel = formatHealthLevel;
5
+ exports.countSessionLedgers = countSessionLedgers;
6
+ /** Compute overall immune health score from failure and antibody data.
7
+ * @requires FAILURE_GENOME @produces HEALTH_SCORE
8
+ * @tags health, score, immune
9
+ */
10
+ function computeHealthScore(failureGenome, antibodyStats) {
11
+ const totalFailures = failureGenome?.totalFailures || 0;
12
+ const totalHits = antibodyStats?.totalHits || 0;
13
+ const base = 100;
14
+ const failurePenalty = Math.min(totalFailures * 2, 40);
15
+ const antibodyBonus = Math.min(totalHits * 3, 20);
16
+ return Math.max(0, Math.min(100, base - failurePenalty + antibodyBonus));
17
+ }
18
+ /** Format a health score as a status level.
19
+ * @requires HEALTH_SCORE @produces HEALTH_STATUS
20
+ * @tags health, format
21
+ */
22
+ function formatHealthLevel(score) {
23
+ if (score >= 90)
24
+ return "Excellent";
25
+ if (score >= 70)
26
+ return "Good";
27
+ if (score >= 50)
28
+ return "Fair";
29
+ return "Poor";
30
+ }
31
+ /** Validate a ledger and return pass/fail counts.
32
+ * @requires SESSION_LIST @produces VALIDATION_COUNTS
33
+ * @tags ledger, validation, audit
34
+ */
35
+ function countSessionLedgers(sessions) {
36
+ const withLedger = sessions.filter((s) => {
37
+ return s.attempts?.some((a) => a.transitions?.length > 0);
38
+ }).length;
39
+ return { total: sessions.length, withLedger };
40
+ }
@@ -97,6 +97,7 @@ function extractFingerprints(cursor) {
97
97
  }
98
98
  return fingerprints;
99
99
  }
100
+ /** @requires CORPUS @produces FINGERPRINT_REPORT */
100
101
  async function reportFingerprints() {
101
102
  const cursor = getReportCursor();
102
103
  const fingerprints = extractFingerprints(cursor);
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.countExported = countExported;
4
+ exports.mergeResults = mergeResults;
5
+ /** Count exported functions in an IR function list.
6
+ * @requires IR_FUNCTIONS @produces EXPORT_COUNT
7
+ * @tags ir, count, export
8
+ */
9
+ function countExported(ir) {
10
+ return ir.filter((f) => f.exported).length;
11
+ }
12
+ /** Merge two results into a combined object.
13
+ * @requires RESULT_A @produces MERGED_RESULT
14
+ * @tags merge, combine
15
+ */
16
+ function mergeResults(a, b) {
17
+ return { first: a, second: b };
18
+ }
@@ -62,6 +62,8 @@ function fingerprintPath(sessionId) {
62
62
  // ── Core API ──
63
63
  /** Register a ledger fingerprint (execution certificate).
64
64
  * Called after a session is recorded — creates an immutable proof of the ledger state. */
65
+ /** Register a ledger fingerprint as an execution certificate. */
66
+ /** @requires LEDGER_DATA @produces FINGERPRINT */
65
67
  function registerFingerprint(sessionId, transitions, ruleHash) {
66
68
  const dir = fingerprintsDir();
67
69
  if (!fs.existsSync(dir)) {
@@ -78,6 +80,8 @@ function registerFingerprint(sessionId, transitions, ruleHash) {
78
80
  return fingerprint;
79
81
  }
80
82
  /** Get a single stored fingerprint by sessionId. Returns null if not registered. */
83
+ /** Get a stored ledger fingerprint by session ID. */
84
+ /** @requires SESSION_ID @produces FINGERPRINT */
81
85
  function getFingerprint(sessionId) {
82
86
  const fpPath = fingerprintPath(sessionId);
83
87
  if (!fs.existsSync(fpPath))
@@ -90,6 +94,7 @@ function getFingerprint(sessionId) {
90
94
  }
91
95
  }
92
96
  /** List all registered fingerprints, sorted by timestamp (oldest first). */
97
+ /** List all registered ledger fingerprints. */
93
98
  function getFingerprintRegistry() {
94
99
  const dir = fingerprintsDir();
95
100
  if (!fs.existsSync(dir))
@@ -112,6 +117,7 @@ function getFingerprintRegistry() {
112
117
  }
113
118
  /** Verify a single session's fingerprint.
114
119
  * Requires the session's transitions to re-hash and compare. */
120
+ /** Verify a single ledger fingerprint against current data. */
115
121
  function verifyFingerprint(sessionId, transitions, currentRuleHash) {
116
122
  const stored = getFingerprint(sessionId);
117
123
  if (!stored) {
@@ -144,6 +150,9 @@ function verifyFingerprint(sessionId, transitions, currentRuleHash) {
144
150
  }
145
151
  /** Verify all registered fingerprints.
146
152
  * Loads each session to re-hash and compare against the stored fingerprint. */
153
+ /** Verify all registered ledger fingerprints and return tampered status. */
154
+ /** @requires FINGERPRINT_DATA @produces VERIFICATION_RESULT */
155
+ /** @requires FINGERPRINT_DATA @produces VERIFICATION_RESULT */
147
156
  function verifyAllFingerprints(currentRuleHash) {
148
157
  const fingerprints = getFingerprintRegistry();
149
158
  const results = [];
@@ -199,6 +208,8 @@ function verifyAllFingerprints(currentRuleHash) {
199
208
  }
200
209
  /** Register fingerprints for all sessions that don't yet have one.
201
210
  * Called during `npm run check` to ensure all sessions are fingerprinted. */
211
+ /** Register fingerprints for all sessions that lack them. */
212
+ /** @requires SESSION_DATA @produces FINGERPRINT_DATA */
202
213
  function registerAllMissingFingerprints() {
203
214
  const sessionsDir = path.resolve(process.env.PROGMUNE_PROJECT_DIR || process.cwd(), ".progmune_corpus/sessions");
204
215
  if (!fs.existsSync(sessionsDir))
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.countTotalTransitions = countTotalTransitions;
4
+ exports.formatTransitionCount = formatTransitionCount;
5
+ exports.hasViolations = hasViolations;
6
+ exports.countSessionsWithViolations = countSessionsWithViolations;
7
+ /** Count total transitions across all session ledgers.
8
+ * @requires SESSION_LIST @produces TRANSITION_COUNT
9
+ * @tags ledger, count, statistics
10
+ */
11
+ function countTotalTransitions(sessions) {
12
+ let count = 0;
13
+ for (const s of sessions) {
14
+ for (const a of (s.attempts || [])) {
15
+ count += (a.transitions || []).length;
16
+ }
17
+ }
18
+ return count;
19
+ }
20
+ /** Format a transition count as a summary string.
21
+ * @requires TRANSITION_COUNT @produces FORMATTED_COUNT
22
+ * @tags ledger, format
23
+ */
24
+ function formatTransitionCount(count) {
25
+ return `${count} total transitions across all sessions`;
26
+ }
27
+ /** Check if a session has any protocol violations in its attempts.
28
+ * @requires SESSION_DATA @produces VIOLATION_CHECK
29
+ * @tags ledger, validation
30
+ */
31
+ function hasViolations(session) {
32
+ return (session.attempts || []).some((a) => (a.violations || []).length > 0);
33
+ }
34
+ /** Count sessions that have violations.
35
+ * @requires SESSION_LIST @produces VIOLATION_COUNT
36
+ * @tags ledger, validation, statistics
37
+ */
38
+ function countSessionsWithViolations(sessions) {
39
+ return sessions.filter(s => hasViolations(s)).length;
40
+ }
package/dist/llm.js CHANGED
@@ -36,11 +36,13 @@ const client = new openai_1.default({ apiKey, baseURL });
36
36
  exports.callCount = 0;
37
37
  function resetCallCount() { exports.callCount = 0; }
38
38
  /** 粗略 token 估算:CJK 字符 ~1.5 token/字,其余 ~0.4 token/字符 */
39
+ /** @requires TEXT @produces TOKEN_COUNT */
39
40
  function estimateTokens(text) {
40
41
  const cjk = (text.match(/[一-鿿㐀-䶿]/g) || []).length;
41
42
  const other = text.length - cjk;
42
43
  return Math.ceil(cjk * 1.5 + other * 0.4);
43
44
  }
45
+ /** @requires PROMPT @produces LLM_RESPONSE */
44
46
  async function generate(prompt) {
45
47
  exports.callCount++;
46
48
  const resp = await client.chat.completions.create({
@@ -51,6 +53,7 @@ async function generate(prompt) {
51
53
  return resp.choices[0]?.message?.content || "";
52
54
  }
53
55
  /** 带 system prompt 的调用:静态规则放 system,动态内容放 user,语义分离便于未来对接各平台缓存策略 */
56
+ /** @requires SYSTEM_PROMPT @produces LLM_RESPONSE */
54
57
  async function chat(systemPrompt, userPrompt) {
55
58
  exports.callCount++;
56
59
  const resp = await client.chat.completions.create({
@@ -91,6 +91,7 @@ function saveEpisodes(episodes) {
91
91
  fs.writeFileSync(EPISODIC_FILE, JSON.stringify(fresh.slice(0, MAX_EPISODES), null, 2));
92
92
  });
93
93
  }
94
+ /** @requires EXECUTION_DATA @produces MEMORY_ID */
94
95
  function recordEpisode(episode) {
95
96
  const episodes = loadEpisodes();
96
97
  const newEpisode = {
@@ -104,6 +105,7 @@ function recordEpisode(episode) {
104
105
  }
105
106
  saveEpisodes(episodes);
106
107
  }
108
+ /** @requires LIMIT @produces EPISODE_LIST */
107
109
  function getRecentEpisodes(limit = 10) {
108
110
  return loadEpisodes().slice(0, limit);
109
111
  }
@@ -172,6 +174,7 @@ function consolidateSemantic(minOccurrences = 3) {
172
174
  saveSemantic(templates);
173
175
  console.error(`[语义记忆] 巩固完成,模板数量: ${templates.length}`);
174
176
  }
177
+ /** @requires INTENT @produces TEMPLATE */
175
178
  function findSemanticTemplate(intent) {
176
179
  const templates = loadSemantic();
177
180
  if (templates.length === 0)
package/dist/planner.js CHANGED
@@ -90,29 +90,72 @@ function determineConstraintType(svl) {
90
90
  case "SVL-4": return "protocol";
91
91
  }
92
92
  }
93
- /** 构建紧凑函数列表(约节省 50% token) */
94
- function buildCompactFuncList(funcs) {
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
+ };
95
100
  return funcs.map((f) => {
96
- const params = (f.params || []).map((p) => `${p.name}:${p.type}`).join(",");
97
- return `${f.name}(${params})->${f.returnType || "any"}`;
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;
98
116
  }).join("\n");
99
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
+ }
100
136
  const SYSTEM_PROMPT = `你是程序合成助手。只输出 JSON 数组,不输出解释。
101
137
 
102
- 格式(紧凑一行):
103
- [{"f":"函数名","to":"变量名","a":[{"n":"参数名","t":"类型","v":值}]},{"r":"变量名"}]
138
+ 格式:[{"f":"函数名","to":"变量名","a":[{"n":"参数名","t":"类型","v":值}]},{"r":"变量名"}]
104
139
 
105
140
  规则:
106
- - "f": 函数名,"to": 接收返回值的变量名(void 函数省略)
107
- - "a": 参数数组,每个参数 n=名称 t=类型 v=值
108
- - 字符串值直接写: "v":"hello",数字: "v":42
109
- - 引用之前返回的变量: "v":"$变量名"($前缀表示这是变量引用)
110
- - {"r":"变量名"} 表示返回该变量
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
+ - 链式调用:看到推荐调用链时,用 $变量名 把生产者输出传给消费者
111
151
 
112
- 示例 — 提取 IR 后校验:
113
- [{"f":"extractIR","to":"ir","a":[{"n":"root","t":"str","v":"."}]},{"f":"validateActionSequence","to":"ok","a":[{"n":"actions","t":"any","v":"$ir"}]},{"r":"ok"}]
114
-
115
- 铁律:只输出 JSON 数组,双引号,不输出解释`;
152
+ 铁律:
153
+ - 函数签名中带引号的参数(如 "SVL-4")是字符串值,直接 v
154
+ - 字符串枚举(SVL等)用带引号的值,禁止 {} as Type
155
+ - 每个call的返回值用"to"命名变量,下一个call通过"$变量名"引用
156
+ - 最后一个action必须是{"r":"变量名"},不能以call结尾
157
+ - 如果你调了函数,必须return它的结果
158
+ - 只输出JSON`;
116
159
  const RETRY_HINT = `输出格式:紧凑 JSON 数组 [{"f":"函数名","to":"变量名","a":[...]}]`;
117
160
  /** 构建重试 prompt:精简但包含必要的 IR 语法提示 */
118
161
  /** 解析 LLM 输出的紧凑 JSON 为 Action[]。
@@ -479,6 +522,7 @@ function attemptSSGRepair(actions, rejection, ir, protocols, namespaceInitialSta
479
522
  }
480
523
  return null;
481
524
  }
525
+ /** @requires INTENT @produces ACTION_PLAN */
482
526
  async function plan(userIntent) {
483
527
  (0, llm_1.resetCallCount)();
484
528
  const irRaw = JSON.parse(fs.readFileSync("ir.json", "utf-8"));
@@ -621,18 +665,65 @@ async function plan(userIntent) {
621
665
  console.error(`💉 ACL-3 抗体注入提示: ${top.fixPath.join(" → ")}`);
622
666
  }
623
667
  const keywords = (0, utils_1.extractKeywords)(userIntent);
668
+ const intentLower = userIntent.toLowerCase();
624
669
  const scored = ir.map((f) => {
625
670
  let score = 0;
671
+ // Name match (existing)
626
672
  for (const kw of keywords) {
627
673
  score += (0, utils_1.jaccardSimilarity)(f.name.toLowerCase(), kw);
628
674
  if (f.name.toLowerCase().includes(kw))
629
675
  score += 0.5;
630
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
+ }
631
711
  return { ...f, score };
632
712
  });
633
713
  scored.sort((a, b) => b.score - a.score);
634
714
  const topFuncs = scored.slice(0, 15);
635
- const compactFuncList = buildCompactFuncList(topFuncs);
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
+ : "";
636
727
  const userIntentPart = userIntent.match(/(?:实现|implement|编写|创建)\s*(\w+)\s*(?:函数|function)?/i);
637
728
  const forbiddenFuncs = [];
638
729
  if (userIntentPart) {
@@ -643,7 +734,7 @@ async function plan(userIntent) {
643
734
  }
644
735
  const protocolChainHint = buildProtocolChainHint(protocols);
645
736
  const userPrompt = `可用函数:
646
- ${compactFuncList}${protocolChainHint}
737
+ ${compactFuncList}${protocolChainHint}${chainHints}${typeHints}
647
738
 
648
739
  需求:${userIntent}${antibodyHint}
649
740
 
@@ -690,7 +781,7 @@ ${RETRY_HINT}
690
781
  });
691
782
  if (legalFuncs.length === topFuncs.length)
692
783
  return compactFuncList;
693
- return buildCompactFuncList(legalFuncs);
784
+ return buildCompactFuncList(legalFuncs, ir);
694
785
  }
695
786
  const maxRetries = 3;
696
787
  for (let r = startRetry; r < maxRetries; r++) {
@@ -53,11 +53,14 @@ const ssg_validator_1 = require("./ssg-validator");
53
53
  // ── Singleton cache ──
54
54
  let cached = null;
55
55
  /** Invalidate the cache (call after protocols.json changes). */
56
+ /** Invalidate cached protocol configuration for reload. */
56
57
  function invalidateProtocolCache() {
57
58
  cached = null;
58
59
  }
59
60
  /** Get the authoritative protocol configuration.
60
61
  * Cached after first call; call invalidateProtocolCache() to force reload. */
62
+ /** Get the authoritative protocol configuration from the single source of truth. */
63
+ /** @requires PROJECT_CONFIG @produces PROTOCOL_CONFIG */
61
64
  function getProtocolConfig() {
62
65
  if (cached)
63
66
  return cached;
@@ -96,10 +99,14 @@ function getProtocolConfig() {
96
99
  }
97
100
  // ── Convenience re-exports ──
98
101
  /** Get namespace initial states only (most common need). */
102
+ /** Get namespace initial states from protocol configuration. */
103
+ /** @requires PROJECT_CONFIG @produces NAMESPACE_STATES */
99
104
  function getNsInit() {
100
105
  return new Map(getProtocolConfig().nsInit);
101
106
  }
102
107
  /** Get current rule hash without loading full config. */
108
+ /** Get the current rule set hash. */
109
+ /** @requires PROTOCOL_CONFIG @produces RULE_HASH */
103
110
  function getRuleHash() {
104
111
  return getProtocolConfig().ruleHash;
105
112
  }