progmune-runtime 2.1.3 → 2.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/planner.js CHANGED
@@ -39,6 +39,7 @@ const runtime_types_1 = require("./runtime-types");
39
39
  const action_runtime_1 = require("./action-runtime");
40
40
  const validator_1 = require("./validator");
41
41
  const semantic_validator_1 = require("./semantic-validator");
42
+ const feedback_1 = require("./feedback");
42
43
  const utils_1 = require("./utils");
43
44
  const failure_corpus_1 = require("./failure-corpus");
44
45
  const memory_layer_1 = require("./memory-layer");
@@ -46,6 +47,7 @@ const ssg_validator_1 = require("./ssg-validator");
46
47
  const protocol_registry_1 = require("./protocol-registry");
47
48
  const semantic_snapshot_1 = require("./semantic-snapshot");
48
49
  const strategy_planner_1 = require("./strategy-planner");
50
+ const semantic_topology_1 = require("./semantic-topology");
49
51
  const fs = __importStar(require("fs"));
50
52
  function enrichActions(actions, ir) {
51
53
  return actions.map(a => {
@@ -119,17 +121,20 @@ function buildCompactFuncList(funcs, allFuncs) {
119
121
  }).join("\n");
120
122
  }
121
123
  /** Semantic matching: check if two capability labels are related.
122
- * Uses exact match, substring, and Jaccard similarity. */
124
+ * Uses SemanticTopology (structural graph) instead of string matching. */
123
125
  function semanticMatch(a, b) {
126
+ try {
127
+ const topo = (0, semantic_topology_1.getTopology)();
128
+ if (topo.size > 0)
129
+ return topo.capabilityMatch(a, b);
130
+ }
131
+ catch { }
132
+ // Fallback: exact + substring
124
133
  if (a === b)
125
134
  return true;
126
135
  if (a.includes(b) || b.includes(a))
127
136
  return true;
128
- // Fuzzy: shared word prefix (e.g. FAILURE_LIST ↔ FAILURE_DATA)
129
- const aWords = a.split("_");
130
- const bWords = b.split("_");
131
- const shared = aWords.filter(w => bWords.some(bw => bw.includes(w) || w.includes(bw)));
132
- return shared.length >= 1 && aWords.length <= 3 && bWords.length <= 3;
137
+ return false;
133
138
  }
134
139
  /** Build capability chain hints from IR: producer→consumer relationships.
135
140
  * Uses semantic matching for fuzzy capability linking. */
@@ -551,6 +556,11 @@ async function plan(userIntent) {
551
556
  const irRaw = JSON.parse(fs.readFileSync("ir.json", "utf-8"));
552
557
  // Support both old (array) and new ({typeMap, functions}) formats
553
558
  const ir = Array.isArray(irRaw) ? irRaw : (irRaw.functions || []);
559
+ // P1: Build Semantic Topology (once per plan call, cached)
560
+ try {
561
+ (0, semantic_topology_1.rebuildTopology)(ir);
562
+ }
563
+ catch { }
554
564
  // Helper: wrap actions into PlanResult
555
565
  let repairMetrics = { applied: false, count: 0, branchIds: [] };
556
566
  const wrapResult = (actions, repair) => ({
@@ -747,6 +757,11 @@ async function plan(userIntent) {
747
757
  score += 0.8;
748
758
  }
749
759
  }
760
+ // Dynamic Credit: multiply by actual success rate (0.1-1.0)
761
+ const successRate = (0, feedback_1.getFunctionSuccessRate)(f.name);
762
+ const creditFactor = 0.3 + successRate * 0.7; // range: 0.3 (always fail) to 1.0 (always succeed)
763
+ if (f.exported && !f.external)
764
+ score *= creditFactor;
750
765
  return { ...f, score };
751
766
  });
752
767
  scored.sort((a, b) => b.score - a.score);
@@ -956,6 +971,17 @@ ${RETRY_HINT}
956
971
  }
957
972
  }
958
973
  }
974
+ // P0: Strategy Enforcement — LLM must follow recommended chain
975
+ if (chains.length > 0 && chains[0].nodes.length >= 2) {
976
+ const topChain = chains[0];
977
+ const requiredFuncs = topChain.nodes.map(n => n.name);
978
+ const chosenFuncs = filtered.filter(a => a.kind === "call").map(a => a.function);
979
+ const missing = requiredFuncs.filter(fn => !chosenFuncs.includes(fn));
980
+ if (missing.length >= requiredFuncs.length * 0.5) {
981
+ // More than 50% of the chain is missing — LLM ignored the strategy
982
+ preCheckErrors.push(`策略违规: 推荐链 ${topChain.explanation},但缺少 ${missing.join(", ")}`);
983
+ }
984
+ }
959
985
  // 1) 基础序列校验
960
986
  const seqResult = (0, validator_1.validateActionSequence)(filtered);
961
987
  if (!seqResult.valid || preCheckErrors.length > 0) {
@@ -0,0 +1,160 @@
1
+ "use strict";
2
+ /**
3
+ * Phase 8: Semantic Topology (P1)
4
+ *
5
+ * Builds a similarity graph from IR structural data:
6
+ * - File co-occurrence (functions in same file are related)
7
+ * - Tag overlap (shared domain tags)
8
+ * - Purpose word overlap (Jaccard on purpose text)
9
+ * - Chain adjacency (producer→consumer links)
10
+ *
11
+ * Replaces simple string matching in capability search.
12
+ */
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.SemanticTopology = void 0;
15
+ exports.getTopology = getTopology;
16
+ exports.rebuildTopology = rebuildTopology;
17
+ class SemanticTopology {
18
+ constructor() {
19
+ this.nodes = new Map();
20
+ this.edges = new Map();
21
+ this.similarityCache = new Map();
22
+ }
23
+ /** Build topology from IR data */
24
+ build(ir) {
25
+ this.nodes.clear();
26
+ this.edges.clear();
27
+ this.similarityCache.clear();
28
+ // 1. Create nodes
29
+ for (const f of ir) {
30
+ if (!f.exported && !f.external)
31
+ continue;
32
+ this.nodes.set(f.name, {
33
+ name: f.name,
34
+ file: f.file || "",
35
+ tags: new Set((f.tags || []).map((t) => t.toLowerCase())),
36
+ purposeWords: new Set((f.purpose || "").toLowerCase().split(/[\s,,]+/).filter((w) => w.length > 2)),
37
+ produces: new Set(f.produces || []),
38
+ requires: new Set(f.requires || []),
39
+ });
40
+ }
41
+ // 2. Build edges: file co-occurrence
42
+ const byFile = new Map();
43
+ for (const [name, node] of this.nodes) {
44
+ if (!byFile.has(node.file))
45
+ byFile.set(node.file, []);
46
+ byFile.get(node.file).push(name);
47
+ }
48
+ for (const names of byFile.values()) {
49
+ for (let i = 0; i < names.length; i++) {
50
+ for (let j = i + 1; j < names.length; j++) {
51
+ this.addEdge(names[i], names[j], 0.3, "co-file");
52
+ }
53
+ }
54
+ }
55
+ // 3. Build edges: tag overlap
56
+ for (const [nameA, nodeA] of this.nodes) {
57
+ for (const [nameB, nodeB] of this.nodes) {
58
+ if (nameA >= nameB)
59
+ continue;
60
+ const tagOverlap = [...nodeA.tags].filter(t => nodeB.tags.has(t)).length;
61
+ if (tagOverlap > 0) {
62
+ const maxTags = Math.max(nodeA.tags.size, nodeB.tags.size) || 1;
63
+ this.addEdge(nameA, nameB, 0.4 * (tagOverlap / maxTags), "tag");
64
+ }
65
+ }
66
+ }
67
+ // 4. Build edges: purpose word overlap
68
+ for (const [nameA, nodeA] of this.nodes) {
69
+ for (const [nameB, nodeB] of this.nodes) {
70
+ if (nameA >= nameB)
71
+ continue;
72
+ const shared = [...nodeA.purposeWords].filter(w => nodeB.purposeWords.has(w)).length;
73
+ const total = [...new Set([...nodeA.purposeWords, ...nodeB.purposeWords])].length || 1;
74
+ const jaccard = shared / total;
75
+ if (jaccard > 0.15) {
76
+ this.addEdge(nameA, nameB, 0.5 * jaccard, "purpose");
77
+ }
78
+ }
79
+ }
80
+ // 5. Build edges: chain adjacency (producer→consumer)
81
+ for (const [nameA, nodeA] of this.nodes) {
82
+ for (const p of nodeA.produces) {
83
+ for (const [nameB, nodeB] of this.nodes) {
84
+ if (nameA === nameB)
85
+ continue;
86
+ if (nodeB.requires.has(p)) {
87
+ this.addEdge(nameA, nameB, 0.7, `chain:${p}`);
88
+ }
89
+ // Fuzzy chain: substring match
90
+ for (const r of nodeB.requires) {
91
+ if (p.includes(r) || r.includes(p)) {
92
+ this.addEdge(nameA, nameB, 0.4, `fuzzy:${p}≈${r}`);
93
+ }
94
+ }
95
+ }
96
+ }
97
+ }
98
+ }
99
+ addEdge(a, b, weight, reason) {
100
+ const key = a < b ? `${a}::${b}` : `${b}::${a}`;
101
+ if (!this.edges.has(a))
102
+ this.edges.set(a, []);
103
+ if (!this.edges.has(b))
104
+ this.edges.set(b, []);
105
+ this.edges.get(a).push({ source: a, target: b, weight, reason });
106
+ this.edges.get(b).push({ source: b, target: a, weight, reason });
107
+ this.similarityCache.set(key, Math.max(this.similarityCache.get(key) || 0, weight));
108
+ }
109
+ /** Get similarity between two functions (0-1). */
110
+ similarity(funcA, funcB) {
111
+ if (funcA === funcB)
112
+ return 1.0;
113
+ const key = funcA < funcB ? `${funcA}::${funcB}` : `${funcB}::${funcA}`;
114
+ return this.similarityCache.get(key) || 0;
115
+ }
116
+ /** Find top N most similar functions to a given function. */
117
+ findSimilar(funcName, topN = 5) {
118
+ const edges = this.edges.get(funcName) || [];
119
+ return edges
120
+ .sort((a, b) => b.weight - a.weight)
121
+ .slice(0, topN)
122
+ .map(e => ({ name: e.target, similarity: e.weight }));
123
+ }
124
+ /** Semantic match: two capability labels are related via topology. */
125
+ capabilityMatch(produce, require) {
126
+ // Direct match
127
+ if (produce === require)
128
+ return true;
129
+ if (produce.includes(require) || require.includes(produce))
130
+ return true;
131
+ // Topology check: are there functions producing 'produce' that are connected
132
+ // to functions requiring 'require'?
133
+ const producers = [...this.nodes.values()].filter(n => n.produces.has(produce));
134
+ const consumers = [...this.nodes.values()].filter(n => n.requires.has(require));
135
+ for (const p of producers) {
136
+ for (const c of consumers) {
137
+ if (this.similarity(p.name, c.name) > 0.2)
138
+ return true;
139
+ }
140
+ }
141
+ return false;
142
+ }
143
+ /** Get node count */
144
+ get size() { return this.nodes.size; }
145
+ }
146
+ exports.SemanticTopology = SemanticTopology;
147
+ // Singleton
148
+ let _topology = null;
149
+ function getTopology(ir) {
150
+ if (!_topology && ir) {
151
+ _topology = new SemanticTopology();
152
+ _topology.build(ir);
153
+ }
154
+ return _topology || new SemanticTopology();
155
+ }
156
+ function rebuildTopology(ir) {
157
+ _topology = new SemanticTopology();
158
+ _topology.build(ir);
159
+ return _topology;
160
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "progmune-runtime",
3
- "version": "2.1.3",
3
+ "version": "2.1.4",
4
4
  "description": "Progmune Runtime — Program Immunology: Constraint-Guided Program Synthesis Runtime",
5
5
  "main": "dist/mcp-server.mjs",
6
6
  "bin": {