depwire-cli 0.9.9 → 0.9.12

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 CHANGED
@@ -78,6 +78,30 @@ Or use directly with `npx`:
78
78
  npx depwire-cli --help
79
79
  ```
80
80
 
81
+ ## Telemetry
82
+
83
+ Depwire collects **anonymous usage data** to help us understand which features are most useful and prioritize development.
84
+
85
+ **What we collect:**
86
+ - Command name (e.g. `parse`, `mcp`, `viz`)
87
+ - Depwire version
88
+ - Operating system (mac/linux/windows)
89
+ - Node.js version
90
+
91
+ **What we never collect:**
92
+ - File paths or code content
93
+ - Repo names or URLs
94
+ - Usernames, emails, or any personal data
95
+
96
+ **To opt out**, set this environment variable:
97
+ ```bash
98
+ DEPWIRE_NO_TELEMETRY=1 depwire parse
99
+ # Or add to your shell profile:
100
+ export DEPWIRE_NO_TELEMETRY=1
101
+ ```
102
+
103
+ Telemetry data is used solely to improve Depwire. [View our privacy policy](https://depwire.dev/privacy)
104
+
81
105
  ## Quick Start
82
106
 
83
107
  ### CLI Usage
@@ -401,7 +425,7 @@ depwire docs --output ./docs
401
425
  depwire docs --update --only conventions
402
426
  ```
403
427
 
404
- **Generated Documents (12 total):**
428
+ **Generated Documents (13 total):**
405
429
 
406
430
  | Document | What It Contains |
407
431
  |----------|------------------|
@@ -417,7 +441,7 @@ depwire docs --update --only conventions
417
441
  | `CURRENT.md` | Complete codebase snapshot (every file, symbol, connection) |
418
442
  | `STATUS.md` | TODO/FIXME/HACK inventory with priority matrix |
419
443
  | `HEALTH.md` | Dependency health score (0-100) across 6 dimensions with recommendations |
420
- | `DEAD_CODE.md` | Dead code analysis — unused symbols by confidence level (high/medium/low) |
444
+ | `DEAD_CODE.md` | Unused symbols by confidence level (high/medium/low) with smart exclusions |
421
445
 
422
446
  Documents are stored in `.depwire/` with `metadata.json` tracking generation timestamps for staleness detection.
423
447
 
@@ -625,7 +649,7 @@ See [SECURITY.md](SECURITY.md) for full details.
625
649
  - [x] WASM migration (Windows support)
626
650
 
627
651
  ### 🔜 Coming Next
628
- - [ ] New language support (C — community requested)
652
+ - [ ] New language support (Java, C++, Ruby — community requested)
629
653
  - [ ] "What If" simulation — simulate refactors before touching code
630
654
  - [ ] Cross-language edge detection (API routes ↔ frontend calls)
631
655
  - [ ] Cloud dashboard (first paid feature)
@@ -1,6 +1,7 @@
1
1
  // src/utils/files.ts
2
2
  import { readdirSync, statSync, existsSync, lstatSync } from "fs";
3
3
  import { join, relative } from "path";
4
+ import os from "os";
4
5
  function scanDirectory(rootDir, baseDir = rootDir) {
5
6
  const files = [];
6
7
  try {
@@ -71,9 +72,21 @@ function findProjectRoot(startDir = process.cwd()) {
71
72
  ".git"
72
73
  // Any git repo
73
74
  ];
75
+ const blocklist = ["Library", "System", "Applications", "usr", "bin", "etc", "var", "private"];
74
76
  let currentDir = startDir;
75
77
  const rootDir = "/";
76
- while (currentDir !== rootDir) {
78
+ const maxDepth = 10;
79
+ let depth = 0;
80
+ const home = os.homedir();
81
+ while (currentDir !== rootDir && depth < maxDepth) {
82
+ if (currentDir === home || !currentDir.startsWith(home)) {
83
+ break;
84
+ }
85
+ const dirName = currentDir.split("/").pop();
86
+ if (dirName && blocklist.includes(dirName)) {
87
+ console.warn(`\u26A0\uFE0F Skipping blocked directory: ${dirName}`);
88
+ break;
89
+ }
77
90
  for (const marker of projectMarkers) {
78
91
  const markerPath = join(currentDir, marker);
79
92
  if (existsSync(markerPath)) {
@@ -85,7 +98,9 @@ function findProjectRoot(startDir = process.cwd()) {
85
98
  break;
86
99
  }
87
100
  currentDir = parentDir;
101
+ depth++;
88
102
  }
103
+ console.warn(`\u26A0\uFE0F No project root found within ${maxDepth} levels. Using current directory: ${startDir}`);
89
104
  return startDir;
90
105
  }
91
106
 
@@ -9211,6 +9226,10 @@ function getToolsList() {
9211
9226
  symbol: {
9212
9227
  type: "string",
9213
9228
  description: "Symbol name (e.g., 'Router') or full ID (e.g., 'src/router.ts::Router')"
9229
+ },
9230
+ file: {
9231
+ type: "string",
9232
+ description: "Optional: File path to disambiguate when multiple symbols have the same name (e.g., 'src/router.ts')"
9214
9233
  }
9215
9234
  },
9216
9235
  required: ["symbol"]
@@ -9442,7 +9461,7 @@ async function handleToolCall(name, args, state) {
9442
9461
  result = handleGetDependents(args.symbol, graph);
9443
9462
  break;
9444
9463
  case "impact_analysis":
9445
- result = handleImpactAnalysis(args.symbol, graph);
9464
+ result = handleImpactAnalysis(args.symbol, graph, args.file);
9446
9465
  break;
9447
9466
  case "get_file_context":
9448
9467
  result = handleGetFileContext(args.filePath, graph);
@@ -9502,9 +9521,12 @@ async function handleToolCall(name, args, state) {
9502
9521
  }
9503
9522
  function createDisambiguationResponse(matches, queryName) {
9504
9523
  const suggestion = matches.length > 0 ? matches[0].id : "";
9524
+ const exampleFile = matches.length > 0 ? matches[0].filePath : "";
9505
9525
  return {
9506
9526
  ambiguous: true,
9507
- message: `Found ${matches.length} symbols named '${queryName}'. Please specify which one by using the full ID (e.g., '${suggestion}').`,
9527
+ message: `Found ${matches.length} symbols named '${queryName}'. Disambiguate by:
9528
+ 1. Using full ID: '${suggestion}'
9529
+ 2. Or adding file parameter: { symbol: '${queryName}', file: '${exampleFile}' }`,
9508
9530
  matches: matches.map((m, index) => ({
9509
9531
  id: m.id,
9510
9532
  kind: m.kind,
@@ -9614,7 +9636,7 @@ function handleGetDependents(symbol, graph) {
9614
9636
  totalCount
9615
9637
  };
9616
9638
  }
9617
- function handleImpactAnalysis(symbol, graph) {
9639
+ function handleImpactAnalysis(symbol, graph, file) {
9618
9640
  const matches = findSymbols(graph, symbol);
9619
9641
  if (matches.length === 0) {
9620
9642
  const fuzzyMatches = searchSymbols(graph, symbol).slice(0, 10);
@@ -9623,10 +9645,21 @@ function handleImpactAnalysis(symbol, graph) {
9623
9645
  suggestion: fuzzyMatches.length > 0 ? `Did you mean: ${fuzzyMatches.map((m) => m.name).join(", ")}?` : "Try using search_symbols to find available symbols"
9624
9646
  };
9625
9647
  }
9626
- if (matches.length > 1) {
9627
- return createDisambiguationResponse(matches, symbol);
9648
+ let filteredMatches = matches;
9649
+ if (file) {
9650
+ filteredMatches = matches.filter((m) => m.filePath === file || m.filePath.endsWith(file));
9651
+ if (filteredMatches.length === 0) {
9652
+ return {
9653
+ error: `Symbol '${symbol}' not found in file '${file}'`,
9654
+ availableFiles: matches.map((m) => m.filePath),
9655
+ suggestion: `The symbol exists in: ${matches.map((m) => m.filePath).join(", ")}`
9656
+ };
9657
+ }
9628
9658
  }
9629
- const target = matches[0];
9659
+ if (filteredMatches.length > 1) {
9660
+ return createDisambiguationResponse(filteredMatches, symbol);
9661
+ }
9662
+ const target = filteredMatches[0];
9630
9663
  const impact = getImpact(graph, target.id);
9631
9664
  const directWithKinds = impact.directDependents.map((dep) => {
9632
9665
  let relationship = "unknown";
package/dist/index.js CHANGED
@@ -27,7 +27,7 @@ import {
27
27
  stashChanges,
28
28
  updateFileInGraph,
29
29
  watchProject
30
- } from "./chunk-VVYRHPAE.js";
30
+ } from "./chunk-S3NZMIIU.js";
31
31
 
32
32
  // src/index.ts
33
33
  import { Command } from "commander";
@@ -474,6 +474,28 @@ function printStats(snapshots) {
474
474
  Overall Trend: ${trend}`);
475
475
  }
476
476
 
477
+ // src/telemetry.ts
478
+ import os from "os";
479
+ var TELEMETRY_URL = "https://telemetry.depwire.dev/event";
480
+ async function trackCommand(command, version = "unknown") {
481
+ if (process.env.DEPWIRE_NO_TELEMETRY === "1" || process.env.DEPWIRE_NO_TELEMETRY === "true" || process.env.DO_NOT_TRACK === "1") {
482
+ return;
483
+ }
484
+ const payload = {
485
+ command,
486
+ version,
487
+ os: os.platform(),
488
+ node: process.version
489
+ };
490
+ fetch(TELEMETRY_URL, {
491
+ method: "POST",
492
+ headers: { "Content-Type": "application/json" },
493
+ body: JSON.stringify(payload),
494
+ signal: AbortSignal.timeout(2e3)
495
+ }).catch(() => {
496
+ });
497
+ }
498
+
477
499
  // src/index.ts
478
500
  var __filename2 = fileURLToPath2(import.meta.url);
479
501
  var __dirname2 = dirname2(__filename2);
@@ -482,6 +504,7 @@ var packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf-8"));
482
504
  var program = new Command();
483
505
  program.name("depwire").description("Code cross-reference graph builder for TypeScript projects").version(packageJson.version);
484
506
  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) => {
507
+ trackCommand("parse", packageJson.version);
485
508
  const startTime = Date.now();
486
509
  try {
487
510
  const projectRoot = directory ? resolve(directory) : findProjectRoot();
@@ -521,6 +544,7 @@ Orphan Files (no cross-references): ${summary.orphanFiles.length}`);
521
544
  }
522
545
  });
523
546
  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) => {
547
+ trackCommand("query", packageJson.version);
524
548
  try {
525
549
  const projectRoot = resolve(directory);
526
550
  const cacheFile = "depwire-output.json";
@@ -569,6 +593,7 @@ Total Transitive Dependents: ${impact.transitiveDependents.length}`);
569
593
  }
570
594
  });
571
595
  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) => {
596
+ trackCommand("viz", packageJson.version);
572
597
  try {
573
598
  const projectRoot = directory ? resolve(directory) : findProjectRoot();
574
599
  console.log(`Parsing project: ${projectRoot}`);
@@ -591,6 +616,7 @@ program.command("viz").description("Launch interactive arc diagram visualization
591
616
  }
592
617
  });
593
618
  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) => {
619
+ trackCommand("temporal", packageJson.version);
594
620
  try {
595
621
  const projectRoot = directory ? resolve(directory) : findProjectRoot();
596
622
  await runTemporalAnalysis(projectRoot, {
@@ -607,6 +633,7 @@ program.command("temporal").description("Visualize how the dependency graph evol
607
633
  }
608
634
  });
609
635
  program.command("mcp").description("Start MCP server for AI coding tools").argument("[directory]", "Project directory to analyze (optional - auto-detects project root or use connect_repo tool to connect later)").action(async (directory) => {
636
+ trackCommand("mcp", packageJson.version);
610
637
  try {
611
638
  const state = createEmptyState();
612
639
  let projectRootToConnect = null;
@@ -669,6 +696,7 @@ program.command("mcp").description("Start MCP server for AI coding tools").argum
669
696
  }
670
697
  });
671
698
  program.command("docs").description("Generate comprehensive codebase documentation").argument("[directory]", "Project directory to document (defaults to current directory or auto-detected project root)").option("-o, --output <path>", "Output directory (default: .depwire/ inside project)").option("--format <type>", "Output format: markdown | json", "markdown").option("--gitignore", "Add .depwire/ to .gitignore automatically").option("--no-gitignore", "Don't modify .gitignore").option("--include <docs>", "Comma-separated list of docs to generate (default: all)", "all").option("--update", "Regenerate existing docs").option("--only <docs>", "Used with --update, regenerate only specific docs").option("--verbose", "Show generation progress").option("--stats", "Show generation statistics at the end").option("--exclude <patterns...>", 'Glob patterns to exclude (e.g., "**/*.test.*" "dist/**")').action(async (directory, options) => {
699
+ trackCommand("docs", packageJson.version);
672
700
  const startTime = Date.now();
673
701
  try {
674
702
  const projectRoot = directory ? resolve(directory) : findProjectRoot();
@@ -766,6 +794,7 @@ ${pattern}
766
794
  }
767
795
  }
768
796
  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) => {
797
+ trackCommand("health", packageJson.version);
769
798
  try {
770
799
  const projectRoot = directory ? resolve(directory) : findProjectRoot();
771
800
  const startTime = Date.now();
@@ -789,6 +818,7 @@ program.command("health").description("Analyze dependency architecture health (0
789
818
  }
790
819
  });
791
820
  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) => {
821
+ trackCommand("dead-code", packageJson.version);
792
822
  try {
793
823
  const projectRoot = directory ? resolve(directory) : findProjectRoot();
794
824
  const startTime = Date.now();
@@ -6,7 +6,7 @@ import {
6
6
  startMcpServer,
7
7
  updateFileInGraph,
8
8
  watchProject
9
- } from "./chunk-VVYRHPAE.js";
9
+ } from "./chunk-S3NZMIIU.js";
10
10
 
11
11
  // src/mcpb-entry.ts
12
12
  import { resolve } from "path";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "depwire-cli",
3
- "version": "0.9.9",
3
+ "version": "0.9.12",
4
4
  "description": "Code cross-reference visualization and AI context engine for TypeScript, JavaScript, Python, Go, Rust, and C. Zero native dependencies — works on Windows, macOS, and Linux.",
5
5
  "type": "module",
6
6
  "bin": {