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
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* NestJS Framework Adapter Spike — Decorator-based Protocol Detection
|
|
4
|
+
*
|
|
5
|
+
* Parses NestJS decorators (@Controller, @Get/@Post/@Put/@Delete,
|
|
6
|
+
* @UseGuards, @UsePipes, @UseInterceptors) using ts-morph to determine
|
|
7
|
+
* the protocol compliance of each route.
|
|
8
|
+
*
|
|
9
|
+
* Unlike Express middleware (which requires inference from function names),
|
|
10
|
+
* NestJS decorators explicitly declare intent:
|
|
11
|
+
* @UseGuards(AuthGuard) → authorization step
|
|
12
|
+
* @UsePipes(ValidationPipe) → input validation step
|
|
13
|
+
* @UseInterceptors(...) → pre/post processing
|
|
14
|
+
*
|
|
15
|
+
* This detector extracts routes, determines their auth/validation status,
|
|
16
|
+
* and identifies security gaps (mutation routes without guards, etc.).
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.analyzeNestJSProject = analyzeNestJSProject;
|
|
20
|
+
exports.analyzeNestJSFile = analyzeNestJSFile;
|
|
21
|
+
exports.formatNestJSReport = formatNestJSReport;
|
|
22
|
+
const ts_morph_1 = require("ts-morph");
|
|
23
|
+
// ── Core Analysis ──
|
|
24
|
+
function analyzeNestJSProject(projectRoot) {
|
|
25
|
+
let project;
|
|
26
|
+
try {
|
|
27
|
+
project = new ts_morph_1.Project({
|
|
28
|
+
tsConfigFilePath: `${projectRoot}/tsconfig.json`,
|
|
29
|
+
skipAddingFilesFromTsConfig: false,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// No tsconfig — try direct file loading
|
|
34
|
+
project = new ts_morph_1.Project();
|
|
35
|
+
try {
|
|
36
|
+
project.addSourceFilesAtPaths(`${projectRoot}/**/*.ts`);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return { controllers: [], routes: [], issues: [] };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const analysis = {
|
|
43
|
+
controllers: [],
|
|
44
|
+
routes: [],
|
|
45
|
+
issues: [],
|
|
46
|
+
};
|
|
47
|
+
for (const file of project.getSourceFiles()) {
|
|
48
|
+
// Skip node_modules and test files
|
|
49
|
+
if (file.getFilePath().includes("node_modules"))
|
|
50
|
+
continue;
|
|
51
|
+
if (/\.(test|spec)\.ts$/.test(file.getFilePath()))
|
|
52
|
+
continue;
|
|
53
|
+
for (const cls of file.getClasses()) {
|
|
54
|
+
const ctrlDec = cls.getDecorator("Controller");
|
|
55
|
+
if (!ctrlDec)
|
|
56
|
+
continue;
|
|
57
|
+
const controllerName = cls.getName() || "UnknownController";
|
|
58
|
+
analysis.controllers.push(controllerName);
|
|
59
|
+
const basePath = getStringArg(ctrlDec, 0) || "";
|
|
60
|
+
const classGuards = extractGuardNames(cls.getDecorator("UseGuards"));
|
|
61
|
+
const classPipes = extractGuardNames(cls.getDecorator("UsePipes"));
|
|
62
|
+
for (const method of cls.getMethods()) {
|
|
63
|
+
const httpMethod = getHttpMethod(method);
|
|
64
|
+
if (!httpMethod)
|
|
65
|
+
continue;
|
|
66
|
+
const routePath = getStringArg(method.getDecorators().find(d => isHttpDecorator(d)), 0) || "";
|
|
67
|
+
const fullPath = basePath + (routePath.startsWith("/") ? routePath : `/${routePath}`);
|
|
68
|
+
// Guards/Pipes: method-level overrides class-level
|
|
69
|
+
const methodGuards = extractGuardNames(method.getDecorator("UseGuards"));
|
|
70
|
+
const methodPipes = extractGuardNames(method.getDecorator("UsePipes"));
|
|
71
|
+
const guards = methodGuards.length > 0 ? methodGuards : classGuards;
|
|
72
|
+
const pipes = methodPipes.length > 0 ? methodPipes : classPipes;
|
|
73
|
+
const route = {
|
|
74
|
+
method: httpMethod,
|
|
75
|
+
path: fullPath,
|
|
76
|
+
controller: controllerName,
|
|
77
|
+
handler: method.getName() || "unknown",
|
|
78
|
+
hasAuthGuard: guards.length > 0,
|
|
79
|
+
hasValidationPipe: pipes.length > 0,
|
|
80
|
+
guards,
|
|
81
|
+
pipes,
|
|
82
|
+
};
|
|
83
|
+
analysis.routes.push(route);
|
|
84
|
+
// ── Security Checks ──
|
|
85
|
+
// POST/PUT/DELETE without auth guard
|
|
86
|
+
// Skip intentionally public routes (login, register, health, etc.)
|
|
87
|
+
if (["POST", "PUT", "DELETE", "PATCH"].includes(httpMethod) && !isPublicRoute(fullPath)) {
|
|
88
|
+
if (!route.hasAuthGuard) {
|
|
89
|
+
analysis.issues.push({
|
|
90
|
+
type: "NESTJS_NO_AUTH",
|
|
91
|
+
severity: "critical",
|
|
92
|
+
route: `${httpMethod} ${fullPath}`,
|
|
93
|
+
controller: controllerName,
|
|
94
|
+
message: `Mutation route ${httpMethod} ${fullPath} has no @UseGuards. Anyone can call it.`,
|
|
95
|
+
fix: `Add @UseGuards(AuthGuard) to the method or controller class.`,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
if (!route.hasValidationPipe) {
|
|
99
|
+
analysis.issues.push({
|
|
100
|
+
type: "NESTJS_NO_VALIDATION",
|
|
101
|
+
severity: "medium",
|
|
102
|
+
route: `${httpMethod} ${fullPath}`,
|
|
103
|
+
controller: controllerName,
|
|
104
|
+
message: `Mutation route ${httpMethod} ${fullPath} has no @UsePipes for input validation.`,
|
|
105
|
+
fix: `Add @UsePipes(ValidationPipe) or a DTO class to validate input.`,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Sensitive GET routes without auth
|
|
110
|
+
if (httpMethod === "GET") {
|
|
111
|
+
const sensitiveTerms = ["admin", "private", "secret", "manage"];
|
|
112
|
+
if (sensitiveTerms.some(t => fullPath.toLowerCase().includes(t)) && !route.hasAuthGuard) {
|
|
113
|
+
analysis.issues.push({
|
|
114
|
+
type: "NESTJS_SENSITIVE_PUBLIC",
|
|
115
|
+
severity: "high",
|
|
116
|
+
route: `${httpMethod} ${fullPath}`,
|
|
117
|
+
controller: controllerName,
|
|
118
|
+
message: `Sensitive GET route ${fullPath} is publicly accessible without @UseGuards.`,
|
|
119
|
+
fix: `Add @UseGuards(AuthGuard) to protect this route.`,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return analysis;
|
|
127
|
+
}
|
|
128
|
+
// ── Helpers ──
|
|
129
|
+
/** Check if a route is intentionally public (login, register, health, etc.). */
|
|
130
|
+
function isPublicRoute(path) {
|
|
131
|
+
// Normalize: ensure leading slash for consistent matching
|
|
132
|
+
const normalized = path.startsWith("/") ? path : `/${path}`;
|
|
133
|
+
const publicPatterns = [
|
|
134
|
+
/\/auth\/login$/i, /\/login$/i,
|
|
135
|
+
/\/auth\/register$/i, /\/register$/i, /\/signup$/i,
|
|
136
|
+
/\/auth\/signup$/i, /\/auth\/signin$/i, /\/signin$/i,
|
|
137
|
+
/\/auth\/refresh$/i, /\/auth\/forgot/i, /\/auth\/reset/i,
|
|
138
|
+
/\/health$/i, /\/healthcheck$/i, /\/ping$/i, /\/status$/i,
|
|
139
|
+
/\/public\//i, /\/static\//i,
|
|
140
|
+
];
|
|
141
|
+
return publicPatterns.some(p => p.test(normalized));
|
|
142
|
+
}
|
|
143
|
+
// ── Low-level Helpers ──
|
|
144
|
+
function getHttpMethod(method) {
|
|
145
|
+
for (const dec of method.getDecorators()) {
|
|
146
|
+
const name = dec.getName();
|
|
147
|
+
if (["Get", "Post", "Put", "Delete", "Patch", "Options", "Head", "All"].includes(name)) {
|
|
148
|
+
return name.toUpperCase();
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
function isHttpDecorator(dec) {
|
|
154
|
+
return ["Get", "Post", "Put", "Delete", "Patch", "Options", "Head", "All"].includes(dec.getName());
|
|
155
|
+
}
|
|
156
|
+
function getStringArg(decorator, index) {
|
|
157
|
+
if (!decorator)
|
|
158
|
+
return undefined;
|
|
159
|
+
const args = decorator.getArguments();
|
|
160
|
+
if (args.length <= index)
|
|
161
|
+
return undefined;
|
|
162
|
+
const text = args[index].getText();
|
|
163
|
+
return text.replace(/^['"]|['"]$/g, "");
|
|
164
|
+
}
|
|
165
|
+
function extractGuardNames(decorator) {
|
|
166
|
+
if (!decorator)
|
|
167
|
+
return [];
|
|
168
|
+
const args = decorator.getArguments();
|
|
169
|
+
const result = [];
|
|
170
|
+
for (const arg of args) {
|
|
171
|
+
const text = arg.getText();
|
|
172
|
+
// Handle: @UseGuards(AuthGuard) or @UseGuards(AuthGuard, AdminGuard)
|
|
173
|
+
const matches = text.match(/\b([A-Z][a-zA-Z0-9]*(?:Guard|Pipe|Interceptor))\b/g);
|
|
174
|
+
if (matches)
|
|
175
|
+
result.push(...matches);
|
|
176
|
+
}
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
// ── File-level and Project-level Convenience ──
|
|
180
|
+
/**
|
|
181
|
+
* Analyze a single TypeScript file for NestJS controllers.
|
|
182
|
+
*/
|
|
183
|
+
function analyzeNestJSFile(filePath) {
|
|
184
|
+
const fs = require("fs");
|
|
185
|
+
if (!fs.existsSync(filePath))
|
|
186
|
+
return null;
|
|
187
|
+
const code = fs.readFileSync(filePath, "utf-8");
|
|
188
|
+
// Quick check: does this file look like NestJS?
|
|
189
|
+
if (!/@Controller\b/.test(code) && !/@nestjs\/common/.test(code))
|
|
190
|
+
return null;
|
|
191
|
+
const project = new ts_morph_1.Project();
|
|
192
|
+
try {
|
|
193
|
+
project.addSourceFileAtPath(filePath);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
// Parse using the same logic as analyzeNestJSProject
|
|
199
|
+
const analysis = {
|
|
200
|
+
controllers: [],
|
|
201
|
+
routes: [],
|
|
202
|
+
issues: [],
|
|
203
|
+
};
|
|
204
|
+
for (const file of project.getSourceFiles()) {
|
|
205
|
+
for (const cls of file.getClasses()) {
|
|
206
|
+
const ctrlDec = cls.getDecorator("Controller");
|
|
207
|
+
if (!ctrlDec)
|
|
208
|
+
continue;
|
|
209
|
+
// ... (same parsing logic)
|
|
210
|
+
const controllerName = cls.getName() || "UnknownController";
|
|
211
|
+
analysis.controllers.push(controllerName);
|
|
212
|
+
const basePath = getStringArg(ctrlDec, 0) || "";
|
|
213
|
+
const classGuards = extractGuardNames(cls.getDecorator("UseGuards"));
|
|
214
|
+
const classPipes = extractGuardNames(cls.getDecorator("UsePipes"));
|
|
215
|
+
for (const method of cls.getMethods()) {
|
|
216
|
+
const httpMethod = getHttpMethod(method);
|
|
217
|
+
if (!httpMethod)
|
|
218
|
+
continue;
|
|
219
|
+
const routePath = getStringArg(method.getDecorators().find(d => isHttpDecorator(d)), 0) || "";
|
|
220
|
+
const fullPath = basePath + (routePath.startsWith("/") ? routePath : `/${routePath}`);
|
|
221
|
+
const methodGuards = extractGuardNames(method.getDecorator("UseGuards"));
|
|
222
|
+
const methodPipes = extractGuardNames(method.getDecorator("UsePipes"));
|
|
223
|
+
const guards = methodGuards.length > 0 ? methodGuards : classGuards;
|
|
224
|
+
const pipes = methodPipes.length > 0 ? methodPipes : classPipes;
|
|
225
|
+
const route = {
|
|
226
|
+
method: httpMethod,
|
|
227
|
+
path: fullPath,
|
|
228
|
+
controller: controllerName,
|
|
229
|
+
handler: method.getName() || "unknown",
|
|
230
|
+
hasAuthGuard: guards.length > 0,
|
|
231
|
+
hasValidationPipe: pipes.length > 0,
|
|
232
|
+
guards,
|
|
233
|
+
pipes,
|
|
234
|
+
};
|
|
235
|
+
analysis.routes.push(route);
|
|
236
|
+
// Security checks
|
|
237
|
+
if (["POST", "PUT", "DELETE", "PATCH"].includes(httpMethod) && !isPublicRoute(fullPath)) {
|
|
238
|
+
if (!route.hasAuthGuard) {
|
|
239
|
+
analysis.issues.push({
|
|
240
|
+
type: "NESTJS_NO_AUTH",
|
|
241
|
+
severity: "critical",
|
|
242
|
+
route: `${httpMethod} ${fullPath}`,
|
|
243
|
+
controller: controllerName,
|
|
244
|
+
message: `Mutation route ${httpMethod} ${fullPath} has no @UseGuards. Anyone can call it.`,
|
|
245
|
+
fix: `Add @UseGuards(AuthGuard) to the method or controller class.`,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
if (!route.hasValidationPipe) {
|
|
249
|
+
analysis.issues.push({
|
|
250
|
+
type: "NESTJS_NO_VALIDATION",
|
|
251
|
+
severity: "medium",
|
|
252
|
+
route: `${httpMethod} ${fullPath}`,
|
|
253
|
+
controller: controllerName,
|
|
254
|
+
message: `Mutation route ${httpMethod} ${fullPath} has no @UsePipes for input validation.`,
|
|
255
|
+
fix: `Add @UsePipes(ValidationPipe) or a DTO class to validate input.`,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (httpMethod === "GET") {
|
|
260
|
+
const sensitiveTerms = ["admin", "private", "secret", "manage"];
|
|
261
|
+
if (sensitiveTerms.some(t => fullPath.toLowerCase().includes(t)) && !route.hasAuthGuard) {
|
|
262
|
+
analysis.issues.push({
|
|
263
|
+
type: "NESTJS_SENSITIVE_PUBLIC",
|
|
264
|
+
severity: "high",
|
|
265
|
+
route: `${httpMethod} ${fullPath}`,
|
|
266
|
+
controller: controllerName,
|
|
267
|
+
message: `Sensitive GET route ${fullPath} is publicly accessible without @UseGuards.`,
|
|
268
|
+
fix: `Add @UseGuards(AuthGuard) to protect this route.`,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return analysis;
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Format a summary report for CLI output.
|
|
279
|
+
*/
|
|
280
|
+
function formatNestJSReport(analysis) {
|
|
281
|
+
if (analysis.controllers.length === 0) {
|
|
282
|
+
return "Not a NestJS project (no @Controller classes found).";
|
|
283
|
+
}
|
|
284
|
+
const lines = [
|
|
285
|
+
`Controllers: ${analysis.controllers.length}`,
|
|
286
|
+
`Routes: ${analysis.routes.length}`,
|
|
287
|
+
`Issues: ${analysis.issues.length}`,
|
|
288
|
+
"",
|
|
289
|
+
];
|
|
290
|
+
if (analysis.issues.length === 0) {
|
|
291
|
+
lines.push("✅ No NestJS security issues detected.");
|
|
292
|
+
return lines.join("\n");
|
|
293
|
+
}
|
|
294
|
+
for (const issue of analysis.issues) {
|
|
295
|
+
const emoji = issue.severity === "critical" ? "🔴" : issue.severity === "high" ? "🟠" : "🟡";
|
|
296
|
+
lines.push(`${emoji} [${issue.type}] ${issue.route}`);
|
|
297
|
+
lines.push(` ${issue.message}`);
|
|
298
|
+
lines.push(` Fix: ${issue.fix}`);
|
|
299
|
+
lines.push("");
|
|
300
|
+
}
|
|
301
|
+
return lines.join("\n");
|
|
302
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* tRPC Framework Adapter — Endpoint Input Validation Detection
|
|
4
|
+
*
|
|
5
|
+
* Detects tRPC router/procedure patterns in TypeScript projects and
|
|
6
|
+
* flags procedures that lack input schema validation:
|
|
7
|
+
*
|
|
8
|
+
* - TRPC_MUTATION_WITHOUT_INPUT_SCHEMA (high)
|
|
9
|
+
* Mutation procedure without .input(z.object(...)) that references
|
|
10
|
+
* `input.` in its body or performs DB writes — unvalidated data
|
|
11
|
+
* reaches business logic.
|
|
12
|
+
*
|
|
13
|
+
* - TRPC_PUBLIC_MUTATION (high)
|
|
14
|
+
* publicProcedure mutation without input schema — unauthenticated
|
|
15
|
+
* AND unvalidated mutation endpoint.
|
|
16
|
+
*
|
|
17
|
+
* - TRPC_PROCEDURE_USES_INPUT_WITHOUT_SCHEMA (medium)
|
|
18
|
+
* Any procedure (query or mutation) whose body reads `input.x`
|
|
19
|
+
* but has no .input() schema declared.
|
|
20
|
+
*
|
|
21
|
+
* Purely regex-based, no AST — follows the express-detector.ts pattern.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.detectTRPCApp = detectTRPCApp;
|
|
25
|
+
exports.extractRouterNames = extractRouterNames;
|
|
26
|
+
exports.extractProcedures = extractProcedures;
|
|
27
|
+
exports.analyzeTRPCFile = analyzeTRPCFile;
|
|
28
|
+
// ── Detection Patterns ──
|
|
29
|
+
const PROCEDURE_TYPE_PATTERN = /\b(publicProcedure|protectedProcedure|adminProcedure)\b/g;
|
|
30
|
+
const DB_WRITE_PATTERN = /\b(db\.(insert|update|delete|create|upsert|execute)|prisma\.\w+\.(create|update|delete|upsert|createMany|updateMany|deleteMany)|drizzle\.(insert|update|delete)|\.(insert|update|delete|create|upsert)\s*\()/i;
|
|
31
|
+
/**
|
|
32
|
+
* Detect whether this file contains tRPC definitions.
|
|
33
|
+
*/
|
|
34
|
+
function detectTRPCApp(code) {
|
|
35
|
+
return (/\b(initTRPC|t\.router)\b/.test(code) ||
|
|
36
|
+
/\brouter\s*\(\s*\{/.test(code) ||
|
|
37
|
+
PROCEDURE_TYPE_PATTERN.test(code));
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Extract tRPC router variable names (export const xxxRouter = router({...})).
|
|
41
|
+
*/
|
|
42
|
+
function extractRouterNames(code) {
|
|
43
|
+
const names = [];
|
|
44
|
+
const re = /(?:export\s+)?(?:const|let)\s+(\w*[Rr]outer\w*)\s*=\s*(?:t\.)?router\s*\(/g;
|
|
45
|
+
let m;
|
|
46
|
+
while ((m = re.exec(code)) !== null) {
|
|
47
|
+
names.push(m[1]);
|
|
48
|
+
}
|
|
49
|
+
return names;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Extract procedure definitions from a tRPC router body.
|
|
53
|
+
*
|
|
54
|
+
* Matches patterns like:
|
|
55
|
+
* add: protectedProcedure.input(z.object({...})).mutation(async ({ctx, input}) => {...}),
|
|
56
|
+
* list: publicProcedure.query(async ({ctx}) => {...}),
|
|
57
|
+
*/
|
|
58
|
+
function extractProcedures(code) {
|
|
59
|
+
const procedures = [];
|
|
60
|
+
// Match: name: <procedureType>.input(...).<kind>( or name: <procedureType>.<kind>(
|
|
61
|
+
const procRe = /([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(publicProcedure|protectedProcedure|adminProcedure)((?:\.\w+\s*\([^()]*\))*)\.(query|mutation)\s*\(/g;
|
|
62
|
+
let m;
|
|
63
|
+
while ((m = procRe.exec(code)) !== null) {
|
|
64
|
+
const name = m[1];
|
|
65
|
+
const procType = m[2].replace(/Procedure$/, "");
|
|
66
|
+
const chain = m[3] || "";
|
|
67
|
+
const kind = m[4];
|
|
68
|
+
const hasInputSchema = /\.input\s*\(/.test(chain);
|
|
69
|
+
// Extract body until matching closing paren of the query/mutation call.
|
|
70
|
+
// Heuristic: scan forward for the closing paren matching the one after
|
|
71
|
+
// the .query( / .mutation( token.
|
|
72
|
+
const openIdx = procRe.lastIndex - 1; // index of '(' after query/mutation
|
|
73
|
+
let depth = 1;
|
|
74
|
+
let closeIdx = openIdx + 1;
|
|
75
|
+
while (closeIdx < code.length && depth > 0) {
|
|
76
|
+
const ch = code[closeIdx];
|
|
77
|
+
if (ch === "(")
|
|
78
|
+
depth++;
|
|
79
|
+
else if (ch === ")")
|
|
80
|
+
depth--;
|
|
81
|
+
closeIdx++;
|
|
82
|
+
}
|
|
83
|
+
const body = code.slice(openIdx + 1, Math.min(closeIdx - 1, openIdx + 2000));
|
|
84
|
+
const usesInputInBody = /input\s*\./.test(body) || /\binput\b/.test(body);
|
|
85
|
+
const doesDbWrite = DB_WRITE_PATTERN.test(body);
|
|
86
|
+
procedures.push({
|
|
87
|
+
name,
|
|
88
|
+
kind,
|
|
89
|
+
procedureType: procType,
|
|
90
|
+
hasInputSchema,
|
|
91
|
+
usesInputInBody,
|
|
92
|
+
doesDbWrite,
|
|
93
|
+
line: code.slice(0, m.index).split("\n").length,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
return procedures;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Analyze a source file for tRPC security issues.
|
|
100
|
+
* Main entry point — mirrors analyzeExpressFile().
|
|
101
|
+
*/
|
|
102
|
+
function analyzeTRPCFile(filePath) {
|
|
103
|
+
const fs = require("fs");
|
|
104
|
+
let code;
|
|
105
|
+
try {
|
|
106
|
+
code = fs.readFileSync(filePath, "utf-8");
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return { hasTRPC: false, routerNames: [], procedures: [], issues: [] };
|
|
110
|
+
}
|
|
111
|
+
if (!detectTRPCApp(code)) {
|
|
112
|
+
return { hasTRPC: false, routerNames: [], procedures: [], issues: [] };
|
|
113
|
+
}
|
|
114
|
+
const routerNames = extractRouterNames(code);
|
|
115
|
+
const procedures = extractProcedures(code);
|
|
116
|
+
const issues = [];
|
|
117
|
+
for (const p of procedures) {
|
|
118
|
+
// Rule 1: Mutation without input schema that writes to DB or uses input
|
|
119
|
+
if (p.kind === "mutation" && !p.hasInputSchema && (p.usesInputInBody || p.doesDbWrite)) {
|
|
120
|
+
issues.push({
|
|
121
|
+
severity: "high",
|
|
122
|
+
rule: "TRPC_MUTATION_WITHOUT_INPUT_SCHEMA",
|
|
123
|
+
procedure: p.name,
|
|
124
|
+
message: `tRPC mutation "${p.name}" has no .input() schema but ` +
|
|
125
|
+
`${p.usesInputInBody ? "references `input` in its body" : "performs DB writes"}. ` +
|
|
126
|
+
`Unvalidated payloads reach business logic.`,
|
|
127
|
+
line: p.line,
|
|
128
|
+
fix: `Add .input(z.object({...})) to "${p.name}" with a Zod schema covering every field read from input.`,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
// Rule 2: Public (unauthenticated) mutation WITHOUT input schema that
|
|
132
|
+
// moves data (uses input or writes to DB). Pure stateless public
|
|
133
|
+
// mutations (e.g. logout clearing a cookie) are exempt.
|
|
134
|
+
if (p.kind === "mutation" && p.procedureType === "public" && !p.hasInputSchema &&
|
|
135
|
+
(p.usesInputInBody || p.doesDbWrite)) {
|
|
136
|
+
issues.push({
|
|
137
|
+
severity: "high",
|
|
138
|
+
rule: "TRPC_PUBLIC_MUTATION",
|
|
139
|
+
procedure: p.name,
|
|
140
|
+
message: `tRPC mutation "${p.name}" is public (no auth) AND has no .input() validation ` +
|
|
141
|
+
`while ${p.usesInputInBody ? "reading `input`" : "writing to the database"}. ` +
|
|
142
|
+
`Anyone can submit arbitrary payloads.`,
|
|
143
|
+
line: p.line,
|
|
144
|
+
fix: `Use protectedProcedure for "${p.name}", and add .input(z.object({...})) for payload validation.`,
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
// Rule 3: Any procedure reading input without a schema
|
|
148
|
+
if (p.usesInputInBody && !p.hasInputSchema && p.kind === "query") {
|
|
149
|
+
issues.push({
|
|
150
|
+
severity: "medium",
|
|
151
|
+
rule: "TRPC_PROCEDURE_USES_INPUT_WITHOUT_SCHEMA",
|
|
152
|
+
procedure: p.name,
|
|
153
|
+
message: `tRPC query "${p.name}" reads \`input\` but declares no .input() schema. ` +
|
|
154
|
+
`Input shape is unvalidated and can drift from the contract.`,
|
|
155
|
+
line: p.line,
|
|
156
|
+
fix: `Declare .input(z.object({...})) on "${p.name}" to pin down the input contract.`,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return { hasTRPC: true, routerNames, procedures, issues };
|
|
161
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Framework Version-Aware Governance Rules
|
|
4
|
+
*
|
|
5
|
+
* Encodes the proxy.ts lesson (2026-08-03):
|
|
6
|
+
* AI follows the framework version the project actually depends on.
|
|
7
|
+
* Human reviewers follow the framework version in their training data.
|
|
8
|
+
* When these differ, the human reviewer is usually wrong.
|
|
9
|
+
*
|
|
10
|
+
* This module reads package.json to detect framework versions,
|
|
11
|
+
* then applies version-specific conventions instead of training-data defaults.
|
|
12
|
+
*/
|
|
13
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
14
|
+
if (k2 === undefined) k2 = k;
|
|
15
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
16
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
17
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
18
|
+
}
|
|
19
|
+
Object.defineProperty(o, k2, desc);
|
|
20
|
+
}) : (function(o, m, k, k2) {
|
|
21
|
+
if (k2 === undefined) k2 = k;
|
|
22
|
+
o[k2] = m[k];
|
|
23
|
+
}));
|
|
24
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
25
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
26
|
+
}) : function(o, v) {
|
|
27
|
+
o["default"] = v;
|
|
28
|
+
});
|
|
29
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
30
|
+
var ownKeys = function(o) {
|
|
31
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
32
|
+
var ar = [];
|
|
33
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
34
|
+
return ar;
|
|
35
|
+
};
|
|
36
|
+
return ownKeys(o);
|
|
37
|
+
};
|
|
38
|
+
return function (mod) {
|
|
39
|
+
if (mod && mod.__esModule) return mod;
|
|
40
|
+
var result = {};
|
|
41
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
42
|
+
__setModuleDefault(result, mod);
|
|
43
|
+
return result;
|
|
44
|
+
};
|
|
45
|
+
})();
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.detectFrameworks = detectFrameworks;
|
|
48
|
+
exports.checkFrameworkConventions = checkFrameworkConventions;
|
|
49
|
+
exports.checkFileRename = checkFileRename;
|
|
50
|
+
exports.generateVersionAwarenessReport = generateVersionAwarenessReport;
|
|
51
|
+
const fs = __importStar(require("fs"));
|
|
52
|
+
const path = __importStar(require("path"));
|
|
53
|
+
// ── Framework Detection ──
|
|
54
|
+
function detectFrameworks(projectPath) {
|
|
55
|
+
const pkgPath = path.join(projectPath, "package.json");
|
|
56
|
+
if (!fs.existsSync(pkgPath))
|
|
57
|
+
return [];
|
|
58
|
+
const frameworks = [];
|
|
59
|
+
try {
|
|
60
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
61
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
62
|
+
for (const [name, version] of Object.entries(deps)) {
|
|
63
|
+
if (typeof version !== "string")
|
|
64
|
+
continue;
|
|
65
|
+
const cleanVersion = version.replace(/^[\^~]/, "");
|
|
66
|
+
const majorVersion = parseInt(cleanVersion.split(".")[0], 10);
|
|
67
|
+
if (isNaN(majorVersion))
|
|
68
|
+
continue;
|
|
69
|
+
frameworks.push({ name, version: cleanVersion, majorVersion });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Can't parse package.json — skip
|
|
74
|
+
}
|
|
75
|
+
return frameworks;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Known breaking changes that AI-generated code follows correctly
|
|
79
|
+
* but human reviewers (with outdated training data) may "fix" incorrectly.
|
|
80
|
+
*/
|
|
81
|
+
const BREAKING_CHANGES = [
|
|
82
|
+
{
|
|
83
|
+
framework: "next",
|
|
84
|
+
sinceVersion: 16,
|
|
85
|
+
oldConvention: "middleware.ts",
|
|
86
|
+
newConvention: "proxy.ts",
|
|
87
|
+
description: "Next.js 16 deprecated `middleware.ts` in favor of `proxy.ts`. " +
|
|
88
|
+
"AI code generators using Next.js 16 will produce `proxy.ts`. " +
|
|
89
|
+
"Human reviewers with Next.js 12-15 training data will expect `middleware.ts`. " +
|
|
90
|
+
"The AI is correct — do NOT rename `proxy.ts` to `middleware.ts`.",
|
|
91
|
+
doNotRename: ["proxy.ts", "src/proxy.ts"],
|
|
92
|
+
lesson: "Framework version conventions override training data. " +
|
|
93
|
+
"When AGENTS.md warns 'This is NOT the Next.js you know', believe it. " +
|
|
94
|
+
"Check node_modules/next/dist/docs/ before 'fixing' file names.",
|
|
95
|
+
},
|
|
96
|
+
// Placeholder for future discoveries
|
|
97
|
+
// Add more entries as we encounter framework breaking changes
|
|
98
|
+
];
|
|
99
|
+
// ── Version-Aware Checks ──
|
|
100
|
+
function checkFrameworkConventions(projectPath) {
|
|
101
|
+
const frameworks = detectFrameworks(projectPath);
|
|
102
|
+
const checks = [];
|
|
103
|
+
for (const change of BREAKING_CHANGES) {
|
|
104
|
+
const fw = frameworks.find(f => f.name === change.framework);
|
|
105
|
+
if (!fw)
|
|
106
|
+
continue;
|
|
107
|
+
const applies = fw.majorVersion >= change.sinceVersion;
|
|
108
|
+
checks.push({
|
|
109
|
+
framework: change.framework,
|
|
110
|
+
versionRange: `>=${change.sinceVersion}.0.0`,
|
|
111
|
+
appliesTo: applies,
|
|
112
|
+
rule: `FW_${change.framework.toUpperCase()}_${change.oldConvention.replace(/\./g, "_").toUpperCase()}_DEPRECATED`,
|
|
113
|
+
description: change.description,
|
|
114
|
+
trainingDataDefault: change.oldConvention,
|
|
115
|
+
actualConvention: change.newConvention,
|
|
116
|
+
affectedFiles: change.doNotRename,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return checks;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Check if a specific file rename is going against framework conventions.
|
|
123
|
+
* Returns the warning if the rename should be reverted.
|
|
124
|
+
*/
|
|
125
|
+
function checkFileRename(projectPath, fromFile, toFile) {
|
|
126
|
+
const checks = checkFrameworkConventions(projectPath);
|
|
127
|
+
for (const change of BREAKING_CHANGES) {
|
|
128
|
+
const fw = detectFrameworks(projectPath).find(f => f.name === change.framework);
|
|
129
|
+
if (!fw || fw.majorVersion < change.sinceVersion)
|
|
130
|
+
continue;
|
|
131
|
+
// Check if we're renaming FROM the new convention TO the old one
|
|
132
|
+
const fromBase = path.basename(fromFile);
|
|
133
|
+
const toBase = path.basename(toFile);
|
|
134
|
+
if (change.doNotRename.includes(fromBase) && toBase === change.oldConvention) {
|
|
135
|
+
return {
|
|
136
|
+
warning: `⚠️ POTENTIAL GOVERNANCE ERROR: Renaming ${fromBase} → ${toBase}\n\n` +
|
|
137
|
+
` Project uses ${change.framework} v${fw.version}\n` +
|
|
138
|
+
` ${change.description}\n\n` +
|
|
139
|
+
` Before renaming framework-generated files, verify the current\n` +
|
|
140
|
+
` version's conventions in node_modules/${change.framework}/dist/docs/`,
|
|
141
|
+
shouldRevert: true,
|
|
142
|
+
lesson: change.lesson,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Generate a report for CLI / governance output.
|
|
150
|
+
*/
|
|
151
|
+
function generateVersionAwarenessReport(projectPath) {
|
|
152
|
+
const frameworks = detectFrameworks(projectPath);
|
|
153
|
+
const checks = checkFrameworkConventions(projectPath);
|
|
154
|
+
const activeChanges = checks.filter(c => c.appliesTo);
|
|
155
|
+
const lines = [
|
|
156
|
+
"═ Framework Version-Aware Governance ═",
|
|
157
|
+
"",
|
|
158
|
+
`Detected ${frameworks.length} framework(s):`,
|
|
159
|
+
...frameworks.map(f => ` - ${f.name} v${f.version}`),
|
|
160
|
+
"",
|
|
161
|
+
];
|
|
162
|
+
if (activeChanges.length === 0) {
|
|
163
|
+
lines.push("No version-convention conflicts detected.");
|
|
164
|
+
}
|
|
165
|
+
else {
|
|
166
|
+
lines.push(`⚠️ ${activeChanges.length} breaking change(s) apply to this project:`);
|
|
167
|
+
lines.push("");
|
|
168
|
+
for (const change of activeChanges) {
|
|
169
|
+
lines.push(` Rule: ${change.rule}`);
|
|
170
|
+
lines.push(` Training data (outdated): ${change.trainingDataDefault}`);
|
|
171
|
+
lines.push(` Actual convention: ${change.actualConvention}`);
|
|
172
|
+
lines.push(` Affected files: ${change.affectedFiles.join(", ")}`);
|
|
173
|
+
lines.push("");
|
|
174
|
+
}
|
|
175
|
+
lines.push(" LESSON: Do not rename framework-convention files based on");
|
|
176
|
+
lines.push(" training data. Verify against the installed framework version.");
|
|
177
|
+
}
|
|
178
|
+
return lines.join("\n");
|
|
179
|
+
}
|