progmune-runtime 2.1.3 → 2.1.5
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/README.md +453 -79
- package/WHITEPAPER.md +213 -91
- package/dist/extract-ir.js +6 -0
- package/dist/failure-collector.js +2 -0
- package/dist/failure-corpus.js +4 -0
- package/dist/feedback.js +56 -2
- package/dist/health-utils.js +1 -0
- package/dist/ledger-registry.js +2 -0
- package/dist/memory-layer.js +55 -11
- package/dist/planner.js +43 -6
- package/dist/semantic-topology.js +160 -0
- package/dist/session-utils.js +1 -0
- package/dist/stdlib.js +3 -0
- package/dist/strategy-planner.js +5 -1
- package/package.json +1 -1
package/dist/memory-layer.js
CHANGED
|
@@ -37,6 +37,7 @@ exports.WorkMemory = void 0;
|
|
|
37
37
|
exports.recordEpisode = recordEpisode;
|
|
38
38
|
exports.getRecentEpisodes = getRecentEpisodes;
|
|
39
39
|
exports.getSuccessfulEpisodes = getSuccessfulEpisodes;
|
|
40
|
+
exports.pruneEpisodicMemory = pruneEpisodicMemory;
|
|
40
41
|
exports.consolidateSemantic = consolidateSemantic;
|
|
41
42
|
exports.findSemanticTemplate = findSemanticTemplate;
|
|
42
43
|
const fs = __importStar(require("fs"));
|
|
@@ -100,10 +101,14 @@ function recordEpisode(episode) {
|
|
|
100
101
|
timestamp: new Date().toISOString(),
|
|
101
102
|
};
|
|
102
103
|
episodes.unshift(newEpisode);
|
|
103
|
-
|
|
104
|
+
// Simple cap: trim to prevent runaway growth between GC cycles
|
|
105
|
+
if (episodes.length > MAX_EPISODES * 1.5) {
|
|
104
106
|
episodes.length = MAX_EPISODES;
|
|
105
107
|
}
|
|
106
108
|
saveEpisodes(episodes);
|
|
109
|
+
// Run GC every 100 episodes
|
|
110
|
+
if (episodes.length % 100 === 0)
|
|
111
|
+
pruneEpisodicMemory();
|
|
107
112
|
}
|
|
108
113
|
/** @requires LIMIT @produces EPISODE_LIST */
|
|
109
114
|
function getRecentEpisodes(limit = 10) {
|
|
@@ -112,6 +117,35 @@ function getRecentEpisodes(limit = 10) {
|
|
|
112
117
|
function getSuccessfulEpisodes(limit = 10) {
|
|
113
118
|
return loadEpisodes().filter(e => e.success).slice(0, limit);
|
|
114
119
|
}
|
|
120
|
+
// ── Semantic GC: prune episodic memory ──
|
|
121
|
+
const MAX_EPISODE_COUNT = 1000;
|
|
122
|
+
const MAX_AGE_DAYS = 30;
|
|
123
|
+
/** Prune episodic memory: keep high-value, recent, diverse episodes.
|
|
124
|
+
* Removes: old failures (>30 days), low-value duplicates, excess beyond max.
|
|
125
|
+
* Called periodically after recording new episodes. */
|
|
126
|
+
function pruneEpisodicMemory() {
|
|
127
|
+
const episodes = loadEpisodes();
|
|
128
|
+
if (episodes.length <= MAX_EPISODE_COUNT)
|
|
129
|
+
return 0;
|
|
130
|
+
const now = Date.now();
|
|
131
|
+
const scored = episodes.map((ep, i) => {
|
|
132
|
+
const ageDays = (now - new Date(ep.timestamp).getTime()) / 86400000;
|
|
133
|
+
// Score: success=+2, recent=+3, older=-1/day
|
|
134
|
+
let score = ep.success ? 2 : 0;
|
|
135
|
+
score += Math.max(0, 3 - ageDays * 0.5); // recent bonus, decays over 6 days
|
|
136
|
+
score -= Math.max(0, (ageDays - MAX_AGE_DAYS) * 0.5); // penalty for >30 days
|
|
137
|
+
return { ep, score, index: i };
|
|
138
|
+
});
|
|
139
|
+
// Keep top MAX_EPISODES by score
|
|
140
|
+
scored.sort((a, b) => b.score - a.score);
|
|
141
|
+
const kept = scored.slice(0, MAX_EPISODE_COUNT).sort((a, b) => a.index - b.index);
|
|
142
|
+
const removed = episodes.length - kept.length;
|
|
143
|
+
if (removed > 0) {
|
|
144
|
+
saveEpisodes(kept.map(s => s.ep));
|
|
145
|
+
console.error(`[Memory] Semantic GC: removed ${removed} low-value episodes, kept ${kept.length}`);
|
|
146
|
+
}
|
|
147
|
+
return removed;
|
|
148
|
+
}
|
|
115
149
|
const SEMANTIC_FILE = path.join(MEMORY_DIR, "semantic.json");
|
|
116
150
|
const SEMANTIC_TTL_MS = 30 * 24 * 60 * 60 * 1000; // 30 天
|
|
117
151
|
function loadSemantic() {
|
|
@@ -174,18 +208,28 @@ function consolidateSemantic(minOccurrences = 3) {
|
|
|
174
208
|
saveSemantic(templates);
|
|
175
209
|
console.error(`[语义记忆] 巩固完成,模板数量: ${templates.length}`);
|
|
176
210
|
}
|
|
177
|
-
/** @requires INTENT @produces TEMPLATE
|
|
211
|
+
/** @requires INTENT @produces TEMPLATE
|
|
212
|
+
* Uses keyword overlap (replaces prefix matching) for semantic recall. */
|
|
178
213
|
function findSemanticTemplate(intent) {
|
|
179
214
|
const templates = loadSemantic();
|
|
180
215
|
if (templates.length === 0)
|
|
181
216
|
return undefined;
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
if (
|
|
185
|
-
return
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
217
|
+
// Extract keywords from intent
|
|
218
|
+
const words = new Set(intent.toLowerCase().split(/[\s,,]+/).filter(w => w.length > 2));
|
|
219
|
+
if (words.size === 0)
|
|
220
|
+
return undefined;
|
|
221
|
+
// Score templates by keyword overlap
|
|
222
|
+
let best;
|
|
223
|
+
let bestScore = 0;
|
|
224
|
+
for (const t of templates) {
|
|
225
|
+
const tWords = t.intentPattern.toLowerCase().split(/[\s,,]+/);
|
|
226
|
+
const shared = tWords.filter(w => words.has(w)).length;
|
|
227
|
+
const total = new Set([...words, ...new Set(tWords)]).size;
|
|
228
|
+
const score = shared / (total || 1); // Jaccard-like
|
|
229
|
+
if (score > 0.5 && t.successRate >= 0.7 && score > bestScore) {
|
|
230
|
+
bestScore = score;
|
|
231
|
+
best = t;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return best;
|
|
191
235
|
}
|
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
|
|
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
|
-
|
|
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) => ({
|
|
@@ -740,6 +750,17 @@ async function plan(userIntent) {
|
|
|
740
750
|
score += matchCount * 0.2;
|
|
741
751
|
}
|
|
742
752
|
}
|
|
753
|
+
// Semantic Capability: useWhen scenario matching
|
|
754
|
+
if (f.useWhen) {
|
|
755
|
+
for (const scenario of f.useWhen) {
|
|
756
|
+
const scenarioWords = scenario.toLowerCase().split(/[\s,]+/);
|
|
757
|
+
const matchCount = scenarioWords.filter((w) => w.length > 3 && intentLower.includes(w)).length;
|
|
758
|
+
if (matchCount >= 2)
|
|
759
|
+
score += 3.0; // strong signal: intent matches use case
|
|
760
|
+
else if (matchCount === 1)
|
|
761
|
+
score += 1.0;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
743
764
|
// Capability Graph: tag match
|
|
744
765
|
if (f.tags) {
|
|
745
766
|
for (const tag of f.tags) {
|
|
@@ -747,6 +768,11 @@ async function plan(userIntent) {
|
|
|
747
768
|
score += 0.8;
|
|
748
769
|
}
|
|
749
770
|
}
|
|
771
|
+
// Dynamic Credit: multiply by actual success rate (0.1-1.0)
|
|
772
|
+
const successRate = (0, feedback_1.getFailureAdjustedCredit)(f.name);
|
|
773
|
+
const creditFactor = 0.3 + successRate * 0.7; // range: 0.3 (always fail) to 1.0 (always succeed)
|
|
774
|
+
if (f.exported && !f.external)
|
|
775
|
+
score *= creditFactor;
|
|
750
776
|
return { ...f, score };
|
|
751
777
|
});
|
|
752
778
|
scored.sort((a, b) => b.score - a.score);
|
|
@@ -956,6 +982,17 @@ ${RETRY_HINT}
|
|
|
956
982
|
}
|
|
957
983
|
}
|
|
958
984
|
}
|
|
985
|
+
// P0: Strategy Enforcement — LLM must follow recommended chain
|
|
986
|
+
if (chains.length > 0 && chains[0].nodes.length >= 2) {
|
|
987
|
+
const topChain = chains[0];
|
|
988
|
+
const requiredFuncs = topChain.nodes.map(n => n.name);
|
|
989
|
+
const chosenFuncs = filtered.filter(a => a.kind === "call").map(a => a.function);
|
|
990
|
+
const missing = requiredFuncs.filter(fn => !chosenFuncs.includes(fn));
|
|
991
|
+
if (missing.length >= requiredFuncs.length * 0.5) {
|
|
992
|
+
// More than 50% of the chain is missing — LLM ignored the strategy
|
|
993
|
+
preCheckErrors.push(`策略违规: 推荐链 ${topChain.explanation},但缺少 ${missing.join(", ")}`);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
959
996
|
// 1) 基础序列校验
|
|
960
997
|
const seqResult = (0, validator_1.validateActionSequence)(filtered);
|
|
961
998
|
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/dist/session-utils.js
CHANGED
|
@@ -6,6 +6,7 @@ exports.formatSessionCounts = formatSessionCounts;
|
|
|
6
6
|
* @requires SESSION_LIST @produces RESOLVED_COUNT
|
|
7
7
|
* @tags session, count, statistics
|
|
8
8
|
*/
|
|
9
|
+
/** @useWhen checking how many sessions succeeded; computing success rate */
|
|
9
10
|
function countResolved(sessions) {
|
|
10
11
|
const resolved = sessions.filter((s) => s.resolved).length;
|
|
11
12
|
return { resolved, unresolved: sessions.length - resolved, total: sessions.length };
|
package/dist/stdlib.js
CHANGED
|
@@ -32,10 +32,12 @@ exports.retry = retry;
|
|
|
32
32
|
exports.debounce = debounce;
|
|
33
33
|
// ── String ──
|
|
34
34
|
/** @requires STRING @produces VALIDATION_RESULT @tags string, email, validation */
|
|
35
|
+
/** @useWhen validating user input; form validation; email checking */
|
|
35
36
|
function isValidEmail(str) {
|
|
36
37
|
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str);
|
|
37
38
|
}
|
|
38
39
|
/** @requires STRING @produces TRUNCATED_STRING @tags string, format */
|
|
40
|
+
/** @useWhen displaying preview text; limiting UI output; shortening strings */
|
|
39
41
|
function truncate(str, maxLen, ellipsis = "...") {
|
|
40
42
|
return str.length <= maxLen ? str : str.slice(0, maxLen - ellipsis.length) + ellipsis;
|
|
41
43
|
}
|
|
@@ -88,6 +90,7 @@ function arrayDiff(a, b) {
|
|
|
88
90
|
}
|
|
89
91
|
// ── Math ──
|
|
90
92
|
/** @requires NUMBERS @produces AVERAGE @tags math, statistics */
|
|
93
|
+
/** @useWhen computing statistics; data analysis; metrics calculation */
|
|
91
94
|
function average(nums) {
|
|
92
95
|
return nums.length === 0 ? 0 : nums.reduce((a, b) => a + b, 0) / nums.length;
|
|
93
96
|
}
|
package/dist/strategy-planner.js
CHANGED
|
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.selectCapabilityChains = selectCapabilityChains;
|
|
13
13
|
exports.formatChainHint = formatChainHint;
|
|
14
14
|
const utils_1 = require("./utils");
|
|
15
|
+
const feedback_1 = require("./feedback");
|
|
15
16
|
/** Build a capability graph from IR functions. */
|
|
16
17
|
function buildCapabilityGraph(ir) {
|
|
17
18
|
const graph = new Map();
|
|
@@ -55,7 +56,10 @@ function scoreNode(node, intentLower, keywords) {
|
|
|
55
56
|
if (w.length > 2 && purposeLower.includes(w))
|
|
56
57
|
score += 0.5;
|
|
57
58
|
}
|
|
58
|
-
|
|
59
|
+
// Dynamic Credit: multiply by actual success rate
|
|
60
|
+
const successRate = (0, feedback_1.getFailureAdjustedCredit)(node.name);
|
|
61
|
+
const creditFactor = 0.3 + successRate * 0.7;
|
|
62
|
+
return score * creditFactor;
|
|
59
63
|
}
|
|
60
64
|
/** Find all capability nodes that produce a given capability label. */
|
|
61
65
|
function findProducers(graph, capability) {
|