@driftless-sh/cli 0.1.32 → 0.1.34

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
@@ -33,98 +33,133 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
33
33
  mod
34
34
  ));
35
35
 
36
- // src/git.ts
37
- var git_exports = {};
38
- __export(git_exports, {
39
- getAuthorName: () => getAuthorName,
40
- getChangedFilesFromDiff: () => getChangedFilesFromDiff,
41
- getChangedFilesList: () => getChangedFilesList,
42
- getGitRemote: () => getGitRemote,
43
- getLastCommitHash: () => getLastCommitHash,
44
- getStagedDiff: () => getStagedDiff,
45
- getUncommittedDiff: () => getUncommittedDiff,
46
- isGitRepo: () => isGitRepo
36
+ // src/api-client.ts
37
+ var api_client_exports = {};
38
+ __export(api_client_exports, {
39
+ api: () => api,
40
+ formatError: () => formatError,
41
+ getApiKey: () => getApiKey,
42
+ getApiUrl: () => getApiUrl
47
43
  });
48
- function getGitRemote() {
44
+ function loadApiKey() {
45
+ const envKey = process.env["DRIFTLESS_API_KEY"];
46
+ if (envKey) return envKey;
49
47
  try {
50
- const url = (0, import_node_child_process.execSync)("git config --get remote.origin.url", {
51
- encoding: "utf8",
52
- cwd: process.cwd()
53
- }).trim();
54
- const match = url.match(/[:/]([^/]+)\/([^/]+?)(?:\.git)?$/);
55
- if (match) {
56
- return { org: match[1], repo: match[2] };
48
+ if ((0, import_node_fs.existsSync)(CONFIG_PATH)) {
49
+ const config = JSON.parse((0, import_node_fs.readFileSync)(CONFIG_PATH, "utf8"));
50
+ return config.api_key || null;
57
51
  }
58
- return null;
59
52
  } catch {
60
- return null;
61
53
  }
54
+ return null;
62
55
  }
63
- function isGitRepo() {
64
- return (0, import_node_fs2.existsSync)((0, import_node_path2.resolve)(process.cwd(), ".git"));
65
- }
66
- function getUncommittedDiff() {
67
- try {
68
- return (0, import_node_child_process.execSync)("git diff", { encoding: "utf8", cwd: process.cwd() }).trim();
69
- } catch {
70
- return "";
56
+ function getBaseUrl() {
57
+ const envUrl = process.env["DRIFTLESS_API_URL"];
58
+ if (envUrl) {
59
+ return envUrl.endsWith("/api/v1") ? envUrl : `${envUrl}/api/v1`;
71
60
  }
72
- }
73
- function getStagedDiff() {
74
61
  try {
75
- return (0, import_node_child_process.execSync)("git diff --staged", { encoding: "utf8", cwd: process.cwd() }).trim();
62
+ if ((0, import_node_fs.existsSync)(CONFIG_PATH)) {
63
+ const config = JSON.parse((0, import_node_fs.readFileSync)(CONFIG_PATH, "utf8"));
64
+ return config.api_url || DEFAULT_URL;
65
+ }
76
66
  } catch {
77
- return "";
78
67
  }
68
+ return DEFAULT_URL;
79
69
  }
80
- function getLastCommitHash() {
81
- try {
82
- return (0, import_node_child_process.execSync)("git rev-parse HEAD", { encoding: "utf8", cwd: process.cwd() }).trim();
83
- } catch {
84
- return "unknown";
70
+ function parseError(e) {
71
+ const msg = e.message;
72
+ const jsonMatch = msg.match(/\{[\s\S]*\}/);
73
+ if (jsonMatch) {
74
+ try {
75
+ const parsed = JSON.parse(jsonMatch[0]);
76
+ const parts = [];
77
+ if (parsed.message) parts.push(parsed.message);
78
+ if (parsed.request_id) parts.push(`request_id: ${parsed.request_id}`);
79
+ if (parsed.retryable !== void 0) parts.push(`retryable: ${parsed.retryable ? "yes" : "no"}`);
80
+ if (parsed.endpoint) parts.push(`endpoint: ${parsed.endpoint}`);
81
+ if (parts.length > 0) return parts.join(" | ");
82
+ } catch {
83
+ }
84
+ }
85
+ const statusMatch = msg.match(/HTTP (\d+):/);
86
+ if (statusMatch) {
87
+ const code = parseInt(statusMatch[1], 10);
88
+ if (code === 500) return "Internal server error \u2014 the API encountered an unexpected issue";
89
+ if (code === 401) return "Authentication failed \u2014 check your API key";
90
+ if (code === 403) return "Access denied \u2014 your API key lacks permission";
91
+ if (code === 404) return "Not found \u2014 the resource does not exist";
92
+ if (code === 429) return "Rate limited \u2014 too many requests, try again later";
93
+ return `Server error (HTTP ${code})`;
85
94
  }
95
+ return msg;
86
96
  }
87
- function getChangedFilesFromDiff(diff) {
88
- const text = diff ?? getUncommittedDiff();
89
- if (!text) return [];
90
- const files = /* @__PURE__ */ new Set();
91
- for (const line of text.split("\n")) {
92
- if (line.startsWith("+++ b/")) {
93
- files.add(line.slice(6));
94
- } else if (line.startsWith("--- a/")) {
95
- } else if (line.startsWith("diff --git ")) {
96
- const match = line.match(/diff --git a\/(.+?) b\/(.+)$/);
97
- if (match) files.add(match[2]);
97
+ function request(method, path, body) {
98
+ return new Promise((resolve8, reject) => {
99
+ const baseUrl = getBaseUrl();
100
+ const fullUrl = `${baseUrl}${path}`;
101
+ const url = new URL(fullUrl);
102
+ const isHttps = url.protocol === "https:";
103
+ const fn = isHttps ? import_node_https.request : import_node_http.request;
104
+ const headers = {
105
+ "Content-Type": "application/json",
106
+ Accept: "application/json"
107
+ };
108
+ const apiKey = loadApiKey();
109
+ if (apiKey) {
110
+ headers["X-API-Key"] = apiKey;
98
111
  }
99
- }
100
- return [...files];
112
+ const req = fn(
113
+ fullUrl,
114
+ { method, headers },
115
+ (res) => {
116
+ let data = "";
117
+ res.on("data", (chunk) => data += chunk.toString());
118
+ res.on("end", () => {
119
+ if (res.statusCode && res.statusCode >= 400) {
120
+ reject(new Error(`HTTP ${res.statusCode}: ${data}`));
121
+ return;
122
+ }
123
+ try {
124
+ resolve8(JSON.parse(data));
125
+ } catch {
126
+ resolve8(data);
127
+ }
128
+ });
129
+ }
130
+ );
131
+ req.on("error", (e) => reject(new Error(`Connection failed: ${e.message}`)));
132
+ if (body) req.write(JSON.stringify(body));
133
+ req.end();
134
+ });
101
135
  }
102
- function getChangedFilesList() {
103
- try {
104
- const unstaged = (0, import_node_child_process.execSync)("git diff --name-only HEAD", { encoding: "utf8", cwd: process.cwd() }).trim();
105
- const staged = (0, import_node_child_process.execSync)("git diff --staged --name-only", { encoding: "utf8", cwd: process.cwd() }).trim();
106
- const files = /* @__PURE__ */ new Set();
107
- if (unstaged) unstaged.split("\n").forEach((f) => files.add(f));
108
- if (staged) staged.split("\n").forEach((f) => files.add(f));
109
- return [...files].filter(Boolean);
110
- } catch {
111
- return [];
112
- }
136
+ function getApiUrl() {
137
+ return getBaseUrl();
113
138
  }
114
- function getAuthorName() {
115
- try {
116
- return (0, import_node_child_process.execSync)("git config user.name", { encoding: "utf8", cwd: process.cwd() }).trim();
117
- } catch {
118
- return "unknown";
119
- }
139
+ function getApiKey() {
140
+ return loadApiKey();
141
+ }
142
+ function formatError(e) {
143
+ return parseError(e);
120
144
  }
121
- var import_node_child_process, import_node_fs2, import_node_path2;
122
- var init_git = __esm({
123
- "src/git.ts"() {
145
+ var import_node_http, import_node_https, import_node_fs, import_node_path, import_node_os, CONFIG_PATH, DEFAULT_URL, api;
146
+ var init_api_client = __esm({
147
+ "src/api-client.ts"() {
124
148
  "use strict";
125
- import_node_child_process = require("node:child_process");
126
- import_node_fs2 = require("node:fs");
127
- import_node_path2 = require("node:path");
149
+ import_node_http = require("node:http");
150
+ import_node_https = require("node:https");
151
+ import_node_fs = require("node:fs");
152
+ import_node_path = require("node:path");
153
+ import_node_os = require("node:os");
154
+ CONFIG_PATH = (0, import_node_path.resolve)((0, import_node_os.homedir)(), ".driftless", "config.json");
155
+ DEFAULT_URL = "http://localhost:3000/api/v1";
156
+ api = {
157
+ get: (path) => request("GET", path),
158
+ post: (path, body) => request("POST", path, body),
159
+ put: (path, body) => request("PUT", path, body),
160
+ patch: (path, body) => request("PATCH", path, body),
161
+ delete: (path) => request("DELETE", path)
162
+ };
128
163
  }
129
164
  });
130
165
 
@@ -126242,7 +126277,7 @@ ${lanes.join("\n")}
126242
126277
  }
126243
126278
  }
126244
126279
  function createImportCallExpressionAMD(arg, containsLexicalThis) {
126245
- const resolve9 = factory2.createUniqueName("resolve");
126280
+ const resolve8 = factory2.createUniqueName("resolve");
126246
126281
  const reject = factory2.createUniqueName("reject");
126247
126282
  const parameters = [
126248
126283
  factory2.createParameterDeclaration(
@@ -126251,7 +126286,7 @@ ${lanes.join("\n")}
126251
126286
  /*dotDotDotToken*/
126252
126287
  void 0,
126253
126288
  /*name*/
126254
- resolve9
126289
+ resolve8
126255
126290
  ),
126256
126291
  factory2.createParameterDeclaration(
126257
126292
  /*modifiers*/
@@ -126268,7 +126303,7 @@ ${lanes.join("\n")}
126268
126303
  factory2.createIdentifier("require"),
126269
126304
  /*typeArguments*/
126270
126305
  void 0,
126271
- [factory2.createArrayLiteralExpression([arg || factory2.createOmittedExpression()]), resolve9, reject]
126306
+ [factory2.createArrayLiteralExpression([arg || factory2.createOmittedExpression()]), resolve8, reject]
126272
126307
  )
126273
126308
  )
126274
126309
  ]);
@@ -212986,8 +213021,8 @@ Additional information: BADCLIENT: Bad error code, ${badCode} not found in range
212986
213021
  installPackage(options) {
212987
213022
  this.packageInstallId++;
212988
213023
  const request2 = { kind: "installPackage", ...options, id: this.packageInstallId };
212989
- const promise = new Promise((resolve9, reject) => {
212990
- (this.packageInstalledPromise ?? (this.packageInstalledPromise = /* @__PURE__ */ new Map())).set(this.packageInstallId, { resolve: resolve9, reject });
213024
+ const promise = new Promise((resolve8, reject) => {
213025
+ (this.packageInstalledPromise ?? (this.packageInstalledPromise = /* @__PURE__ */ new Map())).set(this.packageInstallId, { resolve: resolve8, reject });
212991
213026
  });
212992
213027
  this.installer.send(request2);
212993
213028
  return promise;
@@ -214311,128 +214346,112 @@ var require_dist = __commonJS({
214311
214346
  }
214312
214347
  });
214313
214348
 
214314
- // src/api-client.ts
214315
- var import_node_http = require("node:http");
214316
- var import_node_https = require("node:https");
214317
- var import_node_fs = require("node:fs");
214318
- var import_node_path = require("node:path");
214319
- var import_node_os = require("node:os");
214320
- var CONFIG_PATH = (0, import_node_path.resolve)((0, import_node_os.homedir)(), ".driftless", "config.json");
214321
- function loadApiKey() {
214322
- const envKey = process.env["DRIFTLESS_API_KEY"];
214323
- if (envKey) return envKey;
214349
+ // src/analytics.ts
214350
+ var analytics_exports = {};
214351
+ __export(analytics_exports, {
214352
+ analyticsEvent: () => analyticsEvent
214353
+ });
214354
+ function analyticsEvent(event, distinctId, props = {}) {
214355
+ if (!KEY) return;
214356
+ fetch(`${HOST}/capture/`, {
214357
+ method: "POST",
214358
+ headers: { "Content-Type": "application/json" },
214359
+ body: JSON.stringify({
214360
+ api_key: KEY,
214361
+ event,
214362
+ distinct_id: `workspace:${distinctId}`,
214363
+ properties: { ...props, source: "cli", $lib: "driftless-cli" }
214364
+ })
214365
+ }).catch(() => void 0);
214366
+ }
214367
+ var KEY, HOST;
214368
+ var init_analytics = __esm({
214369
+ "src/analytics.ts"() {
214370
+ "use strict";
214371
+ KEY = "";
214372
+ HOST = "https://us.i.posthog.com";
214373
+ }
214374
+ });
214375
+
214376
+ // src/commands/init.ts
214377
+ init_api_client();
214378
+
214379
+ // src/git.ts
214380
+ var import_node_child_process = require("node:child_process");
214381
+ var import_node_fs2 = require("node:fs");
214382
+ var import_node_path2 = require("node:path");
214383
+ function isGitInstalled() {
214324
214384
  try {
214325
- if ((0, import_node_fs.existsSync)(CONFIG_PATH)) {
214326
- const config = JSON.parse((0, import_node_fs.readFileSync)(CONFIG_PATH, "utf8"));
214327
- return config.api_key || null;
214328
- }
214385
+ (0, import_node_child_process.execSync)("git --version", { stdio: "ignore" });
214386
+ return true;
214329
214387
  } catch {
214388
+ return false;
214330
214389
  }
214331
- return null;
214332
214390
  }
214333
- var DEFAULT_URL = "http://localhost:3000/api/v1";
214334
- function getBaseUrl() {
214335
- const envUrl = process.env["DRIFTLESS_API_URL"];
214336
- if (envUrl) {
214337
- return envUrl.endsWith("/api/v1") ? envUrl : `${envUrl}/api/v1`;
214338
- }
214391
+ function getGitRemote() {
214339
214392
  try {
214340
- if ((0, import_node_fs.existsSync)(CONFIG_PATH)) {
214341
- const config = JSON.parse((0, import_node_fs.readFileSync)(CONFIG_PATH, "utf8"));
214342
- return config.api_url || DEFAULT_URL;
214393
+ const url = (0, import_node_child_process.execSync)("git config --get remote.origin.url", {
214394
+ encoding: "utf8",
214395
+ cwd: process.cwd()
214396
+ }).trim();
214397
+ const match = url.match(/[:/]([^/]+)\/([^/]+?)(?:\.git)?$/);
214398
+ if (match) {
214399
+ return { org: match[1], repo: match[2] };
214343
214400
  }
214401
+ return null;
214344
214402
  } catch {
214403
+ return null;
214345
214404
  }
214346
- return DEFAULT_URL;
214347
214405
  }
214348
- function parseError(e) {
214349
- const msg = e.message;
214350
- const jsonMatch = msg.match(/\{[\s\S]*\}/);
214351
- if (jsonMatch) {
214352
- try {
214353
- const parsed = JSON.parse(jsonMatch[0]);
214354
- const parts = [];
214355
- if (parsed.message) parts.push(parsed.message);
214356
- if (parsed.request_id) parts.push(`request_id: ${parsed.request_id}`);
214357
- if (parsed.retryable !== void 0) parts.push(`retryable: ${parsed.retryable ? "yes" : "no"}`);
214358
- if (parsed.endpoint) parts.push(`endpoint: ${parsed.endpoint}`);
214359
- if (parts.length > 0) return parts.join(" | ");
214360
- } catch {
214361
- }
214362
- }
214363
- const statusMatch = msg.match(/HTTP (\d+):/);
214364
- if (statusMatch) {
214365
- const code = parseInt(statusMatch[1], 10);
214366
- if (code === 500) return "Internal server error \u2014 the API encountered an unexpected issue";
214367
- if (code === 401) return "Authentication failed \u2014 check your API key";
214368
- if (code === 403) return "Access denied \u2014 your API key lacks permission";
214369
- if (code === 404) return "Not found \u2014 the resource does not exist";
214370
- if (code === 429) return "Rate limited \u2014 too many requests, try again later";
214371
- return `Server error (HTTP ${code})`;
214406
+ function isGitRepo() {
214407
+ return (0, import_node_fs2.existsSync)((0, import_node_path2.resolve)(process.cwd(), ".git"));
214408
+ }
214409
+ function getUncommittedDiff() {
214410
+ try {
214411
+ return (0, import_node_child_process.execSync)("git diff", { encoding: "utf8", cwd: process.cwd() }).trim();
214412
+ } catch {
214413
+ return "";
214372
214414
  }
214373
- return msg;
214374
214415
  }
214375
- function request(method, path, body) {
214376
- return new Promise((resolve9, reject) => {
214377
- const baseUrl = getBaseUrl();
214378
- const fullUrl = `${baseUrl}${path}`;
214379
- const url = new URL(fullUrl);
214380
- const isHttps = url.protocol === "https:";
214381
- const fn = isHttps ? import_node_https.request : import_node_http.request;
214382
- const headers = {
214383
- "Content-Type": "application/json",
214384
- Accept: "application/json"
214385
- };
214386
- const apiKey = loadApiKey();
214387
- if (apiKey) {
214388
- headers["X-API-Key"] = apiKey;
214389
- }
214390
- const req = fn(
214391
- fullUrl,
214392
- { method, headers },
214393
- (res) => {
214394
- let data = "";
214395
- res.on("data", (chunk) => data += chunk.toString());
214396
- res.on("end", () => {
214397
- if (res.statusCode && res.statusCode >= 400) {
214398
- reject(new Error(`HTTP ${res.statusCode}: ${data}`));
214399
- return;
214400
- }
214401
- try {
214402
- resolve9(JSON.parse(data));
214403
- } catch {
214404
- resolve9(data);
214405
- }
214406
- });
214407
- }
214408
- );
214409
- req.on("error", (e) => reject(new Error(`Connection failed: ${e.message}`)));
214410
- if (body) req.write(JSON.stringify(body));
214411
- req.end();
214412
- });
214416
+ function getStagedDiff() {
214417
+ try {
214418
+ return (0, import_node_child_process.execSync)("git diff --staged", { encoding: "utf8", cwd: process.cwd() }).trim();
214419
+ } catch {
214420
+ return "";
214421
+ }
214413
214422
  }
214414
- var api = {
214415
- get: (path) => request("GET", path),
214416
- post: (path, body) => request("POST", path, body),
214417
- put: (path, body) => request("PUT", path, body),
214418
- patch: (path, body) => request("PATCH", path, body),
214419
- delete: (path) => request("DELETE", path)
214420
- };
214421
- function getApiUrl() {
214422
- return getBaseUrl();
214423
+ function getLastCommitHash() {
214424
+ try {
214425
+ return (0, import_node_child_process.execSync)("git rev-parse HEAD", { encoding: "utf8", cwd: process.cwd() }).trim();
214426
+ } catch {
214427
+ return "unknown";
214428
+ }
214423
214429
  }
214424
- function getApiKey() {
214425
- return loadApiKey();
214430
+ function getChangedFilesList() {
214431
+ try {
214432
+ const unstaged = (0, import_node_child_process.execSync)("git diff --name-only HEAD", { encoding: "utf8", cwd: process.cwd() }).trim();
214433
+ const staged = (0, import_node_child_process.execSync)("git diff --staged --name-only", { encoding: "utf8", cwd: process.cwd() }).trim();
214434
+ const files = /* @__PURE__ */ new Set();
214435
+ if (unstaged) unstaged.split("\n").forEach((f) => files.add(f));
214436
+ if (staged) staged.split("\n").forEach((f) => files.add(f));
214437
+ return [...files].filter(Boolean);
214438
+ } catch {
214439
+ return [];
214440
+ }
214426
214441
  }
214427
- function formatError(e) {
214428
- return parseError(e);
214442
+ function getAuthorName() {
214443
+ try {
214444
+ return (0, import_node_child_process.execSync)("git config user.name", { encoding: "utf8", cwd: process.cwd() }).trim();
214445
+ } catch {
214446
+ return "unknown";
214447
+ }
214429
214448
  }
214430
214449
 
214431
214450
  // src/commands/init.ts
214432
- init_git();
214433
214451
  var import_scanner = __toESM(require_dist());
214434
214452
  var import_node_fs4 = require("node:fs");
214435
214453
  var import_node_path4 = require("node:path");
214454
+ init_analytics();
214436
214455
 
214437
214456
  // src/commands/install-skill.ts
214438
214457
  var import_node_fs3 = require("node:fs");
@@ -214533,13 +214552,22 @@ async function installSkillCommand() {
214533
214552
  const icon = (s) => s === "created" ? "created \u2713" : s === "updated" ? "updated \u2713" : "already configured \u2713";
214534
214553
  console.log(` CLAUDE.md ${icon(result.claudeMd)}`);
214535
214554
  console.log(` AGENTS.md ${icon(result.agentsMd)}`);
214555
+ try {
214556
+ const { api: api2 } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
214557
+ const me = await api2.get("/me");
214558
+ if (me?.slug) {
214559
+ const { analyticsEvent: analyticsEvent2 } = await Promise.resolve().then(() => (init_analytics(), analytics_exports));
214560
+ analyticsEvent2("cli_install_skill", me.slug, { claude_md: result.claudeMd, agents_md: result.agentsMd });
214561
+ }
214562
+ } catch {
214563
+ }
214536
214564
  return result;
214537
214565
  }
214538
214566
 
214539
214567
  // src/commands/init.ts
214540
214568
  function getVersion() {
214541
214569
  try {
214542
- return "0.1.32";
214570
+ return "0.1.34";
214543
214571
  } catch {
214544
214572
  return "0.0.0";
214545
214573
  }
@@ -214846,13 +214874,17 @@ async function initCommand(args) {
214846
214874
  console.log("");
214847
214875
  console.log(DIVIDER);
214848
214876
  console.log("");
214877
+ if (!isGitInstalled()) {
214878
+ console.error("Error: git is not installed. Install git and try again.");
214879
+ process.exit(1);
214880
+ }
214849
214881
  if (!isGitRepo()) {
214850
- console.error("Error: not a git repository.");
214882
+ console.error("Error: not a git repository. Run `git init` first.");
214851
214883
  process.exit(1);
214852
214884
  }
214853
214885
  const remote = getGitRemote();
214854
214886
  if (!remote) {
214855
- console.error("Error: no git remote found.");
214887
+ console.error("Error: no git remote configured. Run `git remote add origin <url>` first.");
214856
214888
  process.exit(1);
214857
214889
  }
214858
214890
  let workspace;
@@ -214975,7 +215007,8 @@ async function initCommand(args) {
214975
215007
  gotchas: watcher.gotchas,
214976
215008
  ownership: watcher.ownership,
214977
215009
  where_repos: [repo.id],
214978
- created_by: "driftless-init"
215010
+ created_by: "driftless-init",
215011
+ suggested: true
214979
215012
  });
214980
215013
  watchersCreated++;
214981
215014
  } catch {
@@ -215006,11 +215039,18 @@ async function initCommand(args) {
215006
215039
  console.log(" next \u2192 driftless context list");
215007
215040
  console.log("");
215008
215041
  console.log(DIVIDER);
215042
+ analyticsEvent("cli_init_run", workspaceSlug, {
215043
+ framework: summary.framework,
215044
+ endpoints: summary.endpoints ?? components.filter((c) => c.type === "controller").length,
215045
+ components: components.length,
215046
+ watchers_created: watchersCreated
215047
+ });
215009
215048
  process.exit(0);
215010
215049
  }
215011
215050
 
215012
215051
  // src/commands/scan.ts
215013
- init_git();
215052
+ init_api_client();
215053
+ init_analytics();
215014
215054
  function emitJSON(data) {
215015
215055
  console.log(JSON.stringify(data, null, 2));
215016
215056
  }
@@ -215086,6 +215126,7 @@ async function scanCommand(args) {
215086
215126
  });
215087
215127
  const rulesEvaluated = result.rules_evaluated || 0;
215088
215128
  if (result.status === "clean") {
215129
+ analyticsEvent("cli_scan_run", workspace.slug, { violations_found: false, count: 0 });
215089
215130
  if (isJSON) {
215090
215131
  emitJSON({ status: "clean", files: changedFiles, rules_evaluated: rulesEvaluated, violations: [] });
215091
215132
  } else {
@@ -215094,6 +215135,7 @@ async function scanCommand(args) {
215094
215135
  process.exit(0);
215095
215136
  }
215096
215137
  if (!result.violations || result.violations.length === 0) {
215138
+ analyticsEvent("cli_scan_run", workspace.slug, { violations_found: false, count: 0 });
215097
215139
  if (isJSON) {
215098
215140
  emitJSON({ status: "clean", files: changedFiles, rules_evaluated: rulesEvaluated, violations: [] });
215099
215141
  } else {
@@ -215101,6 +215143,7 @@ async function scanCommand(args) {
215101
215143
  }
215102
215144
  process.exit(0);
215103
215145
  }
215146
+ analyticsEvent("cli_scan_run", workspace.slug, { violations_found: true, count: result.violations.length });
215104
215147
  if (isJSON) {
215105
215148
  emitJSON({ status: "violated", files: changedFiles, rules_evaluated: rulesEvaluated, violations: result.violations });
215106
215149
  } else {
@@ -215121,9 +215164,10 @@ ${result.violations.length} violation(s) found (${rulesEvaluated} rule(s) evalua
215121
215164
  }
215122
215165
 
215123
215166
  // src/commands/context.ts
215124
- init_git();
215167
+ init_api_client();
215125
215168
  var import_node_fs5 = require("node:fs");
215126
215169
  var import_node_path5 = require("node:path");
215170
+ init_analytics();
215127
215171
  function parseArgs(args) {
215128
215172
  const flags = {};
215129
215173
  const positional = [];
@@ -215314,14 +215358,20 @@ async function contextCommand(args) {
215314
215358
  if (flags["auto"]) query.push("auto=true");
215315
215359
  if (flags["manual"]) query.push("manual=true");
215316
215360
  if (flags["repo"]) query.push(`repo=${encodeURIComponent(flags["repo"])}`);
215361
+ if (flags["suggested"]) query.push("suggested=true");
215362
+ else query.push("suggested=false");
215317
215363
  const qs = query.length > 0 ? `?${query.join("&")}` : "";
215318
215364
  try {
215319
215365
  const summaries = await api.get(`/workspaces/${workspaceSlug}/watchers${qs}`);
215320
215366
  if (summaries.length === 0 && !isJSON) {
215321
- console.log("No context topics yet.");
215322
- console.log("\nNext:");
215323
- console.log(' driftless context add "auth-boundaries" --what "..." --how "..." --pattern "src/auth/**"');
215324
- console.log(" driftless init # auto-generate topics from your codebase");
215367
+ if (flags["suggested"]) {
215368
+ console.log("No suggested topics. Run `driftless init` to generate suggestions.");
215369
+ } else {
215370
+ console.log("No context topics yet.");
215371
+ console.log("\nNext:");
215372
+ console.log(' driftless context add "auth-boundaries" --what "..." --how "..." --pattern "src/auth/**"');
215373
+ console.log(" driftless init # generate topic suggestions from your codebase");
215374
+ }
215325
215375
  }
215326
215376
  if (isJSON) {
215327
215377
  emitJSON2(summaries);
@@ -215388,6 +215438,7 @@ async function contextCommand(args) {
215388
215438
  }
215389
215439
  try {
215390
215440
  const ctx = await api.get(`/workspaces/${workspaceSlug}/watchers/${slug}`);
215441
+ analyticsEvent("cli_context_get", workspaceSlug, { topic: slug });
215391
215442
  if (isJSON) {
215392
215443
  const sanitized = JSON.parse(JSON.stringify(ctx, (_key, value) => {
215393
215444
  if (typeof value === "string" && value.length > 500) return value.slice(0, 500) + "\u2026";
@@ -215461,6 +215512,7 @@ async function contextCommand(args) {
215461
215512
  ownership: flags["ownership"],
215462
215513
  file_content: fileContent
215463
215514
  });
215515
+ analyticsEvent("context_created", workspaceSlug, { topic: name, has_pattern: !!pattern, has_what: !!flags["what"] });
215464
215516
  if (isJSON) {
215465
215517
  emitJSON2(result);
215466
215518
  } else {
@@ -215514,12 +215566,22 @@ async function contextCommand(args) {
215514
215566
  console.error('PR 3: --gotcha, --invariant, --check, --enforce "<rule-description>"');
215515
215567
  process.exit(1);
215516
215568
  }
215569
+ const currentRemote = getGitRemote();
215570
+ if (currentRemote) {
215571
+ try {
215572
+ const repos = await api.get(`/workspaces/${workspaceSlug}/repos`);
215573
+ const match = repos.find((r) => r.github_org === currentRemote.org && r.github_repo === currentRemote.repo);
215574
+ if (match) updates._add_repo = match.id;
215575
+ } catch {
215576
+ }
215577
+ }
215517
215578
  if (flags["dry-run"]) {
215518
215579
  console.log(`Would update topic '${slug}':`);
215519
215580
  for (const [key, value] of Object.entries(updates)) {
215520
215581
  if (key.startsWith("_")) continue;
215521
215582
  console.log(` ${key}: ${value}`);
215522
215583
  }
215584
+ if (updates._add_repo) console.log(` _add_repo: ${updates._add_repo} (${currentRemote?.org}/${currentRemote?.repo})`);
215523
215585
  if (!isJSON) {
215524
215586
  console.log("\n(Dry run \u2014 no changes written to Driftless Cloud)");
215525
215587
  }
@@ -215533,6 +215595,7 @@ async function contextCommand(args) {
215533
215595
  `/workspaces/${workspaceSlug}/watchers/${slug}`,
215534
215596
  updates
215535
215597
  );
215598
+ analyticsEvent("cli_context_update", workspaceSlug, { topic: slug });
215536
215599
  if (isJSON) {
215537
215600
  emitJSON2(result);
215538
215601
  } else {
@@ -215887,13 +215950,10 @@ Done: ${created} created, ${updated} updated.`);
215887
215950
  Run 'driftless help context' for full reference.`);
215888
215951
  }
215889
215952
 
215890
- // src/commands/session.ts
215891
- init_git();
215892
- var import_node_fs6 = require("node:fs");
215893
- var import_node_path6 = require("node:path");
215953
+ // src/commands/sync.ts
215954
+ init_api_client();
215894
215955
  function parseArgs2(args) {
215895
215956
  const flags = {};
215896
- const positional = [];
215897
215957
  for (let i = 0; i < args.length; i++) {
215898
215958
  if (args[i].startsWith("--")) {
215899
215959
  const key = args[i].slice(2);
@@ -215901,14 +215961,10 @@ function parseArgs2(args) {
215901
215961
  if (next && !next.startsWith("--")) {
215902
215962
  flags[key] = next;
215903
215963
  i++;
215904
- } else {
215905
- flags[key] = true;
215906
- }
215907
- } else {
215908
- positional.push(args[i]);
215964
+ } else flags[key] = true;
215909
215965
  }
215910
215966
  }
215911
- return { flags, positional };
215967
+ return { flags };
215912
215968
  }
215913
215969
  function emitJSON3(data) {
215914
215970
  console.log(JSON.stringify(data, null, 2));
@@ -215926,250 +215982,115 @@ async function resolveWorkspaceSlug2() {
215926
215982
  }
215927
215983
  return remote.org;
215928
215984
  }
215929
- async function sessionCommand(args) {
215985
+ async function syncCommand(args) {
215930
215986
  if (!isGitRepo()) {
215931
215987
  console.error("Error: not a git repository.");
215932
215988
  process.exit(1);
215933
215989
  }
215934
- const workspaceSlug = await resolveWorkspaceSlug2();
215935
- const { flags, positional } = parseArgs2(args);
215936
- const subCommand = positional[0];
215990
+ const { flags } = parseArgs2(args);
215937
215991
  const isJSON = !!flags["json"];
215938
- if (subCommand === "start") {
215939
- const filesFlag = flags["files"];
215940
- let files = [];
215941
- if (filesFlag) {
215942
- files = filesFlag.split(",").map((f) => f.trim()).filter(Boolean);
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) {
216006
+ 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.");
215943
216009
  } else {
215944
- files = getChangedFilesList();
216010
+ emitJSON3({ error: "repo_not_found", repo: `${remote.org}/${remote.repo}` });
215945
216011
  }
215946
- if (files.length === 0) {
215947
- if (!isJSON) {
215948
- console.log("No files specified and no local changes detected.");
215949
- console.log('Usage: driftless session start --files "src/auth/**"');
215950
- } else {
215951
- emitJSON3({ files: [], topics: [], docs: [], rules: [], gotchas: [] });
215952
- }
215953
- process.exit(0);
215954
- }
215955
- const missingFiles = files.filter((f) => !(0, import_node_fs6.existsSync)((0, import_node_path6.resolve)(process.cwd(), f)));
215956
- if (missingFiles.length > 0 && !isJSON) {
215957
- console.error(`Warning: ${missingFiles.length} file(s) not found locally \u2014 matching by pattern only.`);
215958
- }
215959
- try {
215960
- const results = await api.post(
215961
- `/workspaces/${workspaceSlug}/watchers/match-files`,
215962
- { files }
215963
- );
215964
- const topics = [];
215965
- const docs = [];
215966
- const rules = [];
215967
- const gotchas = [];
215968
- const allWhat = [];
215969
- const allHow = [];
215970
- for (const r of results) {
215971
- const ctx = r.context;
215972
- topics.push(ctx.topic);
215973
- if (ctx.description.what) allWhat.push(ctx.description.what);
215974
- if (ctx.description.how) allHow.push(ctx.description.how);
215975
- if (ctx.description.gotchas) gotchas.push(...ctx.description.gotchas);
215976
- if (ctx.anchors.docs) {
215977
- for (const d of ctx.anchors.docs) {
215978
- if (d.path) docs.push(d.path);
215979
- }
215980
- }
215981
- if (ctx.rules) {
215982
- for (const rule of ctx.rules) {
215983
- rules.push(rule.name);
215984
- }
215985
- }
215986
- }
215987
- if (isJSON) {
215988
- emitJSON3({
215989
- files,
215990
- missing_files: missingFiles,
215991
- topics,
215992
- docs: [...new Set(docs)],
215993
- rules,
215994
- gotchas,
215995
- context_summary: {
215996
- what: allWhat,
215997
- how: allHow
215998
- }
215999
- });
216000
- } else {
216001
- console.log(`Session started for ${files.length} file(s):
216012
+ process.exit(0);
216013
+ }
216014
+ const [eventsRes, staleTopics, violations, suggestedTopics] = await Promise.allSettled([
216015
+ api.get(`/workspaces/${workspaceSlug}/watchers/events?repo_id=${repoId}&limit=20`),
216016
+ api.get(`/workspaces/${workspaceSlug}/watchers?stale=true&repo=${repoId}`),
216017
+ api.get(`/workspaces/${workspaceSlug}/violations?repo_id=${repoId}&status=open&limit=20`),
216018
+ api.get(`/workspaces/${workspaceSlug}/watchers?suggested=true`)
216019
+ ]);
216020
+ const events = eventsRes.status === "fulfilled" ? eventsRes.value.events ?? [] : [];
216021
+ const stale = staleTopics.status === "fulfilled" ? staleTopics.value : [];
216022
+ const rawViolations = violations.status === "fulfilled" ? violations.value : [];
216023
+ const openViolations = Array.isArray(rawViolations) ? rawViolations : rawViolations.violations ?? rawViolations.items ?? [];
216024
+ const suggested = suggestedTopics.status === "fulfilled" ? suggestedTopics.value : [];
216025
+ if (isJSON) {
216026
+ emitJSON3({
216027
+ repo: `${remote.org}/${remote.repo}`,
216028
+ stale_topics: stale.map((t) => ({ topic: t.topic, reason: t.stale?.reason ?? null })),
216029
+ recent_events: events.slice(0, 10).map((e) => ({
216030
+ type: e.event_type,
216031
+ topic: e.watcher_slug,
216032
+ detail: e.detail,
216033
+ created_at: e.created_at
216034
+ })),
216035
+ open_violations: openViolations.map((v) => ({
216036
+ id: v.id,
216037
+ rule_id: v.rule_id,
216038
+ status: v.status,
216039
+ author: v.author
216040
+ })),
216041
+ suggested_pending: suggested.length
216042
+ });
216043
+ process.exit(0);
216044
+ }
216045
+ console.log(`\u258C ${remote.org}/${remote.repo}
216002
216046
  `);
216003
- for (const f of files) {
216004
- const exists = (0, import_node_fs6.existsSync)((0, import_node_path6.resolve)(process.cwd(), f));
216005
- console.log(` ${exists ? "\u2713" : "\u2717"} ${f}`);
216006
- }
216007
- if (missingFiles.length > 0) {
216008
- console.log(`
216009
- Warning: ${missingFiles.length} file(s) not found locally.`);
216010
- }
216011
- if (topics.length > 0) {
216012
- console.log(`
216013
- Matched ${topics.length} context topic(s):`);
216014
- for (const r of results) {
216015
- console.log(` \u258C ${r.context.topic} (${r.match_reason})`);
216016
- if (r.context.description.what) {
216017
- console.log(` ${r.context.description.what}`);
216018
- }
216019
- }
216020
- } else {
216021
- console.log("\nNo context topics match these files.");
216022
- }
216023
- if (docs.length > 0) {
216024
- console.log(`
216025
- Anchored docs:`);
216026
- for (const d of [...new Set(docs)]) {
216027
- console.log(` \u{1F4C4} ${d}`);
216028
- }
216029
- }
216030
- if (gotchas.length > 0) {
216031
- console.log(`
216032
- Gotchas:`);
216033
- for (const g of gotchas) {
216034
- console.log(` ! ${g}`);
216035
- }
216036
- }
216037
- if (rules.length > 0) {
216038
- console.log(`
216039
- Rules that will be evaluated:`);
216040
- for (const r of rules) {
216041
- console.log(` \u2713 ${r}`);
216042
- }
216043
- }
216044
- console.log("\nBefore finishing:");
216045
- console.log(" driftless scan --diff");
216046
- console.log(" driftless session end");
216047
- }
216048
- } catch (e) {
216049
- console.error(`Session start failed: ${formatError(e)}`);
216050
- process.exit(1);
216047
+ if (stale.length > 0) {
216048
+ console.log(`\u26A0 ${stale.length} stale topic${stale.length === 1 ? "" : "s"} \u2014 code changed, context not updated:`);
216049
+ for (const t of stale) {
216050
+ console.log(` ${t.topic}`);
216051
+ if (t.stale?.reason) console.log(` reason: ${t.stale.reason}`);
216052
+ console.log(` \u2192 driftless context update ${t.topic} --what "..." --how "..."`);
216051
216053
  }
216052
- return;
216054
+ console.log("");
216053
216055
  }
216054
- if (subCommand === "end") {
216055
- const files = getChangedFilesList();
216056
- if (files.length === 0 && !isJSON) {
216057
- console.log("No local changes detected. Session clean.");
216058
- process.exit(0);
216056
+ if (events.length > 0) {
216057
+ console.log(`Recent activity (${events.length}):`);
216058
+ for (const e of events.slice(0, 10)) {
216059
+ const topic = e.watcher_slug ? `[${e.watcher_slug}]` : "";
216060
+ const detail = e.detail ? ` ${e.detail.slice(0, 80)}` : "";
216061
+ console.log(` ${e.event_type.padEnd(14)} ${topic}${detail}`);
216059
216062
  }
216060
- try {
216061
- const remote = getGitRemote();
216062
- if (!remote) {
216063
- console.error("Error: no git remote found.");
216064
- process.exit(1);
216065
- }
216066
- const me = await api.get("/me");
216067
- const repos = await api.get(`/workspaces/${me.slug}/repos`);
216068
- const repo = repos.find((r) => r.github_org === remote.org && r.github_repo === remote.repo);
216069
- if (!repo) {
216070
- console.error(`Repo '${remote.repo}' not found. Run 'driftless init' first.`);
216071
- process.exit(1);
216072
- }
216073
- const { getUncommittedDiff: getUncommittedDiff2, getStagedDiff: getStagedDiff2, getLastCommitHash: getLastCommitHash2, getAuthorName: getAuthorName2 } = await Promise.resolve().then(() => (init_git(), git_exports));
216074
- const staged = getStagedDiff2();
216075
- const unstaged = getUncommittedDiff2();
216076
- const diff = [staged, unstaged].filter(Boolean).join("\n");
216077
- if (!diff && !isJSON) {
216078
- console.log("No changes to scan. Session clean.");
216079
- process.exit(0);
216080
- }
216081
- const scanResult = await api.post("/scan", {
216082
- workspace_id: me.workspace_id,
216083
- repo_id: repo.id,
216084
- diff,
216085
- commit_hash: getLastCommitHash2(),
216086
- author: getAuthorName2()
216087
- });
216088
- const rulesEvaluated = scanResult.rules_evaluated || 0;
216089
- const violations = scanResult.violations || [];
216090
- const contextResults = files.length > 0 ? await api.post(
216091
- `/workspaces/${me.slug}/watchers/match-files`,
216092
- { files }
216093
- ) : [];
216094
- const staleTopics = contextResults.filter((r) => r.context.stale?.is_stale);
216095
- if (isJSON) {
216096
- emitJSON3({
216097
- files,
216098
- rules_evaluated: rulesEvaluated,
216099
- violations,
216100
- stale_topics: staleTopics.map((r) => ({ topic: r.context.topic, reason: r.context.stale.reason })),
216101
- context_matched: contextResults.map((r) => r.context.topic)
216102
- });
216103
- } else {
216104
- console.log(`Session end \u2014 ${files.length} file(s) changed:
216105
- `);
216106
- for (const f of files) {
216107
- console.log(` \u270E ${f}`);
216108
- }
216109
- console.log(`
216110
- ${rulesEvaluated} rule(s) evaluated.`);
216111
- if (violations.length > 0) {
216112
- console.log(`
216113
- ${violations.length} violation(s) found:`);
216114
- for (const v of violations) {
216115
- console.log(` \u2717 [${v.severity.toUpperCase()}] ${v.rule_name}`);
216116
- console.log(` ${v.file_path}:${v.line_number} \u2014 ${v.explanation}`);
216117
- }
216118
- console.log("\nFix violations before pushing.");
216119
- } else {
216120
- console.log("No violations.");
216121
- }
216122
- if (staleTopics.length > 0) {
216123
- console.log(`
216124
- \u26A0 ${staleTopics.length} stale context topic(s):`);
216125
- for (const s of staleTopics) {
216126
- console.log(` ${s.context.topic} \u2014 ${s.context.stale.reason}`);
216127
- console.log(` \u2192 driftless context update ${s.context.topic} --what "..." --how "..."`);
216128
- }
216129
- }
216130
- if (contextResults.length > 0) {
216131
- console.log(`
216132
- Context touched:`);
216133
- for (const r of contextResults) {
216134
- console.log(` \u258C ${r.context.topic}`);
216135
- }
216136
- }
216137
- console.log("\nLearned something? Update context:");
216138
- console.log(' driftless context update <topic> --gotchas "..."');
216139
- console.log(' driftless context add <new-topic> --what "..." --pattern "..."');
216140
- }
216141
- if (violations.length > 0) {
216142
- process.exit(1);
216143
- }
216144
- } catch (e) {
216145
- console.error(`Session end failed: ${formatError(e)}`);
216146
- process.exit(1);
216063
+ console.log("");
216064
+ }
216065
+ if (openViolations.length > 0) {
216066
+ console.log(`\u2717 ${openViolations.length} open violation${openViolations.length === 1 ? "" : "s"}:`);
216067
+ for (const v of openViolations.slice(0, 5)) {
216068
+ console.log(` ${v.rule_id} ${v.author ? `(${v.author})` : ""}`);
216147
216069
  }
216148
- return;
216070
+ if (openViolations.length > 5) console.log(` ... and ${openViolations.length - 5} more`);
216071
+ console.log("");
216149
216072
  }
216150
- console.log(`Usage: driftless session <start|end> [args]
216151
-
216152
- start \u2014 Show relevant context before editing
216153
- --files "path1,path2" Files to check (default: local changes)
216154
- --json Output as JSON
216155
-
216156
- end \u2014 Scan changes, check violations, suggest context updates
216157
- --json Output as JSON
216158
-
216159
- Examples:
216160
- driftless session start --files "src/auth/**"
216161
- driftless session start # uses local changes
216162
- driftless session end # scan + context report`);
216073
+ if (suggested.length > 0) {
216074
+ console.log(`${suggested.length} suggested topic${suggested.length === 1 ? "" : "s"} from init \u2014 review and confirm:`);
216075
+ console.log(` driftless context list --suggested`);
216076
+ console.log("");
216077
+ }
216078
+ if (stale.length === 0 && events.length === 0 && openViolations.length === 0 && suggested.length === 0) {
216079
+ console.log("Cloud context is up to date. Nothing to sync.");
216080
+ } else {
216081
+ console.log("Review stale topics, then update context before touching code.");
216082
+ }
216083
+ process.exit(0);
216163
216084
  }
216164
216085
 
216165
216086
  // src/commands/login.ts
216166
- var import_node_fs7 = require("node:fs");
216167
- var import_node_path7 = require("node:path");
216087
+ var import_node_fs6 = require("node:fs");
216088
+ var import_node_path6 = require("node:path");
216168
216089
  var import_node_readline = require("node:readline");
216169
216090
  var import_node_child_process2 = require("node:child_process");
216170
216091
  var import_node_os2 = require("node:os");
216171
- var CONFIG_DIR = (0, import_node_path7.resolve)((0, import_node_os2.homedir)(), ".driftless");
216172
- var CONFIG_PATH2 = (0, import_node_path7.resolve)(CONFIG_DIR, "config.json");
216092
+ var CONFIG_DIR = (0, import_node_path6.resolve)((0, import_node_os2.homedir)(), ".driftless");
216093
+ var CONFIG_PATH2 = (0, import_node_path6.resolve)(CONFIG_DIR, "config.json");
216173
216094
  function openBrowser(url) {
216174
216095
  const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
216175
216096
  const child = (0, import_node_child_process2.spawn)(cmd, [url], { stdio: "ignore", detached: true });
@@ -216199,10 +216120,10 @@ async function loginCommand(args) {
216199
216120
  input: process.stdin,
216200
216121
  output: process.stdout
216201
216122
  });
216202
- const apiKey = await new Promise((resolve9) => {
216123
+ const apiKey = await new Promise((resolve8) => {
216203
216124
  rl.question("Paste your API key: ", (answer) => {
216204
216125
  rl.close();
216205
- resolve9(answer.trim());
216126
+ resolve8(answer.trim());
216206
216127
  });
216207
216128
  });
216208
216129
  if (!apiKey.startsWith("drift_")) {
@@ -216215,10 +216136,10 @@ async function loginCommand(args) {
216215
216136
  function saveConfig(apiKey, apiUrl) {
216216
216137
  const url = apiUrl || "https://api.driftless.icu/api/v1";
216217
216138
  try {
216218
- if (!(0, import_node_fs7.existsSync)(CONFIG_DIR)) {
216219
- (0, import_node_fs7.mkdirSync)(CONFIG_DIR, { recursive: true });
216139
+ if (!(0, import_node_fs6.existsSync)(CONFIG_DIR)) {
216140
+ (0, import_node_fs6.mkdirSync)(CONFIG_DIR, { recursive: true });
216220
216141
  }
216221
- (0, import_node_fs7.writeFileSync)(
216142
+ (0, import_node_fs6.writeFileSync)(
216222
216143
  CONFIG_PATH2,
216223
216144
  JSON.stringify({ api_key: apiKey, api_url: url }, null, 2) + "\n"
216224
216145
  );
@@ -216235,9 +216156,9 @@ function saveConfig(apiKey, apiUrl) {
216235
216156
  }
216236
216157
 
216237
216158
  // src/commands/doctor.ts
216238
- init_git();
216239
- var import_node_fs8 = require("node:fs");
216240
- var import_node_path8 = require("node:path");
216159
+ init_api_client();
216160
+ var import_node_fs7 = require("node:fs");
216161
+ var import_node_path7 = require("node:path");
216241
216162
  async function doctorCommand() {
216242
216163
  const checks = [];
216243
216164
  const apiKey = getApiKey();
@@ -216323,9 +216244,9 @@ async function doctorCommand() {
216323
216244
  } else {
216324
216245
  checks.push({ name: "Baseline", status: "warn", detail: "Skipped (no git remote)" });
216325
216246
  }
216326
- const agentsPath = (0, import_node_path8.resolve)(process.cwd(), "AGENTS.md");
216327
- if ((0, import_node_fs8.existsSync)(agentsPath)) {
216328
- const content = (0, import_node_fs8.readFileSync)(agentsPath, "utf-8");
216247
+ const agentsPath = (0, import_node_path7.resolve)(process.cwd(), "AGENTS.md");
216248
+ if ((0, import_node_fs7.existsSync)(agentsPath)) {
216249
+ const content = (0, import_node_fs7.readFileSync)(agentsPath, "utf-8");
216329
216250
  if (content.includes("driftless")) {
216330
216251
  checks.push({ name: "AGENTS.md", status: "ok", detail: "Driftless skill installed" });
216331
216252
  } else {
@@ -216381,7 +216302,7 @@ function pad2(s, n) {
216381
216302
  }
216382
216303
 
216383
216304
  // src/index.ts
216384
- var VERSION = "0.1.32";
216305
+ var VERSION = "0.1.34";
216385
216306
  var HELP_TEXT = `Driftless CLI v${VERSION} \u2014 Living repo context for humans and coding agents
216386
216307
 
216387
216308
  Install: npm install -g @driftless-sh/cli
@@ -216389,8 +216310,7 @@ Docs: https://driftless.icu/docs
216389
216310
  API: https://api.driftless.icu/api/v1
216390
216311
 
216391
216312
  Agent loop:
216392
- driftless session start Show context before editing
216393
- driftless session end Scan changes + context report
216313
+ driftless sync Load context + scan changes in one shot
216394
216314
  driftless context list List all context topics
216395
216315
  driftless context get <topic> Live view of one topic before editing
216396
216316
  driftless context get --diff What context matters for my local changes?
@@ -216405,9 +216325,9 @@ Setup:
216405
216325
  Commands:
216406
216326
  login Authenticate with API key
216407
216327
  init Smart init: scan \u2192 detect patterns \u2192 create topics + rules + anchor docs
216328
+ sync Load context for current changes + scan in one shot
216408
216329
  scan Evaluate staged + uncommitted changes against rules
216409
216330
  scan --diff Evaluate uncommitted changes only
216410
- session Agent session: start (context) \u2192 end (scan + report)
216411
216331
  context Live repo context (topics, search, anchors)
216412
216332
  install-skill Install AGENTS.md into current repo
216413
216333
  doctor Check environment health (auth, API, git, workspace, repo, baseline)
@@ -216429,10 +216349,6 @@ Context subcommands:
216429
216349
  delete <topic> Delete a topic
216430
216350
  load --files "p1,p2" Match topics by file paths
216431
216351
 
216432
- Session subcommands:
216433
- start [--files "p1,p2"] Show relevant context before editing
216434
- end Scan changes, check violations, suggest updates
216435
-
216436
216352
  Flags:
216437
216353
  --json Output as JSON (default is human-readable)
216438
216354
  --dry-run Preview changes without writing to Driftless Cloud
@@ -216450,8 +216366,8 @@ Output format:
216450
216366
  Use --json flag for machine-readable output (agents, CI).
216451
216367
 
216452
216368
  Examples:
216453
- driftless session start --files "src/auth/**"
216454
- driftless session end
216369
+ driftless sync # context + scan for current changes
216370
+ driftless sync --json # machine-readable output
216455
216371
 
216456
216372
  driftless context add "b2b-guard" \\
216457
216373
  --what "Guard que protege endpoints B2B" \\
@@ -216597,8 +216513,8 @@ async function main() {
216597
216513
  case "context":
216598
216514
  await contextCommand(args.slice(1));
216599
216515
  break;
216600
- case "session":
216601
- await sessionCommand(args.slice(1));
216516
+ case "sync":
216517
+ await syncCommand(args.slice(1));
216602
216518
  break;
216603
216519
  case "install-skill":
216604
216520
  await installSkillCommand();