progmune-runtime 2.1.5 → 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 +326 -59
- 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/failure-corpus.js
CHANGED
|
@@ -37,6 +37,19 @@ exports.saveCheckpoint = saveCheckpoint;
|
|
|
37
37
|
exports.loadCheckpoint = loadCheckpoint;
|
|
38
38
|
exports.clearCheckpoint = clearCheckpoint;
|
|
39
39
|
exports.recordFailure = recordFailure;
|
|
40
|
+
exports.recordTrajectory = recordTrajectory;
|
|
41
|
+
exports.recordSuccess = recordSuccess;
|
|
42
|
+
exports.recordRepair = recordRepair;
|
|
43
|
+
exports.recordFailureV2 = recordFailureV2;
|
|
44
|
+
exports.loadFailuresV2 = loadFailuresV2;
|
|
45
|
+
exports.loadTrajectories = loadTrajectories;
|
|
46
|
+
exports.queryByResult = queryByResult;
|
|
47
|
+
exports.queryByViolationType = queryByViolationType;
|
|
48
|
+
exports.queryByProtocol = queryByProtocol;
|
|
49
|
+
exports.getTopPatterns = getTopPatterns;
|
|
50
|
+
exports.corpusTrajectoryStats = corpusTrajectoryStats;
|
|
51
|
+
exports.getRepairStats = getRepairStats;
|
|
52
|
+
exports.corpusV2Size = corpusV2Size;
|
|
40
53
|
exports.recordSession = recordSession;
|
|
41
54
|
exports.getAllFailures = getAllFailures;
|
|
42
55
|
exports.getFailuresBySVL = getFailuresBySVL;
|
|
@@ -47,6 +60,8 @@ exports.getLearnedPatterns = getLearnedPatterns;
|
|
|
47
60
|
exports.queryAntibodies = queryAntibodies;
|
|
48
61
|
exports.getSemanticHeatmap = getSemanticHeatmap;
|
|
49
62
|
exports.getAntibodyStats = getAntibodyStats;
|
|
63
|
+
exports.recordEdgeRejection = recordEdgeRejection;
|
|
64
|
+
exports.getEdgeConfidence = getEdgeConfidence;
|
|
50
65
|
exports.generateCandidateRules = generateCandidateRules;
|
|
51
66
|
const fs = __importStar(require("fs"));
|
|
52
67
|
const path = __importStar(require("path"));
|
|
@@ -91,7 +106,7 @@ function clearCheckpoint(intent) {
|
|
|
91
106
|
try {
|
|
92
107
|
fs.unlinkSync(checkpointPath(intent));
|
|
93
108
|
}
|
|
94
|
-
catch { }
|
|
109
|
+
catch { /* checkpoint file may not exist */ }
|
|
95
110
|
}
|
|
96
111
|
/** Record a constraint violation to the failure corpus. */
|
|
97
112
|
function recordFailure(record) {
|
|
@@ -105,7 +120,7 @@ function recordFailure(record) {
|
|
|
105
120
|
try {
|
|
106
121
|
seq = parseInt(fs.readFileSync(seqFile, 'utf-8'), 10);
|
|
107
122
|
}
|
|
108
|
-
catch { }
|
|
123
|
+
catch { /* checkpoint file may not exist */ }
|
|
109
124
|
seq++;
|
|
110
125
|
fs.writeFileSync(seqFile, String(seq));
|
|
111
126
|
const id = `fail_${Date.now()}_${seq}`;
|
|
@@ -121,6 +136,228 @@ function recordFailure(record) {
|
|
|
121
136
|
console.error(`[FailureCorpus] ${record.violatedSVL} | 尝试 ${record.plannerAttempt}/${record.plannerRetryTotal} | ${id}`);
|
|
122
137
|
});
|
|
123
138
|
}
|
|
139
|
+
const TRAJECTORY_DIR = path.join(CORPUS_DIR, "trajectories");
|
|
140
|
+
function writeTrajectoryFile(record) {
|
|
141
|
+
setImmediate(() => {
|
|
142
|
+
try {
|
|
143
|
+
const date = record.timestamp.slice(0, 10);
|
|
144
|
+
const dateDir = path.join(TRAJECTORY_DIR, date);
|
|
145
|
+
ensureDir(dateDir);
|
|
146
|
+
fs.writeFileSync(path.join(dateDir, `${record.id}.json`), JSON.stringify(record, null, 2));
|
|
147
|
+
}
|
|
148
|
+
catch { /* corpus write must never crash */ }
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
let _trajSeq = 0;
|
|
152
|
+
function nextTrajId() {
|
|
153
|
+
if (_trajSeq === 0) {
|
|
154
|
+
const seqFile = path.join(TRAJECTORY_DIR, ".seq");
|
|
155
|
+
try {
|
|
156
|
+
_trajSeq = parseInt(fs.readFileSync(seqFile, "utf-8"), 10);
|
|
157
|
+
}
|
|
158
|
+
catch { }
|
|
159
|
+
}
|
|
160
|
+
_trajSeq++;
|
|
161
|
+
setImmediate(() => {
|
|
162
|
+
try {
|
|
163
|
+
fs.writeFileSync(path.join(TRAJECTORY_DIR, ".seq"), String(_trajSeq));
|
|
164
|
+
}
|
|
165
|
+
catch { }
|
|
166
|
+
});
|
|
167
|
+
return `T-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Record any trajectory — success, violation, repair, or optimal.
|
|
171
|
+
* This is the single entry point for the Code World Model Dataset.
|
|
172
|
+
*/
|
|
173
|
+
function recordTrajectory(params) {
|
|
174
|
+
ensureDir(TRAJECTORY_DIR);
|
|
175
|
+
const defaultCtx = { nestingDepth: 0, exceptionHandled: false, insideLoop: false, branchCount: 0, asyncContext: false };
|
|
176
|
+
const defaultReward = { safety: 0.5, latency: 0.5, maintainability: 0.5, security: 0.5, auditability: 0.5, custom: {} };
|
|
177
|
+
const record = {
|
|
178
|
+
id: nextTrajId(),
|
|
179
|
+
timestamp: new Date().toISOString(),
|
|
180
|
+
protocol: params.protocol,
|
|
181
|
+
initialState: params.initialState,
|
|
182
|
+
finalState: params.finalState,
|
|
183
|
+
trajectory: params.trajectory,
|
|
184
|
+
result: params.result,
|
|
185
|
+
reward: params.result === "success" || params.result === "optimal" ? (params.reward || defaultReward) : undefined,
|
|
186
|
+
violation: params.result === "violation" || params.result === "repair" ? {
|
|
187
|
+
type: params.violationType || "other",
|
|
188
|
+
failingStepIndex: params.failingStepIndex || 0,
|
|
189
|
+
expectedStates: params.finalState,
|
|
190
|
+
actualStates: params.initialState,
|
|
191
|
+
fixPath: params.fixPath,
|
|
192
|
+
description: params.violationDesc || "",
|
|
193
|
+
} : undefined,
|
|
194
|
+
repairFrom: params.repairFrom,
|
|
195
|
+
context: params.context || defaultCtx,
|
|
196
|
+
successRate: params.successRate || 0,
|
|
197
|
+
metadata: {
|
|
198
|
+
source: params.source || "llm",
|
|
199
|
+
intent: params.intent,
|
|
200
|
+
sessionId: params.sessionId,
|
|
201
|
+
},
|
|
202
|
+
goal: params.goal,
|
|
203
|
+
feedback: params.feedback,
|
|
204
|
+
cost: params.cost,
|
|
205
|
+
};
|
|
206
|
+
writeTrajectoryFile(record);
|
|
207
|
+
console.error(`[Trajectory] ${record.result} | ${params.protocol} | ${record.id}`);
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Record a successful trajectory — positive sample for reward learning.
|
|
211
|
+
* Call this when validation passes without violations.
|
|
212
|
+
*/
|
|
213
|
+
function recordSuccess(params) {
|
|
214
|
+
recordTrajectory({ ...params, result: "success", successRate: 1.0 });
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Record a repair trajectory — shows what fix was applied and whether it worked.
|
|
218
|
+
*/
|
|
219
|
+
function recordRepair(params) {
|
|
220
|
+
recordTrajectory({
|
|
221
|
+
...params,
|
|
222
|
+
result: "repair",
|
|
223
|
+
successRate: params.success ? 1.0 : 0.0,
|
|
224
|
+
failingStepIndex: 0,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
// ── Backward-compatible wrappers ──
|
|
228
|
+
/** @deprecated Use recordTrajectory() with result: "violation" instead. */
|
|
229
|
+
function recordFailureV2(record) {
|
|
230
|
+
recordTrajectory({
|
|
231
|
+
protocol: record.protocol,
|
|
232
|
+
initialState: record.actualStateSequence,
|
|
233
|
+
finalState: record.expectedStateSequence,
|
|
234
|
+
trajectory: record.actionSequence,
|
|
235
|
+
result: "violation",
|
|
236
|
+
violationType: record.violationType,
|
|
237
|
+
violationDesc: record.violationType,
|
|
238
|
+
failingStepIndex: record.failingStepIndex,
|
|
239
|
+
fixPath: record.ssgFixPath,
|
|
240
|
+
context: record.contextFeatures,
|
|
241
|
+
successRate: record.successRate,
|
|
242
|
+
intent: record.intent,
|
|
243
|
+
sessionId: record.parentSessionId,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
/** @deprecated Use loadTrajectories() instead. */
|
|
247
|
+
function loadFailuresV2() {
|
|
248
|
+
return loadTrajectories()
|
|
249
|
+
.filter(t => t.result === "violation")
|
|
250
|
+
.map(t => ({
|
|
251
|
+
failureId: t.id,
|
|
252
|
+
timestamp: t.timestamp,
|
|
253
|
+
protocol: t.protocol,
|
|
254
|
+
codeSnippet: (t.trajectory || []).join("; "),
|
|
255
|
+
expectedStateSequence: t.finalState || [],
|
|
256
|
+
actualStateSequence: t.initialState || [],
|
|
257
|
+
violationType: t.violation?.type || "other",
|
|
258
|
+
failingStepIndex: t.violation?.failingStepIndex || 0,
|
|
259
|
+
contextFeatures: t.context,
|
|
260
|
+
repairAttempts: [],
|
|
261
|
+
successRate: t.successRate,
|
|
262
|
+
actionSequence: t.trajectory || [],
|
|
263
|
+
ssgStateAtViolation: t.initialState || [],
|
|
264
|
+
ssgFixPath: t.violation?.fixPath || [],
|
|
265
|
+
parentSessionId: t.metadata?.sessionId,
|
|
266
|
+
intent: t.metadata?.intent || "",
|
|
267
|
+
}));
|
|
268
|
+
}
|
|
269
|
+
// ═══════════════════════════════════════════════════════════════
|
|
270
|
+
// P0: Trajectory query API
|
|
271
|
+
// ═══════════════════════════════════════════════════════════════
|
|
272
|
+
/** Load all trajectory records from the corpus. */
|
|
273
|
+
function loadTrajectories() {
|
|
274
|
+
const results = [];
|
|
275
|
+
if (!fs.existsSync(TRAJECTORY_DIR))
|
|
276
|
+
return results;
|
|
277
|
+
const dateDirs = fs.readdirSync(TRAJECTORY_DIR, { withFileTypes: true });
|
|
278
|
+
for (const entry of dateDirs) {
|
|
279
|
+
if (!entry.isDirectory())
|
|
280
|
+
continue;
|
|
281
|
+
const files = fs.readdirSync(path.join(TRAJECTORY_DIR, entry.name));
|
|
282
|
+
for (const file of files) {
|
|
283
|
+
if (!file.endsWith(".json"))
|
|
284
|
+
continue;
|
|
285
|
+
try {
|
|
286
|
+
const raw = fs.readFileSync(path.join(TRAJECTORY_DIR, entry.name, file), "utf-8");
|
|
287
|
+
results.push(JSON.parse(raw));
|
|
288
|
+
}
|
|
289
|
+
catch { /* skip corrupted files */ }
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return results.sort((a, b) => b.timestamp.localeCompare(a.timestamp));
|
|
293
|
+
}
|
|
294
|
+
/** Query trajectories by result type. */
|
|
295
|
+
function queryByResult(result) {
|
|
296
|
+
return loadTrajectories().filter(t => t.result === result);
|
|
297
|
+
}
|
|
298
|
+
/** Query violations by type. */
|
|
299
|
+
function queryByViolationType(type) {
|
|
300
|
+
return loadTrajectories().filter(t => t.violation?.type === type);
|
|
301
|
+
}
|
|
302
|
+
/** Query trajectories by protocol. */
|
|
303
|
+
function queryByProtocol(protocol) {
|
|
304
|
+
return loadTrajectories().filter(t => t.protocol === protocol);
|
|
305
|
+
}
|
|
306
|
+
/** Get top N violation patterns across all trajectories. */
|
|
307
|
+
function getTopPatterns(limit = 10) {
|
|
308
|
+
const all = loadTrajectories().filter(t => t.result === "violation");
|
|
309
|
+
const buckets = {};
|
|
310
|
+
for (const t of all) {
|
|
311
|
+
const ctx = t.context;
|
|
312
|
+
const key = `${t.violation?.type || "other"}|d${ctx.nestingDepth}|${ctx.exceptionHandled ? "try" : "no-try"}|${ctx.insideLoop ? "loop" : "no-loop"}`;
|
|
313
|
+
if (!buckets[key])
|
|
314
|
+
buckets[key] = { records: [] };
|
|
315
|
+
buckets[key].records.push(t);
|
|
316
|
+
}
|
|
317
|
+
return Object.entries(buckets)
|
|
318
|
+
.sort((a, b) => b[1].records.length - a[1].records.length)
|
|
319
|
+
.slice(0, limit)
|
|
320
|
+
.map(([key, bucket]) => {
|
|
321
|
+
const [vt, ...rest] = key.split("|");
|
|
322
|
+
const avg = bucket.records.reduce((s, t) => s + t.successRate, 0) / bucket.records.length;
|
|
323
|
+
return {
|
|
324
|
+
violationType: vt,
|
|
325
|
+
contextKey: rest.join(" "),
|
|
326
|
+
count: bucket.records.length,
|
|
327
|
+
avgSuccessRate: avg,
|
|
328
|
+
};
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
/** Get total trajectory count and breakdown by result type. */
|
|
332
|
+
function corpusTrajectoryStats() {
|
|
333
|
+
const all = loadTrajectories();
|
|
334
|
+
return {
|
|
335
|
+
total: all.length,
|
|
336
|
+
success: all.filter(t => t.result === "success").length,
|
|
337
|
+
violation: all.filter(t => t.result === "violation").length,
|
|
338
|
+
repair: all.filter(t => t.result === "repair").length,
|
|
339
|
+
optimal: all.filter(t => t.result === "optimal").length,
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
/** Get repair acceptance statistics for P4 Reward Model training data. */
|
|
343
|
+
function getRepairStats() {
|
|
344
|
+
const all = loadTrajectories().filter(t => t.result === "repair");
|
|
345
|
+
const accepted = all.filter(t => t.feedback?.accepted === true).length;
|
|
346
|
+
const rejected = all.filter(t => t.feedback?.rejected === true).length;
|
|
347
|
+
const total = all.length;
|
|
348
|
+
const acceptanceRate = total > 0 ? accepted / total : 0;
|
|
349
|
+
const costs = all
|
|
350
|
+
.map(t => t.cost?.latency)
|
|
351
|
+
.filter((l) => l !== undefined);
|
|
352
|
+
const avgLatency = costs.length > 0
|
|
353
|
+
? costs.reduce((s, l) => s + l, 0) / costs.length
|
|
354
|
+
: 0;
|
|
355
|
+
return { acceptedRepairs: accepted, rejectedRepairs: rejected, totalRepairs: total, acceptanceRate, avgLatency };
|
|
356
|
+
}
|
|
357
|
+
/** @deprecated Use corpusTrajectoryStats() instead. */
|
|
358
|
+
function corpusV2Size() {
|
|
359
|
+
return corpusTrajectoryStats().total;
|
|
360
|
+
}
|
|
124
361
|
/** 记录一个完整的意图会话(支持 ExecutionSession 和旧 IntentSession) */
|
|
125
362
|
/**
|
|
126
363
|
* 保存执行会话(含所有尝试、违规、状态转移)。
|
|
@@ -179,7 +416,7 @@ function getAllFailures() {
|
|
|
179
416
|
try {
|
|
180
417
|
records.push(JSON.parse(fs.readFileSync(path.join(dirPath, file), "utf-8")));
|
|
181
418
|
}
|
|
182
|
-
catch { }
|
|
419
|
+
catch { /* checkpoint file may not exist */ }
|
|
183
420
|
}
|
|
184
421
|
}
|
|
185
422
|
}
|
|
@@ -287,7 +524,7 @@ function getAllSessions() {
|
|
|
287
524
|
const raw = JSON.parse(fs.readFileSync(path.join(SESSIONS_DIR, file), "utf-8"));
|
|
288
525
|
sessions.push(normalizeSession(raw));
|
|
289
526
|
}
|
|
290
|
-
catch { }
|
|
527
|
+
catch { /* checkpoint file may not exist */ }
|
|
291
528
|
}
|
|
292
529
|
}
|
|
293
530
|
return sessions;
|
|
@@ -379,11 +616,11 @@ function getLearnedPatterns() {
|
|
|
379
616
|
for (const v of a.violations) {
|
|
380
617
|
if (!v.fixPath || v.fixPath.length === 0)
|
|
381
618
|
continue;
|
|
382
|
-
if (v.svl !== 4)
|
|
383
|
-
continue; // only SVL-4 has fix paths
|
|
384
619
|
const violatedLabel = `SVL-${v.svl}`;
|
|
385
|
-
|
|
386
|
-
const
|
|
620
|
+
// Use violatedConstraint if available, otherwise fall back to missingStates
|
|
621
|
+
const constraintKey = v.violatedConstraint
|
|
622
|
+
|| (v.missingStates || ["unknown"]).join(",");
|
|
623
|
+
const signature = `${violatedLabel}:${constraintKey}`;
|
|
387
624
|
const existing = agg.get(signature);
|
|
388
625
|
const ts = new Date(s.startedAt).toISOString();
|
|
389
626
|
if (existing) {
|
|
@@ -396,7 +633,7 @@ function getLearnedPatterns() {
|
|
|
396
633
|
}
|
|
397
634
|
else {
|
|
398
635
|
agg.set(signature, {
|
|
399
|
-
violation: `${violatedLabel}: ${
|
|
636
|
+
violation: `${violatedLabel}: ${constraintKey}`,
|
|
400
637
|
fixPath: v.fixPath,
|
|
401
638
|
count: 1,
|
|
402
639
|
intents: new Set([s.intent]),
|
|
@@ -566,6 +803,82 @@ function getAntibodyStats() {
|
|
|
566
803
|
.slice(0, 10);
|
|
567
804
|
return { totalHits, fastPathHits, injectedHintHits, totalLLMCallsSaved, totalTokensSaved, byLevel, topSignatures };
|
|
568
805
|
}
|
|
806
|
+
// ── Edge rejection tracking ──
|
|
807
|
+
const MEMORY_DIR = path.join(CORPUS_DIR, "..", ".progmune_memory");
|
|
808
|
+
const REJECTION_FILE = path.join(MEMORY_DIR, "edge_rejections.json");
|
|
809
|
+
/** Record that the LLM rejected a recommended edge (picked a different function). */
|
|
810
|
+
function recordEdgeRejection(producer, consumer) {
|
|
811
|
+
(0, file_lock_1.withLock)("edge_rejections", () => {
|
|
812
|
+
let data = {};
|
|
813
|
+
try {
|
|
814
|
+
if (fs.existsSync(REJECTION_FILE))
|
|
815
|
+
data = JSON.parse(fs.readFileSync(REJECTION_FILE, "utf-8"));
|
|
816
|
+
}
|
|
817
|
+
catch { }
|
|
818
|
+
const key = `${producer}→${consumer}`;
|
|
819
|
+
data[key] = (data[key] || 0) + 1;
|
|
820
|
+
fs.writeFileSync(REJECTION_FILE, JSON.stringify(data, null, 2));
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
function getEdgeRejections() {
|
|
824
|
+
try {
|
|
825
|
+
if (!fs.existsSync(REJECTION_FILE))
|
|
826
|
+
return new Map();
|
|
827
|
+
const data = JSON.parse(fs.readFileSync(REJECTION_FILE, "utf-8"));
|
|
828
|
+
return new Map(Object.entries(data));
|
|
829
|
+
}
|
|
830
|
+
catch {
|
|
831
|
+
return new Map();
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
function getEdgeConfidence(producer, consumer) {
|
|
835
|
+
const sessions = getAllSessions();
|
|
836
|
+
const edgeStats = new Map();
|
|
837
|
+
for (const s of sessions) {
|
|
838
|
+
for (const a of s.attempts) {
|
|
839
|
+
const calls = a.transitions
|
|
840
|
+
.filter(t => t.function)
|
|
841
|
+
.sort((x, y) => x.actionIndex - y.actionIndex);
|
|
842
|
+
const isSuccess = a.outcome === "success";
|
|
843
|
+
for (let i = 0; i < calls.length - 1; i++) {
|
|
844
|
+
const edgeKey = `${calls[i].function}→${calls[i + 1].function}`;
|
|
845
|
+
if (!edgeStats.has(edgeKey)) {
|
|
846
|
+
edgeStats.set(edgeKey, { success: 0, failure: 0 });
|
|
847
|
+
}
|
|
848
|
+
const stats = edgeStats.get(edgeKey);
|
|
849
|
+
if (isSuccess)
|
|
850
|
+
stats.success++;
|
|
851
|
+
else
|
|
852
|
+
stats.failure++;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
const rejections = getEdgeRejections();
|
|
857
|
+
const results = [];
|
|
858
|
+
for (const [key, stats] of edgeStats) {
|
|
859
|
+
const [prod, cons] = key.split("→");
|
|
860
|
+
if (producer && prod !== producer)
|
|
861
|
+
continue;
|
|
862
|
+
if (consumer && cons !== consumer)
|
|
863
|
+
continue;
|
|
864
|
+
// Factor in explicit LLM rejections as negative signal
|
|
865
|
+
const rejectionCount = rejections.get(key) || 0;
|
|
866
|
+
const effectiveSuccess = Math.max(0, stats.success - rejectionCount);
|
|
867
|
+
const effectiveTotal = stats.success + stats.failure + rejectionCount;
|
|
868
|
+
// Laplace smoothing: Beta(1,1) prior
|
|
869
|
+
const confidence = (effectiveSuccess + 1) / (effectiveTotal + 2);
|
|
870
|
+
results.push({
|
|
871
|
+
producer: prod,
|
|
872
|
+
consumer: cons,
|
|
873
|
+
successCount: stats.success,
|
|
874
|
+
failureCount: stats.failure,
|
|
875
|
+
totalCount: effectiveTotal,
|
|
876
|
+
confidence: Math.round(confidence * 100) / 100,
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
results.sort((a, b) => b.confidence * b.totalCount - a.confidence * a.totalCount);
|
|
880
|
+
return results;
|
|
881
|
+
}
|
|
569
882
|
/** Generate candidate immune rules from failure patterns. */
|
|
570
883
|
function generateCandidateRules() {
|
|
571
884
|
const genome = getFailureGenome();
|
package/dist/feedback.js
CHANGED
|
@@ -87,16 +87,26 @@ function getWeightedSuccessRate(funcName) {
|
|
|
87
87
|
return totalWeight > 0 ? weightedSuccess / totalWeight : 0.5;
|
|
88
88
|
}
|
|
89
89
|
/** @requires FUNCTION_NAME @produces FAILURE_ADJUSTED_CREDIT
|
|
90
|
-
* Credit score adjusted by failure severity.
|
|
91
|
-
*
|
|
92
|
-
*
|
|
90
|
+
* Credit score adjusted by failure severity with Laplace smoothing.
|
|
91
|
+
*
|
|
92
|
+
* Laplace (add-1) smoothing eliminates small-sample bias:
|
|
93
|
+
* - 1/1 success → ~0.67 (not 1.0 — still uncertain)
|
|
94
|
+
* - 99/100 success → ~0.98 (approaches empirical rate)
|
|
95
|
+
* - 0/1 failure → ~0.33 (not 0.0 — allows redemption)
|
|
96
|
+
* - Cold start → 0.5 (neutral prior)
|
|
97
|
+
*
|
|
98
|
+
* SVL-4 protocol violations are penalized 3× more than SVL-1.
|
|
99
|
+
* Time-weighted via exponential decay (half-life = 1 day). */
|
|
93
100
|
function getFailureAdjustedCredit(funcName) {
|
|
94
101
|
const records = loadFeedback();
|
|
95
102
|
const funcRecords = records
|
|
96
103
|
.filter(r => r.functionName === funcName)
|
|
97
104
|
.map(r => ({ ...r, age: (Date.now() - new Date(r.timestamp).getTime()) / 86400000 }));
|
|
105
|
+
// Laplace prior: Beta(1,1) → pseudocount of 1 success + 1 failure
|
|
106
|
+
const LAPLACE_PRIOR_SUCCESS = 1;
|
|
107
|
+
const LAPLACE_PRIOR_TOTAL = 2;
|
|
98
108
|
if (funcRecords.length === 0)
|
|
99
|
-
return 0.5
|
|
109
|
+
return LAPLACE_PRIOR_SUCCESS / LAPLACE_PRIOR_TOTAL; // 0.5
|
|
100
110
|
const SVL_PENALTY = {
|
|
101
111
|
"SVL-1": 1.0, // missing function — minor
|
|
102
112
|
"SVL-2": 1.5, // type mismatch — moderate
|
|
@@ -116,7 +126,8 @@ function getFailureAdjustedCredit(funcName) {
|
|
|
116
126
|
// weightedSuccess stays 0 for failures
|
|
117
127
|
}
|
|
118
128
|
}
|
|
119
|
-
|
|
129
|
+
// Laplace smoothing: (success + 1) / (total + 2)
|
|
130
|
+
return (weightedSuccess + LAPLACE_PRIOR_SUCCESS) / (totalWeight + LAPLACE_PRIOR_TOTAL);
|
|
120
131
|
}
|
|
121
132
|
/** @requires EXECUTION_DATA @produces RUN_ID */
|
|
122
133
|
function recordRun(intent, actions, success, error) {
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
/**
|
|
4
|
+
* Unit tests for feedback module — pure functions only.
|
|
5
|
+
* Does NOT hit the filesystem (file I/O functions are integration-tested separately).
|
|
6
|
+
*/
|
|
7
|
+
const vitest_1 = require("vitest");
|
|
8
|
+
// We test the pure scoring math indirectly by reading the source patterns.
|
|
9
|
+
// The key behaviors to verify:
|
|
10
|
+
// 1. Cold start returns neutral (no penalty)
|
|
11
|
+
// 2. Consistent success returns high credit
|
|
12
|
+
// 3. SVL-severity affects penalty proportionally
|
|
13
|
+
// Since these functions read from feedback.json on disk,
|
|
14
|
+
// we verify the module exports and type contracts.
|
|
15
|
+
const feedback_1 = require("./feedback");
|
|
16
|
+
(0, vitest_1.describe)("feedback module", () => {
|
|
17
|
+
(0, vitest_1.it)("loadFeedback returns an array", () => {
|
|
18
|
+
const records = (0, feedback_1.loadFeedback)();
|
|
19
|
+
(0, vitest_1.expect)(Array.isArray(records)).toBe(true);
|
|
20
|
+
});
|
|
21
|
+
(0, vitest_1.it)("getFunctionSuccessRate returns 0.5 for unknown function (no history)", () => {
|
|
22
|
+
const rate = (0, feedback_1.getFunctionSuccessRate)("__nonexistent_func_xyz__");
|
|
23
|
+
(0, vitest_1.expect)(rate).toBe(0.5);
|
|
24
|
+
});
|
|
25
|
+
(0, vitest_1.it)("getWeightedSuccessRate returns 0.5 for unknown function", () => {
|
|
26
|
+
const rate = (0, feedback_1.getWeightedSuccessRate)("__nonexistent_func_xyz__");
|
|
27
|
+
(0, vitest_1.expect)(rate).toBe(0.5);
|
|
28
|
+
});
|
|
29
|
+
(0, vitest_1.it)("getFailureAdjustedCredit returns 0.5 for unknown function (Laplace prior: neutral)", () => {
|
|
30
|
+
const credit = (0, feedback_1.getFailureAdjustedCredit)("__nonexistent_func_xyz__");
|
|
31
|
+
(0, vitest_1.expect)(credit).toBe(0.5);
|
|
32
|
+
});
|
|
33
|
+
(0, vitest_1.it)("getFailureAdjustedCredit returns a number between 0 and 1", () => {
|
|
34
|
+
const credit = (0, feedback_1.getFailureAdjustedCredit)("generateJWT");
|
|
35
|
+
(0, vitest_1.expect)(credit).toBeGreaterThanOrEqual(0);
|
|
36
|
+
(0, vitest_1.expect)(credit).toBeLessThanOrEqual(1);
|
|
37
|
+
});
|
|
38
|
+
(0, vitest_1.it)("Laplace smoothing: cold start = 0.5 (neutral prior)", () => {
|
|
39
|
+
// No records → prior Beta(1,1) → 1/(1+1) = 0.5
|
|
40
|
+
(0, vitest_1.expect)((0, feedback_1.getFailureAdjustedCredit)("__unknown_fn__")).toBe(0.5);
|
|
41
|
+
});
|
|
42
|
+
(0, vitest_1.it)("Laplace smoothing: 0/1 record is not zero (allows redemption)", () => {
|
|
43
|
+
// Laplace: (0+1)/(1+2) ≈ 0.33, not 0.0
|
|
44
|
+
// This is only true if there are real records (not cold start).
|
|
45
|
+
// Cold start with no records returns 0.5 by definition.
|
|
46
|
+
const credit = (0, feedback_1.getFailureAdjustedCredit)("svl1_test_1780509556916");
|
|
47
|
+
(0, vitest_1.expect)(credit).toBeGreaterThan(0);
|
|
48
|
+
});
|
|
49
|
+
});
|