@driftless-sh/cli 0.1.49 → 0.1.51
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 +391 -153
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
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.
|
|
214845
|
+
return "0.1.51";
|
|
214629
214846
|
} catch {
|
|
214630
214847
|
return "0.0.0";
|
|
214631
214848
|
}
|
|
@@ -215285,6 +215502,101 @@ ${result.violations.length} violation(s) found (${rulesEvaluated} rule(s) evalua
|
|
|
215285
215502
|
|
|
215286
215503
|
// src/commands/context.ts
|
|
215287
215504
|
init_api_client();
|
|
215505
|
+
|
|
215506
|
+
// src/repo-resolver.ts
|
|
215507
|
+
init_api_client();
|
|
215508
|
+
function notLinkedMessage(remote, workspaceSlug) {
|
|
215509
|
+
return `Repo '${remote.org}/${remote.repo}' is not registered in workspace '${workspaceSlug}'. Run \`driftless init\` to register it.`;
|
|
215510
|
+
}
|
|
215511
|
+
function formatDiagnostics(d) {
|
|
215512
|
+
const lines = [];
|
|
215513
|
+
if (d.remote) lines.push(` repo: ${d.remote.org}/${d.remote.repo}`);
|
|
215514
|
+
lines.push(` tried slugs: ${d.triedSlugs.length ? d.triedSlugs.join(", ") : "(none)"}`);
|
|
215515
|
+
lines.push(` config slug: ${d.configSlug ?? "(not cached \u2014 run `driftless login` or `driftless init`)"}`);
|
|
215516
|
+
if (!d.meAttempted) {
|
|
215517
|
+
lines.push(" /me: not attempted");
|
|
215518
|
+
} else if (d.meReturned) {
|
|
215519
|
+
lines.push(` /me: returned slug=${d.meSlug ?? "(none)"} workspace_id=${d.meWorkspaceId ?? "(none)"}`);
|
|
215520
|
+
} else {
|
|
215521
|
+
lines.push(` /me: FAILED \u2014 ${d.meError ?? "unknown error"}`);
|
|
215522
|
+
}
|
|
215523
|
+
if (d.failedEndpoint) lines.push(` last endpoint: ${d.failedEndpoint} (rejected)`);
|
|
215524
|
+
return lines.join("\n");
|
|
215525
|
+
}
|
|
215526
|
+
async function fetchRepos(slug) {
|
|
215527
|
+
try {
|
|
215528
|
+
const raw = await api.get(`/workspaces/${slug}/repos`);
|
|
215529
|
+
if (Array.isArray(raw)) return { repos: raw };
|
|
215530
|
+
return { error: "unexpected response shape" };
|
|
215531
|
+
} catch (e) {
|
|
215532
|
+
return { error: e instanceof Error ? e.message : String(e) };
|
|
215533
|
+
}
|
|
215534
|
+
}
|
|
215535
|
+
async function resolveRepo() {
|
|
215536
|
+
const remote = getGitRemote();
|
|
215537
|
+
if (!remote) return { ok: false, reason: "no_remote" };
|
|
215538
|
+
const configSlug = getCachedWorkspace().slug;
|
|
215539
|
+
let meSlug;
|
|
215540
|
+
let meWorkspaceId;
|
|
215541
|
+
let meReturned = false;
|
|
215542
|
+
let meError;
|
|
215543
|
+
try {
|
|
215544
|
+
const me = await api.get("/me", { timeoutMs: 8e3, retries: 1 });
|
|
215545
|
+
meReturned = true;
|
|
215546
|
+
meSlug = me?.slug;
|
|
215547
|
+
meWorkspaceId = me?.workspace_id;
|
|
215548
|
+
if (meSlug) saveWorkspaceToConfig(meSlug, meWorkspaceId);
|
|
215549
|
+
} catch (e) {
|
|
215550
|
+
meError = e instanceof Error ? e.message : String(e);
|
|
215551
|
+
}
|
|
215552
|
+
const candidates = [...new Set([configSlug, meSlug, remote.org].filter(Boolean))];
|
|
215553
|
+
let lastLinkedSlug;
|
|
215554
|
+
let failedEndpoint;
|
|
215555
|
+
for (const slug of candidates) {
|
|
215556
|
+
const result = await fetchRepos(slug);
|
|
215557
|
+
if ("error" in result) {
|
|
215558
|
+
failedEndpoint = `/workspaces/${slug}/repos`;
|
|
215559
|
+
continue;
|
|
215560
|
+
}
|
|
215561
|
+
const repo = result.repos.find((r) => r.github_org === remote.org && r.github_repo === remote.repo);
|
|
215562
|
+
if (repo) {
|
|
215563
|
+
saveWorkspaceToConfig(slug, meWorkspaceId);
|
|
215564
|
+
const source = slug === configSlug ? "config" : slug === meSlug ? "me" : "git-org";
|
|
215565
|
+
return {
|
|
215566
|
+
ok: true,
|
|
215567
|
+
workspaceSlug: slug,
|
|
215568
|
+
repoId: repo.id,
|
|
215569
|
+
remote,
|
|
215570
|
+
hasScanBaseline: !!repo.scan_summary,
|
|
215571
|
+
source
|
|
215572
|
+
};
|
|
215573
|
+
}
|
|
215574
|
+
lastLinkedSlug = slug;
|
|
215575
|
+
}
|
|
215576
|
+
const diagnostics = {
|
|
215577
|
+
remote,
|
|
215578
|
+
triedSlugs: candidates,
|
|
215579
|
+
configSlug,
|
|
215580
|
+
meAttempted: true,
|
|
215581
|
+
meReturned,
|
|
215582
|
+
meSlug,
|
|
215583
|
+
meWorkspaceId,
|
|
215584
|
+
meError,
|
|
215585
|
+
failedEndpoint
|
|
215586
|
+
};
|
|
215587
|
+
if (lastLinkedSlug) {
|
|
215588
|
+
return { ok: false, reason: "not_linked", workspaceSlug: lastLinkedSlug, remote, diagnostics };
|
|
215589
|
+
}
|
|
215590
|
+
return {
|
|
215591
|
+
ok: false,
|
|
215592
|
+
reason: "no_workspace",
|
|
215593
|
+
remote,
|
|
215594
|
+
detail: formatDiagnostics(diagnostics),
|
|
215595
|
+
diagnostics
|
|
215596
|
+
};
|
|
215597
|
+
}
|
|
215598
|
+
|
|
215599
|
+
// src/commands/context.ts
|
|
215288
215600
|
var import_node_fs5 = require("node:fs");
|
|
215289
215601
|
var import_node_path5 = require("node:path");
|
|
215290
215602
|
init_analytics();
|
|
@@ -215941,68 +216253,61 @@ ${label} (${items.length}):`);
|
|
|
215941
216253
|
if (missingFiles.length > 0 && !isJSON) {
|
|
215942
216254
|
console.error(`File(s) not found locally: ${missingFiles.join(", ")}. Matching by pattern only.`);
|
|
215943
216255
|
}
|
|
215944
|
-
|
|
215945
|
-
|
|
215946
|
-
|
|
215947
|
-
|
|
215948
|
-
|
|
215949
|
-
);
|
|
215950
|
-
console.log(`Would match ${results.length} topic${results.length === 1 ? "" : "s"}:`);
|
|
215951
|
-
for (const r of results) {
|
|
215952
|
-
console.log(` ${r.context.topic} via ${r.match_reason}`);
|
|
215953
|
-
}
|
|
215954
|
-
if (missingFiles.length > 0) {
|
|
215955
|
-
console.log(`
|
|
215956
|
-
Warnings:`);
|
|
215957
|
-
for (const f of missingFiles) {
|
|
215958
|
-
console.log(` ${f} does not exist locally \u2014 matched by pattern only`);
|
|
215959
|
-
}
|
|
215960
|
-
}
|
|
215961
|
-
if (!isJSON) {
|
|
215962
|
-
console.log("\n(Dry run \u2014 no context delivered)");
|
|
215963
|
-
}
|
|
215964
|
-
if (isJSON) {
|
|
215965
|
-
emitJSON2({ dry_run: true, matched: results.map((r) => ({ topic: r.context.topic, reason: r.match_reason })), missing_files: missingFiles });
|
|
215966
|
-
}
|
|
215967
|
-
} catch (e) {
|
|
215968
|
-
console.error(`Dry run failed: ${formatError(e)}`);
|
|
215969
|
-
process.exit(1);
|
|
216256
|
+
const resolution = await resolveRepo();
|
|
216257
|
+
if (!resolution.ok) {
|
|
216258
|
+
if (resolution.reason === "not_linked") {
|
|
216259
|
+
console.error(notLinkedMessage(resolution.remote, resolution.workspaceSlug));
|
|
216260
|
+
} else {
|
|
216261
|
+
console.error("Error: could not resolve workspace/repo. Run `driftless doctor`.");
|
|
215970
216262
|
}
|
|
215971
|
-
process.exit(
|
|
216263
|
+
process.exit(1);
|
|
215972
216264
|
}
|
|
216265
|
+
const wsSlug = resolution.workspaceSlug;
|
|
216266
|
+
const repoId = resolution.repoId;
|
|
216267
|
+
let payload;
|
|
215973
216268
|
try {
|
|
215974
|
-
|
|
215975
|
-
`/workspaces/${
|
|
216269
|
+
payload = await api.post(
|
|
216270
|
+
`/workspaces/${wsSlug}/repos/${repoId}/context/load`,
|
|
215976
216271
|
{ files }
|
|
215977
216272
|
);
|
|
215978
|
-
if (results.length === 0 && !isJSON) {
|
|
215979
|
-
console.log(`No context topics match these files.`);
|
|
215980
|
-
}
|
|
215981
|
-
if (isJSON) {
|
|
215982
|
-
emitJSON2(results);
|
|
215983
|
-
} else {
|
|
215984
|
-
if (results.length > 0) {
|
|
215985
|
-
console.log(`Matched ${results.length} context topic${results.length === 1 ? "" : "s"} for ${files.length} file${files.length === 1 ? "" : "s"}:
|
|
215986
|
-
`);
|
|
215987
|
-
for (const r of results) {
|
|
215988
|
-
try {
|
|
215989
|
-
const fullCtx = await api.get(`/workspaces/${workspaceSlug}/watchers/${r.context.topic}`);
|
|
215990
|
-
renderContextHuman(fullCtx);
|
|
215991
|
-
console.log("");
|
|
215992
|
-
} catch {
|
|
215993
|
-
console.log(`\u258C ${r.context.topic} (${r.match_reason})`);
|
|
215994
|
-
console.log(` ${r.context.summary}`);
|
|
215995
|
-
console.log("");
|
|
215996
|
-
}
|
|
215997
|
-
}
|
|
215998
|
-
} else {
|
|
215999
|
-
console.log(`No context topics match these files. No context covers the changed paths.`);
|
|
216000
|
-
}
|
|
216001
|
-
}
|
|
216002
216273
|
} catch (e) {
|
|
216003
216274
|
console.error(`Load failed: ${formatError(e)}`);
|
|
216004
216275
|
process.exit(1);
|
|
216005
216276
|
}
|
|
216277
|
+
if (flags["dry-run"] && !isJSON) {
|
|
216278
|
+
console.log("(Dry run \u2014 context loaded for inspection, nothing written)\n");
|
|
216279
|
+
}
|
|
216280
|
+
if (isJSON) {
|
|
216281
|
+
emitJSON2(payload);
|
|
216282
|
+
process.exit(0);
|
|
216283
|
+
}
|
|
216284
|
+
const allTopics = /* @__PURE__ */ new Set();
|
|
216285
|
+
for (const f of payload.files) {
|
|
216286
|
+
const c = f.coverage;
|
|
216287
|
+
console.log(`\u258C ${f.file}`);
|
|
216288
|
+
console.log(` coverage ${c.status}${c.high_risk ? " \u26A0 high-risk" : ""}`);
|
|
216289
|
+
console.log(` action ${c.recommended_action}`);
|
|
216290
|
+
const dt = c.direct_topics ?? [];
|
|
216291
|
+
const it = c.indirect_topics ?? [];
|
|
216292
|
+
if (dt.length) console.log(` direct ${dt.join(", ")}`);
|
|
216293
|
+
if (it.length) console.log(` indirect ${it.join(", ")}`);
|
|
216294
|
+
if (!dt.length && !it.length) console.log(` topics (none \u2014 no context covers this file)`);
|
|
216295
|
+
if (c.has_stale) console.log(` \u26A0 a matching topic is stale`);
|
|
216296
|
+
if (c.has_orphaned) console.log(` \u26A0 a matching topic is orphaned`);
|
|
216297
|
+
for (const t of [...dt, ...it]) allTopics.add(t);
|
|
216298
|
+
console.log("");
|
|
216299
|
+
}
|
|
216300
|
+
for (const slug of [...allTopics].sort()) {
|
|
216301
|
+
try {
|
|
216302
|
+
const fullCtx = await api.get(`/workspaces/${wsSlug}/watchers/${slug}`);
|
|
216303
|
+
renderContextHuman(fullCtx);
|
|
216304
|
+
console.log("");
|
|
216305
|
+
} catch {
|
|
216306
|
+
}
|
|
216307
|
+
}
|
|
216308
|
+
if (allTopics.size === 0) {
|
|
216309
|
+
console.log("No context covers the requested paths. Consider `driftless context add`.");
|
|
216310
|
+
}
|
|
216006
216311
|
return;
|
|
216007
216312
|
}
|
|
216008
216313
|
if (subCommand === "export") {
|
|
@@ -216163,101 +216468,6 @@ Run 'driftless help context' for full reference.`);
|
|
|
216163
216468
|
|
|
216164
216469
|
// src/commands/sync.ts
|
|
216165
216470
|
init_api_client();
|
|
216166
|
-
|
|
216167
|
-
// src/repo-resolver.ts
|
|
216168
|
-
init_api_client();
|
|
216169
|
-
function notLinkedMessage(remote, workspaceSlug) {
|
|
216170
|
-
return `Repo '${remote.org}/${remote.repo}' is not registered in workspace '${workspaceSlug}'. Run \`driftless init\` to register it.`;
|
|
216171
|
-
}
|
|
216172
|
-
function formatDiagnostics(d) {
|
|
216173
|
-
const lines = [];
|
|
216174
|
-
if (d.remote) lines.push(` repo: ${d.remote.org}/${d.remote.repo}`);
|
|
216175
|
-
lines.push(` tried slugs: ${d.triedSlugs.length ? d.triedSlugs.join(", ") : "(none)"}`);
|
|
216176
|
-
lines.push(` config slug: ${d.configSlug ?? "(not cached \u2014 run `driftless login` or `driftless init`)"}`);
|
|
216177
|
-
if (!d.meAttempted) {
|
|
216178
|
-
lines.push(" /me: not attempted");
|
|
216179
|
-
} else if (d.meReturned) {
|
|
216180
|
-
lines.push(` /me: returned slug=${d.meSlug ?? "(none)"} workspace_id=${d.meWorkspaceId ?? "(none)"}`);
|
|
216181
|
-
} else {
|
|
216182
|
-
lines.push(` /me: FAILED \u2014 ${d.meError ?? "unknown error"}`);
|
|
216183
|
-
}
|
|
216184
|
-
if (d.failedEndpoint) lines.push(` last endpoint: ${d.failedEndpoint} (rejected)`);
|
|
216185
|
-
return lines.join("\n");
|
|
216186
|
-
}
|
|
216187
|
-
async function fetchRepos(slug) {
|
|
216188
|
-
try {
|
|
216189
|
-
const raw = await api.get(`/workspaces/${slug}/repos`);
|
|
216190
|
-
if (Array.isArray(raw)) return { repos: raw };
|
|
216191
|
-
return { error: "unexpected response shape" };
|
|
216192
|
-
} catch (e) {
|
|
216193
|
-
return { error: e instanceof Error ? e.message : String(e) };
|
|
216194
|
-
}
|
|
216195
|
-
}
|
|
216196
|
-
async function resolveRepo() {
|
|
216197
|
-
const remote = getGitRemote();
|
|
216198
|
-
if (!remote) return { ok: false, reason: "no_remote" };
|
|
216199
|
-
const configSlug = getCachedWorkspace().slug;
|
|
216200
|
-
let meSlug;
|
|
216201
|
-
let meWorkspaceId;
|
|
216202
|
-
let meReturned = false;
|
|
216203
|
-
let meError;
|
|
216204
|
-
try {
|
|
216205
|
-
const me = await api.get("/me", { timeoutMs: 8e3, retries: 1 });
|
|
216206
|
-
meReturned = true;
|
|
216207
|
-
meSlug = me?.slug;
|
|
216208
|
-
meWorkspaceId = me?.workspace_id;
|
|
216209
|
-
if (meSlug) saveWorkspaceToConfig(meSlug, meWorkspaceId);
|
|
216210
|
-
} catch (e) {
|
|
216211
|
-
meError = e instanceof Error ? e.message : String(e);
|
|
216212
|
-
}
|
|
216213
|
-
const candidates = [...new Set([configSlug, meSlug, remote.org].filter(Boolean))];
|
|
216214
|
-
let lastLinkedSlug;
|
|
216215
|
-
let failedEndpoint;
|
|
216216
|
-
for (const slug of candidates) {
|
|
216217
|
-
const result = await fetchRepos(slug);
|
|
216218
|
-
if ("error" in result) {
|
|
216219
|
-
failedEndpoint = `/workspaces/${slug}/repos`;
|
|
216220
|
-
continue;
|
|
216221
|
-
}
|
|
216222
|
-
const repo = result.repos.find((r) => r.github_org === remote.org && r.github_repo === remote.repo);
|
|
216223
|
-
if (repo) {
|
|
216224
|
-
saveWorkspaceToConfig(slug, meWorkspaceId);
|
|
216225
|
-
const source = slug === configSlug ? "config" : slug === meSlug ? "me" : "git-org";
|
|
216226
|
-
return {
|
|
216227
|
-
ok: true,
|
|
216228
|
-
workspaceSlug: slug,
|
|
216229
|
-
repoId: repo.id,
|
|
216230
|
-
remote,
|
|
216231
|
-
hasScanBaseline: !!repo.scan_summary,
|
|
216232
|
-
source
|
|
216233
|
-
};
|
|
216234
|
-
}
|
|
216235
|
-
lastLinkedSlug = slug;
|
|
216236
|
-
}
|
|
216237
|
-
const diagnostics = {
|
|
216238
|
-
remote,
|
|
216239
|
-
triedSlugs: candidates,
|
|
216240
|
-
configSlug,
|
|
216241
|
-
meAttempted: true,
|
|
216242
|
-
meReturned,
|
|
216243
|
-
meSlug,
|
|
216244
|
-
meWorkspaceId,
|
|
216245
|
-
meError,
|
|
216246
|
-
failedEndpoint
|
|
216247
|
-
};
|
|
216248
|
-
if (lastLinkedSlug) {
|
|
216249
|
-
return { ok: false, reason: "not_linked", workspaceSlug: lastLinkedSlug, remote, diagnostics };
|
|
216250
|
-
}
|
|
216251
|
-
return {
|
|
216252
|
-
ok: false,
|
|
216253
|
-
reason: "no_workspace",
|
|
216254
|
-
remote,
|
|
216255
|
-
detail: formatDiagnostics(diagnostics),
|
|
216256
|
-
diagnostics
|
|
216257
|
-
};
|
|
216258
|
-
}
|
|
216259
|
-
|
|
216260
|
-
// src/commands/sync.ts
|
|
216261
216471
|
function parseArgs2(args) {
|
|
216262
216472
|
const flags = {};
|
|
216263
216473
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -216657,8 +216867,8 @@ async function graphCommand(args) {
|
|
|
216657
216867
|
const isJSON = args.includes("--json");
|
|
216658
216868
|
const depthIdx = args.indexOf("--depth");
|
|
216659
216869
|
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]');
|
|
216870
|
+
if (sub !== "file" && sub !== "impact" && sub !== "coverage") {
|
|
216871
|
+
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
216872
|
process.exit(1);
|
|
216663
216873
|
}
|
|
216664
216874
|
const resolution = await resolveRepo();
|
|
@@ -216673,6 +216883,34 @@ async function graphCommand(args) {
|
|
|
216673
216883
|
const { workspaceSlug, repoId } = resolution;
|
|
216674
216884
|
const base = `/workspaces/${workspaceSlug}/repos/${repoId}/graph`;
|
|
216675
216885
|
try {
|
|
216886
|
+
if (sub === "coverage") {
|
|
216887
|
+
const r = await api.get(`/workspaces/${workspaceSlug}/repos/${repoId}/architecture-summary`);
|
|
216888
|
+
const cov = r?.coverage;
|
|
216889
|
+
if (!cov || !cov.summary) {
|
|
216890
|
+
console.error("No coverage in architecture-summary. The API may be on an older build, or run `driftless init` first.");
|
|
216891
|
+
process.exit(1);
|
|
216892
|
+
}
|
|
216893
|
+
if (isJSON) {
|
|
216894
|
+
emitJSON4(cov);
|
|
216895
|
+
process.exit(0);
|
|
216896
|
+
}
|
|
216897
|
+
const s = cov.summary;
|
|
216898
|
+
console.log("\u258C context coverage\n");
|
|
216899
|
+
console.log(` status ${s.status}`);
|
|
216900
|
+
console.log(` components ${s.covered}/${s.total} covered (strong ${s.strong} \xB7 partial ${s.partial} \xB7 none ${s.uncovered})`);
|
|
216901
|
+
if (s.high_risk_uncovered > 0) console.log(` \u26A0 high-risk uncovered: ${s.high_risk_uncovered}`);
|
|
216902
|
+
if (s.stale_count > 0 || s.orphaned_count > 0) {
|
|
216903
|
+
console.log(` flags stale ${s.stale_count} \xB7 orphaned ${s.orphaned_count}`);
|
|
216904
|
+
}
|
|
216905
|
+
const mods = Object.entries(cov.by_module).sort((a, b) => a[0].localeCompare(b[0]));
|
|
216906
|
+
if (mods.length) {
|
|
216907
|
+
console.log("\n modules:");
|
|
216908
|
+
for (const [name, m] of mods) {
|
|
216909
|
+
console.log(` ${name} \u2014 ${m.status} (${m.covered}/${m.total})`);
|
|
216910
|
+
}
|
|
216911
|
+
}
|
|
216912
|
+
process.exit(0);
|
|
216913
|
+
}
|
|
216676
216914
|
if (sub === "file") {
|
|
216677
216915
|
const path = args[1];
|
|
216678
216916
|
if (!path || path.startsWith("--")) {
|
|
@@ -216752,7 +216990,7 @@ async function graphCommand(args) {
|
|
|
216752
216990
|
}
|
|
216753
216991
|
|
|
216754
216992
|
// src/index.ts
|
|
216755
|
-
var VERSION = "0.1.
|
|
216993
|
+
var VERSION = "0.1.51";
|
|
216756
216994
|
var HELP_TEXT = `Driftless CLI v${VERSION} \u2014 Living repo context for humans and coding agents
|
|
216757
216995
|
|
|
216758
216996
|
Install: npm install -g @driftless-sh/cli
|