@driftless-sh/cli 0.1.39 → 0.1.43

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
@@ -172,12 +172,21 @@ function isTransient(err) {
172
172
  const msg = err instanceof Error ? err.message : String(err);
173
173
  return !/HTTP 4\d\d:/.test(msg);
174
174
  }
175
+ function defaultsFor(method) {
176
+ const isGet = method === "GET";
177
+ return {
178
+ timeoutMs: isGet ? 15e3 : 2e4,
179
+ retries: isGet ? 1 : 0
180
+ };
181
+ }
175
182
  async function request(method, path, body, opts) {
176
- const retries = opts?.retries ?? 0;
183
+ const d = defaultsFor(method);
184
+ const timeoutMs = opts?.timeoutMs ?? d.timeoutMs;
185
+ const retries = opts?.retries ?? d.retries;
177
186
  let lastErr;
178
187
  for (let attempt = 0; attempt <= retries; attempt++) {
179
188
  try {
180
- return await singleRequest(method, path, body, opts?.timeoutMs);
189
+ return await singleRequest(method, path, body, timeoutMs);
181
190
  } catch (e) {
182
191
  lastErr = e;
183
192
  if (attempt === retries || !isTransient(e)) break;
@@ -214633,7 +214642,7 @@ async function installSkillCommand() {
214633
214642
  // src/commands/init.ts
214634
214643
  function getVersion() {
214635
214644
  try {
214636
- return "0.1.39";
214645
+ return "0.1.43";
214637
214646
  } catch {
214638
214647
  return "0.0.0";
214639
214648
  }
@@ -215103,7 +215112,7 @@ async function initCommand(args) {
215103
215112
  watchersSkipped++;
215104
215113
  }
215105
215114
  }
215106
- step("creating context", `${watchersCreated} topics \u2713`);
215115
+ step("creating context", `${watchersCreated} suggested \u2713`);
215107
215116
  } else {
215108
215117
  step("creating context", "skipped (use --suggest to auto-generate)");
215109
215118
  }
@@ -215120,14 +215129,14 @@ async function initCommand(args) {
215120
215129
  step("installing skill", skillLine);
215121
215130
  console.log("");
215122
215131
  console.log("\u2713 repo context bootstrapped");
215123
- console.log(` \u251C\u2500 topics ${suggest ? watchersCreated : "(use --suggest to generate)"}`);
215132
+ console.log(` \u251C\u2500 suggested ${suggest ? `${watchersCreated} topic(s) \u2014 pending review, not yet active` : "(use --suggest to generate)"}`);
215124
215133
  console.log(` \u251C\u2500 rules ${suggest ? rulesCreated : "(use --suggest to generate)"}`);
215125
215134
  console.log(` \u251C\u2500 docs found ${existingDocs.length} (ask your agent to sync them)`);
215126
215135
  console.log(` \u251C\u2500 components ${components.length}`);
215127
215136
  console.log(` \u2514\u2500 relations ${relationCount}`);
215128
215137
  console.log("");
215129
215138
  console.log(" dashboard \u2192 driftless.icu/ecosystem");
215130
- console.log(" next \u2192 driftless context list");
215139
+ console.log(` next \u2192 ${suggest && watchersCreated > 0 ? "driftless context list --suggested # review & confirm suggestions" : "driftless context list"}`);
215131
215140
  console.log("");
215132
215141
  console.log(DIVIDER);
215133
215142
  analyticsEvent("cli_init_run", workspaceSlug, {
@@ -215315,11 +215324,13 @@ function renderSummaryHuman(items) {
215315
215324
  const topicWidth = Math.max(...items.map((i) => i.topic.length), 12) + 2;
215316
215325
  const badgeWidth = Math.max(...items.map((i) => formatBadges(i.badges).length), 8) + 2;
215317
215326
  const statusWidth = Math.max(...items.map((i) => (i.classification?.status || "").length), 8) + 2;
215327
+ const kindWidth = Math.max(...items.map((i) => (i.classification?.kind || "").length), 8) + 2;
215318
215328
  for (const item of items) {
215319
215329
  const topic = pad(item.topic, topicWidth);
215320
215330
  const badges = pad(formatBadges(item.badges), badgeWidth);
215321
215331
  const status = pad(item.classification?.status || "", statusWidth);
215322
- console.log(`${topic}${status}${badges}${item.summary}`);
215332
+ const kind = pad(item.classification?.kind || "", kindWidth);
215333
+ console.log(`${topic}${status}${kind}${badges}${item.summary}`);
215323
215334
  }
215324
215335
  console.log(`
215325
215336
  ${items.length} topic${items.length === 1 ? "" : "s"}.`);
@@ -215468,6 +215479,41 @@ async function contextCommand(args) {
215468
215479
  const { flags, positional } = parseArgs(args);
215469
215480
  const subCommand = positional[0];
215470
215481
  const isJSON = !!flags["json"];
215482
+ if (subCommand === "doctor") {
215483
+ let audit;
215484
+ try {
215485
+ audit = await api.get(`/workspaces/${workspaceSlug}/watchers/audit`);
215486
+ } catch (e) {
215487
+ console.error(`Failed to audit context: ${formatError(e)}`);
215488
+ process.exit(1);
215489
+ }
215490
+ if (isJSON) {
215491
+ emitJSON2(audit);
215492
+ const blocking2 = audit.orphaned.length + audit.repo_leak.length;
215493
+ process.exit(blocking2 > 0 ? 1 : 0);
215494
+ }
215495
+ const section = (label, items) => {
215496
+ if (items.length === 0) return;
215497
+ console.log(`
215498
+ ${label} (${items.length}):`);
215499
+ for (const it of items) {
215500
+ console.log(` ${it.slug}${it.reason ? ` \u2014 ${it.reason}` : ""}`);
215501
+ }
215502
+ };
215503
+ console.log(`Context audit \u2014 ${audit.total} topic${audit.total === 1 ? "" : "s"}`);
215504
+ section("\u26A0 stale (code changed, context not updated)", audit.stale);
215505
+ section("\u2717 orphaned (repo deleted \u2014 hidden from agents)", audit.orphaned);
215506
+ section("\u2022 draft (suggested, never confirmed)", audit.draft);
215507
+ section("\u2022 docs-pending (doc anchored, never synced)", audit.docs_pending);
215508
+ section("\u2717 repo-leak (references unknown repo ids)", audit.repo_leak);
215509
+ const blocking = audit.orphaned.length + audit.repo_leak.length;
215510
+ if (audit.stale.length + audit.draft.length + audit.docs_pending.length + blocking === 0) {
215511
+ console.log("\nAll context is healthy.");
215512
+ } else if (blocking > 0) {
215513
+ console.log("\nResolve orphaned / repo-leak topics \u2014 they indicate deleted-repo fallout.");
215514
+ }
215515
+ process.exit(blocking > 0 ? 1 : 0);
215516
+ }
215471
215517
  if (subCommand === "list") {
215472
215518
  const query = [];
215473
215519
  if (flags["stale"]) query.push("stale=true");
@@ -216167,12 +216213,14 @@ async function resolveRepo() {
216167
216213
  const repo = result.repos.find((r) => r.github_org === remote.org && r.github_repo === remote.repo);
216168
216214
  if (repo) {
216169
216215
  saveWorkspaceToConfig(slug, meWorkspaceId);
216216
+ const source = slug === configSlug ? "config" : slug === meSlug ? "me" : "git-org";
216170
216217
  return {
216171
216218
  ok: true,
216172
216219
  workspaceSlug: slug,
216173
216220
  repoId: repo.id,
216174
216221
  remote,
216175
- hasScanBaseline: !!repo.scan_summary
216222
+ hasScanBaseline: !!repo.scan_summary,
216223
+ source
216176
216224
  };
216177
216225
  }
216178
216226
  lastLinkedSlug = slug;
@@ -216255,21 +216303,26 @@ async function syncCommand(args) {
216255
216303
  }
216256
216304
  process.exit(1);
216257
216305
  }
216258
- const { workspaceSlug, repoId, remote } = resolution;
216259
- const [eventsRes, staleTopics, violations, suggestedTopics] = await Promise.allSettled([
216306
+ const { workspaceSlug, repoId, remote, source } = resolution;
216307
+ const [eventsRes, staleTopics, violations, suggestedTopics, prActivity] = await Promise.allSettled([
216260
216308
  api.get(`/workspaces/${workspaceSlug}/watchers/events?repo_id=${repoId}&limit=20`),
216261
216309
  api.get(`/workspaces/${workspaceSlug}/watchers?stale=true&repo=${repoId}`),
216262
216310
  api.get(`/workspaces/${workspaceSlug}/violations?repo_id=${repoId}&status=open&limit=20`),
216263
- api.get(`/workspaces/${workspaceSlug}/watchers?suggested=true&repo=${repoId}`)
216311
+ api.get(`/workspaces/${workspaceSlug}/watchers?suggested=true&repo=${repoId}`),
216312
+ api.get(`/workspaces/${workspaceSlug}/pr-activity?repo_id=${repoId}&limit=5`)
216264
216313
  ]);
216265
216314
  const events = eventsRes.status === "fulfilled" ? eventsRes.value.events ?? [] : [];
216266
216315
  const stale = staleTopics.status === "fulfilled" ? staleTopics.value : [];
216267
216316
  const rawViolations = violations.status === "fulfilled" ? violations.value : [];
216268
216317
  const openViolations = Array.isArray(rawViolations) ? rawViolations : rawViolations.violations ?? rawViolations.items ?? [];
216269
216318
  const suggested = suggestedTopics.status === "fulfilled" ? suggestedTopics.value : [];
216319
+ const rawPr = prActivity.status === "fulfilled" ? prActivity.value : [];
216320
+ const prs = Array.isArray(rawPr) ? rawPr : rawPr.items ?? [];
216270
216321
  if (isJSON) {
216271
216322
  emitJSON3({
216272
216323
  repo: `${remote.org}/${remote.repo}`,
216324
+ workspace: workspaceSlug,
216325
+ resolved_via: source,
216273
216326
  stale_topics: stale.map((t) => ({ topic: t.topic, reason: t.stale?.reason ?? null })),
216274
216327
  recent_events: events.slice(0, 10).map((e) => ({
216275
216328
  type: e.event_type,
@@ -216283,11 +216336,20 @@ async function syncCommand(args) {
216283
216336
  status: v.status,
216284
216337
  author: v.author
216285
216338
  })),
216286
- suggested_pending: suggested.length
216339
+ suggested_pending: suggested.length,
216340
+ pr_activity: prs.slice(0, 5).map((p) => ({
216341
+ pr_number: p.pr_number,
216342
+ title: p.pr_title,
216343
+ author: p.pr_author,
216344
+ risk: p.risk ?? null,
216345
+ topics: p.watcher_slugs ?? [],
216346
+ observed_at: p.observed_at
216347
+ }))
216287
216348
  });
216288
216349
  process.exit(0);
216289
216350
  }
216290
- console.log(`\u258C ${remote.org}/${remote.repo}
216351
+ console.log(`\u258C ${remote.org}/${remote.repo}`);
216352
+ console.log(` workspace: ${workspaceSlug} (resolved via ${source})
216291
216353
  `);
216292
216354
  if (stale.length > 0) {
216293
216355
  console.log(`\u26A0 ${stale.length} stale topic${stale.length === 1 ? "" : "s"} \u2014 code changed, context not updated:`);
@@ -216315,12 +216377,21 @@ async function syncCommand(args) {
216315
216377
  if (openViolations.length > 5) console.log(` ... and ${openViolations.length - 5} more`);
216316
216378
  console.log("");
216317
216379
  }
216380
+ if (prs.length > 0) {
216381
+ console.log(`PR activity (${prs.length}, via GitHub App \u2014 remote, not local):`);
216382
+ for (const p of prs.slice(0, 5)) {
216383
+ const risk = p.risk ? `[${p.risk}] ` : "";
216384
+ const topics = (p.watcher_slugs ?? []).length ? ` \u2192 ${(p.watcher_slugs ?? []).join(", ")}` : "";
216385
+ console.log(` ${risk}#${p.pr_number} ${String(p.pr_title ?? "").slice(0, 70)}${topics}`);
216386
+ }
216387
+ console.log("");
216388
+ }
216318
216389
  if (suggested.length > 0) {
216319
216390
  console.log(`${suggested.length} suggested topic${suggested.length === 1 ? "" : "s"} from init \u2014 review and confirm:`);
216320
216391
  console.log(` driftless context list --suggested`);
216321
216392
  console.log("");
216322
216393
  }
216323
- if (stale.length === 0 && events.length === 0 && openViolations.length === 0 && suggested.length === 0) {
216394
+ if (stale.length === 0 && events.length === 0 && openViolations.length === 0 && suggested.length === 0 && prs.length === 0) {
216324
216395
  console.log("Cloud context is up to date. Nothing to sync.");
216325
216396
  } else {
216326
216397
  console.log("Review stale topics, then update context before touching code.");
@@ -216449,7 +216520,7 @@ async function doctorCommand() {
216449
216520
  if (localOrNoKey) {
216450
216521
  checks.push({ name: "Workspace", status: "warn", detail: "Skipped (local API or no key)" });
216451
216522
  } else if (resolution.ok) {
216452
- checks.push({ name: "Workspace", status: "ok", detail: resolution.workspaceSlug });
216523
+ checks.push({ name: "Workspace", status: "ok", detail: `${resolution.workspaceSlug} (source: ${resolution.source})` });
216453
216524
  } else if (resolution.reason === "not_linked" && resolution.workspaceSlug) {
216454
216525
  checks.push({ name: "Workspace", status: "ok", detail: resolution.workspaceSlug });
216455
216526
  } else if (resolution.reason === "no_workspace" && resolution.diagnostics) {
@@ -216492,18 +216563,23 @@ async function doctorCommand() {
216492
216563
  if (me?.slug) {
216493
216564
  try {
216494
216565
  const integrations = await api.get(`/workspaces/${me.slug}/integrations`);
216495
- const ghApp = integrations.find((i) => i.type === "github_app" && i.active);
216496
- if (ghApp) {
216566
+ const ghApp = integrations.find((i) => i.type === "github_app");
216567
+ if (ghApp && ghApp.active) {
216497
216568
  checks.push({ name: "GitHub App", status: "ok", detail: "Installed and active" });
216569
+ } else if (ghApp && !ghApp.active) {
216570
+ checks.push({ name: "GitHub App", status: "warn", detail: "Installed, activating \u2014 wait ~60s then re-run doctor" });
216498
216571
  } else {
216499
- checks.push({ name: "GitHub App", status: "warn", detail: "Not installed \u2014 driftless.icu/ecosystem \u2192 Settings \u2192 Integrations (if just installed, wait ~60s)" });
216572
+ checks.push({ name: "GitHub App", status: "warn", detail: "Not installed \u2014 driftless.icu/ecosystem \u2192 Settings \u2192 Integrations" });
216500
216573
  }
216501
216574
  } catch (err) {
216502
- const isNotFound = err?.status === 404 || String(err?.message ?? "").includes("404");
216575
+ const msg = String(err?.message ?? "");
216576
+ const isNotFound = err?.status === 404 || /HTTP 404|404/.test(msg);
216503
216577
  if (isNotFound) {
216504
216578
  checks.push({ name: "GitHub App", status: "warn", detail: "Not installed \u2014 driftless.icu/ecosystem \u2192 Settings \u2192 Integrations" });
216579
+ } else if (/timed out|Connection failed|ENOTFOUND|ECONNREFUSED/i.test(msg)) {
216580
+ checks.push({ name: "GitHub App", status: "warn", detail: "Could not verify \u2014 API unreachable (network/timeout)" });
216505
216581
  } else {
216506
- checks.push({ name: "GitHub App", status: "warn", detail: "Could not verify \u2014 check your connection or API key" });
216582
+ checks.push({ name: "GitHub App", status: "warn", detail: "Could not verify \u2014 API key may lack permission for this workspace" });
216507
216583
  }
216508
216584
  }
216509
216585
  } else {
@@ -216543,7 +216619,7 @@ function pad2(s, n) {
216543
216619
  }
216544
216620
 
216545
216621
  // src/index.ts
216546
- var VERSION = "0.1.39";
216622
+ var VERSION = "0.1.43";
216547
216623
  var HELP_TEXT = `Driftless CLI v${VERSION} \u2014 Living repo context for humans and coding agents
216548
216624
 
216549
216625
  Install: npm install -g @driftless-sh/cli
@@ -216588,6 +216664,7 @@ Context subcommands:
216588
216664
  update <topic> --invariant ".." Append an invariant
216589
216665
  update <topic> --check "..." Append a required check
216590
216666
  delete <topic> Delete a topic
216667
+ doctor Audit context health (stale/orphaned/draft/docs-pending/repo-leak)
216591
216668
  load --files "p1,p2" Match topics by file paths
216592
216669
 
216593
216670
  Flags:
@@ -216688,6 +216765,7 @@ Subcommands:
216688
216765
  sync <topic> --note "..." Add a note to a topic
216689
216766
  update <topic> [opts] Update topic fields
216690
216767
  delete <topic> Delete a topic
216768
+ doctor Audit context health (stale/orphaned/draft/docs-pending/repo-leak)
216691
216769
  load --files "p1,p2,..." Match topics for given file paths
216692
216770
 
216693
216771
  List filters:
@@ -216825,7 +216903,11 @@ async function main() {
216825
216903
  }
216826
216904
  }
216827
216905
  main().catch((err) => {
216828
- console.error("Error:", err.message);
216906
+ const msg = err?.message ?? String(err);
216907
+ console.error("Error:", msg);
216908
+ if (/Connection failed|timed out|ENOTFOUND|ECONNREFUSED|Unauthorized|HTTP 401|HTTP 403|resolve workspace/i.test(msg)) {
216909
+ console.error("\nNext: run `driftless doctor` to diagnose auth/connectivity/workspace.");
216910
+ }
216829
216911
  process.exit(1);
216830
216912
  });
216831
216913
  /*! Bundled license information: