progmune-runtime 2.1.6 → 3.2.0
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 +108 -468
- package/dist/ablation-study.js +144 -0
- package/dist/ablation-study.test.js +18 -0
- package/dist/action-runtime.js +3 -1
- package/dist/active-learning.js +211 -0
- package/dist/analytics.js +139 -0
- package/dist/asset-factory.js +309 -0
- package/dist/asset-growth.js +244 -0
- package/dist/asset-promotion.js +382 -0
- package/dist/asset-quality.js +550 -0
- package/dist/audit/business-translator.js +285 -0
- package/dist/audit/cli.js +66 -0
- package/dist/audit/formatters/html.js +379 -0
- package/dist/audit/formatters/json.js +11 -0
- package/dist/audit/formatters/markdown.js +192 -0
- package/dist/audit/formatters/terminal.js +189 -0
- package/dist/audit/index.js +25 -0
- package/dist/audit/report-builder.js +318 -0
- package/dist/audit/types.js +8 -0
- package/dist/audit.js +3 -3
- package/dist/auto-benchmark-generator.js +137 -0
- package/dist/auto-benchmark-generator.test.js +45 -0
- package/dist/auto-protocol-synthesizer.js +362 -0
- package/dist/auto-protocol-synthesizer.test.js +82 -0
- package/dist/autonomous-patch.js +175 -0
- package/dist/autonomous-patch.test.js +128 -0
- package/dist/badge/badge-server.js +98 -0
- package/dist/behavior-miner.js +442 -0
- package/dist/belief-layer.js +475 -0
- package/dist/benchmark-count.js +5 -0
- package/dist/benchmark-generator.js +211 -0
- package/dist/benchmark-harness.js +201 -0
- package/dist/benchmark-pass-rate.js +7 -0
- package/dist/benchmark-report.js +8 -3
- package/dist/benchmark-save.js +14 -1
- package/dist/bootstrap-validation.js +197 -0
- package/dist/bootstrap-validation.test.js +51 -0
- package/dist/branch-ledger.js +1 -1
- package/dist/capability-gap.js +130 -0
- package/dist/certify-html.js +351 -0
- package/dist/certify.js +326 -0
- package/dist/check.js +4 -4
- package/dist/compliance-miner.js +447 -0
- package/dist/continuous-benchmark.js +194 -0
- package/dist/continuous-benchmark.test.js +116 -0
- package/dist/corpus-stats.js +173 -0
- package/dist/counterfactual-engine.js +288 -0
- package/dist/coverage-dashboard.js +109 -0
- package/dist/coverage-system.test.js +205 -0
- package/dist/cross-repo-precision.js +352 -0
- package/dist/cve-benchmark.js +180 -0
- package/dist/cve-benchmark.test.js +28 -0
- package/dist/cve-collector.js +73 -0
- package/dist/data-quality.js +141 -0
- package/dist/decision-engine.js +388 -0
- package/dist/derive-metadata.js +250 -0
- package/dist/difficulty-active.test.js +198 -0
- package/dist/difficulty-map.js +244 -0
- package/dist/discovery-analytics.js +125 -0
- package/dist/discovery-model.js +149 -0
- package/dist/discovery-optimize.test.js +199 -0
- package/dist/discovery-trace.js +276 -0
- package/dist/discovery-trace.test.js +97 -0
- package/dist/emitter.js +83 -1
- package/dist/enterprise-dashboard.js +405 -0
- package/dist/eval-hardening.js +297 -0
- package/dist/eval-hardening.test.js +85 -0
- package/dist/evaluation-campaign.js +359 -0
- package/dist/evaluation-campaign.test.js +181 -0
- package/dist/evidence-growth.js +143 -0
- package/dist/evidence-repository.js +209 -0
- package/dist/evidence-system.js +441 -0
- package/dist/execute.js +15 -7
- package/dist/experimental/software-physics.js +291 -0
- package/dist/experimental/state-inference.js +516 -0
- package/dist/experimental/unsupervised-physics.js +230 -0
- package/dist/extract-ir-python.js +54 -7
- package/dist/extract-ir.js +376 -12
- package/dist/failure-collector.js +2 -2
- package/dist/failure-corpus.js +322 -9
- package/dist/feedback.js +16 -5
- package/dist/feedback.test.js +49 -0
- package/dist/file-lock.js +1 -1
- package/dist/flywheel-batch.js +292 -0
- package/dist/frameworks/express-cli.js +237 -0
- package/dist/frameworks/express-detector.js +445 -0
- package/dist/frameworks/express-detector.test.js +206 -0
- package/dist/frameworks/index.js +30 -0
- package/dist/frameworks/nestjs-detector.js +302 -0
- package/dist/frameworks/trpc-detector.js +161 -0
- package/dist/frameworks/version-awareness.js +179 -0
- package/dist/function-synonyms.js +164 -0
- package/dist/function-synonyms.test.js +68 -0
- package/dist/generalization.test.js +352 -0
- package/dist/goal-annotator.js +113 -0
- package/dist/goal-planner.js +563 -0
- package/dist/gold-cve.js +164 -0
- package/dist/gold-cve.test.js +104 -0
- package/dist/gold-quality.js +206 -0
- package/dist/gold-tiers.js +241 -0
- package/dist/governance-dashboard.js +327 -0
- package/dist/graph-viz.js +240 -0
- package/dist/guided-frontier.js +195 -0
- package/dist/hierarchical-planner.js +148 -0
- package/dist/identifier-parser.js +260 -0
- package/dist/immune-metrics.js +93 -0
- package/dist/immune-receiver.js +158 -0
- package/dist/immune-reporter.js +1 -1
- package/dist/improvement-orchestrator.js +206 -0
- package/dist/inject-p0-vocabulary.js +300 -0
- package/dist/intent-parser.js +218 -0
- package/dist/invariant-algebra.js +476 -0
- package/dist/invariant-calculus.js +533 -0
- package/dist/ir-utils.js +70 -0
- package/dist/ir-utils.test.js +50 -0
- package/dist/knowledge-api.js +312 -0
- package/dist/knowledge-evolution.js +452 -0
- package/dist/knowledge-explorer.js +506 -0
- package/dist/knowledge-flywheel.js +274 -0
- package/dist/knowledge-governance.js +338 -0
- package/dist/knowledge-governance.test.js +150 -0
- package/dist/knowledge-graph.js +181 -0
- package/dist/knowledge-guided-synth.js +246 -0
- package/dist/knowledge-loop.test.js +77 -0
- package/dist/knowledge-object.js +316 -0
- package/dist/knowledge-package.js +98 -0
- package/dist/kpi-dashboard.js +561 -0
- package/dist/l3-cross-function.js +280 -0
- package/dist/learning-ranker.js +148 -0
- package/dist/learning-ranker.test.js +291 -0
- package/dist/ledger/accountability.js +322 -0
- package/dist/ledger/chain-builder.js +185 -0
- package/dist/ledger/cli.js +222 -0
- package/dist/ledger/index.js +13 -0
- package/dist/ledger/signatures.js +193 -0
- package/dist/ledger/types.js +9 -0
- package/dist/llm.js +74 -3
- package/dist/load-benchmarks.js +8 -3
- package/dist/logger.js +66 -0
- package/dist/logger.test.js +37 -0
- package/dist/logistic-reward.js +339 -0
- package/dist/logistic-reward.test.js +180 -0
- package/dist/macro-graph.js +193 -0
- package/dist/macro-repair.js +183 -0
- package/dist/mcp-server.mjs +1202 -483
- package/dist/memory-layer.js +42 -5
- package/dist/multi-repo-precision.js +422 -0
- package/dist/name-free-protocol.js +425 -0
- package/dist/name-free-protocol.test.js +170 -0
- package/dist/name-scrambling.js +138 -0
- package/dist/name-scrambling.test.js +16 -0
- package/dist/p3-observability.test.js +281 -0
- package/dist/p5-orchestrator.test.js +225 -0
- package/dist/pairwise-preference.js +294 -0
- package/dist/pairwise-preference.test.js +140 -0
- package/dist/planner-constraints.js +104 -0
- package/dist/planner-prompts.js +155 -0
- package/dist/planner-telemetry.js +415 -0
- package/dist/planner-trace.js +214 -0
- package/dist/planner.js +162 -167
- package/dist/plsb/artifact.js +116 -0
- package/dist/plsb/cli.js +71 -0
- package/dist/plsb/index.js +19 -0
- package/dist/plsb/leaderboard.js +249 -0
- package/dist/plsb/report-md.js +156 -0
- package/dist/plsb/schema.js +179 -0
- package/dist/plsb-benchmark.js +284 -0
- package/dist/plsb-benchmark.test.js +119 -0
- package/dist/policy/cli.js +134 -0
- package/dist/policy/engine.js +333 -0
- package/dist/policy/index.js +12 -0
- package/dist/policy/types.js +59 -0
- package/dist/policy-miner.js +505 -0
- package/dist/precision-analyze.js +229 -0
- package/dist/precision-benchmark.js +147 -0
- package/dist/precision-label-c.js +134 -0
- package/dist/precision-label.js +193 -0
- package/dist/precision-report-c.js +149 -0
- package/dist/precision-report.js +246 -0
- package/dist/progmune-status.js +108 -0
- package/dist/proof-engine.js +479 -0
- package/dist/proof-provenance.js +315 -0
- package/dist/protocol-coverage.js +294 -0
- package/dist/protocol-detector.js +1189 -0
- package/dist/protocol-embedding-expanded.js +297 -0
- package/dist/protocol-embedding-expanded.test.js +97 -0
- package/dist/protocol-embedding.js +195 -0
- package/dist/protocol-embedding.test.js +82 -0
- package/dist/protocol-extractor-v2.js +354 -0
- package/dist/protocol-extractor-v2.test.js +140 -0
- package/dist/protocol-extractor.js +310 -0
- package/dist/protocol-extractor.test.js +113 -0
- package/dist/protocol-foundation.js +322 -0
- package/dist/protocol-foundation.test.js +163 -0
- package/dist/protocol-frontier.js +243 -0
- package/dist/protocol-frontier.test.js +92 -0
- package/dist/protocol-gap-analyzer.js +228 -0
- package/dist/protocol-gap-analyzer.test.js +49 -0
- package/dist/protocol-invariants.js +276 -0
- package/dist/protocol-invariants.test.js +111 -0
- package/dist/protocol-knowledge.js +464 -0
- package/dist/protocol-miner.js +343 -0
- package/dist/protocol-mining.js +207 -0
- package/dist/protocol-mining.test.js +37 -0
- package/dist/protocol-registry.js +1 -1
- package/dist/protocol-security-benchmark.js +222 -0
- package/dist/protocol-vulnerability.js +257 -0
- package/dist/protocol-vulnerability.test.js +60 -0
- package/dist/python-benchmark.js +120 -0
- package/dist/python-emitter.js +163 -45
- package/dist/python-protocol-extractor.js +187 -0
- package/dist/python-protocol-extractor.test.js +116 -0
- package/dist/realworld-benchmark.js +646 -0
- package/dist/realworld-benchmark.test.js +36 -0
- package/dist/repair-arch.test.js +411 -0
- package/dist/repair-evolution.test.js +454 -0
- package/dist/repair-executor.js +719 -0
- package/dist/repair-proposal.js +4 -4
- package/dist/repair-ranker.js +141 -0
- package/dist/repair-strategies.js +419 -0
- package/dist/repair-taxonomy.js +234 -0
- package/dist/repair-types.js +12 -0
- package/dist/repo-evaluator.js +250 -0
- package/dist/repo-evaluator.test.js +128 -0
- package/dist/resource-abstraction.js +242 -0
- package/dist/resource-detector.js +211 -0
- package/dist/result.test.js +43 -0
- package/dist/reward-system.js +411 -0
- package/dist/reward-system.test.js +175 -0
- package/dist/risk-model.js +215 -0
- package/dist/rule-miner.js +234 -7
- package/dist/rule-specificity.js +254 -0
- package/dist/runtime-types.js +27 -0
- package/dist/scaffold.js +208 -0
- package/dist/scale-collector.test.js +101 -0
- package/dist/scale-trajectory-collector.js +128 -0
- package/dist/sdk.js +250 -0
- package/dist/search-planner.js +4 -41
- package/dist/semantic-snapshot.js +1 -1
- package/dist/semantic-topology.js +121 -0
- package/dist/semantic-trace.js +310 -317
- package/dist/sequence-extractor.js +343 -0
- package/dist/skill-library.js +245 -0
- package/dist/skill-planner.test.js +189 -0
- package/dist/software-physics.js +291 -0
- package/dist/software-physics.test.js +81 -0
- package/dist/ssg-precision.js +478 -0
- package/dist/ssg-validator.js +71 -21
- package/dist/state-inference-doubleblind.test.js +160 -0
- package/dist/state-inference.js +516 -0
- package/dist/state-inference.test.js +115 -0
- package/dist/state-machine-fingerprint.js +345 -0
- package/dist/state-machine-fingerprint.test.js +120 -0
- package/dist/state-miner.js +386 -0
- package/dist/state-name-inference.js +213 -0
- package/dist/state-name-inference.test.js +69 -0
- package/dist/strategy-planner.js +262 -96
- package/dist/strategy-planner.test.js +135 -0
- package/dist/telemetry-analytics.test.js +402 -0
- package/dist/terminal-format.js +68 -0
- package/dist/terminal-format.test.js +83 -0
- package/dist/topology-factory.js +196 -0
- package/dist/topology-representation.js +242 -0
- package/dist/topology-representation.test.js +27 -0
- package/dist/trajectory-augmentation.js +254 -0
- package/dist/trajectory-augmentation.test.js +63 -0
- package/dist/trajectory-corpus.js +440 -0
- package/dist/trajectory-corpus.test.js +32 -0
- package/dist/trajectory-feedback.test.js +116 -0
- package/dist/transition-synthesizer.js +286 -0
- package/dist/transition-synthesizer.test.js +123 -0
- package/dist/trust/api-semantic-mapper.js +809 -0
- package/dist/trust/call-graph-propagator.js +225 -0
- package/dist/trust/cli.js +122 -0
- package/dist/trust/compliance-scorer.js +283 -0
- package/dist/trust/confidence-calculator.js +261 -0
- package/dist/trust/engine.js +1145 -0
- package/dist/trust/explainability.js +85 -0
- package/dist/trust/formatters/ci.js +42 -0
- package/dist/trust/formatters/json.js +11 -0
- package/dist/trust/formatters/terminal.js +152 -0
- package/dist/trust/index.js +39 -0
- package/dist/trust/phase1-verify.js +171 -0
- package/dist/trust/protocol-domain-validator.js +697 -0
- package/dist/trust/score-calculator.js +282 -0
- package/dist/trust/ssg-bridge.js +641 -0
- package/dist/trust/ssg-bridge.test.js +269 -0
- package/dist/trust/types.js +67 -0
- package/dist/trust/violation-trace.js +335 -0
- package/dist/trust-api.js +179 -0
- package/dist/trust-calibration.js +279 -0
- package/dist/unknown-protocol-discovery.js +339 -0
- package/dist/unknown-protocol-discovery.test.js +102 -0
- package/dist/unsupervised-physics.js +230 -0
- package/dist/unsupervised-physics.test.js +95 -0
- package/dist/utils.test.js +37 -0
- package/dist/validator.js +187 -10
- package/dist/verification-intelligence.js +475 -0
- package/dist/verify-api.js +432 -0
- package/dist/vi-impact-report.js +293 -0
- package/dist/wl-fingerprint.js +162 -0
- package/dist/wl-fingerprint.test.js +130 -0
- package/dist/zeroshot-strategy.js +139 -0
- package/docs/Progmune_/346/212/225/350/265/204/344/272/272/347/231/275/347/232/256/344/271/246_v2.0.html +576 -0
- package/docs/Progmune_/351/241/271/347/233/256/345/205/250/350/247/243.html +710 -0
- package/package.json +74 -7
- package/protocols.json +1956 -50
- package/.dockerignore +0 -14
- package/.mcp.json +0 -11
- package/.progmune_allowlist +0 -50
- package/.test_report/test_report.md +0 -87
- package/Dockerfile +0 -9
- package/FAQ.md +0 -167
- package/WHITEPAPER.md +0 -540
- package/demo-project/auth.ts +0 -55
- package/demo-project/tsconfig.json +0 -8
- package/dist/acl-breakdown.js +0 -13
- package/dist/all-sessions.js +0 -11
- package/dist/antibody-stats.js +0 -11
- package/dist/branch-tree-count.js +0 -14
- package/dist/common-fixpath.js +0 -12
- package/dist/constraint-types.js +0 -12
- package/dist/exec-metrics.js +0 -11
- package/dist/failure-report.js +0 -11
- package/dist/fast-path-hits.js +0 -13
- package/dist/fingerprint-list.js +0 -15
- package/dist/gen-history-log.js +0 -13
- package/dist/heatmap-data.js +0 -11
- package/dist/recent-session.js +0 -12
- package/dist/svl-distribution.js +0 -11
- package/dist/terminal-status.js +0 -11
- package/dist/token-savings.js +0 -11
- package/dist/total-repairs.js +0 -12
- package/dist/unresolved-count.js +0 -12
- package/dist/valid-fingerprints.js +0 -13
- package/dist/verify-ledgers.js +0 -11
- package/docs/whitepaper-style.css +0 -77
- package/docs/whitepaper-v2.1.md +0 -609
- package/docs/whitepaper-v2.2.md +0 -1064
- package/docs/whitepaper-v2.2.pdf +0 -0
- package/fly.toml +0 -31
- package/public/dashboard.html +0 -119
- package/server/hub.js +0 -116
- package/test/replay-golden/sess_1780063202050_mgeld.json +0 -9
- package/test/replay-golden/sess_1780064032560_gocld.json +0 -354
- package/test/replay-golden/sess_1780064413331_s2709.json +0 -606
- package/test/replay-golden/sess_1780064792710_y3avo.json +0 -614
- package/test/replay-golden.ts +0 -84
- package/test_benchmark.js +0 -165
- package/test_comprehensive.mjs +0 -638
- package/test_concurrency.js +0 -129
- package/test_ir_robustness.js +0 -85
- package/test_semantic_contracts.js +0 -269
- package/test_ssg_stress.js +0 -156
- package/test_svl3.js +0 -58
- package/tsconfig.json +0 -17
package/dist/strategy-planner.js
CHANGED
|
@@ -14,12 +14,20 @@ exports.formatChainHint = formatChainHint;
|
|
|
14
14
|
const utils_1 = require("./utils");
|
|
15
15
|
const feedback_1 = require("./feedback");
|
|
16
16
|
const semantic_topology_1 = require("./semantic-topology");
|
|
17
|
+
const planner_constraints_1 = require("./planner-constraints");
|
|
18
|
+
const failure_corpus_1 = require("./failure-corpus");
|
|
17
19
|
/** Build a capability graph from IR functions. */
|
|
18
20
|
function buildCapabilityGraph(ir) {
|
|
21
|
+
const SKIP_FILES = new Set(["src/strategy-planner.ts", "src/planner.ts"]);
|
|
22
|
+
const SKIP_TAGS = new Set(); // P8.3: Python IR now supported
|
|
19
23
|
const graph = new Map();
|
|
20
24
|
for (const f of ir) {
|
|
21
25
|
if (!f.exported)
|
|
22
26
|
continue;
|
|
27
|
+
if (SKIP_FILES.has(f.file))
|
|
28
|
+
continue;
|
|
29
|
+
if ((f.tags || []).some(t => SKIP_TAGS.has(t)))
|
|
30
|
+
continue; // skip language-specific
|
|
23
31
|
graph.set(f.name, {
|
|
24
32
|
name: f.name,
|
|
25
33
|
purpose: f.purpose || "",
|
|
@@ -28,6 +36,8 @@ function buildCapabilityGraph(ir) {
|
|
|
28
36
|
produces: f.produces || [],
|
|
29
37
|
useWhen: f.useWhen || [],
|
|
30
38
|
score: 0,
|
|
39
|
+
hasExplicitMeta: !f._requiresDerived && !f._producesDerived
|
|
40
|
+
&& (f.requires || []).length > 0 && (f.produces || []).length > 0,
|
|
31
41
|
});
|
|
32
42
|
}
|
|
33
43
|
return graph;
|
|
@@ -37,26 +47,54 @@ function buildCapabilityGraph(ir) {
|
|
|
37
47
|
function scoreNode(node, intentLower, keywords) {
|
|
38
48
|
let score = 0;
|
|
39
49
|
let hasMatch = false;
|
|
50
|
+
// ── Primary verb detection: first keyword is the main action ──
|
|
51
|
+
const primaryVerb = keywords.length > 0 ? keywords[0] : "";
|
|
52
|
+
const nameWords = node.name.replace(/([A-Z])/g, " $1").toLowerCase().split(/[\s_]+/).filter(w => w.length > 1);
|
|
53
|
+
const nameLower = node.name.toLowerCase();
|
|
40
54
|
// Name match
|
|
41
55
|
for (const kw of keywords) {
|
|
42
|
-
|
|
56
|
+
// Substring match
|
|
57
|
+
if (nameLower.includes(kw)) {
|
|
43
58
|
score += 1;
|
|
44
59
|
hasMatch = true;
|
|
60
|
+
// Exact word match
|
|
61
|
+
if (nameWords.includes(kw)) {
|
|
62
|
+
// Primary verb (first keyword) that STARTS the function name → decisive bonus
|
|
63
|
+
// e.g., "extract" matches "extractIR" → +5, beats generic "validate" matches
|
|
64
|
+
if (kw === primaryVerb && nameLower.startsWith(kw))
|
|
65
|
+
score += 5;
|
|
66
|
+
else
|
|
67
|
+
score += 2;
|
|
68
|
+
}
|
|
45
69
|
}
|
|
46
|
-
const js = (0, utils_1.jaccardSimilarity)(
|
|
70
|
+
const js = (0, utils_1.jaccardSimilarity)(nameLower, kw);
|
|
47
71
|
if (js > 0.2) {
|
|
48
72
|
score += js;
|
|
49
73
|
hasMatch = true;
|
|
50
74
|
}
|
|
51
75
|
}
|
|
52
|
-
// Purpose match
|
|
76
|
+
// Purpose match — semantic bridge when function names differ from intent
|
|
53
77
|
const purposeLower = node.purpose.toLowerCase();
|
|
78
|
+
const intentWords = intentLower.split(/[\s,,]+/).filter(w => w.length > 2);
|
|
54
79
|
for (const kw of keywords) {
|
|
55
80
|
if (purposeLower.includes(kw)) {
|
|
56
81
|
score += 2;
|
|
57
82
|
hasMatch = true;
|
|
58
83
|
}
|
|
59
84
|
}
|
|
85
|
+
// Jaccard purpose similarity: "send receipt" ↔ "dispatch a message to recipient"
|
|
86
|
+
if (purposeLower.length > 0) {
|
|
87
|
+
const purposeWords = purposeLower.split(/[\s,,]+/).filter(w => w.length > 2);
|
|
88
|
+
if (purposeWords.length > 0 && intentWords.length > 0) {
|
|
89
|
+
const shared = intentWords.filter(w => purposeWords.includes(w)).length;
|
|
90
|
+
const union = new Set([...intentWords, ...purposeWords]).size;
|
|
91
|
+
const purposeJaccard = shared / union;
|
|
92
|
+
if (purposeJaccard > 0.1) {
|
|
93
|
+
score += purposeJaccard * 5;
|
|
94
|
+
hasMatch = true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
60
98
|
// Tag match
|
|
61
99
|
for (const tag of node.tags) {
|
|
62
100
|
if (intentLower.includes(tag.toLowerCase())) {
|
|
@@ -65,7 +103,6 @@ function scoreNode(node, intentLower, keywords) {
|
|
|
65
103
|
}
|
|
66
104
|
}
|
|
67
105
|
// Semantic word overlap in purpose
|
|
68
|
-
const intentWords = intentLower.split(/[\s,,]+/);
|
|
69
106
|
for (const w of intentWords) {
|
|
70
107
|
if (w.length > 2 && purposeLower.includes(w)) {
|
|
71
108
|
score += 0.5;
|
|
@@ -90,66 +127,64 @@ function scoreNode(node, intentLower, keywords) {
|
|
|
90
127
|
// Require at least one match to be relevant
|
|
91
128
|
if (!hasMatch)
|
|
92
129
|
return 0;
|
|
93
|
-
//
|
|
130
|
+
// Bonus: explicit annotations are more reliable than derived ones
|
|
131
|
+
if (node.hasExplicitMeta)
|
|
132
|
+
score *= 1.3;
|
|
133
|
+
// L3: Dynamic Credit — multiply by actual success rate
|
|
94
134
|
const successRate = (0, feedback_1.getFailureAdjustedCredit)(node.name);
|
|
95
135
|
const creditFactor = 0.3 + successRate * 0.7;
|
|
96
|
-
|
|
136
|
+
score *= creditFactor;
|
|
137
|
+
// Rule → Planner: apply mined rule constraints
|
|
138
|
+
const constraints = (0, planner_constraints_1.getConstraints)();
|
|
139
|
+
const { multiplier } = (0, planner_constraints_1.applyConstraints)(node.name, node.purpose, constraints);
|
|
140
|
+
return score * multiplier;
|
|
97
141
|
}
|
|
98
|
-
/**
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
142
|
+
/** Pick the highest-scoring node from a list. */
|
|
143
|
+
function topByScore(nodes) {
|
|
144
|
+
return nodes.sort((a, b) => b.score - a.score)[0];
|
|
145
|
+
}
|
|
146
|
+
/** Shared logic for finding nodes that produce or consume a capability label.
|
|
147
|
+
* Falls back to topology similarity if no direct data-flow match.
|
|
148
|
+
*
|
|
149
|
+
* @param field - which side of the data-flow edge to match on
|
|
150
|
+
* @param isProducer - when true the field value acts as the "produce" side
|
|
151
|
+
* of capabilityMatch; when false it acts as the "require" side. */
|
|
152
|
+
function findRelated(graph, capability, allNodes, field, isProducer) {
|
|
153
|
+
const results = [];
|
|
102
154
|
for (const node of graph.values()) {
|
|
103
|
-
if (node.
|
|
104
|
-
|
|
155
|
+
if (node[field].some(v => v === capability || capability.includes(v) || v.includes(capability))) {
|
|
156
|
+
results.push(node);
|
|
105
157
|
}
|
|
106
158
|
}
|
|
107
|
-
// Topology fallback: find semantically related
|
|
108
|
-
if (
|
|
159
|
+
// Topology fallback: find semantically related nodes via capabilityMatch
|
|
160
|
+
if (results.length === 0) {
|
|
109
161
|
try {
|
|
110
162
|
const topo = (0, semantic_topology_1.getTopology)();
|
|
111
163
|
for (const node of allNodes) {
|
|
112
|
-
if (node.
|
|
113
|
-
for (const
|
|
114
|
-
|
|
115
|
-
|
|
164
|
+
if (node[field].length > 0) {
|
|
165
|
+
for (const v of node[field]) {
|
|
166
|
+
const matched = isProducer
|
|
167
|
+
? topo.capabilityMatch(v, capability)
|
|
168
|
+
: topo.capabilityMatch(capability, v);
|
|
169
|
+
if (matched && !results.includes(node)) {
|
|
170
|
+
results.push(node);
|
|
116
171
|
break;
|
|
117
172
|
}
|
|
118
173
|
}
|
|
119
174
|
}
|
|
120
175
|
}
|
|
121
176
|
}
|
|
122
|
-
catch { }
|
|
177
|
+
catch { /* topology fallback unavailable — non-critical */ }
|
|
123
178
|
}
|
|
124
|
-
return
|
|
179
|
+
return results;
|
|
125
180
|
}
|
|
126
|
-
/** Find all capability nodes that
|
|
127
|
-
|
|
181
|
+
/** Find all capability nodes that produce a given capability label. */
|
|
182
|
+
function findProducers(graph, capability, allNodes) {
|
|
183
|
+
return findRelated(graph, capability, allNodes, "produces", true);
|
|
184
|
+
}
|
|
185
|
+
/** Find all capability nodes that require a given capability label. */
|
|
128
186
|
function findConsumers(graph, capability, allNodes) {
|
|
129
|
-
|
|
130
|
-
for (const node of graph.values()) {
|
|
131
|
-
if (node.requires.some(r => r === capability || capability.includes(r) || r.includes(capability))) {
|
|
132
|
-
consumers.push(node);
|
|
133
|
-
}
|
|
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
|
-
}
|
|
152
|
-
return consumers;
|
|
187
|
+
return findRelated(graph, capability, allNodes, "requires", false);
|
|
153
188
|
}
|
|
154
189
|
/**
|
|
155
190
|
* Select the best capability chain for an intent.
|
|
@@ -160,11 +195,19 @@ function findConsumers(graph, capability, allNodes) {
|
|
|
160
195
|
* 3. For each producer, trace forward: producer → consumer → consumer...
|
|
161
196
|
* 4. Score each chain and return top N
|
|
162
197
|
*/
|
|
163
|
-
function selectCapabilityChains(intent, ir, maxChains = 5) {
|
|
198
|
+
function selectCapabilityChains(intent, ir, maxChains = 5, llmSeeds) {
|
|
199
|
+
// Guard: null/undefined/empty intent
|
|
200
|
+
if (!intent || typeof intent !== "string" || !intent.trim())
|
|
201
|
+
return { chains: [], needsLLM: false };
|
|
202
|
+
// Ensure semantic topology is built (cached after first build)
|
|
203
|
+
const startTopo = Date.now();
|
|
204
|
+
(0, semantic_topology_1.getTopology)(ir);
|
|
205
|
+
const topoMs = Date.now() - startTopo;
|
|
164
206
|
const intentLower = intent.toLowerCase();
|
|
165
207
|
const keywords = (0, utils_1.extractKeywords)(intent);
|
|
166
208
|
const graph = buildCapabilityGraph(ir);
|
|
167
209
|
// Score all nodes
|
|
210
|
+
const startScore = Date.now();
|
|
168
211
|
for (const node of graph.values()) {
|
|
169
212
|
node.score = scoreNode(node, intentLower, keywords);
|
|
170
213
|
}
|
|
@@ -174,75 +217,180 @@ function selectCapabilityChains(intent, ir, maxChains = 5) {
|
|
|
174
217
|
let seeds = [...graph.values()]
|
|
175
218
|
.filter(n => n.score > dynamicThreshold && (n.produces.length > 0 || n.score > dynamicThreshold + 2))
|
|
176
219
|
.sort((a, b) => b.score - a.score);
|
|
177
|
-
if (seeds.length === 0 && dynamicThreshold > 0.
|
|
220
|
+
if (seeds.length === 0 && dynamicThreshold > 0.3) {
|
|
178
221
|
dynamicThreshold *= 0.5;
|
|
179
222
|
seeds = [...graph.values()]
|
|
180
223
|
.filter(n => n.score > dynamicThreshold && (n.produces.length > 0 || n.score > dynamicThreshold + 1))
|
|
181
224
|
.sort((a, b) => b.score - a.score);
|
|
182
225
|
}
|
|
183
|
-
|
|
226
|
+
// Best-effort fallback: prefer nodes with edges AND some score
|
|
227
|
+
if (seeds.length === 0) {
|
|
228
|
+
seeds = [...graph.values()]
|
|
229
|
+
.filter(n => n.score > 0 && (n.produces.length > 0 || n.requires.length > 0))
|
|
230
|
+
.sort((a, b) => b.score - a.score)
|
|
231
|
+
.slice(0, 5);
|
|
232
|
+
}
|
|
233
|
+
// Ultimate fallback: just take any exported nodes with score > 0
|
|
234
|
+
if (seeds.length === 0) {
|
|
235
|
+
seeds = [...graph.values()].filter(n => n.score > 0).slice(0, 3);
|
|
236
|
+
}
|
|
237
|
+
seeds = seeds.slice(0, graph.size > 1000 ? 10 : graph.size > 500 ? 20 : 15);
|
|
238
|
+
// ── Capability Ranking: preload edge confidence from session history ──
|
|
239
|
+
const edgeConfMap = new Map();
|
|
240
|
+
for (const ec of (0, failure_corpus_1.getEdgeConfidence)()) {
|
|
241
|
+
edgeConfMap.set(`${ec.producer}→${ec.consumer}`, ec.confidence);
|
|
242
|
+
}
|
|
243
|
+
// ── LLM seed injection: bridge the semantic gap ──
|
|
244
|
+
if (llmSeeds && llmSeeds.length > 0) {
|
|
245
|
+
const seedNames = new Set(seeds.map(n => n.name));
|
|
246
|
+
const maxScore = seeds.length > 0 ? Math.max(...seeds.map(n => n.score)) : 1;
|
|
247
|
+
for (const fnName of llmSeeds) {
|
|
248
|
+
if (seedNames.has(fnName))
|
|
249
|
+
continue;
|
|
250
|
+
const node = graph.get(fnName);
|
|
251
|
+
if (node) {
|
|
252
|
+
node.score = Math.max(node.score, maxScore);
|
|
253
|
+
seeds.unshift(node);
|
|
254
|
+
seedNames.add(fnName); // keep seedNames in sync for edge expansion
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// Edge Confidence expansion: high-conf downstream from seeds
|
|
258
|
+
let edgeAdded = 0;
|
|
259
|
+
for (const [edgeKey, conf] of edgeConfMap) {
|
|
260
|
+
if (conf < 0.9)
|
|
261
|
+
continue;
|
|
262
|
+
const [prod, cons] = edgeKey.split("→");
|
|
263
|
+
if (!seedNames.has(prod))
|
|
264
|
+
continue;
|
|
265
|
+
if (seedNames.has(cons))
|
|
266
|
+
continue;
|
|
267
|
+
const consumerNode = graph.get(cons);
|
|
268
|
+
if (consumerNode) {
|
|
269
|
+
consumerNode.score = Math.max(consumerNode.score, maxScore * 0.8);
|
|
270
|
+
seeds.push(consumerNode);
|
|
271
|
+
edgeAdded++;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (edgeAdded > 0)
|
|
275
|
+
console.error(`[EdgeExp] ${edgeAdded}高置信边扩展种子 (+${edgeAdded} nodes)`);
|
|
276
|
+
}
|
|
184
277
|
const allNodes = [...graph.values()];
|
|
185
278
|
const chains = [];
|
|
279
|
+
const BEAM_WIDTH = graph.size > 500 ? 3 : 5;
|
|
280
|
+
const MAX_CHAIN_LEN = parseInt(process.env.PROGMUNE_MAX_CHAIN_LEN || "5", 10);
|
|
281
|
+
const SCORE_FLOOR = graph.size > 1000 ? 0.2 : 0.1; // always filter noise
|
|
186
282
|
for (const seed of seeds) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
let
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
totalScore += bestConsumer.score;
|
|
205
|
-
current = bestConsumer;
|
|
206
|
-
extended = true;
|
|
207
|
-
leapDecay = 1.0; // reset decay on direct match
|
|
208
|
-
break;
|
|
283
|
+
let beam = [{
|
|
284
|
+
chain: [seed],
|
|
285
|
+
visited: new Set([seed.name]),
|
|
286
|
+
totalScore: seed.score,
|
|
287
|
+
leapDecay: 1.0,
|
|
288
|
+
deadEnd: false,
|
|
289
|
+
}];
|
|
290
|
+
let bestEntry = null;
|
|
291
|
+
for (let depth = 0; depth < MAX_CHAIN_LEN && beam.length > 0; depth++) {
|
|
292
|
+
const nextBeam = [];
|
|
293
|
+
for (const entry of beam) {
|
|
294
|
+
if (entry.deadEnd || entry.chain.length >= MAX_CHAIN_LEN) {
|
|
295
|
+
// Dead-end or max length: finalize this branch
|
|
296
|
+
if (!bestEntry || (entry.totalScore / entry.chain.length) > (bestEntry.totalScore / bestEntry.chain.length)) {
|
|
297
|
+
bestEntry = entry;
|
|
298
|
+
}
|
|
299
|
+
continue;
|
|
209
300
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
301
|
+
const current = entry.chain[entry.chain.length - 1];
|
|
302
|
+
let expanded = false;
|
|
303
|
+
// Strategy 1: direct data flow (produces → requires)
|
|
304
|
+
for (const p of current.produces) {
|
|
305
|
+
const consumers = findConsumers(graph, p, allNodes)
|
|
306
|
+
.filter(c => !entry.visited.has(c.name) && c.score >= SCORE_FLOOR);
|
|
307
|
+
for (const consumer of consumers) {
|
|
308
|
+
const newVisited = new Set(entry.visited);
|
|
309
|
+
newVisited.add(consumer.name);
|
|
310
|
+
// Capability Ranking: edge confidence × consumer score
|
|
311
|
+
const edgeKey = `${current.name}→${consumer.name}`;
|
|
312
|
+
const edgeConf = edgeConfMap.get(edgeKey) ?? 0.5; // Laplace prior for unseen edges
|
|
313
|
+
const rankedScore = consumer.score * (0.5 + edgeConf * 0.5);
|
|
314
|
+
nextBeam.push({
|
|
315
|
+
chain: [...entry.chain, consumer],
|
|
316
|
+
visited: newVisited,
|
|
317
|
+
totalScore: entry.totalScore + rankedScore,
|
|
318
|
+
leapDecay: 1.0,
|
|
319
|
+
deadEnd: consumer.score === 0 && consumer.produces.length === 0,
|
|
320
|
+
});
|
|
321
|
+
expanded = true;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
// Strategy 2: semantic leap — topology similarity
|
|
325
|
+
if (!expanded) {
|
|
326
|
+
try {
|
|
327
|
+
const topo = (0, semantic_topology_1.getTopology)();
|
|
328
|
+
const similar = topo.findSimilar(current.name, 10)
|
|
329
|
+
.filter(s => !entry.visited.has(s.name) && s.similarity > 0.25);
|
|
330
|
+
for (const s of similar.slice(0, 3)) {
|
|
331
|
+
const bestMatch = graph.get(s.name);
|
|
332
|
+
if (bestMatch && bestMatch.score >= SCORE_FLOOR) {
|
|
333
|
+
const newVisited = new Set(entry.visited);
|
|
334
|
+
newVisited.add(bestMatch.name);
|
|
335
|
+
nextBeam.push({
|
|
336
|
+
chain: [...entry.chain, bestMatch],
|
|
337
|
+
visited: newVisited,
|
|
338
|
+
totalScore: entry.totalScore + bestMatch.score * entry.leapDecay,
|
|
339
|
+
leapDecay: entry.leapDecay * 0.7, // decay 30% per leap
|
|
340
|
+
deadEnd: false,
|
|
341
|
+
});
|
|
342
|
+
expanded = true;
|
|
343
|
+
}
|
|
226
344
|
}
|
|
227
345
|
}
|
|
346
|
+
catch { /* semantic leap unavailable — non-critical */ }
|
|
347
|
+
}
|
|
348
|
+
// Dead-end: no consumers found → finalize this branch
|
|
349
|
+
if (!expanded) {
|
|
350
|
+
if (!bestEntry || (entry.totalScore / entry.chain.length) > (bestEntry.totalScore / bestEntry.chain.length)) {
|
|
351
|
+
bestEntry = entry;
|
|
352
|
+
}
|
|
228
353
|
}
|
|
229
|
-
catch { }
|
|
230
354
|
}
|
|
355
|
+
// Beam pruning: keep top BEAM_WIDTH by average score
|
|
356
|
+
nextBeam.sort((a, b) => (b.totalScore / b.chain.length) - (a.totalScore / a.chain.length));
|
|
357
|
+
beam = nextBeam.slice(0, BEAM_WIDTH);
|
|
231
358
|
}
|
|
359
|
+
// Any remaining beam entries compete for best
|
|
360
|
+
for (const entry of beam) {
|
|
361
|
+
if (!bestEntry || (entry.totalScore / entry.chain.length) > (bestEntry.totalScore / bestEntry.chain.length)) {
|
|
362
|
+
bestEntry = entry;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (!bestEntry || bestEntry.chain.length < 1)
|
|
366
|
+
continue;
|
|
367
|
+
const chain = bestEntry.chain;
|
|
368
|
+
let totalScore = bestEntry.totalScore;
|
|
369
|
+
const visited = bestEntry.visited;
|
|
232
370
|
// Backward trace: does seed need something? Find producers.
|
|
233
371
|
if (seed.requires.length > 0) {
|
|
234
372
|
const producers = findProducers(graph, seed.requires[0], allNodes)
|
|
235
373
|
.filter(p => !visited.has(p.name));
|
|
236
374
|
if (producers.length > 0) {
|
|
237
|
-
const bestProducer = producers
|
|
375
|
+
const bestProducer = topByScore(producers);
|
|
238
376
|
chain.unshift(bestProducer);
|
|
239
377
|
totalScore += bestProducer.score;
|
|
240
378
|
}
|
|
241
379
|
}
|
|
380
|
+
// Hybrid: supplement graph chain with high-score keyword matches the graph missed
|
|
381
|
+
const missed = allNodes
|
|
382
|
+
.filter(n => !visited.has(n.name) && n.score >= SCORE_FLOOR)
|
|
383
|
+
.sort((a, b) => b.score - a.score)
|
|
384
|
+
.slice(0, 2); // at most 2 supplements
|
|
385
|
+
for (const m of missed) {
|
|
386
|
+
chain.push(m);
|
|
387
|
+
visited.add(m.name);
|
|
388
|
+
totalScore += m.score * 0.5; // half weight — graph-discovered nodes are preferred
|
|
389
|
+
}
|
|
242
390
|
if (chain.length >= 1) {
|
|
243
391
|
chains.push({
|
|
244
392
|
nodes: chain,
|
|
245
|
-
score: totalScore / chain.length,
|
|
393
|
+
score: totalScore / chain.length,
|
|
246
394
|
explanation: chain.map(n => n.name).join(" → "),
|
|
247
395
|
});
|
|
248
396
|
}
|
|
@@ -258,16 +406,34 @@ function selectCapabilityChains(intent, ir, maxChains = 5) {
|
|
|
258
406
|
unique.push(c);
|
|
259
407
|
}
|
|
260
408
|
}
|
|
261
|
-
|
|
409
|
+
const scoreMs = Date.now() - startScore;
|
|
410
|
+
// Detect semantic gap: very few seeds passed threshold → LLM could help
|
|
411
|
+
const needsLLM = seeds.length === 0 || (seeds.length <= 2 && graph.size > 50);
|
|
412
|
+
if (graph.size > 200) {
|
|
413
|
+
console.error(`[Strategy] ${graph.size} nodes | topo=${topoMs}ms score=${scoreMs}ms | ${unique.length} chains | needsLLM=${needsLLM}`);
|
|
414
|
+
}
|
|
415
|
+
return { chains: unique.slice(0, maxChains), needsLLM };
|
|
262
416
|
}
|
|
263
|
-
/** Format chains as a hint
|
|
417
|
+
/** Format chains as a routing hint (not full path).
|
|
418
|
+
* Only shows top 2 high-confidence next steps per entry,
|
|
419
|
+
* so the LLM gets guidance without being forced into long chains. */
|
|
264
420
|
function formatChainHint(chains) {
|
|
265
421
|
if (chains.length === 0)
|
|
266
422
|
return "";
|
|
267
|
-
const lines = ["\n
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
423
|
+
const lines = ["\n建议的下一步调用 (已从历史成功链中验证):"];
|
|
424
|
+
const shown = new Set();
|
|
425
|
+
for (const c of chains.slice(0, 3)) {
|
|
426
|
+
const pairs = [];
|
|
427
|
+
for (let i = 0; i < c.nodes.length - 1 && pairs.length < 2; i++) {
|
|
428
|
+
const key = `${c.nodes[i].name}→${c.nodes[i + 1].name}`;
|
|
429
|
+
if (!shown.has(key)) {
|
|
430
|
+
shown.add(key);
|
|
431
|
+
pairs.push(`${c.nodes[i].name} → ${c.nodes[i + 1].name}`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
if (pairs.length > 0) {
|
|
435
|
+
lines.push(` ${pairs.join(" | ")} (★${c.score.toFixed(1)})`);
|
|
436
|
+
}
|
|
271
437
|
}
|
|
272
438
|
return lines.join("\n");
|
|
273
439
|
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* Unit tests for strategy-planner pure functions.
|
|
5
|
+
* Tests the graph-building and chain-selection logic in isolation.
|
|
6
|
+
*/
|
|
7
|
+
const vitest_1 = require("vitest");
|
|
8
|
+
const strategy_planner_1 = require("./strategy-planner");
|
|
9
|
+
// Minimal mock IR with functions covering auth and data domains
|
|
10
|
+
const mockIR = [
|
|
11
|
+
{
|
|
12
|
+
name: "validatePassword",
|
|
13
|
+
purpose: "Validate a user's password against stored hash",
|
|
14
|
+
tags: ["auth", "security"],
|
|
15
|
+
requires: ["password", "hash"],
|
|
16
|
+
produces: ["validation_result"],
|
|
17
|
+
useWhen: ["user login", "password reset"],
|
|
18
|
+
file: "src/auth.ts",
|
|
19
|
+
exported: true,
|
|
20
|
+
params: [
|
|
21
|
+
{ name: "password", type: "string" },
|
|
22
|
+
{ name: "hash", type: "string" },
|
|
23
|
+
],
|
|
24
|
+
returnType: "boolean",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: "generateJWT",
|
|
28
|
+
purpose: "Generate a JWT token for authenticated user",
|
|
29
|
+
tags: ["auth", "token"],
|
|
30
|
+
requires: ["validation_result"],
|
|
31
|
+
produces: ["jwt_token"],
|
|
32
|
+
useWhen: ["after successful login"],
|
|
33
|
+
file: "src/auth.ts",
|
|
34
|
+
exported: true,
|
|
35
|
+
params: [{ name: "userId", type: "string" }],
|
|
36
|
+
returnType: "string",
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
name: "fetchUser",
|
|
40
|
+
purpose: "Fetch user data from the database",
|
|
41
|
+
tags: ["data", "user"],
|
|
42
|
+
requires: ["userId"],
|
|
43
|
+
produces: ["user_data"],
|
|
44
|
+
useWhen: ["user profile", "dashboard"],
|
|
45
|
+
file: "src/data.ts",
|
|
46
|
+
exported: true,
|
|
47
|
+
params: [{ name: "userId", type: "string" }],
|
|
48
|
+
returnType: "object",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "saveToDatabase",
|
|
52
|
+
purpose: "Save record to persistent storage",
|
|
53
|
+
tags: ["data", "storage"],
|
|
54
|
+
requires: ["record"],
|
|
55
|
+
produces: ["saved_id"],
|
|
56
|
+
useWhen: ["data persistence"],
|
|
57
|
+
file: "src/data.ts",
|
|
58
|
+
exported: true,
|
|
59
|
+
params: [{ name: "record", type: "object" }],
|
|
60
|
+
returnType: "string",
|
|
61
|
+
},
|
|
62
|
+
// Non-exported function — should be skipped
|
|
63
|
+
{
|
|
64
|
+
name: "internalHelper",
|
|
65
|
+
purpose: "Internal helper — not exported",
|
|
66
|
+
tags: [],
|
|
67
|
+
requires: [],
|
|
68
|
+
produces: [],
|
|
69
|
+
file: "src/internal.ts",
|
|
70
|
+
exported: false,
|
|
71
|
+
params: [],
|
|
72
|
+
returnType: "void",
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
(0, vitest_1.describe)("selectCapabilityChains", () => {
|
|
76
|
+
(0, vitest_1.it)("returns chains for an auth intent", () => {
|
|
77
|
+
const { chains } = (0, strategy_planner_1.selectCapabilityChains)("authenticate user", mockIR, 3);
|
|
78
|
+
(0, vitest_1.expect)(chains.length).toBeGreaterThan(0);
|
|
79
|
+
// Should find at least validatePassword or generateJWT
|
|
80
|
+
const names = chains.flatMap(c => c.nodes.map(n => n.name));
|
|
81
|
+
(0, vitest_1.expect)(names.some(n => n.includes("validate") || n.includes("JWT"))).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
(0, vitest_1.it)("returns chains for a data intent", () => {
|
|
84
|
+
const { chains } = (0, strategy_planner_1.selectCapabilityChains)("fetch user data", mockIR, 3);
|
|
85
|
+
(0, vitest_1.expect)(chains.length).toBeGreaterThan(0);
|
|
86
|
+
const names = chains.flatMap(c => c.nodes.map(n => n.name));
|
|
87
|
+
(0, vitest_1.expect)(names.some(n => n.includes("fetchUser"))).toBe(true);
|
|
88
|
+
});
|
|
89
|
+
(0, vitest_1.it)("respects maxChains limit", () => {
|
|
90
|
+
const { chains } = (0, strategy_planner_1.selectCapabilityChains)("data", mockIR, 1);
|
|
91
|
+
(0, vitest_1.expect)(chains.length).toBeLessThanOrEqual(1);
|
|
92
|
+
});
|
|
93
|
+
(0, vitest_1.it)("skips planner internals (self-referential guard)", () => {
|
|
94
|
+
const selfIR = [{
|
|
95
|
+
name: "selectCapabilityChains",
|
|
96
|
+
purpose: "planner logic",
|
|
97
|
+
tags: [],
|
|
98
|
+
requires: [],
|
|
99
|
+
produces: [],
|
|
100
|
+
file: "src/strategy-planner.ts",
|
|
101
|
+
exported: true,
|
|
102
|
+
params: [],
|
|
103
|
+
returnType: "any",
|
|
104
|
+
}];
|
|
105
|
+
const { chains } = (0, strategy_planner_1.selectCapabilityChains)("planning", selfIR, 3);
|
|
106
|
+
// strategy-planner.ts and planner.ts should be filtered out
|
|
107
|
+
const names = chains.flatMap(c => c.nodes.map(n => n.name));
|
|
108
|
+
(0, vitest_1.expect)(names).not.toContain("selectCapabilityChains");
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
(0, vitest_1.describe)("selectCapabilityChains — edge cases", () => {
|
|
112
|
+
(0, vitest_1.it)("returns empty array for null intent", () => {
|
|
113
|
+
(0, vitest_1.expect)((0, strategy_planner_1.selectCapabilityChains)(null, mockIR)).toEqual({ chains: [], needsLLM: false });
|
|
114
|
+
});
|
|
115
|
+
(0, vitest_1.it)("returns empty array for undefined intent", () => {
|
|
116
|
+
(0, vitest_1.expect)((0, strategy_planner_1.selectCapabilityChains)(undefined, mockIR)).toEqual({ chains: [], needsLLM: false });
|
|
117
|
+
});
|
|
118
|
+
(0, vitest_1.it)("returns empty array for empty string intent", () => {
|
|
119
|
+
(0, vitest_1.expect)((0, strategy_planner_1.selectCapabilityChains)("", mockIR)).toEqual({ chains: [], needsLLM: false });
|
|
120
|
+
});
|
|
121
|
+
(0, vitest_1.it)("returns empty array for whitespace-only intent", () => {
|
|
122
|
+
(0, vitest_1.expect)((0, strategy_planner_1.selectCapabilityChains)(" ", mockIR)).toEqual({ chains: [], needsLLM: false });
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
(0, vitest_1.describe)("formatChainHint", () => {
|
|
126
|
+
(0, vitest_1.it)("returns empty string for empty chains", () => {
|
|
127
|
+
(0, vitest_1.expect)((0, strategy_planner_1.formatChainHint)([])).toBe("");
|
|
128
|
+
});
|
|
129
|
+
(0, vitest_1.it)("formats a single chain", () => {
|
|
130
|
+
const { chains } = (0, strategy_planner_1.selectCapabilityChains)("authenticate user", mockIR, 1);
|
|
131
|
+
const hint = (0, strategy_planner_1.formatChainHint)(chains);
|
|
132
|
+
(0, vitest_1.expect)(hint).toContain("建议的下一步调用");
|
|
133
|
+
(0, vitest_1.expect)(hint).toContain("★");
|
|
134
|
+
});
|
|
135
|
+
});
|