@driftless-sh/cli 0.1.48 → 0.1.50

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
@@ -213387,15 +213387,23 @@ var require_nestjs_extractor = __commonJS({
213387
213387
  };
213388
213388
  Object.defineProperty(exports2, "__esModule", { value: true });
213389
213389
  exports2.extractNestJS = extractNestJS;
213390
+ exports2.extractNestJSWithReport = extractNestJSWithReport;
213390
213391
  var node_path_1 = __importDefault(require("node:path"));
213391
213392
  var node_fs_1 = __importDefault(require("node:fs"));
213392
213393
  var typescript_1 = __importDefault(require_typescript());
213393
- function discoverFiles(rootPath) {
213394
+ var DEFAULT_MAX_FILE_BYTES = 15e5;
213395
+ function maxFileBytes() {
213396
+ const raw = Number(process.env.DRIFTLESS_MAX_FILE_BYTES);
213397
+ return Number.isFinite(raw) && raw > 0 ? Math.floor(raw) : DEFAULT_MAX_FILE_BYTES;
213398
+ }
213399
+ function discoverScanFiles(rootPath) {
213394
213400
  const files = [];
213401
+ const skipped = [];
213395
213402
  const srcDir = node_fs_1.default.existsSync(node_path_1.default.join(rootPath, "src")) ? node_path_1.default.join(rootPath, "src") : rootPath;
213396
213403
  if (!node_fs_1.default.existsSync(srcDir))
213397
- return files;
213404
+ return { files, skipped };
213398
213405
  const SKIP = /* @__PURE__ */ new Set(["node_modules", "dist", ".next", "build", ".turbo", "coverage"]);
213406
+ const cap = maxFileBytes();
213399
213407
  function walk(dir) {
213400
213408
  const entries = node_fs_1.default.readdirSync(dir, { withFileTypes: true });
213401
213409
  for (const entry of entries) {
@@ -213404,13 +213412,23 @@ var require_nestjs_extractor = __commonJS({
213404
213412
  if (SKIP.has(entry.name))
213405
213413
  continue;
213406
213414
  walk(full);
213407
- } else if (entry.isFile() && entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts") && !entry.name.endsWith(".spec.ts") && !entry.name.endsWith(".test.ts")) {
213408
- files.push(full);
213415
+ } else if (entry.isFile() && (entry.name.endsWith(".ts") || entry.name.endsWith(".tsx")) && !entry.name.endsWith(".d.ts") && !entry.name.endsWith(".spec.ts") && !entry.name.endsWith(".test.ts")) {
213416
+ let size = 0;
213417
+ try {
213418
+ size = node_fs_1.default.statSync(full).size;
213419
+ } catch {
213420
+ continue;
213421
+ }
213422
+ if (size > cap) {
213423
+ skipped.push({ path: node_path_1.default.relative(rootPath, full), bytes: size });
213424
+ } else {
213425
+ files.push(full);
213426
+ }
213409
213427
  }
213410
213428
  }
213411
213429
  }
213412
213430
  walk(srcDir);
213413
- return files;
213431
+ return { files, skipped };
213414
213432
  }
213415
213433
  function parseFile(filePath, rootPath) {
213416
213434
  const sourceText = node_fs_1.default.readFileSync(filePath, "utf8");
@@ -213880,10 +213898,15 @@ var require_nestjs_extractor = __commonJS({
213880
213898
  return c;
213881
213899
  }
213882
213900
  function extractNestJS(rootPath) {
213901
+ return extractNestJSWithReport(rootPath).components;
213902
+ }
213903
+ function extractNestJSWithReport(rootPath) {
213883
213904
  const components = [];
213884
- const files = discoverFiles(rootPath).filter((f) => isInSourceDir(node_path_1.default.relative(rootPath, f))).sort();
213905
+ const discovered = discoverScanFiles(rootPath);
213906
+ const files = discovered.files.filter((f) => isInSourceDir(node_path_1.default.relative(rootPath, f))).sort();
213907
+ const skipped = discovered.skipped.filter((s) => isInSourceDir(s.path));
213885
213908
  if (files.length === 0)
213886
- return components;
213909
+ return { components, skipped };
213887
213910
  const symbolIndex = /* @__PURE__ */ new Map();
213888
213911
  const moduleByDir = /* @__PURE__ */ new Map();
213889
213912
  for (const file of files) {
@@ -214051,7 +214074,7 @@ var require_nestjs_extractor = __commonJS({
214051
214074
  }
214052
214075
  }
214053
214076
  }
214054
- return components;
214077
+ return { components, skipped };
214055
214078
  }
214056
214079
  function extractModuleRelations(cls, symbolIndex) {
214057
214080
  const meta = cls.moduleMeta;
@@ -214321,12 +214344,222 @@ var require_enricher = __commonJS({
214321
214344
  }
214322
214345
  });
214323
214346
 
214347
+ // ../../libs/scanner/dist/coverage.js
214348
+ var require_coverage = __commonJS({
214349
+ "../../libs/scanner/dist/coverage.js"(exports2) {
214350
+ "use strict";
214351
+ Object.defineProperty(exports2, "__esModule", { value: true });
214352
+ exports2.computeCoverage = computeCoverage;
214353
+ exports2.summarizeCoverage = summarizeCoverage;
214354
+ function normalizePath(p) {
214355
+ return p.trim().replace(/\\/g, "/").replace(/^\.\//, "").replace(/^\/+/, "");
214356
+ }
214357
+ function globToRegExp(glob) {
214358
+ const g = normalizePath(glob);
214359
+ let out = "";
214360
+ for (let i = 0; i < g.length; i++) {
214361
+ const c = g[i];
214362
+ if (c === "*") {
214363
+ if (g[i + 1] === "*") {
214364
+ i++;
214365
+ if (g[i + 1] === "/")
214366
+ i++;
214367
+ out += ".*";
214368
+ } else {
214369
+ out += "[^/]*";
214370
+ }
214371
+ } else if (c === "?") {
214372
+ out += "[^/]";
214373
+ } else if ("\\^$+.()|{}[]".includes(c)) {
214374
+ out += "\\" + c;
214375
+ } else {
214376
+ out += c;
214377
+ }
214378
+ }
214379
+ return new RegExp("^" + out + "$");
214380
+ }
214381
+ function matchesPattern(filePath, pattern) {
214382
+ if (!pattern)
214383
+ return false;
214384
+ try {
214385
+ return globToRegExp(pattern).test(normalizePath(filePath));
214386
+ } catch {
214387
+ return false;
214388
+ }
214389
+ }
214390
+ var SENSITIVE = /(auth|admin|billing|payment|credit|invoice|token|secret|webhook|api[-_]?key|password|session)/i;
214391
+ function isHighRisk(c) {
214392
+ if (c.type === "guard")
214393
+ return true;
214394
+ if (c.type === "endpoint") {
214395
+ const guards = c.metadata?.guard_names;
214396
+ if (!guards || guards.length === 0)
214397
+ return true;
214398
+ }
214399
+ const hay = `${c.file_path} ${c.name} ${c.metadata?.path ?? ""}`;
214400
+ return SENSITIVE.test(hay);
214401
+ }
214402
+ var RANK_TO_SPECIFICITY = {
214403
+ 4: "file",
214404
+ 3: "pattern",
214405
+ 2: "repo",
214406
+ 1: "weak"
214407
+ };
214408
+ function matchRank(filePath, topic, repoId) {
214409
+ const np = normalizePath(filePath);
214410
+ const files = topic.where_files ?? [];
214411
+ if (files.some((f) => normalizePath(f) === np))
214412
+ return 4;
214413
+ if (matchesPattern(np, topic.pattern))
214414
+ return 3;
214415
+ const repoScoped = !!repoId && !!topic.where_repos && topic.where_repos.includes(repoId);
214416
+ if (repoScoped) {
214417
+ const hasAnchors = files.length > 0 || !!topic.pattern;
214418
+ return hasAnchors ? 2 : 1;
214419
+ }
214420
+ return 0;
214421
+ }
214422
+ function reviewLevel(topic) {
214423
+ if (topic.suggested)
214424
+ return "suggested";
214425
+ if (topic.status === "reviewed")
214426
+ return "reviewed";
214427
+ if (topic.status === "draft")
214428
+ return "draft";
214429
+ return "none";
214430
+ }
214431
+ var STATUS_RANK = {
214432
+ none: 0,
214433
+ suggested: 1,
214434
+ draft: 2,
214435
+ reviewed: 3
214436
+ };
214437
+ function strongerStatus(a, b) {
214438
+ return STATUS_RANK[a] >= STATUS_RANK[b] ? a : b;
214439
+ }
214440
+ function decideAction(best, highRisk, hasStale, hasOrphaned) {
214441
+ if (highRisk && best === "none")
214442
+ return "document-now";
214443
+ if (hasOrphaned)
214444
+ return "reattach-or-remove";
214445
+ if (hasStale)
214446
+ return "refresh-stale";
214447
+ if (best === "none")
214448
+ return "add-topic";
214449
+ if (best === "suggested")
214450
+ return "review-suggested";
214451
+ if (best === "draft")
214452
+ return "promote-draft";
214453
+ return "none";
214454
+ }
214455
+ function computeCoverage(components, topics, opts = {}) {
214456
+ return components.map((c) => {
214457
+ const direct = [];
214458
+ const indirect = [];
214459
+ let bestRank = 0;
214460
+ let best = "none";
214461
+ let hasStale = false;
214462
+ let hasOrphaned = false;
214463
+ for (const t of topics) {
214464
+ const rank = matchRank(c.file_path, t, opts.repoId);
214465
+ if (rank === 0)
214466
+ continue;
214467
+ if (t.stale)
214468
+ hasStale = true;
214469
+ if (t.status === "orphaned")
214470
+ hasOrphaned = true;
214471
+ if (rank >= 4)
214472
+ direct.push(t.slug);
214473
+ else
214474
+ indirect.push(t.slug);
214475
+ if (rank > bestRank)
214476
+ bestRank = rank;
214477
+ best = strongerStatus(best, reviewLevel(t));
214478
+ }
214479
+ const highRisk = isHighRisk(c);
214480
+ const specificity = bestRank === 0 ? "weak" : RANK_TO_SPECIFICITY[bestRank];
214481
+ return {
214482
+ component: `${c.type}:${normalizePath(c.file_path)}:${c.name}:${c.line_number}`,
214483
+ file_path: c.file_path,
214484
+ best_status: best,
214485
+ direct_topics: [...new Set(direct)].sort(),
214486
+ indirect_topics: [...new Set(indirect)].sort(),
214487
+ match_specificity: specificity,
214488
+ has_stale: hasStale,
214489
+ has_orphaned: hasOrphaned,
214490
+ is_high_risk: highRisk,
214491
+ recommended_action: decideAction(best, highRisk, hasStale, hasOrphaned)
214492
+ };
214493
+ });
214494
+ }
214495
+ function summarizeCoverage(rows) {
214496
+ const by_status = { reviewed: 0, draft: 0, suggested: 0, none: 0 };
214497
+ const by_specificity = {
214498
+ component: 0,
214499
+ file: 0,
214500
+ pattern: 0,
214501
+ repo: 0,
214502
+ weak: 0
214503
+ };
214504
+ let covered = 0;
214505
+ let strong = 0;
214506
+ let high_risk_uncovered = 0;
214507
+ let stale_count = 0;
214508
+ let orphaned_count = 0;
214509
+ for (const r of rows) {
214510
+ by_status[r.best_status]++;
214511
+ by_specificity[r.match_specificity]++;
214512
+ if (r.best_status !== "none")
214513
+ covered++;
214514
+ if (r.best_status === "reviewed" && (r.match_specificity === "file" || r.match_specificity === "component")) {
214515
+ strong++;
214516
+ }
214517
+ if (r.is_high_risk && r.best_status === "none")
214518
+ high_risk_uncovered++;
214519
+ if (r.has_stale)
214520
+ stale_count++;
214521
+ if (r.has_orphaned)
214522
+ orphaned_count++;
214523
+ }
214524
+ const total = rows.length;
214525
+ const uncovered = total - covered;
214526
+ const partial = covered - strong;
214527
+ let status;
214528
+ if (total === 0)
214529
+ status = "empty";
214530
+ else if (high_risk_uncovered > 0)
214531
+ status = "critical-gap";
214532
+ else if (uncovered > 0)
214533
+ status = "gap";
214534
+ else if (partial > 0)
214535
+ status = "partial";
214536
+ else
214537
+ status = "covered";
214538
+ return {
214539
+ total,
214540
+ covered,
214541
+ uncovered,
214542
+ by_status,
214543
+ by_specificity,
214544
+ strong,
214545
+ partial,
214546
+ high_risk_uncovered,
214547
+ stale_count,
214548
+ orphaned_count,
214549
+ has_stale: stale_count > 0,
214550
+ has_orphaned: orphaned_count > 0,
214551
+ status
214552
+ };
214553
+ }
214554
+ }
214555
+ });
214556
+
214324
214557
  // ../../libs/scanner/dist/index.js
214325
214558
  var require_dist = __commonJS({
214326
214559
  "../../libs/scanner/dist/index.js"(exports2) {
214327
214560
  "use strict";
214328
214561
  Object.defineProperty(exports2, "__esModule", { value: true });
214329
- exports2.enrichComponents = exports2.extractNestJS = exports2.identifyRepo = void 0;
214562
+ exports2.summarizeCoverage = exports2.computeCoverage = exports2.enrichComponents = exports2.extractNestJSWithReport = exports2.extractNestJS = exports2.identifyRepo = void 0;
214330
214563
  exports2.scanRepo = scanRepo2;
214331
214564
  var identity_1 = require_identity();
214332
214565
  Object.defineProperty(exports2, "identifyRepo", { enumerable: true, get: function() {
@@ -214336,16 +214569,27 @@ var require_dist = __commonJS({
214336
214569
  Object.defineProperty(exports2, "extractNestJS", { enumerable: true, get: function() {
214337
214570
  return nestjs_extractor_1.extractNestJS;
214338
214571
  } });
214572
+ Object.defineProperty(exports2, "extractNestJSWithReport", { enumerable: true, get: function() {
214573
+ return nestjs_extractor_1.extractNestJSWithReport;
214574
+ } });
214339
214575
  var enricher_1 = require_enricher();
214340
214576
  Object.defineProperty(exports2, "enrichComponents", { enumerable: true, get: function() {
214341
214577
  return enricher_1.enrichComponents;
214342
214578
  } });
214579
+ var coverage_1 = require_coverage();
214580
+ Object.defineProperty(exports2, "computeCoverage", { enumerable: true, get: function() {
214581
+ return coverage_1.computeCoverage;
214582
+ } });
214583
+ Object.defineProperty(exports2, "summarizeCoverage", { enumerable: true, get: function() {
214584
+ return coverage_1.summarizeCoverage;
214585
+ } });
214343
214586
  async function scanRepo2(rootPath) {
214344
214587
  const startMs = Date.now();
214345
214588
  const startMem = process.memoryUsage().heapUsed;
214346
214589
  const identity = (0, identity_1.identifyRepo)(rootPath);
214347
- let components = (0, nestjs_extractor_1.extractNestJS)(rootPath);
214348
- components = await (0, enricher_1.enrichComponents)(components);
214590
+ const extracted = (0, nestjs_extractor_1.extractNestJSWithReport)(rootPath);
214591
+ const skipped = extracted.skipped;
214592
+ let components = await (0, enricher_1.enrichComponents)(extracted.components);
214349
214593
  const uniqueFiles = new Set(components.map((c) => c.file_path));
214350
214594
  const stats = {
214351
214595
  total_files: uniqueFiles.size,
@@ -214364,7 +214608,8 @@ var require_dist = __commonJS({
214364
214608
  telemetry: {
214365
214609
  duration_ms: durationMs,
214366
214610
  memory_mb: memoryMb,
214367
- files_parsed: uniqueFiles.size
214611
+ files_parsed: uniqueFiles.size,
214612
+ skipped_files: skipped
214368
214613
  }
214369
214614
  };
214370
214615
  }
@@ -214597,7 +214842,7 @@ async function installSkillCommand() {
214597
214842
  // src/commands/init.ts
214598
214843
  function getVersion() {
214599
214844
  try {
214600
- return "0.1.48";
214845
+ return "0.1.50";
214601
214846
  } catch {
214602
214847
  return "0.0.0";
214603
214848
  }
@@ -214992,6 +215237,17 @@ async function initCommand(args) {
214992
215237
  } else {
214993
215238
  console.log(` Scan: ${scanMs}ms`);
214994
215239
  }
215240
+ const skippedFiles = telemetry?.skipped_files ?? [];
215241
+ if (skippedFiles.length > 0) {
215242
+ const mb = (n) => `${(n / 1e6).toFixed(1)} MB`;
215243
+ console.warn(` \u26A0 ${skippedFiles.length} file(s) skipped \u2014 over the per-file scan cap (not parsed):`);
215244
+ for (const f of skippedFiles.slice(0, 10)) {
215245
+ console.warn(` ${f.path} (${mb(f.bytes)})`);
215246
+ }
215247
+ if (skippedFiles.length > 10) console.warn(` \u2026 and ${skippedFiles.length - 10} more`);
215248
+ console.warn(" Generated/minified/vendored bundles are skipped to avoid OOM.");
215249
+ console.warn(" Override with DRIFTLESS_MAX_FILE_BYTES=<bytes> if a real source file was skipped.");
215250
+ }
214995
215251
  console.log("");
214996
215252
  let repo;
214997
215253
  try {
@@ -216618,8 +216874,8 @@ async function graphCommand(args) {
216618
216874
  const isJSON = args.includes("--json");
216619
216875
  const depthIdx = args.indexOf("--depth");
216620
216876
  const depth = depthIdx !== -1 && args[depthIdx + 1] ? `&depth=${encodeURIComponent(args[depthIdx + 1])}` : "";
216621
- if (sub !== "file" && sub !== "impact") {
216622
- console.error('Usage:\n driftless graph file <path> [--depth N] [--json]\n driftless graph impact --files "a,b" [--depth N] [--json]');
216877
+ if (sub !== "file" && sub !== "impact" && sub !== "coverage") {
216878
+ console.error('Usage:\n driftless graph file <path> [--depth N] [--json]\n driftless graph impact --files "a,b" [--depth N] [--json]\n driftless graph coverage [--json]');
216623
216879
  process.exit(1);
216624
216880
  }
216625
216881
  const resolution = await resolveRepo();
@@ -216634,6 +216890,34 @@ async function graphCommand(args) {
216634
216890
  const { workspaceSlug, repoId } = resolution;
216635
216891
  const base = `/workspaces/${workspaceSlug}/repos/${repoId}/graph`;
216636
216892
  try {
216893
+ if (sub === "coverage") {
216894
+ const r = await api.get(`/workspaces/${workspaceSlug}/repos/${repoId}/architecture-summary`);
216895
+ const cov = r?.coverage;
216896
+ if (!cov || !cov.summary) {
216897
+ console.error("No coverage in architecture-summary. The API may be on an older build, or run `driftless init` first.");
216898
+ process.exit(1);
216899
+ }
216900
+ if (isJSON) {
216901
+ emitJSON4(cov);
216902
+ process.exit(0);
216903
+ }
216904
+ const s = cov.summary;
216905
+ console.log("\u258C context coverage\n");
216906
+ console.log(` status ${s.status}`);
216907
+ console.log(` components ${s.covered}/${s.total} covered (strong ${s.strong} \xB7 partial ${s.partial} \xB7 none ${s.uncovered})`);
216908
+ if (s.high_risk_uncovered > 0) console.log(` \u26A0 high-risk uncovered: ${s.high_risk_uncovered}`);
216909
+ if (s.stale_count > 0 || s.orphaned_count > 0) {
216910
+ console.log(` flags stale ${s.stale_count} \xB7 orphaned ${s.orphaned_count}`);
216911
+ }
216912
+ const mods = Object.entries(cov.by_module).sort((a, b) => a[0].localeCompare(b[0]));
216913
+ if (mods.length) {
216914
+ console.log("\n modules:");
216915
+ for (const [name, m] of mods) {
216916
+ console.log(` ${name} \u2014 ${m.status} (${m.covered}/${m.total})`);
216917
+ }
216918
+ }
216919
+ process.exit(0);
216920
+ }
216637
216921
  if (sub === "file") {
216638
216922
  const path = args[1];
216639
216923
  if (!path || path.startsWith("--")) {
@@ -216713,7 +216997,7 @@ async function graphCommand(args) {
216713
216997
  }
216714
216998
 
216715
216999
  // src/index.ts
216716
- var VERSION = "0.1.48";
217000
+ var VERSION = "0.1.50";
216717
217001
  var HELP_TEXT = `Driftless CLI v${VERSION} \u2014 Living repo context for humans and coding agents
216718
217002
 
216719
217003
  Install: npm install -g @driftless-sh/cli