@driftless-sh/cli 0.1.49 → 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
@@ -214344,12 +214344,222 @@ var require_enricher = __commonJS({
214344
214344
  }
214345
214345
  });
214346
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
+
214347
214557
  // ../../libs/scanner/dist/index.js
214348
214558
  var require_dist = __commonJS({
214349
214559
  "../../libs/scanner/dist/index.js"(exports2) {
214350
214560
  "use strict";
214351
214561
  Object.defineProperty(exports2, "__esModule", { value: true });
214352
- exports2.enrichComponents = exports2.extractNestJSWithReport = exports2.extractNestJS = exports2.identifyRepo = void 0;
214562
+ exports2.summarizeCoverage = exports2.computeCoverage = exports2.enrichComponents = exports2.extractNestJSWithReport = exports2.extractNestJS = exports2.identifyRepo = void 0;
214353
214563
  exports2.scanRepo = scanRepo2;
214354
214564
  var identity_1 = require_identity();
214355
214565
  Object.defineProperty(exports2, "identifyRepo", { enumerable: true, get: function() {
@@ -214366,6 +214576,13 @@ var require_dist = __commonJS({
214366
214576
  Object.defineProperty(exports2, "enrichComponents", { enumerable: true, get: function() {
214367
214577
  return enricher_1.enrichComponents;
214368
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
+ } });
214369
214586
  async function scanRepo2(rootPath) {
214370
214587
  const startMs = Date.now();
214371
214588
  const startMem = process.memoryUsage().heapUsed;
@@ -214625,7 +214842,7 @@ async function installSkillCommand() {
214625
214842
  // src/commands/init.ts
214626
214843
  function getVersion() {
214627
214844
  try {
214628
- return "0.1.49";
214845
+ return "0.1.50";
214629
214846
  } catch {
214630
214847
  return "0.0.0";
214631
214848
  }
@@ -216657,8 +216874,8 @@ async function graphCommand(args) {
216657
216874
  const isJSON = args.includes("--json");
216658
216875
  const depthIdx = args.indexOf("--depth");
216659
216876
  const depth = depthIdx !== -1 && args[depthIdx + 1] ? `&depth=${encodeURIComponent(args[depthIdx + 1])}` : "";
216660
- if (sub !== "file" && sub !== "impact") {
216661
- 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]');
216662
216879
  process.exit(1);
216663
216880
  }
216664
216881
  const resolution = await resolveRepo();
@@ -216673,6 +216890,34 @@ async function graphCommand(args) {
216673
216890
  const { workspaceSlug, repoId } = resolution;
216674
216891
  const base = `/workspaces/${workspaceSlug}/repos/${repoId}/graph`;
216675
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
+ }
216676
216921
  if (sub === "file") {
216677
216922
  const path = args[1];
216678
216923
  if (!path || path.startsWith("--")) {
@@ -216752,7 +216997,7 @@ async function graphCommand(args) {
216752
216997
  }
216753
216998
 
216754
216999
  // src/index.ts
216755
- var VERSION = "0.1.49";
217000
+ var VERSION = "0.1.50";
216756
217001
  var HELP_TEXT = `Driftless CLI v${VERSION} \u2014 Living repo context for humans and coding agents
216757
217002
 
216758
217003
  Install: npm install -g @driftless-sh/cli