@driftless-sh/cli 0.1.34 → 0.1.36

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
@@ -214567,7 +214567,7 @@ async function installSkillCommand() {
214567
214567
  // src/commands/init.ts
214568
214568
  function getVersion() {
214569
214569
  try {
214570
- return "0.1.34";
214570
+ return "0.1.36";
214571
214571
  } catch {
214572
214572
  return "0.0.0";
214573
214573
  }
@@ -214866,6 +214866,15 @@ function step(label, value) {
214866
214866
  }
214867
214867
  async function initCommand(args) {
214868
214868
  const version = getVersion();
214869
+ let srcOverride;
214870
+ let suggest = false;
214871
+ for (let i = 0; i < args.length; i++) {
214872
+ if (args[i] === "--src" && i + 1 < args.length && !args[i + 1].startsWith("--")) {
214873
+ srcOverride = args[++i];
214874
+ } else if (args[i] === "--suggest") {
214875
+ suggest = true;
214876
+ }
214877
+ }
214869
214878
  console.log("D R I F T L E S S");
214870
214879
  console.log(`v${version}`);
214871
214880
  console.log("");
@@ -214901,10 +214910,12 @@ async function initCommand(args) {
214901
214910
  const workspaceSlug = workspace.slug;
214902
214911
  console.log(`Repository: ${remote.org}/${remote.repo}`);
214903
214912
  console.log(`Workspace: ${workspaceSlug} \u2713`);
214913
+ if (srcOverride) console.log(`Scan root: ${srcOverride}`);
214904
214914
  console.log("");
214905
214915
  const cwd = process.cwd();
214916
+ const scanRoot = srcOverride ? (0, import_node_path4.resolve)(cwd, srcOverride) : cwd;
214906
214917
  const scanStart = Date.now();
214907
- const scanResult = await (0, import_scanner.scanRepo)(cwd);
214918
+ const scanResult = await (0, import_scanner.scanRepo)(scanRoot);
214908
214919
  const scanMs = Date.now() - scanStart;
214909
214920
  const earlyPatterns = analyzeCodePatterns(scanResult.components);
214910
214921
  const summary = {
@@ -214924,6 +214935,12 @@ async function initCommand(args) {
214924
214935
  }
214925
214936
  };
214926
214937
  const components = scanResult.components;
214938
+ if (components.length === 0) {
214939
+ console.warn(`\u26A0 0 components found in ${srcOverride ? `--src ${srcOverride}` : "repo root"}.`);
214940
+ console.warn(" For NestJS monorepos, point --src at the app source root, not the package root.");
214941
+ console.warn(" Example: driftless init --src apps/api/src (not apps/api)");
214942
+ console.warn(" Continuing with baseline upload \u2014 no components or relations will be registered.");
214943
+ }
214927
214944
  const relationCount = components.reduce(
214928
214945
  (sum, component) => sum + (component.relations?.length || 0),
214929
214946
  0
@@ -214976,46 +214993,53 @@ async function initCommand(args) {
214976
214993
  step("uploading components", "failed (continuing)");
214977
214994
  }
214978
214995
  }
214979
- const patterns = earlyPatterns;
214980
- const smartRules = generateSmartRules(repo.id, patterns);
214981
214996
  let rulesCreated = 0;
214982
- let rulesSkipped = 0;
214983
- for (const rule of smartRules) {
214984
- try {
214985
- const result = await api.post(`/workspaces/${workspaceSlug}/rules`, { ...rule, auto_suggested: true });
214986
- if (result?.skipped) {
214997
+ if (suggest) {
214998
+ const smartRules = generateSmartRules(repo.id, earlyPatterns);
214999
+ let rulesSkipped = 0;
215000
+ for (const rule of smartRules) {
215001
+ try {
215002
+ const result = await api.post(`/workspaces/${workspaceSlug}/rules`, { ...rule, auto_suggested: true });
215003
+ if (result?.skipped) rulesSkipped++;
215004
+ else rulesCreated++;
215005
+ } catch {
214987
215006
  rulesSkipped++;
214988
- } else {
214989
- rulesCreated++;
214990
215007
  }
214991
- } catch {
214992
- rulesSkipped++;
214993
215008
  }
215009
+ step("creating rules", `${rulesCreated} created \xB7 ${rulesSkipped} skipped \u2713`);
215010
+ } else {
215011
+ step("creating rules", "skipped (use --suggest to auto-generate)");
214994
215012
  }
214995
- step("creating rules", `${rulesCreated} created \xB7 ${rulesSkipped} skipped \u2713`);
214996
- const smartWatchers = generateSmartWatchers(components);
214997
215013
  let watchersCreated = 0;
214998
- let watchersSkipped = 0;
214999
- for (const watcher of smartWatchers) {
215000
- try {
215001
- await api.post(`/workspaces/${workspaceSlug}/watchers`, {
215002
- name: watcher.name,
215003
- what: watcher.what,
215004
- how: watcher.how,
215005
- pattern: watcher.pattern,
215006
- decisions: watcher.decisions,
215007
- gotchas: watcher.gotchas,
215008
- ownership: watcher.ownership,
215009
- where_repos: [repo.id],
215010
- created_by: "driftless-init",
215011
- suggested: true
215012
- });
215013
- watchersCreated++;
215014
- } catch {
215015
- watchersSkipped++;
215014
+ if (suggest) {
215015
+ const smartWatchers = generateSmartWatchers(components);
215016
+ let watchersSkipped = 0;
215017
+ for (const watcher of smartWatchers) {
215018
+ try {
215019
+ await api.post(`/workspaces/${workspaceSlug}/watchers`, {
215020
+ name: watcher.name,
215021
+ what: watcher.what,
215022
+ how: watcher.how,
215023
+ pattern: watcher.pattern,
215024
+ decisions: watcher.decisions,
215025
+ gotchas: watcher.gotchas,
215026
+ ownership: watcher.ownership,
215027
+ where_repos: [repo.id],
215028
+ created_by: "driftless-init",
215029
+ suggested: true,
215030
+ origin: "auto",
215031
+ status: "draft",
215032
+ kind: "domain-map"
215033
+ });
215034
+ watchersCreated++;
215035
+ } catch {
215036
+ watchersSkipped++;
215037
+ }
215016
215038
  }
215039
+ step("creating context", `${watchersCreated} topics \u2713`);
215040
+ } else {
215041
+ step("creating context", "skipped (use --suggest to auto-generate)");
215017
215042
  }
215018
- step("creating context", `${watchersCreated} watchers \u2713`);
215019
215043
  const existingDocs = detectExistingDocs(cwd);
215020
215044
  step("detecting docs", `${existingDocs.length} found (not auto-synced)`);
215021
215045
  let skillLine = "skipped";
@@ -215029,8 +215053,8 @@ async function initCommand(args) {
215029
215053
  step("installing skill", skillLine);
215030
215054
  console.log("");
215031
215055
  console.log("\u2713 repo context bootstrapped");
215032
- console.log(` \u251C\u2500 watchers ${watchersCreated}`);
215033
- console.log(` \u251C\u2500 rules ${rulesCreated}`);
215056
+ console.log(` \u251C\u2500 topics ${suggest ? watchersCreated : "(use --suggest to generate)"}`);
215057
+ console.log(` \u251C\u2500 rules ${suggest ? rulesCreated : "(use --suggest to generate)"}`);
215034
215058
  console.log(` \u251C\u2500 docs found ${existingDocs.length} (ask your agent to sync them)`);
215035
215059
  console.log(` \u251C\u2500 components ${components.length}`);
215036
215060
  console.log(` \u2514\u2500 relations ${relationCount}`);
@@ -215212,10 +215236,12 @@ function renderSummaryHuman(items) {
215212
215236
  }
215213
215237
  const topicWidth = Math.max(...items.map((i) => i.topic.length), 12) + 2;
215214
215238
  const badgeWidth = Math.max(...items.map((i) => formatBadges(i.badges).length), 8) + 2;
215239
+ const statusWidth = Math.max(...items.map((i) => (i.classification?.status || "").length), 8) + 2;
215215
215240
  for (const item of items) {
215216
215241
  const topic = pad(item.topic, topicWidth);
215217
215242
  const badges = pad(formatBadges(item.badges), badgeWidth);
215218
- console.log(`${topic}${badges}${item.summary}`);
215243
+ const status = pad(item.classification?.status || "", statusWidth);
215244
+ console.log(`${topic}${status}${badges}${item.summary}`);
215219
215245
  }
215220
215246
  console.log(`
215221
215247
  ${items.length} topic${items.length === 1 ? "" : "s"}.`);
@@ -215224,11 +215250,19 @@ function renderContextHuman(ctx) {
215224
215250
  console.log(`\u258C ${ctx.topic}`);
215225
215251
  console.log(` ${ctx.summary}`);
215226
215252
  if (ctx.version !== void 0) console.log(` version: ${ctx.version}`);
215253
+ if (ctx.classification) {
215254
+ const { origin, status, kind } = ctx.classification;
215255
+ console.log(` origin: ${origin} status: ${status} kind: ${kind}`);
215256
+ }
215227
215257
  if (ctx.stale.is_stale) {
215228
215258
  console.log(`
215229
215259
  \u26A0 STALE: ${ctx.stale.reason || "Context may be outdated"}`);
215230
215260
  console.log(` \u2192 driftless context update ${ctx.topic} --what "..." --how "..."`);
215231
215261
  }
215262
+ if (ctx.metadata.created_by === "driftless-init") {
215263
+ console.log(`
215264
+ \u24D8 auto-suggested by init \u2014 confirm with \`driftless context update ${ctx.topic} --what "..."\``);
215265
+ }
215232
215266
  console.log("");
215233
215267
  if (ctx.description.what) console.log(` what: ${ctx.description.what}`);
215234
215268
  if (ctx.description.how) console.log(` how: ${ctx.description.how}`);
@@ -215357,9 +215391,23 @@ async function contextCommand(args) {
215357
215391
  if (flags["docs"]) query.push("docs=true");
215358
215392
  if (flags["auto"]) query.push("auto=true");
215359
215393
  if (flags["manual"]) query.push("manual=true");
215360
- if (flags["repo"]) query.push(`repo=${encodeURIComponent(flags["repo"])}`);
215361
215394
  if (flags["suggested"]) query.push("suggested=true");
215362
215395
  else query.push("suggested=false");
215396
+ if (flags["status"]) query.push(`status=${encodeURIComponent(flags["status"])}`);
215397
+ if (flags["kind"]) query.push(`kind=${encodeURIComponent(flags["kind"])}`);
215398
+ if (flags["repo"]) {
215399
+ query.push(`repo=${encodeURIComponent(flags["repo"])}`);
215400
+ } else if (flags["auto"]) {
215401
+ const currentRemote = getGitRemote();
215402
+ if (currentRemote) {
215403
+ try {
215404
+ const repos = await api.get(`/workspaces/${workspaceSlug}/repos`);
215405
+ const match = repos.find((r) => r.github_org === currentRemote.org && r.github_repo === currentRemote.repo);
215406
+ if (match) query.push(`repo=${encodeURIComponent(match.id)}`);
215407
+ } catch {
215408
+ }
215409
+ }
215410
+ }
215363
215411
  const qs = query.length > 0 ? `?${query.join("&")}` : "";
215364
215412
  try {
215365
215413
  const summaries = await api.get(`/workspaces/${workspaceSlug}/watchers${qs}`);
@@ -215494,10 +215542,17 @@ async function contextCommand(args) {
215494
215542
  }
215495
215543
  }
215496
215544
  const pattern = flags["pattern"];
215497
- if (pattern && !isJSON) {
215545
+ if (pattern) {
215546
+ if (pattern.includes(",")) {
215547
+ console.error(`Error: pattern "${pattern}" contains a comma. Only one glob is accepted per topic.`);
215548
+ console.error(" Create separate topics for each path, or use a broader glob.");
215549
+ process.exit(1);
215550
+ }
215498
215551
  const localCount = countLocalFilesMatching(pattern);
215499
215552
  if (localCount === 0) {
215500
- console.error(`Warning: pattern "${pattern}" matches 0 files locally. Topic created anyway.`);
215553
+ console.error(`Error: pattern "${pattern}" matches 0 files locally. Topic not created.`);
215554
+ console.error(" Verify the glob against your repo root, or use --where for explicit file paths.");
215555
+ process.exit(1);
215501
215556
  }
215502
215557
  }
215503
215558
  try {
@@ -215510,7 +215565,9 @@ async function contextCommand(args) {
215510
215565
  decisions: flags["decisions"],
215511
215566
  gotchas: flags["gotchas"],
215512
215567
  ownership: flags["ownership"],
215513
- file_content: fileContent
215568
+ file_content: fileContent,
215569
+ status: flags["status"],
215570
+ kind: flags["kind"]
215514
215571
  });
215515
215572
  analyticsEvent("context_created", workspaceSlug, { topic: name, has_pattern: !!pattern, has_what: !!flags["what"] });
215516
215573
  if (isJSON) {
@@ -215541,12 +215598,21 @@ async function contextCommand(args) {
215541
215598
  if (flags["decisions"]) updates.decisions = flags["decisions"];
215542
215599
  if (flags["gotchas"]) updates.gotchas = flags["gotchas"];
215543
215600
  if (flags["ownership"]) updates.ownership = flags["ownership"];
215544
- const gotchaFlag = flags["gotcha"] || flags["gotchas_append"];
215545
- if (gotchaFlag) updates._append_gotchas = gotchaFlag;
215546
- const invariantFlag = flags["invariant"];
215547
- if (invariantFlag) updates._append_invariants = invariantFlag;
215548
- const checkFlag = flags["check"];
215549
- if (checkFlag) updates._append_required_checks = checkFlag;
215601
+ if (flags["status"]) updates.status = flags["status"];
215602
+ const gotchaValues = [];
215603
+ const invariantValues = [];
215604
+ const checkValues = [];
215605
+ for (let i = 0; i < args.length - 1; i++) {
215606
+ const a = args[i];
215607
+ const b = args[i + 1];
215608
+ if (!b || b.startsWith("--")) continue;
215609
+ if (a === "--gotcha" || a === "--gotchas_append") gotchaValues.push(b);
215610
+ if (a === "--invariant") invariantValues.push(b);
215611
+ if (a === "--check") checkValues.push(b);
215612
+ }
215613
+ if (gotchaValues.length > 0) updates._append_gotchas = gotchaValues.join("\n");
215614
+ if (invariantValues.length > 0) updates._append_invariants = invariantValues.join("\n");
215615
+ if (checkValues.length > 0) updates._append_required_checks = checkValues.join("\n");
215550
215616
  const enforceFlag = flags["enforce"];
215551
215617
  if (enforceFlag && typeof enforceFlag === "string") {
215552
215618
  updates._enforce_rule = enforceFlag;
@@ -215554,16 +215620,18 @@ async function contextCommand(args) {
215554
215620
  updates._enforce_rule = true;
215555
215621
  }
215556
215622
  const eventParts = [];
215557
- if (gotchaFlag) eventParts.push(`gotcha appended`);
215558
- if (invariantFlag) eventParts.push(`invariant appended`);
215559
- if (checkFlag) eventParts.push(`check appended`);
215623
+ if (gotchaValues.length > 0) eventParts.push(`gotcha appended`);
215624
+ if (invariantValues.length > 0) eventParts.push(`invariant appended`);
215625
+ if (checkValues.length > 0) eventParts.push(`check appended`);
215560
215626
  if (enforceFlag) eventParts.push(`enforce requested`);
215561
215627
  if (eventParts.length > 0) {
215562
215628
  updates._event_detail = `CONTEXT_UPDATED: ${eventParts.join(", ")}`;
215563
215629
  }
215564
215630
  if (Object.keys(updates).length === 0) {
215565
- console.error("No fields to update. Use --what, --how, --pattern, --decisions, --gotchas, --ownership");
215566
- console.error('PR 3: --gotcha, --invariant, --check, --enforce "<rule-description>"');
215631
+ console.error("No fields to update.");
215632
+ console.error("Scalar: --what, --how, --pattern, --decisions, --gotchas, --ownership");
215633
+ console.error("Append: --gotcha (repeatable), --invariant (repeatable), --check (repeatable)");
215634
+ console.error('Enforce: --enforce "<rule-description>"');
215567
215635
  process.exit(1);
215568
215636
  }
215569
215637
  const currentRemote = getGitRemote();
@@ -215672,7 +215740,12 @@ async function contextCommand(args) {
215672
215740
  }
215673
215741
  const updates = { last_updated: /* @__PURE__ */ new Date() };
215674
215742
  if (fileContent) updates.file_content = fileContent;
215675
- if (docFlag) updates.anchored_doc_path = docFlag;
215743
+ if (docFlag) {
215744
+ updates.anchored_doc_path = docFlag;
215745
+ updates.origin = "doc";
215746
+ updates.status = "draft";
215747
+ updates.kind = "docs-pending";
215748
+ }
215676
215749
  if (noteFlag) updates.decisions = noteFlag;
215677
215750
  if (filesFlag) updates.where_files = filesFlag.split(",").map((f) => f.trim()).filter(Boolean);
215678
215751
  if (patternFlag) updates.pattern = patternFlag;
@@ -215952,6 +216025,39 @@ Run 'driftless help context' for full reference.`);
215952
216025
 
215953
216026
  // src/commands/sync.ts
215954
216027
  init_api_client();
216028
+
216029
+ // src/repo-resolver.ts
216030
+ init_api_client();
216031
+ function notLinkedMessage(remote, workspaceSlug) {
216032
+ return `Repo '${remote.org}/${remote.repo}' is not registered in workspace '${workspaceSlug}'. Run \`driftless init\` to register it.`;
216033
+ }
216034
+ async function resolveRepo() {
216035
+ const remote = getGitRemote();
216036
+ if (!remote) return { ok: false, reason: "no_remote" };
216037
+ let workspaceSlug;
216038
+ try {
216039
+ const me = await api.get("/me");
216040
+ workspaceSlug = me?.slug;
216041
+ } catch {
216042
+ }
216043
+ if (!workspaceSlug) return { ok: false, reason: "no_workspace", remote };
216044
+ let repo;
216045
+ try {
216046
+ const repos = await api.get(`/workspaces/${workspaceSlug}/repos`);
216047
+ repo = repos.find((r) => r.github_org === remote.org && r.github_repo === remote.repo);
216048
+ } catch {
216049
+ }
216050
+ if (!repo) return { ok: false, reason: "not_linked", workspaceSlug, remote };
216051
+ return {
216052
+ ok: true,
216053
+ workspaceSlug,
216054
+ repoId: repo.id,
216055
+ remote,
216056
+ hasScanBaseline: !!repo.scan_summary
216057
+ };
216058
+ }
216059
+
216060
+ // src/commands/sync.ts
215955
216061
  function parseArgs2(args) {
215956
216062
  const flags = {};
215957
216063
  for (let i = 0; i < args.length; i++) {
@@ -215969,19 +216075,6 @@ function parseArgs2(args) {
215969
216075
  function emitJSON3(data) {
215970
216076
  console.log(JSON.stringify(data, null, 2));
215971
216077
  }
215972
- async function resolveWorkspaceSlug2() {
215973
- try {
215974
- const me = await api.get("/me");
215975
- if (me?.slug) return me.slug;
215976
- } catch {
215977
- }
215978
- const remote = getGitRemote();
215979
- if (!remote) {
215980
- console.error("Error: no git remote found.");
215981
- process.exit(1);
215982
- }
215983
- return remote.org;
215984
- }
215985
216078
  async function syncCommand(args) {
215986
216079
  if (!isGitRepo()) {
215987
216080
  console.error("Error: not a git repository.");
@@ -215989,33 +216082,23 @@ async function syncCommand(args) {
215989
216082
  }
215990
216083
  const { flags } = parseArgs2(args);
215991
216084
  const isJSON = !!flags["json"];
215992
- const remote = getGitRemote();
215993
- if (!remote) {
215994
- console.error("Error: no git remote configured.");
215995
- process.exit(1);
215996
- }
215997
- const workspaceSlug = await resolveWorkspaceSlug2();
215998
- let repoId = null;
215999
- try {
216000
- const repos = await api.get(`/workspaces/${workspaceSlug}/repos`);
216001
- const match = repos.find((r) => r.github_org === remote.org && r.github_repo === remote.repo);
216002
- if (match) repoId = match.id;
216003
- } catch {
216004
- }
216005
- if (!repoId) {
216085
+ const resolution = await resolveRepo();
216086
+ if (!resolution.ok) {
216087
+ const msg = resolution.reason === "no_remote" ? "Error: no git remote configured." : resolution.reason === "no_workspace" ? "Error: could not resolve workspace. Run `driftless doctor` to diagnose." : notLinkedMessage(resolution.remote, resolution.workspaceSlug);
216006
216088
  if (!isJSON) {
216007
- console.log(`Repo '${remote.org}/${remote.repo}' not found in workspace.`);
216008
- console.log("Run driftless init to register this repo first.");
216089
+ console.log(`\u26A0 ${msg}`);
216090
+ if (resolution.reason !== "no_remote") console.log(" Run `driftless doctor` to diagnose.");
216009
216091
  } else {
216010
- emitJSON3({ error: "repo_not_found", repo: `${remote.org}/${remote.repo}` });
216092
+ emitJSON3({ error: resolution.reason, message: msg });
216011
216093
  }
216012
- process.exit(0);
216094
+ process.exit(1);
216013
216095
  }
216096
+ const { workspaceSlug, repoId, remote } = resolution;
216014
216097
  const [eventsRes, staleTopics, violations, suggestedTopics] = await Promise.allSettled([
216015
216098
  api.get(`/workspaces/${workspaceSlug}/watchers/events?repo_id=${repoId}&limit=20`),
216016
216099
  api.get(`/workspaces/${workspaceSlug}/watchers?stale=true&repo=${repoId}`),
216017
216100
  api.get(`/workspaces/${workspaceSlug}/violations?repo_id=${repoId}&status=open&limit=20`),
216018
- api.get(`/workspaces/${workspaceSlug}/watchers?suggested=true`)
216101
+ api.get(`/workspaces/${workspaceSlug}/watchers?suggested=true&repo=${repoId}`)
216019
216102
  ]);
216020
216103
  const events = eventsRes.status === "fulfilled" ? eventsRes.value.events ?? [] : [];
216021
216104
  const stale = staleTopics.status === "fulfilled" ? staleTopics.value : [];
@@ -216210,38 +216293,22 @@ async function doctorCommand() {
216210
216293
  } else {
216211
216294
  checks.push({ name: "Workspace", status: "warn", detail: "Skipped (local API or no key)" });
216212
216295
  }
216213
- if (remote) {
216214
- const me = await getMe();
216215
- if (me?.slug) {
216216
- const repos = await getRepos(me.slug);
216217
- const linked = repos?.find((r) => r.github_org === remote.org && r.github_repo === remote.repo);
216218
- if (linked) {
216219
- checks.push({ name: "Repo linked", status: "ok", detail: `${remote.org}/${remote.repo}` });
216220
- } else {
216221
- checks.push({ name: "Repo linked", status: "warn", detail: `Not connected. Run \`driftless init\`` });
216222
- }
216223
- } else {
216224
- checks.push({ name: "Repo linked", status: "warn", detail: "Could not verify (workspace unknown)" });
216225
- }
216296
+ const resolution = await resolveRepo();
216297
+ if (resolution.ok) {
216298
+ checks.push({ name: "Repo linked", status: "ok", detail: `${resolution.remote.org}/${resolution.remote.repo}` });
216299
+ checks.push({
216300
+ name: "Baseline",
216301
+ status: resolution.hasScanBaseline ? "ok" : "warn",
216302
+ detail: resolution.hasScanBaseline ? "Scan data present" : "No scan baseline. Run `driftless init`"
216303
+ });
216304
+ } else if (resolution.reason === "not_linked") {
216305
+ checks.push({ name: "Repo linked", status: "warn", detail: notLinkedMessage(resolution.remote, resolution.workspaceSlug) });
216306
+ checks.push({ name: "Baseline", status: "warn", detail: "Could not verify (repo not registered)" });
216307
+ } else if (resolution.reason === "no_workspace") {
216308
+ checks.push({ name: "Repo linked", status: "warn", detail: "Could not verify (workspace unknown)" });
216309
+ checks.push({ name: "Baseline", status: "warn", detail: "Could not verify (workspace unknown)" });
216226
216310
  } else {
216227
216311
  checks.push({ name: "Repo linked", status: "warn", detail: "Skipped (no git remote)" });
216228
- }
216229
- if (remote) {
216230
- const me = await getMe();
216231
- if (me?.slug) {
216232
- const repos = await getRepos(me.slug);
216233
- const linked = repos?.find((r) => r.github_org === remote.org && r.github_repo === remote.repo);
216234
- if (linked?.scan_summary) {
216235
- checks.push({ name: "Baseline", status: "ok", detail: "Scan data present" });
216236
- } else if (linked) {
216237
- checks.push({ name: "Baseline", status: "warn", detail: "Repo connected but no scan baseline. Run `driftless init`" });
216238
- } else {
216239
- checks.push({ name: "Baseline", status: "warn", detail: "Could not verify" });
216240
- }
216241
- } else {
216242
- checks.push({ name: "Baseline", status: "warn", detail: "Could not verify" });
216243
- }
216244
- } else {
216245
216312
  checks.push({ name: "Baseline", status: "warn", detail: "Skipped (no git remote)" });
216246
216313
  }
216247
216314
  const agentsPath = (0, import_node_path7.resolve)(process.cwd(), "AGENTS.md");
@@ -216264,13 +216331,18 @@ async function doctorCommand() {
216264
216331
  if (ghApp) {
216265
216332
  checks.push({ name: "GitHub App", status: "ok", detail: "Installed and active" });
216266
216333
  } else {
216267
- checks.push({ name: "GitHub App", status: "warn", detail: "Not installed. Install via dashboard Settings \u2192 GitHub" });
216334
+ checks.push({ name: "GitHub App", status: "warn", detail: "Not installed \u2014 driftless.icu/ecosystem \u2192 Settings \u2192 Integrations (if just installed, wait ~60s)" });
216335
+ }
216336
+ } catch (err) {
216337
+ const isNotFound = err?.status === 404 || String(err?.message ?? "").includes("404");
216338
+ if (isNotFound) {
216339
+ checks.push({ name: "GitHub App", status: "warn", detail: "Not installed \u2014 driftless.icu/ecosystem \u2192 Settings \u2192 Integrations" });
216340
+ } else {
216341
+ checks.push({ name: "GitHub App", status: "warn", detail: "Could not verify \u2014 check your connection or API key" });
216268
216342
  }
216269
- } catch {
216270
- checks.push({ name: "GitHub App", status: "warn", detail: "Could not verify" });
216271
216343
  }
216272
216344
  } else {
216273
- checks.push({ name: "GitHub App", status: "warn", detail: "Could not verify" });
216345
+ checks.push({ name: "GitHub App", status: "warn", detail: "Could not verify (workspace unknown)" });
216274
216346
  }
216275
216347
  } else {
216276
216348
  checks.push({ name: "GitHub App", status: "warn", detail: "Skipped (not a git repo)" });
@@ -216302,7 +216374,7 @@ function pad2(s, n) {
216302
216374
  }
216303
216375
 
216304
216376
  // src/index.ts
216305
- var VERSION = "0.1.34";
216377
+ var VERSION = "0.1.36";
216306
216378
  var HELP_TEXT = `Driftless CLI v${VERSION} \u2014 Living repo context for humans and coding agents
216307
216379
 
216308
216380
  Install: npm install -g @driftless-sh/cli
@@ -216319,7 +216391,7 @@ Agent loop:
216319
216391
 
216320
216392
  Setup:
216321
216393
  driftless login --key <api-key> Authenticate
216322
- driftless init Scan repo, bootstrap context, suggest rules
216394
+ driftless init [--src <path>] Scan repo, bootstrap context (add --suggest for auto topics+rules)
216323
216395
  driftless doctor Check environment health
216324
216396
 
216325
216397
  Commands:
@@ -216370,8 +216442,8 @@ Examples:
216370
216442
  driftless sync --json # machine-readable output
216371
216443
 
216372
216444
  driftless context add "b2b-guard" \\
216373
- --what "Guard que protege endpoints B2B" \\
216374
- --how "Verifica org_id en el token" \\
216445
+ --what "Guard protecting B2B endpoints" \\
216446
+ --how "Verifies org_id from the auth token" \\
216375
216447
  --pattern "src/shared/guards/**"
216376
216448
 
216377
216449
  driftless context get sdk
@@ -216393,22 +216465,30 @@ Options:
216393
216465
  Example:
216394
216466
  driftless login --key drift_xxx
216395
216467
  `,
216396
- init: `driftless init
216468
+ init: `driftless init [--src <path>] [--suggest]
216397
216469
 
216398
216470
  Smart initialization \u2014 zero friction:
216399
216471
  1. Detects framework, system type, auth patterns (Pass 1)
216400
216472
  2. AST scan: extracts endpoints, guards, services, modules, relations (Pass 2)
216401
216473
  3. Analyzes code patterns: guard enforcement, multi-tenant, large files, legacy
216402
216474
  4. Uploads baseline + components + relations to Cloud
216403
- 5. Creates specific rules from detected patterns (not generic ones)
216404
- 6. Creates context topics for each detected module with smart gotchas
216405
- 7. Detects existing docs (not auto-synced \u2014 ask your agent to sync them)
216475
+ 5. Detects existing docs (not auto-synced \u2014 ask your agent to sync them)
216476
+ 6. Installs AGENTS.md skill
216477
+
216478
+ With --suggest (optional):
216479
+ Also auto-generates rules and context topics from detected patterns.
216480
+
216481
+ Options:
216482
+ --src <path> Scan root relative to cwd (monorepo support).
216483
+ Git root stays at cwd; scanner starts from <path>.
216484
+ --suggest Auto-generate rules and context topics. Off by default.
216406
216485
 
216407
216486
  Must be run from a git repository root.
216408
216487
 
216409
- Example:
216410
- cd my-nestjs-repo
216488
+ Examples:
216411
216489
  driftless init
216490
+ driftless init --src apps/api/src
216491
+ driftless init --src apps/api/src --suggest
216412
216492
  `,
216413
216493
  scan: `driftless scan [--diff]
216414
216494
 
@@ -216505,25 +216585,53 @@ async function main() {
216505
216585
  const command = args[0];
216506
216586
  switch (command) {
216507
216587
  case "init":
216508
- await initCommand(args.slice(1));
216588
+ if (args[1] === "--help") {
216589
+ showCommandHelp("init");
216590
+ } else {
216591
+ await initCommand(args.slice(1));
216592
+ }
216509
216593
  break;
216510
216594
  case "scan":
216511
- await scanCommand(args.slice(1));
216595
+ if (args[1] === "--help") {
216596
+ showCommandHelp("scan");
216597
+ } else {
216598
+ await scanCommand(args.slice(1));
216599
+ }
216512
216600
  break;
216513
216601
  case "context":
216514
- await contextCommand(args.slice(1));
216602
+ if (args[1] === "--help") {
216603
+ showCommandHelp("context");
216604
+ } else {
216605
+ await contextCommand(args.slice(1));
216606
+ }
216515
216607
  break;
216516
216608
  case "sync":
216517
- await syncCommand(args.slice(1));
216609
+ if (args[1] === "--help") {
216610
+ showCommandHelp("sync");
216611
+ } else {
216612
+ await syncCommand(args.slice(1));
216613
+ }
216518
216614
  break;
216519
216615
  case "install-skill":
216520
- await installSkillCommand();
216616
+ if (args[1] === "--help") {
216617
+ showCommandHelp("install-skill");
216618
+ } else {
216619
+ await installSkillCommand();
216620
+ }
216521
216621
  break;
216522
216622
  case "login":
216523
- loginCommand(args.slice(1));
216623
+ if (args[1] === "--help") {
216624
+ showCommandHelp("login");
216625
+ } else {
216626
+ loginCommand(args.slice(1));
216627
+ }
216524
216628
  break;
216525
216629
  case "doctor":
216526
- await doctorCommand();
216630
+ if (args[1] === "--help") {
216631
+ showCommandHelp("doctor");
216632
+ } else {
216633
+ await doctorCommand();
216634
+ }
216527
216635
  break;
216528
216636
  case "help":
216529
216637
  if (args[1]) {