depwire-cli 0.9.24 → 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/index.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  stashChanges,
18
18
  updateFileInGraph,
19
19
  watchProject
20
- } from "./chunk-ORGAO3HT.js";
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-QHVWDUSX.js";
34
+ } from "./chunk-YYY5TNG7.js";
34
35
 
35
36
  // src/index.ts
36
37
  import { Command } from "commander";
37
- import { resolve as resolve2, dirname as dirname2, join as join3 } from "path";
38
- import { writeFileSync, readFileSync as readFileSync2, existsSync } from "fs";
39
- import { fileURLToPath as fileURLToPath2 } from "url";
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((resolve3) => {
309
- const server = net.createServer().once("error", () => resolve3(false)).once("listening", () => {
309
+ const isAvailable = await new Promise((resolve4) => {
310
+ const server = net.createServer().once("error", () => resolve4(false)).once("listening", () => {
310
311
  server.close();
311
- resolve3(true);
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((resolve3, reject) => {
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
- resolve3();
359
+ resolve4();
359
360
  process.exit(0);
360
361
  });
361
362
  });
@@ -502,13 +503,269 @@ async function trackCommand(command, version = "unknown") {
502
503
  // src/commands/whatif.ts
503
504
  import { resolve } from "path";
504
505
  import chalk from "chalk";
506
+
507
+ // src/viz/whatif-server.ts
508
+ import express2 from "express";
509
+ import open2 from "open";
510
+ import { fileURLToPath as fileURLToPath2 } from "url";
511
+ import { dirname as dirname2, join as join3 } from "path";
512
+
513
+ // src/viz/generate-whatif-html.ts
514
+ function generateWhatIfHtml(currentVizData, simulatedVizData, simulationResult, operation, target) {
515
+ const { healthDelta, diff } = simulationResult;
516
+ const deltaSign = healthDelta.delta >= 0 ? "+" : "";
517
+ const deltaLabel = healthDelta.delta === 0 ? "unchanged" : healthDelta.improved ? `${deltaSign}${healthDelta.delta} \u2713 improved` : `${healthDelta.delta} \u2717 degraded`;
518
+ const deltaColor = healthDelta.delta === 0 ? "#fbbf24" : healthDelta.improved ? "#4ade80" : "#f87171";
519
+ const opBadge = operation !== "none" ? `<span style="background:${deltaColor};color:#000;padding:4px 12px;border-radius:4px;font-weight:700;font-size:13px;text-transform:uppercase;margin-left:12px;">${operation} ${target}</span>` : "";
520
+ const brokenImportsHtml = diff.brokenImports.length > 0 ? `<details style="margin-top:16px;background:#16213e;border:1px solid #2a2a4a;border-radius:8px;padding:12px 16px;">
521
+ <summary style="cursor:pointer;color:#f87171;font-weight:600;font-size:14px;">Broken Imports (${diff.brokenImports.length})</summary>
522
+ <ul style="margin:8px 0 0 16px;padding:0;list-style:none;">
523
+ ${diff.brokenImports.map((bi) => `<li style="color:#e0e0e0;font-size:13px;padding:4px 0;font-family:monospace;">${bi.file} \u2192 <span style="color:#f87171;">${bi.importedSymbol}</span></li>`).join("")}
524
+ </ul>
525
+ </details>` : "";
526
+ const currentDataJson = JSON.stringify(currentVizData);
527
+ const simulatedDataJson = JSON.stringify(simulatedVizData);
528
+ return `<!DOCTYPE html>
529
+ <html lang="en">
530
+ <head>
531
+ <meta charset="UTF-8">
532
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
533
+ <title>Depwire \u2014 What If Simulation</title>
534
+ <link rel="stylesheet" href="/style.css">
535
+ <style>
536
+ body { overflow: auto; height: auto; }
537
+ .whatif-header {
538
+ background: #16213e;
539
+ border-bottom: 1px solid #2a2a4a;
540
+ padding: 16px 24px;
541
+ display: flex;
542
+ align-items: center;
543
+ gap: 16px;
544
+ }
545
+ .whatif-header h1 {
546
+ margin: 0;
547
+ font-size: 20px;
548
+ font-weight: 600;
549
+ background: linear-gradient(135deg, #4a9eff, #7c3aed);
550
+ -webkit-background-clip: text;
551
+ -webkit-text-fill-color: transparent;
552
+ background-clip: text;
553
+ }
554
+ .health-banner {
555
+ background: #0f1729;
556
+ border: 1px solid #2a2a4a;
557
+ border-radius: 8px;
558
+ padding: 16px 24px;
559
+ margin: 16px 24px;
560
+ display: flex;
561
+ align-items: center;
562
+ gap: 32px;
563
+ flex-wrap: wrap;
564
+ }
565
+ .health-score {
566
+ font-size: 22px;
567
+ font-weight: 700;
568
+ }
569
+ .health-stat {
570
+ font-size: 14px;
571
+ color: #a0a0a0;
572
+ }
573
+ .health-stat strong {
574
+ color: #e0e0e0;
575
+ font-size: 18px;
576
+ }
577
+ .panels {
578
+ display: flex;
579
+ flex-direction: row;
580
+ gap: 0;
581
+ width: 100%;
582
+ height: calc(100vh - 180px);
583
+ min-height: 400px;
584
+ }
585
+ .panel {
586
+ flex: 1;
587
+ min-width: 0;
588
+ display: flex;
589
+ flex-direction: column;
590
+ border-right: 1px solid #2a2a4a;
591
+ overflow: hidden;
592
+ position: relative;
593
+ }
594
+ .panel:last-child { border-right: none; }
595
+ .panel-label {
596
+ background: #16213e;
597
+ padding: 8px 16px;
598
+ font-size: 13px;
599
+ font-weight: 600;
600
+ color: #a0a0a0;
601
+ border-bottom: 1px solid #2a2a4a;
602
+ display: flex;
603
+ justify-content: space-between;
604
+ flex-shrink: 0;
605
+ }
606
+ .panel-diagram {
607
+ flex: 1;
608
+ overflow: hidden;
609
+ position: relative;
610
+ }
611
+ .panel-diagram svg {
612
+ display: block;
613
+ width: 100%;
614
+ height: 100%;
615
+ }
616
+ .broken-section {
617
+ padding: 0 24px 24px;
618
+ }
619
+ </style>
620
+ </head>
621
+ <body>
622
+ <div class="whatif-header">
623
+ <h1>depwire \u2014 What If Simulation</h1>
624
+ ${opBadge}
625
+ </div>
626
+
627
+ <div class="health-banner">
628
+ <div class="health-score" style="color:${deltaColor}">
629
+ Health Score: ${healthDelta.before} \u2192 ${healthDelta.after}
630
+ <span style="font-size:16px;margin-left:8px;">(${deltaLabel})</span>
631
+ </div>
632
+ <div class="health-stat"><strong>${diff.affectedNodes.length}</strong> Affected Nodes</div>
633
+ <div class="health-stat"><strong>${diff.brokenImports.length}</strong> Broken Imports</div>
634
+ <div class="health-stat"><strong>${diff.removedEdges.length}</strong> Removed Edges</div>
635
+ </div>
636
+
637
+ <div class="panels">
638
+ <div class="panel">
639
+ <div class="panel-label">
640
+ <span>Current</span>
641
+ <span>${currentVizData.stats.totalFiles} files</span>
642
+ </div>
643
+ <div class="panel-diagram" id="arc-diagram-current">
644
+ <svg id="svg-current"></svg>
645
+ </div>
646
+ <div class="tooltip" id="tooltip-current"></div>
647
+ </div>
648
+ <div class="panel">
649
+ <div class="panel-label">
650
+ <span>After ${operation !== "none" ? operation.toUpperCase() : "\u2014"}</span>
651
+ <span>${simulatedVizData.stats.totalFiles} files</span>
652
+ </div>
653
+ <div class="panel-diagram" id="arc-diagram-simulated">
654
+ <svg id="svg-simulated"></svg>
655
+ </div>
656
+ <div class="tooltip" id="tooltip-simulated"></div>
657
+ </div>
658
+ </div>
659
+
660
+ <div class="broken-section">
661
+ ${brokenImportsHtml}
662
+ </div>
663
+
664
+ <script>window.__depwireWhatIf = true;</script>
665
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.9.0/d3.min.js"></script>
666
+ <script src="/arc.js"></script>
667
+ <script>
668
+ const currentData = ${currentDataJson};
669
+ const simulatedData = ${simulatedDataJson};
670
+
671
+ const left = window.createArcDiagram('arc-diagram-current', 'svg-current', 'tooltip-current', currentData);
672
+ const right = window.createArcDiagram('arc-diagram-simulated', 'svg-simulated', 'tooltip-simulated', simulatedData);
673
+
674
+ left.render();
675
+ right.render();
676
+
677
+ window.addEventListener('resize', () => {
678
+ left.render();
679
+ right.render();
680
+ });
681
+ </script>
682
+ </body>
683
+ </html>`;
684
+ }
685
+
686
+ // src/viz/whatif-server.ts
687
+ var __filename2 = fileURLToPath2(import.meta.url);
688
+ var __dirname2 = dirname2(__filename2);
689
+ async function findAvailablePort2(startPort, maxAttempts = 10) {
690
+ const net = await import("net");
691
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
692
+ const testPort = startPort + attempt;
693
+ const isAvailable = await new Promise((resolve4) => {
694
+ const server = net.createServer();
695
+ server.once("error", () => {
696
+ resolve4(false);
697
+ });
698
+ server.once("listening", () => {
699
+ server.close();
700
+ resolve4(true);
701
+ });
702
+ server.listen(testPort, "127.0.0.1");
703
+ });
704
+ if (isAvailable) {
705
+ if (attempt > 0) {
706
+ console.error(`Port ${startPort} in use, using port ${testPort} instead`);
707
+ }
708
+ return testPort;
709
+ }
710
+ }
711
+ throw new Error(`No available ports found between ${startPort} and ${startPort + maxAttempts - 1}`);
712
+ }
713
+ async function serveWhatIfViz(currentVizData, simulatedVizData, simulationResult, operation, target) {
714
+ const availablePort = await findAvailablePort2(3335);
715
+ const app = express2();
716
+ app.get("/", (_req, res) => {
717
+ const html = generateWhatIfHtml(currentVizData, simulatedVizData, simulationResult, operation, target);
718
+ res.type("html").send(html);
719
+ });
720
+ app.get("/favicon.ico", (_req, res) => {
721
+ res.sendFile(join3(__dirname2, "..", "..", "icon.png"));
722
+ });
723
+ const publicDir = join3(__dirname2, "viz", "public");
724
+ app.use(express2.static(publicDir));
725
+ app.get("/api/graph", (_req, res) => {
726
+ res.json(currentVizData);
727
+ });
728
+ app.get("/api/current", (_req, res) => {
729
+ res.json(currentVizData);
730
+ });
731
+ app.get("/api/simulated", (_req, res) => {
732
+ res.json(simulatedVizData);
733
+ });
734
+ app.get("/api/result", (_req, res) => {
735
+ res.json(simulationResult);
736
+ });
737
+ const server = app.listen(availablePort, "127.0.0.1", () => {
738
+ const url = `http://127.0.0.1:${availablePort}`;
739
+ console.error(`
740
+ Opening What If UI at ${url}`);
741
+ console.error("Press Ctrl+C to stop\n");
742
+ open2(url);
743
+ });
744
+ process.on("SIGINT", () => {
745
+ console.error("\nShutting down What If server...");
746
+ server.close(() => {
747
+ process.exit(0);
748
+ });
749
+ });
750
+ }
751
+
752
+ // src/commands/whatif.ts
505
753
  async function whatif(dir, options) {
506
754
  if (!options.simulate) {
507
- console.log("Usage: depwire whatif [dir] --simulate <action> --target <file> [options]");
508
- console.log("");
509
- console.log("Actions: move, delete, rename, split, merge");
510
- console.log("");
511
- console.log("Run without --simulate to open interactive browser UI (Phase B)");
755
+ const projectRoot2 = dir === "." ? findProjectRoot() : resolve(dir);
756
+ console.error(`Parsing project: ${projectRoot2}`);
757
+ const parsedFiles2 = await parseProject(projectRoot2);
758
+ const graph2 = buildGraph(parsedFiles2);
759
+ console.error(`Built graph: ${graph2.order} symbols, ${graph2.size} edges`);
760
+ const vizData = prepareVizData(graph2, projectRoot2);
761
+ const emptyResult = {
762
+ action: { type: "delete", target: "" },
763
+ originalGraph: { nodeCount: graph2.order, edgeCount: graph2.size, healthScore: 0 },
764
+ simulatedGraph: { nodeCount: graph2.order, edgeCount: graph2.size, healthScore: 0 },
765
+ diff: { addedEdges: [], removedEdges: [], affectedNodes: [], brokenImports: [], circularDepsIntroduced: [], circularDepsResolved: [] },
766
+ healthDelta: { before: 0, after: 0, delta: 0, improved: false, dimensionChanges: [] }
767
+ };
768
+ await serveWhatIfViz(vizData, vizData, emptyResult, "none", "");
512
769
  return;
513
770
  }
514
771
  const validActions = ["move", "delete", "rename", "split", "merge"];
@@ -522,11 +779,11 @@ async function whatif(dir, options) {
522
779
  }
523
780
  const action = buildAction(options);
524
781
  const projectRoot = dir === "." ? findProjectRoot() : resolve(dir);
525
- console.log(`Parsing project: ${projectRoot}`);
782
+ console.error(`Parsing project: ${projectRoot}`);
526
783
  const parsedFiles = await parseProject(projectRoot);
527
784
  const graph = buildGraph(parsedFiles);
528
- console.log(`Built graph: ${graph.order} symbols, ${graph.size} edges`);
529
- console.log("");
785
+ console.error(`Built graph: ${graph.order} symbols, ${graph.size} edges`);
786
+ console.error("");
530
787
  const engine = new SimulationEngine(graph);
531
788
  try {
532
789
  const result = engine.simulate(action);
@@ -631,18 +888,198 @@ function formatAction(action) {
631
888
  }
632
889
  }
633
890
 
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
1014
+ var __filename3 = fileURLToPath3(import.meta.url);
1015
+ var __dirname3 = dirname3(__filename3);
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
+
634
1071
  // src/index.ts
635
- var __filename2 = fileURLToPath2(import.meta.url);
636
- var __dirname2 = dirname2(__filename2);
637
- var packageJsonPath = join3(__dirname2, "../package.json");
638
- var packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
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"));
639
1076
  var program = new Command();
640
1077
  program.name("depwire").description("Code cross-reference graph builder for TypeScript projects").version(packageJson.version);
641
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) => {
642
1079
  trackCommand("parse", packageJson.version);
643
1080
  const startTime = Date.now();
644
1081
  try {
645
- const projectRoot = directory ? resolve2(directory) : findProjectRoot();
1082
+ const projectRoot = directory ? resolve3(directory) : findProjectRoot();
646
1083
  console.log(`Parsing project: ${projectRoot}`);
647
1084
  const parsedFiles = await parseProject(projectRoot, {
648
1085
  exclude: options.exclude,
@@ -681,12 +1118,12 @@ Orphan Files (no cross-references): ${summary.orphanFiles.length}`);
681
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) => {
682
1119
  trackCommand("query", packageJson.version);
683
1120
  try {
684
- const projectRoot = resolve2(directory);
1121
+ const projectRoot = resolve3(directory);
685
1122
  const cacheFile = "depwire-output.json";
686
1123
  let graph;
687
1124
  if (existsSync(cacheFile)) {
688
1125
  console.log("Loading from cache...");
689
- const json = JSON.parse(readFileSync2(cacheFile, "utf-8"));
1126
+ const json = JSON.parse(readFileSync3(cacheFile, "utf-8"));
690
1127
  graph = importFromJSON(json);
691
1128
  } else {
692
1129
  console.log("Parsing project...");
@@ -730,7 +1167,7 @@ Total Transitive Dependents: ${impact.transitiveDependents.length}`);
730
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) => {
731
1168
  trackCommand("viz", packageJson.version);
732
1169
  try {
733
- const projectRoot = directory ? resolve2(directory) : findProjectRoot();
1170
+ const projectRoot = directory ? resolve3(directory) : findProjectRoot();
734
1171
  console.log(`Parsing project: ${projectRoot}`);
735
1172
  const parsedFiles = await parseProject(projectRoot, {
736
1173
  exclude: options.exclude,
@@ -753,7 +1190,7 @@ program.command("viz").description("Launch interactive arc diagram visualization
753
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) => {
754
1191
  trackCommand("temporal", packageJson.version);
755
1192
  try {
756
- const projectRoot = directory ? resolve2(directory) : findProjectRoot();
1193
+ const projectRoot = directory ? resolve3(directory) : findProjectRoot();
757
1194
  await runTemporalAnalysis(projectRoot, {
758
1195
  commits: parseInt(options.commits, 10),
759
1196
  strategy: options.strategy,
@@ -773,11 +1210,11 @@ program.command("mcp").description("Start MCP server for AI coding tools").argum
773
1210
  const state = createEmptyState();
774
1211
  let projectRootToConnect = null;
775
1212
  if (directory) {
776
- projectRootToConnect = resolve2(directory);
1213
+ projectRootToConnect = resolve3(directory);
777
1214
  } else {
778
1215
  const detectedRoot = findProjectRoot();
779
1216
  const cwd = process.cwd();
780
- if (detectedRoot !== cwd || existsSync(join3(cwd, "package.json")) || existsSync(join3(cwd, "tsconfig.json")) || existsSync(join3(cwd, "go.mod")) || existsSync(join3(cwd, "pyproject.toml")) || existsSync(join3(cwd, "setup.py")) || existsSync(join3(cwd, ".git"))) {
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"))) {
781
1218
  projectRootToConnect = detectedRoot;
782
1219
  }
783
1220
  }
@@ -834,8 +1271,8 @@ program.command("docs").description("Generate comprehensive codebase documentati
834
1271
  trackCommand("docs", packageJson.version);
835
1272
  const startTime = Date.now();
836
1273
  try {
837
- const projectRoot = directory ? resolve2(directory) : findProjectRoot();
838
- const outputDir = options.output ? resolve2(options.output) : join3(projectRoot, ".depwire");
1274
+ const projectRoot = directory ? resolve3(directory) : findProjectRoot();
1275
+ const outputDir = options.output ? resolve3(options.output) : join5(projectRoot, ".depwire");
839
1276
  const includeList = options.include.split(",").map((s) => s.trim());
840
1277
  const onlyList = options.only ? options.only.split(",").map((s) => s.trim()) : void 0;
841
1278
  if (options.gitignore === void 0 && !existsSyncNode(outputDir)) {
@@ -897,16 +1334,16 @@ async function promptGitignore() {
897
1334
  input: process.stdin,
898
1335
  output: process.stdout
899
1336
  });
900
- return new Promise((resolve3) => {
1337
+ return new Promise((resolve4) => {
901
1338
  rl.question("Add .depwire/ to .gitignore? [Y/n] ", (answer) => {
902
1339
  rl.close();
903
1340
  const normalized = answer.trim().toLowerCase();
904
- resolve3(normalized === "" || normalized === "y" || normalized === "yes");
1341
+ resolve4(normalized === "" || normalized === "y" || normalized === "yes");
905
1342
  });
906
1343
  });
907
1344
  }
908
1345
  function addToGitignore(projectRoot, pattern) {
909
- const gitignorePath = join3(projectRoot, ".gitignore");
1346
+ const gitignorePath = join5(projectRoot, ".gitignore");
910
1347
  try {
911
1348
  let content = "";
912
1349
  if (existsSyncNode(gitignorePath)) {
@@ -931,7 +1368,7 @@ ${pattern}
931
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) => {
932
1369
  trackCommand("health", packageJson.version);
933
1370
  try {
934
- const projectRoot = directory ? resolve2(directory) : findProjectRoot();
1371
+ const projectRoot = directory ? resolve3(directory) : findProjectRoot();
935
1372
  const startTime = Date.now();
936
1373
  const parsedFiles = await parseProject(projectRoot);
937
1374
  const graph = buildGraph(parsedFiles);
@@ -955,7 +1392,7 @@ program.command("health").description("Analyze dependency architecture health (0
955
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) => {
956
1393
  trackCommand("dead-code", packageJson.version);
957
1394
  try {
958
- const projectRoot = directory ? resolve2(directory) : findProjectRoot();
1395
+ const projectRoot = directory ? resolve3(directory) : findProjectRoot();
959
1396
  const startTime = Date.now();
960
1397
  const parsedFiles = await parseProject(projectRoot);
961
1398
  const graph = buildGraph(parsedFiles);
@@ -991,4 +1428,13 @@ program.command("whatif").description("Simulate architectural changes before tou
991
1428
  process.exit(1);
992
1429
  }
993
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
+ });
994
1440
  program.parse();
@@ -4,11 +4,11 @@ import {
4
4
  startMcpServer,
5
5
  updateFileInGraph,
6
6
  watchProject
7
- } from "./chunk-ORGAO3HT.js";
7
+ } from "./chunk-B2KGFBZL.js";
8
8
  import {
9
9
  buildGraph,
10
10
  parseProject
11
- } from "./chunk-QHVWDUSX.js";
11
+ } from "./chunk-YYY5TNG7.js";
12
12
 
13
13
  // src/mcpb-entry.ts
14
14
  import { resolve } from "path";