progmune-runtime 2.1.5 → 2.1.6

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.
@@ -13,6 +13,7 @@ exports.selectCapabilityChains = selectCapabilityChains;
13
13
  exports.formatChainHint = formatChainHint;
14
14
  const utils_1 = require("./utils");
15
15
  const feedback_1 = require("./feedback");
16
+ const semantic_topology_1 = require("./semantic-topology");
16
17
  /** Build a capability graph from IR functions. */
17
18
  function buildCapabilityGraph(ir) {
18
19
  const graph = new Map();
@@ -25,60 +26,129 @@ function buildCapabilityGraph(ir) {
25
26
  tags: f.tags || [],
26
27
  requires: f.requires || [],
27
28
  produces: f.produces || [],
29
+ useWhen: f.useWhen || [],
28
30
  score: 0,
29
31
  });
30
32
  }
31
33
  return graph;
32
34
  }
33
- /** Score a capability node against an intent. */
35
+ /** Score a capability node against an intent.
36
+ * Returns 0 for irrelevant nodes (no keyword match at all). */
34
37
  function scoreNode(node, intentLower, keywords) {
35
38
  let score = 0;
39
+ let hasMatch = false;
36
40
  // Name match
37
41
  for (const kw of keywords) {
38
- if (node.name.toLowerCase().includes(kw))
42
+ if (node.name.toLowerCase().includes(kw)) {
39
43
  score += 1;
40
- score += (0, utils_1.jaccardSimilarity)(node.name.toLowerCase(), kw);
44
+ hasMatch = true;
45
+ }
46
+ const js = (0, utils_1.jaccardSimilarity)(node.name.toLowerCase(), kw);
47
+ if (js > 0.2) {
48
+ score += js;
49
+ hasMatch = true;
50
+ }
41
51
  }
42
52
  // Purpose match
43
53
  const purposeLower = node.purpose.toLowerCase();
44
54
  for (const kw of keywords) {
45
- if (purposeLower.includes(kw))
55
+ if (purposeLower.includes(kw)) {
46
56
  score += 2;
57
+ hasMatch = true;
58
+ }
47
59
  }
48
60
  // Tag match
49
61
  for (const tag of node.tags) {
50
- if (intentLower.includes(tag.toLowerCase()))
62
+ if (intentLower.includes(tag.toLowerCase())) {
51
63
  score += 1.5;
64
+ hasMatch = true;
65
+ }
52
66
  }
53
67
  // Semantic word overlap in purpose
54
68
  const intentWords = intentLower.split(/[\s,,]+/);
55
69
  for (const w of intentWords) {
56
- if (w.length > 2 && purposeLower.includes(w))
70
+ if (w.length > 2 && purposeLower.includes(w)) {
57
71
  score += 0.5;
72
+ hasMatch = true;
73
+ }
58
74
  }
75
+ // useWhen scenario match
76
+ if (node.useWhen) {
77
+ for (const scenario of node.useWhen) {
78
+ const scenarioWords = scenario.toLowerCase().split(/[\s,]+/);
79
+ const matchCount = scenarioWords.filter((w) => w.length > 3 && intentLower.includes(w)).length;
80
+ if (matchCount >= 2) {
81
+ score += 3.0;
82
+ hasMatch = true;
83
+ }
84
+ else if (matchCount === 1) {
85
+ score += 1.0;
86
+ hasMatch = true;
87
+ }
88
+ }
89
+ }
90
+ // Require at least one match to be relevant
91
+ if (!hasMatch)
92
+ return 0;
59
93
  // Dynamic Credit: multiply by actual success rate
60
94
  const successRate = (0, feedback_1.getFailureAdjustedCredit)(node.name);
61
95
  const creditFactor = 0.3 + successRate * 0.7;
62
96
  return score * creditFactor;
63
97
  }
64
- /** Find all capability nodes that produce a given capability label. */
65
- function findProducers(graph, capability) {
98
+ /** Find all capability nodes that produce a given capability label.
99
+ * Falls back to topology similarity if no direct data-flow match. */
100
+ function findProducers(graph, capability, allNodes) {
66
101
  const producers = [];
67
102
  for (const node of graph.values()) {
68
103
  if (node.produces.some(p => p === capability || capability.includes(p) || p.includes(capability))) {
69
104
  producers.push(node);
70
105
  }
71
106
  }
107
+ // Topology fallback: find semantically related producers
108
+ if (producers.length === 0) {
109
+ try {
110
+ const topo = (0, semantic_topology_1.getTopology)();
111
+ for (const node of allNodes) {
112
+ if (node.produces.length > 0) {
113
+ for (const p of node.produces) {
114
+ if (topo.capabilityMatch(p, capability) && !producers.includes(node)) {
115
+ producers.push(node);
116
+ break;
117
+ }
118
+ }
119
+ }
120
+ }
121
+ }
122
+ catch { }
123
+ }
72
124
  return producers;
73
125
  }
74
- /** Find all capability nodes that require a given capability label. */
75
- function findConsumers(graph, capability) {
126
+ /** Find all capability nodes that require a given capability label.
127
+ * Falls back to topology similarity if no direct data-flow match. */
128
+ function findConsumers(graph, capability, allNodes) {
76
129
  const consumers = [];
77
130
  for (const node of graph.values()) {
78
131
  if (node.requires.some(r => r === capability || capability.includes(r) || r.includes(capability))) {
79
132
  consumers.push(node);
80
133
  }
81
134
  }
135
+ // Topology fallback
136
+ if (consumers.length === 0) {
137
+ try {
138
+ const topo = (0, semantic_topology_1.getTopology)();
139
+ for (const node of allNodes) {
140
+ if (node.requires.length > 0) {
141
+ for (const r of node.requires) {
142
+ if (topo.capabilityMatch(capability, r) && !consumers.includes(node)) {
143
+ consumers.push(node);
144
+ break;
145
+ }
146
+ }
147
+ }
148
+ }
149
+ }
150
+ catch { }
151
+ }
82
152
  return consumers;
83
153
  }
84
154
  /**
@@ -98,39 +168,70 @@ function selectCapabilityChains(intent, ir, maxChains = 5) {
98
168
  for (const node of graph.values()) {
99
169
  node.score = scoreNode(node, intentLower, keywords);
100
170
  }
101
- // Find seed nodes: highest-scoring nodes that produce something
102
- const seeds = [...graph.values()]
103
- .filter(n => n.produces.length > 0 && n.score > 0)
104
- .sort((a, b) => b.score - a.score)
105
- .slice(0, 10);
171
+ // Dynamic threshold: tighten for large IR to prevent score dilution
172
+ let dynamicThreshold = graph.size > 500 ? 2.0 : graph.size > 200 ? 1.5 : 1.0;
173
+ // Fallback: if no seeds found, halve threshold
174
+ let seeds = [...graph.values()]
175
+ .filter(n => n.score > dynamicThreshold && (n.produces.length > 0 || n.score > dynamicThreshold + 2))
176
+ .sort((a, b) => b.score - a.score);
177
+ if (seeds.length === 0 && dynamicThreshold > 0.5) {
178
+ dynamicThreshold *= 0.5;
179
+ seeds = [...graph.values()]
180
+ .filter(n => n.score > dynamicThreshold && (n.produces.length > 0 || n.score > dynamicThreshold + 1))
181
+ .sort((a, b) => b.score - a.score);
182
+ }
183
+ seeds = seeds.slice(0, graph.size > 500 ? 30 : 15);
184
+ const allNodes = [...graph.values()];
106
185
  const chains = [];
107
186
  for (const seed of seeds) {
108
187
  // Build chain: seed → consumer → consumer...
109
188
  const chain = [seed];
110
189
  const visited = new Set([seed.name]);
111
190
  let totalScore = seed.score;
112
- // Forward trace: for each produce of the last node, find consumers
191
+ // Forward trace: data flow semantic leap
113
192
  let current = seed;
114
193
  let extended = true;
194
+ let leapDecay = 1.0; // weight decay for semantic leaps
115
195
  while (extended && chain.length < 8) {
116
196
  extended = false;
197
+ // Strategy 1: direct data flow (produces → requires)
117
198
  for (const p of current.produces) {
118
- const consumers = findConsumers(graph, p).filter(c => !visited.has(c.name));
199
+ const consumers = findConsumers(graph, p, allNodes).filter(c => !visited.has(c.name));
119
200
  if (consumers.length > 0) {
120
- // Pick best-scoring consumer
121
201
  const bestConsumer = consumers.sort((a, b) => b.score - a.score)[0];
122
202
  chain.push(bestConsumer);
123
203
  visited.add(bestConsumer.name);
124
204
  totalScore += bestConsumer.score;
125
205
  current = bestConsumer;
126
206
  extended = true;
207
+ leapDecay = 1.0; // reset decay on direct match
127
208
  break;
128
209
  }
129
210
  }
211
+ // Strategy 2: semantic leap — use topology similarity
212
+ if (!extended) {
213
+ try {
214
+ const topo = (0, semantic_topology_1.getTopology)();
215
+ const similar = topo.findSimilar(current.name, 10)
216
+ .filter(s => !visited.has(s.name) && s.similarity > 0.2);
217
+ if (similar.length > 0) {
218
+ const bestMatch = graph.get(similar[0].name);
219
+ if (bestMatch && bestMatch.score > 0) {
220
+ chain.push(bestMatch);
221
+ visited.add(bestMatch.name);
222
+ totalScore += bestMatch.score * leapDecay; // decayed score
223
+ current = bestMatch;
224
+ extended = true;
225
+ leapDecay *= 0.7; // each semantic leap loses 30% weight
226
+ }
227
+ }
228
+ }
229
+ catch { }
230
+ }
130
231
  }
131
232
  // Backward trace: does seed need something? Find producers.
132
233
  if (seed.requires.length > 0) {
133
- const producers = findProducers(graph, seed.requires[0])
234
+ const producers = findProducers(graph, seed.requires[0], allNodes)
134
235
  .filter(p => !visited.has(p.name));
135
236
  if (producers.length > 0) {
136
237
  const bestProducer = producers.sort((a, b) => b.score - a.score)[0];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "progmune-runtime",
3
- "version": "2.1.5",
3
+ "version": "2.1.6",
4
4
  "description": "Progmune Runtime — Program Immunology: Constraint-Guided Program Synthesis Runtime",
5
5
  "main": "dist/mcp-server.mjs",
6
6
  "bin": {