depwire-cli 0.9.25 → 0.9.26
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/dist/{chunk-ORGAO3HT.js → chunk-B2KGFBZL.js} +59 -1
- package/dist/{chunk-QHVWDUSX.js → chunk-YYY5TNG7.js} +1446 -1
- package/dist/index.js +220 -30
- package/dist/mcpb-entry.js +2 -2
- package/dist/sdk.d.ts +49 -1
- package/dist/sdk.js +3 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
stashChanges,
|
|
18
18
|
updateFileInGraph,
|
|
19
19
|
watchProject
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-B2KGFBZL.js";
|
|
21
21
|
import {
|
|
22
22
|
SimulationEngine,
|
|
23
23
|
analyzeDeadCode,
|
|
@@ -29,14 +29,15 @@ import {
|
|
|
29
29
|
getHealthTrend,
|
|
30
30
|
getImpact,
|
|
31
31
|
parseProject,
|
|
32
|
+
scanSecurity,
|
|
32
33
|
searchSymbols
|
|
33
|
-
} from "./chunk-
|
|
34
|
+
} from "./chunk-YYY5TNG7.js";
|
|
34
35
|
|
|
35
36
|
// src/index.ts
|
|
36
37
|
import { Command } from "commander";
|
|
37
|
-
import { resolve as
|
|
38
|
-
import { writeFileSync, readFileSync as
|
|
39
|
-
import { fileURLToPath as
|
|
38
|
+
import { resolve as resolve3, dirname as dirname4, join as join5 } from "path";
|
|
39
|
+
import { writeFileSync, readFileSync as readFileSync3, existsSync } from "fs";
|
|
40
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
40
41
|
|
|
41
42
|
// src/graph/serializer.ts
|
|
42
43
|
import { DirectedGraph } from "graphology";
|
|
@@ -305,10 +306,10 @@ async function findAvailablePort(startPort) {
|
|
|
305
306
|
const net = await import("net");
|
|
306
307
|
for (let attempt = 0; attempt < 10; attempt++) {
|
|
307
308
|
const testPort = startPort + attempt;
|
|
308
|
-
const isAvailable = await new Promise((
|
|
309
|
-
const server = net.createServer().once("error", () =>
|
|
309
|
+
const isAvailable = await new Promise((resolve4) => {
|
|
310
|
+
const server = net.createServer().once("error", () => resolve4(false)).once("listening", () => {
|
|
310
311
|
server.close();
|
|
311
|
-
|
|
312
|
+
resolve4(true);
|
|
312
313
|
}).listen(testPort, "127.0.0.1");
|
|
313
314
|
});
|
|
314
315
|
if (isAvailable) {
|
|
@@ -349,13 +350,13 @@ async function startTemporalServer(snapshots, projectRoot, preferredPort = 3334)
|
|
|
349
350
|
console.log(" (Could not open browser automatically)");
|
|
350
351
|
});
|
|
351
352
|
});
|
|
352
|
-
await new Promise((
|
|
353
|
+
await new Promise((resolve4, reject) => {
|
|
353
354
|
server.on("error", reject);
|
|
354
355
|
process.on("SIGINT", () => {
|
|
355
356
|
console.log("\n\nShutting down temporal server...");
|
|
356
357
|
server.close(() => {
|
|
357
358
|
console.log("Server stopped");
|
|
358
|
-
|
|
359
|
+
resolve4();
|
|
359
360
|
process.exit(0);
|
|
360
361
|
});
|
|
361
362
|
});
|
|
@@ -689,14 +690,14 @@ async function findAvailablePort2(startPort, maxAttempts = 10) {
|
|
|
689
690
|
const net = await import("net");
|
|
690
691
|
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
691
692
|
const testPort = startPort + attempt;
|
|
692
|
-
const isAvailable = await new Promise((
|
|
693
|
+
const isAvailable = await new Promise((resolve4) => {
|
|
693
694
|
const server = net.createServer();
|
|
694
695
|
server.once("error", () => {
|
|
695
|
-
|
|
696
|
+
resolve4(false);
|
|
696
697
|
});
|
|
697
698
|
server.once("listening", () => {
|
|
698
699
|
server.close();
|
|
699
|
-
|
|
700
|
+
resolve4(true);
|
|
700
701
|
});
|
|
701
702
|
server.listen(testPort, "127.0.0.1");
|
|
702
703
|
});
|
|
@@ -887,18 +888,198 @@ function formatAction(action) {
|
|
|
887
888
|
}
|
|
888
889
|
}
|
|
889
890
|
|
|
890
|
-
// src/
|
|
891
|
+
// src/commands/security.ts
|
|
892
|
+
import { resolve as resolve2, dirname as dirname3, join as join4 } from "path";
|
|
893
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
894
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
895
|
+
|
|
896
|
+
// src/security/reporter.ts
|
|
897
|
+
import chalk2 from "chalk";
|
|
898
|
+
var SEVERITY_COLORS = {
|
|
899
|
+
critical: chalk2.red.bold,
|
|
900
|
+
high: chalk2.red,
|
|
901
|
+
medium: chalk2.yellow,
|
|
902
|
+
low: chalk2.blue,
|
|
903
|
+
info: chalk2.dim
|
|
904
|
+
};
|
|
905
|
+
var SEVERITY_LABELS = {
|
|
906
|
+
critical: "CRITICAL",
|
|
907
|
+
high: "HIGH",
|
|
908
|
+
medium: "MEDIUM",
|
|
909
|
+
low: "LOW",
|
|
910
|
+
info: "INFO"
|
|
911
|
+
};
|
|
912
|
+
function formatTable(result, elapsedMs) {
|
|
913
|
+
const lines = [];
|
|
914
|
+
const sep = "\u2500".repeat(62);
|
|
915
|
+
lines.push("");
|
|
916
|
+
lines.push(chalk2.bold("Depwire Security Scan"));
|
|
917
|
+
lines.push("");
|
|
918
|
+
const summaryParts = [
|
|
919
|
+
result.summary.critical > 0 ? chalk2.red.bold(`${result.summary.critical} Critical`) : null,
|
|
920
|
+
result.summary.high > 0 ? chalk2.red(`${result.summary.high} High`) : null,
|
|
921
|
+
result.summary.medium > 0 ? chalk2.yellow(`${result.summary.medium} Medium`) : null,
|
|
922
|
+
result.summary.low > 0 ? chalk2.blue(`${result.summary.low} Low`) : null,
|
|
923
|
+
result.summary.info > 0 ? chalk2.dim(`${result.summary.info} Info`) : null
|
|
924
|
+
].filter(Boolean);
|
|
925
|
+
if (summaryParts.length > 0) {
|
|
926
|
+
lines.push(`\u250C${sep}\u2510`);
|
|
927
|
+
lines.push(`\u2502 ${summaryParts.join(" \u2502 ")} \u2502`);
|
|
928
|
+
lines.push(`\u2514${sep}\u2518`);
|
|
929
|
+
} else {
|
|
930
|
+
lines.push(chalk2.green.bold(" No security findings detected."));
|
|
931
|
+
}
|
|
932
|
+
lines.push("");
|
|
933
|
+
const severityOrder = ["critical", "high", "medium", "low", "info"];
|
|
934
|
+
for (const severity of severityOrder) {
|
|
935
|
+
const group = result.findings.filter((f) => f.severity === severity);
|
|
936
|
+
if (group.length === 0) continue;
|
|
937
|
+
const colorFn = SEVERITY_COLORS[severity];
|
|
938
|
+
lines.push(colorFn(SEVERITY_LABELS[severity]));
|
|
939
|
+
for (const finding of group) {
|
|
940
|
+
lines.push(` ${colorFn(`[${finding.id}]`)} ${finding.title}`);
|
|
941
|
+
lines.push(` File: ${finding.file}${finding.line ? `:${finding.line}` : ""}`);
|
|
942
|
+
lines.push(` ${chalk2.dim(finding.description)}`);
|
|
943
|
+
lines.push(` ${chalk2.dim("Fix:")} ${finding.suggestedFix}`);
|
|
944
|
+
if (finding.graphReachability?.elevatedBy) {
|
|
945
|
+
lines.push(` ${chalk2.magenta("\u2191 Elevated:")} ${finding.graphReachability.elevatedBy}`);
|
|
946
|
+
}
|
|
947
|
+
lines.push("");
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
const elapsed = (elapsedMs / 1e3).toFixed(1);
|
|
951
|
+
lines.push(chalk2.dim(`Scanned ${result.filesScanned} files in ${elapsed}s`));
|
|
952
|
+
lines.push(chalk2.dim("Run with --format json for machine output"));
|
|
953
|
+
lines.push(chalk2.dim("Run with --format sarif for GitHub Security integration"));
|
|
954
|
+
lines.push("");
|
|
955
|
+
return lines.join("\n");
|
|
956
|
+
}
|
|
957
|
+
function formatJSON(result) {
|
|
958
|
+
return JSON.stringify(result, null, 2);
|
|
959
|
+
}
|
|
960
|
+
function formatSARIF(result, version) {
|
|
961
|
+
const rules = result.findings.map((f) => ({
|
|
962
|
+
id: f.id,
|
|
963
|
+
shortDescription: { text: f.title },
|
|
964
|
+
fullDescription: { text: f.description },
|
|
965
|
+
help: { text: f.suggestedFix },
|
|
966
|
+
properties: {
|
|
967
|
+
severity: f.severity,
|
|
968
|
+
vulnerabilityClass: f.vulnerabilityClass
|
|
969
|
+
}
|
|
970
|
+
}));
|
|
971
|
+
const uniqueRules = Array.from(
|
|
972
|
+
new Map(rules.map((r) => [r.id, r])).values()
|
|
973
|
+
);
|
|
974
|
+
const results = result.findings.map((f) => {
|
|
975
|
+
let level;
|
|
976
|
+
if (f.severity === "critical" || f.severity === "high") level = "error";
|
|
977
|
+
else if (f.severity === "medium") level = "warning";
|
|
978
|
+
else level = "note";
|
|
979
|
+
const sarifResult = {
|
|
980
|
+
ruleId: f.id,
|
|
981
|
+
level,
|
|
982
|
+
message: { text: `${f.title}: ${f.description}` },
|
|
983
|
+
locations: [
|
|
984
|
+
{
|
|
985
|
+
physicalLocation: {
|
|
986
|
+
artifactLocation: { uri: f.file },
|
|
987
|
+
region: f.line ? { startLine: f.line } : void 0
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
]
|
|
991
|
+
};
|
|
992
|
+
return sarifResult;
|
|
993
|
+
});
|
|
994
|
+
const sarif = {
|
|
995
|
+
$schema: "https://json.schemastore.org/sarif-2.1.0.json",
|
|
996
|
+
version: "2.1.0",
|
|
997
|
+
runs: [
|
|
998
|
+
{
|
|
999
|
+
tool: {
|
|
1000
|
+
driver: {
|
|
1001
|
+
name: "depwire",
|
|
1002
|
+
version,
|
|
1003
|
+
rules: uniqueRules
|
|
1004
|
+
}
|
|
1005
|
+
},
|
|
1006
|
+
results
|
|
1007
|
+
}
|
|
1008
|
+
]
|
|
1009
|
+
};
|
|
1010
|
+
return JSON.stringify(sarif, null, 2);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// src/commands/security.ts
|
|
891
1014
|
var __filename3 = fileURLToPath3(import.meta.url);
|
|
892
1015
|
var __dirname3 = dirname3(__filename3);
|
|
893
|
-
|
|
894
|
-
|
|
1016
|
+
function getVersion() {
|
|
1017
|
+
try {
|
|
1018
|
+
let dir = __dirname3;
|
|
1019
|
+
for (let i = 0; i < 5; i++) {
|
|
1020
|
+
const pkgPath = join4(dir, "package.json");
|
|
1021
|
+
try {
|
|
1022
|
+
const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
|
|
1023
|
+
if (pkg.name === "depwire-cli") return pkg.version;
|
|
1024
|
+
} catch {
|
|
1025
|
+
}
|
|
1026
|
+
dir = dirname3(dir);
|
|
1027
|
+
}
|
|
1028
|
+
} catch {
|
|
1029
|
+
}
|
|
1030
|
+
return "0.0.0";
|
|
1031
|
+
}
|
|
1032
|
+
var SEVERITY_ORDER = ["critical", "high", "medium", "low", "info"];
|
|
1033
|
+
async function securityCommand(dir, options) {
|
|
1034
|
+
const projectRoot = dir === "." ? findProjectRoot() : resolve2(dir);
|
|
1035
|
+
console.error(`Scanning: ${projectRoot}`);
|
|
1036
|
+
const startTime = Date.now();
|
|
1037
|
+
const parsedFiles = await parseProject(projectRoot);
|
|
1038
|
+
console.error(`Parsed ${parsedFiles.length} files`);
|
|
1039
|
+
const graph = buildGraph(parsedFiles);
|
|
1040
|
+
console.error(`Built graph: ${graph.order} symbols, ${graph.size} edges`);
|
|
1041
|
+
const result = await scanSecurity(projectRoot, graph, {
|
|
1042
|
+
target: options.target,
|
|
1043
|
+
classes: options.class,
|
|
1044
|
+
format: options.format || "table",
|
|
1045
|
+
graphAware: true
|
|
1046
|
+
});
|
|
1047
|
+
const elapsedMs = Date.now() - startTime;
|
|
1048
|
+
const format = options.format || "table";
|
|
1049
|
+
if (format === "json") {
|
|
1050
|
+
console.log(formatJSON(result));
|
|
1051
|
+
} else if (format === "sarif") {
|
|
1052
|
+
console.log(formatSARIF(result, getVersion()));
|
|
1053
|
+
} else {
|
|
1054
|
+
console.log(formatTable(result, elapsedMs));
|
|
1055
|
+
}
|
|
1056
|
+
if (options.failOn) {
|
|
1057
|
+
const threshold = options.failOn;
|
|
1058
|
+
const thresholdIdx = SEVERITY_ORDER.indexOf(threshold);
|
|
1059
|
+
if (thresholdIdx >= 0) {
|
|
1060
|
+
const hasFindings = result.findings.some(
|
|
1061
|
+
(f) => SEVERITY_ORDER.indexOf(f.severity) <= thresholdIdx
|
|
1062
|
+
);
|
|
1063
|
+
if (hasFindings) {
|
|
1064
|
+
console.error(`Findings at or above ${threshold} severity detected \u2014 exiting with code 1`);
|
|
1065
|
+
process.exit(1);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
// src/index.ts
|
|
1072
|
+
var __filename4 = fileURLToPath4(import.meta.url);
|
|
1073
|
+
var __dirname4 = dirname4(__filename4);
|
|
1074
|
+
var packageJsonPath = join5(__dirname4, "../package.json");
|
|
1075
|
+
var packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf-8"));
|
|
895
1076
|
var program = new Command();
|
|
896
1077
|
program.name("depwire").description("Code cross-reference graph builder for TypeScript projects").version(packageJson.version);
|
|
897
1078
|
program.command("parse").description("Parse a TypeScript project and build dependency graph").argument("[directory]", "Project directory to parse (defaults to current directory or auto-detected project root)").option("-o, --output <path>", "Output JSON file path", "depwire-output.json").option("--pretty", "Pretty-print JSON output").option("--stats", "Print summary statistics").option("--exclude <patterns...>", 'Glob patterns to exclude (e.g., "**/*.test.*" "dist/**")').option("--verbose", "Show detailed parsing progress").action(async (directory, options) => {
|
|
898
1079
|
trackCommand("parse", packageJson.version);
|
|
899
1080
|
const startTime = Date.now();
|
|
900
1081
|
try {
|
|
901
|
-
const projectRoot = directory ?
|
|
1082
|
+
const projectRoot = directory ? resolve3(directory) : findProjectRoot();
|
|
902
1083
|
console.log(`Parsing project: ${projectRoot}`);
|
|
903
1084
|
const parsedFiles = await parseProject(projectRoot, {
|
|
904
1085
|
exclude: options.exclude,
|
|
@@ -937,12 +1118,12 @@ Orphan Files (no cross-references): ${summary.orphanFiles.length}`);
|
|
|
937
1118
|
program.command("query").description("Query impact analysis for a symbol").argument("<directory>", "Project directory").argument("<symbol-name>", "Symbol name to query").action(async (directory, symbolName) => {
|
|
938
1119
|
trackCommand("query", packageJson.version);
|
|
939
1120
|
try {
|
|
940
|
-
const projectRoot =
|
|
1121
|
+
const projectRoot = resolve3(directory);
|
|
941
1122
|
const cacheFile = "depwire-output.json";
|
|
942
1123
|
let graph;
|
|
943
1124
|
if (existsSync(cacheFile)) {
|
|
944
1125
|
console.log("Loading from cache...");
|
|
945
|
-
const json = JSON.parse(
|
|
1126
|
+
const json = JSON.parse(readFileSync3(cacheFile, "utf-8"));
|
|
946
1127
|
graph = importFromJSON(json);
|
|
947
1128
|
} else {
|
|
948
1129
|
console.log("Parsing project...");
|
|
@@ -986,7 +1167,7 @@ Total Transitive Dependents: ${impact.transitiveDependents.length}`);
|
|
|
986
1167
|
program.command("viz").description("Launch interactive arc diagram visualization").argument("[directory]", "Project directory to visualize (defaults to current directory or auto-detected project root)").option("-p, --port <number>", "Server port", "3333").option("--no-open", "Don't auto-open browser").option("--exclude <patterns...>", 'Glob patterns to exclude (e.g., "**/*.test.*" "dist/**")').option("--verbose", "Show detailed parsing progress").action(async (directory, options) => {
|
|
987
1168
|
trackCommand("viz", packageJson.version);
|
|
988
1169
|
try {
|
|
989
|
-
const projectRoot = directory ?
|
|
1170
|
+
const projectRoot = directory ? resolve3(directory) : findProjectRoot();
|
|
990
1171
|
console.log(`Parsing project: ${projectRoot}`);
|
|
991
1172
|
const parsedFiles = await parseProject(projectRoot, {
|
|
992
1173
|
exclude: options.exclude,
|
|
@@ -1009,7 +1190,7 @@ program.command("viz").description("Launch interactive arc diagram visualization
|
|
|
1009
1190
|
program.command("temporal").description("Visualize how the dependency graph evolved over git history").argument("[directory]", "Project directory to analyze (defaults to current directory or auto-detected project root)").option("--commits <number>", "Number of commits to sample", "20").option("--strategy <type>", "Sampling strategy: even, weekly, monthly", "even").option("-p, --port <number>", "Server port", "3334").option("--output <path>", "Save snapshots to custom path (default: .depwire/temporal/)").option("--verbose", "Show progress for each commit being parsed").option("--stats", "Show summary statistics at end").action(async (directory, options) => {
|
|
1010
1191
|
trackCommand("temporal", packageJson.version);
|
|
1011
1192
|
try {
|
|
1012
|
-
const projectRoot = directory ?
|
|
1193
|
+
const projectRoot = directory ? resolve3(directory) : findProjectRoot();
|
|
1013
1194
|
await runTemporalAnalysis(projectRoot, {
|
|
1014
1195
|
commits: parseInt(options.commits, 10),
|
|
1015
1196
|
strategy: options.strategy,
|
|
@@ -1029,11 +1210,11 @@ program.command("mcp").description("Start MCP server for AI coding tools").argum
|
|
|
1029
1210
|
const state = createEmptyState();
|
|
1030
1211
|
let projectRootToConnect = null;
|
|
1031
1212
|
if (directory) {
|
|
1032
|
-
projectRootToConnect =
|
|
1213
|
+
projectRootToConnect = resolve3(directory);
|
|
1033
1214
|
} else {
|
|
1034
1215
|
const detectedRoot = findProjectRoot();
|
|
1035
1216
|
const cwd = process.cwd();
|
|
1036
|
-
if (detectedRoot !== cwd || existsSync(
|
|
1217
|
+
if (detectedRoot !== cwd || existsSync(join5(cwd, "package.json")) || existsSync(join5(cwd, "tsconfig.json")) || existsSync(join5(cwd, "go.mod")) || existsSync(join5(cwd, "pyproject.toml")) || existsSync(join5(cwd, "setup.py")) || existsSync(join5(cwd, ".git"))) {
|
|
1037
1218
|
projectRootToConnect = detectedRoot;
|
|
1038
1219
|
}
|
|
1039
1220
|
}
|
|
@@ -1090,8 +1271,8 @@ program.command("docs").description("Generate comprehensive codebase documentati
|
|
|
1090
1271
|
trackCommand("docs", packageJson.version);
|
|
1091
1272
|
const startTime = Date.now();
|
|
1092
1273
|
try {
|
|
1093
|
-
const projectRoot = directory ?
|
|
1094
|
-
const outputDir = options.output ?
|
|
1274
|
+
const projectRoot = directory ? resolve3(directory) : findProjectRoot();
|
|
1275
|
+
const outputDir = options.output ? resolve3(options.output) : join5(projectRoot, ".depwire");
|
|
1095
1276
|
const includeList = options.include.split(",").map((s) => s.trim());
|
|
1096
1277
|
const onlyList = options.only ? options.only.split(",").map((s) => s.trim()) : void 0;
|
|
1097
1278
|
if (options.gitignore === void 0 && !existsSyncNode(outputDir)) {
|
|
@@ -1153,16 +1334,16 @@ async function promptGitignore() {
|
|
|
1153
1334
|
input: process.stdin,
|
|
1154
1335
|
output: process.stdout
|
|
1155
1336
|
});
|
|
1156
|
-
return new Promise((
|
|
1337
|
+
return new Promise((resolve4) => {
|
|
1157
1338
|
rl.question("Add .depwire/ to .gitignore? [Y/n] ", (answer) => {
|
|
1158
1339
|
rl.close();
|
|
1159
1340
|
const normalized = answer.trim().toLowerCase();
|
|
1160
|
-
|
|
1341
|
+
resolve4(normalized === "" || normalized === "y" || normalized === "yes");
|
|
1161
1342
|
});
|
|
1162
1343
|
});
|
|
1163
1344
|
}
|
|
1164
1345
|
function addToGitignore(projectRoot, pattern) {
|
|
1165
|
-
const gitignorePath =
|
|
1346
|
+
const gitignorePath = join5(projectRoot, ".gitignore");
|
|
1166
1347
|
try {
|
|
1167
1348
|
let content = "";
|
|
1168
1349
|
if (existsSyncNode(gitignorePath)) {
|
|
@@ -1187,7 +1368,7 @@ ${pattern}
|
|
|
1187
1368
|
program.command("health").description("Analyze dependency architecture health (0-100 score)").argument("[directory]", "Project directory to analyze (defaults to current directory or auto-detected project root)").option("--json", "Output as JSON").option("--verbose", "Show detailed breakdown").action(async (directory, options) => {
|
|
1188
1369
|
trackCommand("health", packageJson.version);
|
|
1189
1370
|
try {
|
|
1190
|
-
const projectRoot = directory ?
|
|
1371
|
+
const projectRoot = directory ? resolve3(directory) : findProjectRoot();
|
|
1191
1372
|
const startTime = Date.now();
|
|
1192
1373
|
const parsedFiles = await parseProject(projectRoot);
|
|
1193
1374
|
const graph = buildGraph(parsedFiles);
|
|
@@ -1211,7 +1392,7 @@ program.command("health").description("Analyze dependency architecture health (0
|
|
|
1211
1392
|
program.command("dead-code").description("Identify dead code - symbols defined but never referenced").argument("[directory]", "Project directory to analyze (defaults to current directory or auto-detected project root)").option("--confidence <level>", "Minimum confidence level to show: high, medium, low (default: medium)", "medium").option("--json", "Output as JSON (for CI/automation)").option("--verbose", "Show detailed info for each dead symbol").option("--stats", "Show summary statistics").option("--include-tests", "Include test files in analysis").option("--include-low", "Shortcut for --confidence low").option("--debug", "Show debug information (exclusion stats)").action(async (directory, options) => {
|
|
1212
1393
|
trackCommand("dead-code", packageJson.version);
|
|
1213
1394
|
try {
|
|
1214
|
-
const projectRoot = directory ?
|
|
1395
|
+
const projectRoot = directory ? resolve3(directory) : findProjectRoot();
|
|
1215
1396
|
const startTime = Date.now();
|
|
1216
1397
|
const parsedFiles = await parseProject(projectRoot);
|
|
1217
1398
|
const graph = buildGraph(parsedFiles);
|
|
@@ -1247,4 +1428,13 @@ program.command("whatif").description("Simulate architectural changes before tou
|
|
|
1247
1428
|
process.exit(1);
|
|
1248
1429
|
}
|
|
1249
1430
|
});
|
|
1431
|
+
program.command("security").description("Scan codebase for security vulnerabilities (deterministic, no API key required)").argument("[directory]", "Project directory to scan (defaults to current directory or auto-detected project root)").option("--target <file>", "Scan a single file instead of the whole repo").option("--class <classes...>", "Only run specific vulnerability class checks").option("--format <format>", "Output format: table (default), json, sarif", "table").option("--fail-on <level>", "Exit with code 1 if findings at this severity or above").action(async (directory, options) => {
|
|
1432
|
+
trackCommand("security", packageJson.version);
|
|
1433
|
+
try {
|
|
1434
|
+
await securityCommand(directory || ".", options);
|
|
1435
|
+
} catch (err) {
|
|
1436
|
+
console.error("Error running security scan:", err);
|
|
1437
|
+
process.exit(1);
|
|
1438
|
+
}
|
|
1439
|
+
});
|
|
1250
1440
|
program.parse();
|
package/dist/mcpb-entry.js
CHANGED
|
@@ -4,11 +4,11 @@ import {
|
|
|
4
4
|
startMcpServer,
|
|
5
5
|
updateFileInGraph,
|
|
6
6
|
watchProject
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-B2KGFBZL.js";
|
|
8
8
|
import {
|
|
9
9
|
buildGraph,
|
|
10
10
|
parseProject
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-YYY5TNG7.js";
|
|
12
12
|
|
|
13
13
|
// src/mcpb-entry.ts
|
|
14
14
|
import { resolve } from "path";
|
package/dist/sdk.d.ts
CHANGED
|
@@ -222,6 +222,54 @@ declare class SimulationEngine {
|
|
|
222
222
|
private computeHealthScore;
|
|
223
223
|
}
|
|
224
224
|
|
|
225
|
+
type Severity = 'critical' | 'high' | 'medium' | 'low' | 'info';
|
|
226
|
+
type VulnerabilityClass = 'dependency-cve' | 'shell-injection' | 'code-injection' | 'secrets' | 'path-traversal' | 'auth' | 'input-validation' | 'information-disclosure' | 'architecture' | 'cryptography' | 'supply-chain' | 'frontend-xss';
|
|
227
|
+
interface SecurityFinding {
|
|
228
|
+
id: string;
|
|
229
|
+
severity: Severity;
|
|
230
|
+
vulnerabilityClass: VulnerabilityClass;
|
|
231
|
+
file: string;
|
|
232
|
+
line?: number;
|
|
233
|
+
symbol?: string;
|
|
234
|
+
title: string;
|
|
235
|
+
description: string;
|
|
236
|
+
attackScenario: string;
|
|
237
|
+
suggestedFix: string;
|
|
238
|
+
graphReachability?: {
|
|
239
|
+
entryPoints: string[];
|
|
240
|
+
reachableFrom: number;
|
|
241
|
+
elevatedBy: string;
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
interface SecurityScanResult {
|
|
245
|
+
scannedAt: string;
|
|
246
|
+
projectRoot: string;
|
|
247
|
+
filesScanned: number;
|
|
248
|
+
findings: SecurityFinding[];
|
|
249
|
+
summary: {
|
|
250
|
+
critical: number;
|
|
251
|
+
high: number;
|
|
252
|
+
medium: number;
|
|
253
|
+
low: number;
|
|
254
|
+
info: number;
|
|
255
|
+
total: number;
|
|
256
|
+
};
|
|
257
|
+
dependencyAudit: {
|
|
258
|
+
ran: boolean;
|
|
259
|
+
packageManager: string | null;
|
|
260
|
+
rawOutput: string;
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
interface SecurityScanOptions {
|
|
264
|
+
target?: string;
|
|
265
|
+
classes?: VulnerabilityClass[];
|
|
266
|
+
format?: 'table' | 'json' | 'sarif';
|
|
267
|
+
failOn?: Severity;
|
|
268
|
+
graphAware?: boolean;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
declare function scanSecurity(projectRoot: string, graph: DirectedGraph, options?: SecurityScanOptions): Promise<SecurityScanResult>;
|
|
272
|
+
|
|
225
273
|
/**
|
|
226
274
|
* depwire-cli SDK — Public API Surface
|
|
227
275
|
*
|
|
@@ -234,4 +282,4 @@ declare class SimulationEngine {
|
|
|
234
282
|
/** Current SDK version — matches depwire-cli npm version */
|
|
235
283
|
declare const DepwireSDKVersion: string;
|
|
236
284
|
|
|
237
|
-
export { type BrokenImport, DepwireSDKVersion, type GraphDiff, type HealthDelta, type SimulationAction, SimulationEngine, type SimulationResult, analyzeDeadCode, buildGraph, calculateHealthScore, generateDocs, getArchitectureSummary, getImpact, parseProject, searchSymbols };
|
|
285
|
+
export { type BrokenImport, DepwireSDKVersion, type GraphDiff, type HealthDelta, type SecurityFinding, type SecurityScanOptions, type SecurityScanResult, type Severity, type SimulationAction, SimulationEngine, type SimulationResult, type VulnerabilityClass, analyzeDeadCode, buildGraph, calculateHealthScore, generateDocs, getArchitectureSummary, getImpact, parseProject, scanSecurity, searchSymbols };
|
package/dist/sdk.js
CHANGED
|
@@ -7,8 +7,9 @@ import {
|
|
|
7
7
|
getArchitectureSummary,
|
|
8
8
|
getImpact,
|
|
9
9
|
parseProject,
|
|
10
|
+
scanSecurity,
|
|
10
11
|
searchSymbols
|
|
11
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-YYY5TNG7.js";
|
|
12
13
|
|
|
13
14
|
// src/sdk.ts
|
|
14
15
|
import { readFileSync } from "fs";
|
|
@@ -28,5 +29,6 @@ export {
|
|
|
28
29
|
getArchitectureSummary,
|
|
29
30
|
getImpact,
|
|
30
31
|
parseProject,
|
|
32
|
+
scanSecurity,
|
|
31
33
|
searchSymbols
|
|
32
34
|
};
|