@driftless-sh/cli 0.1.34 → 0.1.35

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/README.md CHANGED
@@ -12,10 +12,11 @@ npm install -g @driftless-sh/cli
12
12
 
13
13
  ```bash
14
14
  driftless login --key <api-key> # Generate one at driftless.icu → Settings → API keys
15
- driftless init # Scan repo, bootstrap context, suggest rules
16
- driftless session start # See relevant context before editing
15
+ driftless init # Scan repo, upload baseline, install skill
16
+ driftless init --suggest # Also auto-generate rules + context topics
17
+ driftless sync # Pull stale topics, violations, and suggested topics
18
+ driftless context get <slug> # Load context before editing
17
19
  driftless scan --diff # Check changes against rules before pushing
18
- driftless session end # Scan + context report + stale detection
19
20
  driftless install-skill # Write AGENTS.md for Claude Code / Cursor
20
21
  ```
21
22
 
@@ -48,34 +49,38 @@ Auth is stored in `~/.driftless/config.json`. You can also set `DRIFTLESS_API_KE
48
49
 
49
50
  ### `driftless init`
50
51
 
51
- Full repo initialization. Runs a 3-pass scan:
52
+ Smart repo initialization:
52
53
 
53
- 1. **Identity** — detects framework, system type, auth patterns (zero tokens)
54
- 2. **AST extraction** — extracts endpoints, guards, services, modules, DTOs with fields + validations, and their relations (zero tokens)
54
+ 1. **Identity** — detects framework, system type, auth patterns (local, zero tokens)
55
+ 2. **AST extraction** — extracts endpoints, guards, services, modules, relations (local, zero tokens)
55
56
  3. **Upload** — sends baseline + components + relations to Cloud
57
+ 4. **Skill install** — writes `AGENTS.md` and `.driftless/skill.md`
56
58
 
57
59
  ```bash
58
60
  cd my-nestjs-repo
59
61
  driftless init
60
62
  ```
61
63
 
62
- After init, Driftless suggests architectural rules based on detected patterns and creates context topics for each module.
64
+ #### Flags
63
65
 
64
- ---
65
-
66
- ### `driftless session`
67
-
68
- The agent loop — start with context, finish with a report.
66
+ | Flag | Description |
67
+ |------|-------------|
68
+ | `--suggest` | Also auto-generate rules and context topics from detected patterns. Off by default. |
69
+ | `--src <path>` | **Monorepo support** — scan from a subpath while git root stays at cwd. Example: `--src apps/api/src` |
69
70
 
70
71
  ```bash
71
- driftless session start # Show context for local changes
72
- driftless session start --files "src/auth/**" # Show context for specific files
73
- driftless session end # Scan changes, check violations, suggest updates
74
- ```
72
+ # Standard repo
73
+ driftless init
74
+
75
+ # Standard repo with auto-generated topics + rules
76
+ driftless init --suggest
75
77
 
76
- **Before editing:** `session start` tells you which context topics, docs, and gotchas apply to the files you're about to touch.
78
+ # Monorepo: NestJS app lives at apps/api/src
79
+ driftless init --src apps/api/src
77
80
 
78
- **Before pushing:** `session end` runs `scan --diff`, reports violations, and flags stale context that needs updating.
81
+ # Monorepo with auto-generated topics + rules
82
+ driftless init --src apps/api/src --suggest
83
+ ```
79
84
 
80
85
  ---
81
86
 
@@ -153,7 +158,7 @@ driftless context sync auth --doc docs/auth.md --dry-run
153
158
 
154
159
  A topic can track components in two ways:
155
160
 
156
- - **`--pattern`** — glob pattern that resolves dynamically (e.g. `src/sdk/**`)
161
+ - **`--pattern`** — glob pattern that resolves dynamically (e.g. `src/sdk/**`). One glob per topic — commas are not supported.
157
162
  - **`--where`** — explicit file path
158
163
 
159
164
  ```bash
@@ -173,6 +178,17 @@ driftless context add "b2b-guard" \
173
178
  --how "Verifies org_id in the token"
174
179
  ```
175
180
 
181
+ #### Appending multiple gotchas at once
182
+
183
+ `--gotcha` can be passed multiple times in one call — all values are appended:
184
+
185
+ ```bash
186
+ driftless context update auth \
187
+ --gotcha "Reset token on org switch" \
188
+ --gotcha "Test tokens never include production claims" \
189
+ --gotcha "Clerk JWTs expire after 1h"
190
+ ```
191
+
176
192
  #### Fields
177
193
 
178
194
  | Field | Description |
@@ -254,6 +270,11 @@ Show help for all commands or a specific command.
254
270
  driftless help # All commands
255
271
  driftless help context # Context subcommands
256
272
  driftless help scan # Scan details
273
+
274
+ # --help also works on any command without hitting the API
275
+ driftless init --help
276
+ driftless scan --help
277
+ driftless context --help
257
278
  ```
258
279
 
259
280
  ---
@@ -283,14 +304,15 @@ This creates `AGENTS.md` with instructions for Claude Code to use Driftless befo
283
304
  ### Custom Agents
284
305
 
285
306
  ```bash
286
- # Before starting work
287
- driftless session start --files "src/auth/**"
307
+ # Load context for the files you're about to touch
308
+ driftless context get <slug>
309
+ driftless context load --files "src/auth/**"
288
310
 
289
- # Before finishing work
290
- driftless session end
311
+ # Before pushing
312
+ driftless scan --diff
291
313
 
292
314
  # After discovering new context
293
- driftless context add "name" --what "..." --how "..." --where "..."
315
+ driftless context update <slug> --gotcha "..." --decisions "..."
294
316
  ```
295
317
 
296
318
  Use `--json` flag for machine-readable output that agents can parse.
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.35";
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 = {
@@ -214976,46 +214987,53 @@ async function initCommand(args) {
214976
214987
  step("uploading components", "failed (continuing)");
214977
214988
  }
214978
214989
  }
214979
- const patterns = earlyPatterns;
214980
- const smartRules = generateSmartRules(repo.id, patterns);
214981
214990
  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) {
214991
+ if (suggest) {
214992
+ const smartRules = generateSmartRules(repo.id, earlyPatterns);
214993
+ let rulesSkipped = 0;
214994
+ for (const rule of smartRules) {
214995
+ try {
214996
+ const result = await api.post(`/workspaces/${workspaceSlug}/rules`, { ...rule, auto_suggested: true });
214997
+ if (result?.skipped) rulesSkipped++;
214998
+ else rulesCreated++;
214999
+ } catch {
214987
215000
  rulesSkipped++;
214988
- } else {
214989
- rulesCreated++;
214990
215001
  }
214991
- } catch {
214992
- rulesSkipped++;
214993
215002
  }
215003
+ step("creating rules", `${rulesCreated} created \xB7 ${rulesSkipped} skipped \u2713`);
215004
+ } else {
215005
+ step("creating rules", "skipped (use --suggest to auto-generate)");
214994
215006
  }
214995
- step("creating rules", `${rulesCreated} created \xB7 ${rulesSkipped} skipped \u2713`);
214996
- const smartWatchers = generateSmartWatchers(components);
214997
215007
  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++;
215008
+ if (suggest) {
215009
+ const smartWatchers = generateSmartWatchers(components);
215010
+ let watchersSkipped = 0;
215011
+ for (const watcher of smartWatchers) {
215012
+ try {
215013
+ await api.post(`/workspaces/${workspaceSlug}/watchers`, {
215014
+ name: watcher.name,
215015
+ what: watcher.what,
215016
+ how: watcher.how,
215017
+ pattern: watcher.pattern,
215018
+ decisions: watcher.decisions,
215019
+ gotchas: watcher.gotchas,
215020
+ ownership: watcher.ownership,
215021
+ where_repos: [repo.id],
215022
+ created_by: "driftless-init",
215023
+ suggested: true,
215024
+ origin: "auto",
215025
+ status: "draft",
215026
+ kind: "domain-map"
215027
+ });
215028
+ watchersCreated++;
215029
+ } catch {
215030
+ watchersSkipped++;
215031
+ }
215016
215032
  }
215033
+ step("creating context", `${watchersCreated} topics \u2713`);
215034
+ } else {
215035
+ step("creating context", "skipped (use --suggest to auto-generate)");
215017
215036
  }
215018
- step("creating context", `${watchersCreated} watchers \u2713`);
215019
215037
  const existingDocs = detectExistingDocs(cwd);
215020
215038
  step("detecting docs", `${existingDocs.length} found (not auto-synced)`);
215021
215039
  let skillLine = "skipped";
@@ -215029,8 +215047,8 @@ async function initCommand(args) {
215029
215047
  step("installing skill", skillLine);
215030
215048
  console.log("");
215031
215049
  console.log("\u2713 repo context bootstrapped");
215032
- console.log(` \u251C\u2500 watchers ${watchersCreated}`);
215033
- console.log(` \u251C\u2500 rules ${rulesCreated}`);
215050
+ console.log(` \u251C\u2500 topics ${suggest ? watchersCreated : "(use --suggest to generate)"}`);
215051
+ console.log(` \u251C\u2500 rules ${suggest ? rulesCreated : "(use --suggest to generate)"}`);
215034
215052
  console.log(` \u251C\u2500 docs found ${existingDocs.length} (ask your agent to sync them)`);
215035
215053
  console.log(` \u251C\u2500 components ${components.length}`);
215036
215054
  console.log(` \u2514\u2500 relations ${relationCount}`);
@@ -215212,10 +215230,12 @@ function renderSummaryHuman(items) {
215212
215230
  }
215213
215231
  const topicWidth = Math.max(...items.map((i) => i.topic.length), 12) + 2;
215214
215232
  const badgeWidth = Math.max(...items.map((i) => formatBadges(i.badges).length), 8) + 2;
215233
+ const statusWidth = Math.max(...items.map((i) => (i.classification?.status || "").length), 8) + 2;
215215
215234
  for (const item of items) {
215216
215235
  const topic = pad(item.topic, topicWidth);
215217
215236
  const badges = pad(formatBadges(item.badges), badgeWidth);
215218
- console.log(`${topic}${badges}${item.summary}`);
215237
+ const status = pad(item.classification?.status || "", statusWidth);
215238
+ console.log(`${topic}${status}${badges}${item.summary}`);
215219
215239
  }
215220
215240
  console.log(`
215221
215241
  ${items.length} topic${items.length === 1 ? "" : "s"}.`);
@@ -215224,11 +215244,19 @@ function renderContextHuman(ctx) {
215224
215244
  console.log(`\u258C ${ctx.topic}`);
215225
215245
  console.log(` ${ctx.summary}`);
215226
215246
  if (ctx.version !== void 0) console.log(` version: ${ctx.version}`);
215247
+ if (ctx.classification) {
215248
+ const { origin, status, kind } = ctx.classification;
215249
+ console.log(` origin: ${origin} status: ${status} kind: ${kind}`);
215250
+ }
215227
215251
  if (ctx.stale.is_stale) {
215228
215252
  console.log(`
215229
215253
  \u26A0 STALE: ${ctx.stale.reason || "Context may be outdated"}`);
215230
215254
  console.log(` \u2192 driftless context update ${ctx.topic} --what "..." --how "..."`);
215231
215255
  }
215256
+ if (ctx.metadata.created_by === "driftless-init") {
215257
+ console.log(`
215258
+ \u24D8 auto-suggested by init \u2014 confirm with \`driftless context update ${ctx.topic} --what "..."\``);
215259
+ }
215232
215260
  console.log("");
215233
215261
  if (ctx.description.what) console.log(` what: ${ctx.description.what}`);
215234
215262
  if (ctx.description.how) console.log(` how: ${ctx.description.how}`);
@@ -215357,9 +215385,23 @@ async function contextCommand(args) {
215357
215385
  if (flags["docs"]) query.push("docs=true");
215358
215386
  if (flags["auto"]) query.push("auto=true");
215359
215387
  if (flags["manual"]) query.push("manual=true");
215360
- if (flags["repo"]) query.push(`repo=${encodeURIComponent(flags["repo"])}`);
215361
215388
  if (flags["suggested"]) query.push("suggested=true");
215362
215389
  else query.push("suggested=false");
215390
+ if (flags["status"]) query.push(`status=${encodeURIComponent(flags["status"])}`);
215391
+ if (flags["kind"]) query.push(`kind=${encodeURIComponent(flags["kind"])}`);
215392
+ if (flags["repo"]) {
215393
+ query.push(`repo=${encodeURIComponent(flags["repo"])}`);
215394
+ } else if (flags["auto"]) {
215395
+ const currentRemote = getGitRemote();
215396
+ if (currentRemote) {
215397
+ try {
215398
+ const repos = await api.get(`/workspaces/${workspaceSlug}/repos`);
215399
+ const match = repos.find((r) => r.github_org === currentRemote.org && r.github_repo === currentRemote.repo);
215400
+ if (match) query.push(`repo=${encodeURIComponent(match.id)}`);
215401
+ } catch {
215402
+ }
215403
+ }
215404
+ }
215363
215405
  const qs = query.length > 0 ? `?${query.join("&")}` : "";
215364
215406
  try {
215365
215407
  const summaries = await api.get(`/workspaces/${workspaceSlug}/watchers${qs}`);
@@ -215495,6 +215537,9 @@ async function contextCommand(args) {
215495
215537
  }
215496
215538
  const pattern = flags["pattern"];
215497
215539
  if (pattern && !isJSON) {
215540
+ if (pattern.includes(",")) {
215541
+ console.error(`Warning: pattern "${pattern}" contains a comma. Only one glob is accepted per topic. Create separate topics or remove the comma.`);
215542
+ }
215498
215543
  const localCount = countLocalFilesMatching(pattern);
215499
215544
  if (localCount === 0) {
215500
215545
  console.error(`Warning: pattern "${pattern}" matches 0 files locally. Topic created anyway.`);
@@ -215510,7 +215555,9 @@ async function contextCommand(args) {
215510
215555
  decisions: flags["decisions"],
215511
215556
  gotchas: flags["gotchas"],
215512
215557
  ownership: flags["ownership"],
215513
- file_content: fileContent
215558
+ file_content: fileContent,
215559
+ status: flags["status"],
215560
+ kind: flags["kind"]
215514
215561
  });
215515
215562
  analyticsEvent("context_created", workspaceSlug, { topic: name, has_pattern: !!pattern, has_what: !!flags["what"] });
215516
215563
  if (isJSON) {
@@ -215541,12 +215588,21 @@ async function contextCommand(args) {
215541
215588
  if (flags["decisions"]) updates.decisions = flags["decisions"];
215542
215589
  if (flags["gotchas"]) updates.gotchas = flags["gotchas"];
215543
215590
  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;
215591
+ if (flags["status"]) updates.status = flags["status"];
215592
+ const gotchaValues = [];
215593
+ const invariantValues = [];
215594
+ const checkValues = [];
215595
+ for (let i = 0; i < args.length - 1; i++) {
215596
+ const a = args[i];
215597
+ const b = args[i + 1];
215598
+ if (!b || b.startsWith("--")) continue;
215599
+ if (a === "--gotcha" || a === "--gotchas_append") gotchaValues.push(b);
215600
+ if (a === "--invariant") invariantValues.push(b);
215601
+ if (a === "--check") checkValues.push(b);
215602
+ }
215603
+ if (gotchaValues.length > 0) updates._append_gotchas = gotchaValues.join("\n");
215604
+ if (invariantValues.length > 0) updates._append_invariants = invariantValues.join("\n");
215605
+ if (checkValues.length > 0) updates._append_required_checks = checkValues.join("\n");
215550
215606
  const enforceFlag = flags["enforce"];
215551
215607
  if (enforceFlag && typeof enforceFlag === "string") {
215552
215608
  updates._enforce_rule = enforceFlag;
@@ -215554,16 +215610,18 @@ async function contextCommand(args) {
215554
215610
  updates._enforce_rule = true;
215555
215611
  }
215556
215612
  const eventParts = [];
215557
- if (gotchaFlag) eventParts.push(`gotcha appended`);
215558
- if (invariantFlag) eventParts.push(`invariant appended`);
215559
- if (checkFlag) eventParts.push(`check appended`);
215613
+ if (gotchaValues.length > 0) eventParts.push(`gotcha appended`);
215614
+ if (invariantValues.length > 0) eventParts.push(`invariant appended`);
215615
+ if (checkValues.length > 0) eventParts.push(`check appended`);
215560
215616
  if (enforceFlag) eventParts.push(`enforce requested`);
215561
215617
  if (eventParts.length > 0) {
215562
215618
  updates._event_detail = `CONTEXT_UPDATED: ${eventParts.join(", ")}`;
215563
215619
  }
215564
215620
  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>"');
215621
+ console.error("No fields to update.");
215622
+ console.error("Scalar: --what, --how, --pattern, --decisions, --gotchas, --ownership");
215623
+ console.error("Append: --gotcha (repeatable), --invariant (repeatable), --check (repeatable)");
215624
+ console.error('Enforce: --enforce "<rule-description>"');
215567
215625
  process.exit(1);
215568
215626
  }
215569
215627
  const currentRemote = getGitRemote();
@@ -215672,7 +215730,12 @@ async function contextCommand(args) {
215672
215730
  }
215673
215731
  const updates = { last_updated: /* @__PURE__ */ new Date() };
215674
215732
  if (fileContent) updates.file_content = fileContent;
215675
- if (docFlag) updates.anchored_doc_path = docFlag;
215733
+ if (docFlag) {
215734
+ updates.anchored_doc_path = docFlag;
215735
+ updates.origin = "doc";
215736
+ updates.status = "draft";
215737
+ updates.kind = "docs-pending";
215738
+ }
215676
215739
  if (noteFlag) updates.decisions = noteFlag;
215677
215740
  if (filesFlag) updates.where_files = filesFlag.split(",").map((f) => f.trim()).filter(Boolean);
215678
215741
  if (patternFlag) updates.pattern = patternFlag;
@@ -216004,18 +216067,18 @@ async function syncCommand(args) {
216004
216067
  }
216005
216068
  if (!repoId) {
216006
216069
  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.");
216070
+ console.log(`\u26A0 Repo '${remote.org}/${remote.repo}' not found in workspace '${workspaceSlug}'.`);
216071
+ console.log(" Run `driftless init` to register this repo, or `driftless doctor` to diagnose.");
216009
216072
  } else {
216010
- emitJSON3({ error: "repo_not_found", repo: `${remote.org}/${remote.repo}` });
216073
+ emitJSON3({ error: "repo_not_found", repo: `${remote.org}/${remote.repo}`, workspace: workspaceSlug });
216011
216074
  }
216012
- process.exit(0);
216075
+ process.exit(1);
216013
216076
  }
216014
216077
  const [eventsRes, staleTopics, violations, suggestedTopics] = await Promise.allSettled([
216015
216078
  api.get(`/workspaces/${workspaceSlug}/watchers/events?repo_id=${repoId}&limit=20`),
216016
216079
  api.get(`/workspaces/${workspaceSlug}/watchers?stale=true&repo=${repoId}`),
216017
216080
  api.get(`/workspaces/${workspaceSlug}/violations?repo_id=${repoId}&status=open&limit=20`),
216018
- api.get(`/workspaces/${workspaceSlug}/watchers?suggested=true`)
216081
+ api.get(`/workspaces/${workspaceSlug}/watchers?suggested=true&repo=${repoId}`)
216019
216082
  ]);
216020
216083
  const events = eventsRes.status === "fulfilled" ? eventsRes.value.events ?? [] : [];
216021
216084
  const stale = staleTopics.status === "fulfilled" ? staleTopics.value : [];
@@ -216264,13 +216327,18 @@ async function doctorCommand() {
216264
216327
  if (ghApp) {
216265
216328
  checks.push({ name: "GitHub App", status: "ok", detail: "Installed and active" });
216266
216329
  } else {
216267
- checks.push({ name: "GitHub App", status: "warn", detail: "Not installed. Install via dashboard Settings \u2192 GitHub" });
216330
+ checks.push({ name: "GitHub App", status: "warn", detail: "Not installed \u2014 driftless.icu/ecosystem \u2192 Settings \u2192 Integrations (if just installed, wait ~60s)" });
216331
+ }
216332
+ } catch (err) {
216333
+ const isNotFound = err?.status === 404 || String(err?.message ?? "").includes("404");
216334
+ if (isNotFound) {
216335
+ checks.push({ name: "GitHub App", status: "warn", detail: "Not installed \u2014 driftless.icu/ecosystem \u2192 Settings \u2192 Integrations" });
216336
+ } else {
216337
+ checks.push({ name: "GitHub App", status: "warn", detail: "Could not verify \u2014 check your connection or API key" });
216268
216338
  }
216269
- } catch {
216270
- checks.push({ name: "GitHub App", status: "warn", detail: "Could not verify" });
216271
216339
  }
216272
216340
  } else {
216273
- checks.push({ name: "GitHub App", status: "warn", detail: "Could not verify" });
216341
+ checks.push({ name: "GitHub App", status: "warn", detail: "Could not verify (workspace unknown)" });
216274
216342
  }
216275
216343
  } else {
216276
216344
  checks.push({ name: "GitHub App", status: "warn", detail: "Skipped (not a git repo)" });
@@ -216302,7 +216370,7 @@ function pad2(s, n) {
216302
216370
  }
216303
216371
 
216304
216372
  // src/index.ts
216305
- var VERSION = "0.1.34";
216373
+ var VERSION = "0.1.35";
216306
216374
  var HELP_TEXT = `Driftless CLI v${VERSION} \u2014 Living repo context for humans and coding agents
216307
216375
 
216308
216376
  Install: npm install -g @driftless-sh/cli
@@ -216319,7 +216387,7 @@ Agent loop:
216319
216387
 
216320
216388
  Setup:
216321
216389
  driftless login --key <api-key> Authenticate
216322
- driftless init Scan repo, bootstrap context, suggest rules
216390
+ driftless init [--src <path>] Scan repo, bootstrap context (add --suggest for auto topics+rules)
216323
216391
  driftless doctor Check environment health
216324
216392
 
216325
216393
  Commands:
@@ -216370,8 +216438,8 @@ Examples:
216370
216438
  driftless sync --json # machine-readable output
216371
216439
 
216372
216440
  driftless context add "b2b-guard" \\
216373
- --what "Guard que protege endpoints B2B" \\
216374
- --how "Verifica org_id en el token" \\
216441
+ --what "Guard protecting B2B endpoints" \\
216442
+ --how "Verifies org_id from the auth token" \\
216375
216443
  --pattern "src/shared/guards/**"
216376
216444
 
216377
216445
  driftless context get sdk
@@ -216393,22 +216461,30 @@ Options:
216393
216461
  Example:
216394
216462
  driftless login --key drift_xxx
216395
216463
  `,
216396
- init: `driftless init
216464
+ init: `driftless init [--src <path>] [--suggest]
216397
216465
 
216398
216466
  Smart initialization \u2014 zero friction:
216399
216467
  1. Detects framework, system type, auth patterns (Pass 1)
216400
216468
  2. AST scan: extracts endpoints, guards, services, modules, relations (Pass 2)
216401
216469
  3. Analyzes code patterns: guard enforcement, multi-tenant, large files, legacy
216402
216470
  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)
216471
+ 5. Detects existing docs (not auto-synced \u2014 ask your agent to sync them)
216472
+ 6. Installs AGENTS.md skill
216473
+
216474
+ With --suggest (optional):
216475
+ Also auto-generates rules and context topics from detected patterns.
216476
+
216477
+ Options:
216478
+ --src <path> Scan root relative to cwd (monorepo support).
216479
+ Git root stays at cwd; scanner starts from <path>.
216480
+ --suggest Auto-generate rules and context topics. Off by default.
216406
216481
 
216407
216482
  Must be run from a git repository root.
216408
216483
 
216409
- Example:
216410
- cd my-nestjs-repo
216484
+ Examples:
216411
216485
  driftless init
216486
+ driftless init --src apps/api/src
216487
+ driftless init --src apps/api/src --suggest
216412
216488
  `,
216413
216489
  scan: `driftless scan [--diff]
216414
216490
 
@@ -216505,25 +216581,53 @@ async function main() {
216505
216581
  const command = args[0];
216506
216582
  switch (command) {
216507
216583
  case "init":
216508
- await initCommand(args.slice(1));
216584
+ if (args[1] === "--help") {
216585
+ showCommandHelp("init");
216586
+ } else {
216587
+ await initCommand(args.slice(1));
216588
+ }
216509
216589
  break;
216510
216590
  case "scan":
216511
- await scanCommand(args.slice(1));
216591
+ if (args[1] === "--help") {
216592
+ showCommandHelp("scan");
216593
+ } else {
216594
+ await scanCommand(args.slice(1));
216595
+ }
216512
216596
  break;
216513
216597
  case "context":
216514
- await contextCommand(args.slice(1));
216598
+ if (args[1] === "--help") {
216599
+ showCommandHelp("context");
216600
+ } else {
216601
+ await contextCommand(args.slice(1));
216602
+ }
216515
216603
  break;
216516
216604
  case "sync":
216517
- await syncCommand(args.slice(1));
216605
+ if (args[1] === "--help") {
216606
+ showCommandHelp("sync");
216607
+ } else {
216608
+ await syncCommand(args.slice(1));
216609
+ }
216518
216610
  break;
216519
216611
  case "install-skill":
216520
- await installSkillCommand();
216612
+ if (args[1] === "--help") {
216613
+ showCommandHelp("install-skill");
216614
+ } else {
216615
+ await installSkillCommand();
216616
+ }
216521
216617
  break;
216522
216618
  case "login":
216523
- loginCommand(args.slice(1));
216619
+ if (args[1] === "--help") {
216620
+ showCommandHelp("login");
216621
+ } else {
216622
+ loginCommand(args.slice(1));
216623
+ }
216524
216624
  break;
216525
216625
  case "doctor":
216526
- await doctorCommand();
216626
+ if (args[1] === "--help") {
216627
+ showCommandHelp("doctor");
216628
+ } else {
216629
+ await doctorCommand();
216630
+ }
216527
216631
  break;
216528
216632
  case "help":
216529
216633
  if (args[1]) {