@driftless-sh/cli 0.1.18 → 0.1.20

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
@@ -214179,6 +214179,8 @@ var require_dist = __commonJS({
214179
214179
  return enricher_1.enrichComponents;
214180
214180
  } });
214181
214181
  async function scanRepo2(rootPath) {
214182
+ const startMs = Date.now();
214183
+ const startMem = process.memoryUsage().heapUsed;
214182
214184
  const identity = (0, identity_1.identifyRepo)(rootPath);
214183
214185
  let components = (0, nestjs_extractor_1.extractNestJS)(rootPath);
214184
214186
  components = await (0, enricher_1.enrichComponents)(components);
@@ -214191,7 +214193,18 @@ var require_dist = __commonJS({
214191
214193
  total_modules: components.filter((c) => c.type === "module").length,
214192
214194
  total_dtos: components.filter((c) => c.type === "dto").length
214193
214195
  };
214194
- return { identity, components, stats };
214196
+ const durationMs = Date.now() - startMs;
214197
+ const memoryMb = Math.round((process.memoryUsage().heapUsed - startMem) / 1024 / 1024);
214198
+ return {
214199
+ identity,
214200
+ components,
214201
+ stats,
214202
+ telemetry: {
214203
+ duration_ms: durationMs,
214204
+ memory_mb: memoryMb,
214205
+ files_parsed: uniqueFiles.size
214206
+ }
214207
+ };
214195
214208
  }
214196
214209
  }
214197
214210
  });
@@ -214230,6 +214243,28 @@ function getBaseUrl() {
214230
214243
  }
214231
214244
  return DEFAULT_URL;
214232
214245
  }
214246
+ function parseError(e) {
214247
+ const msg = e.message;
214248
+ const jsonMatch = msg.match(/\{[^}]*"message"[^}]*\}/);
214249
+ if (jsonMatch) {
214250
+ try {
214251
+ const parsed = JSON.parse(jsonMatch[0]);
214252
+ if (parsed.message) return parsed.message;
214253
+ } catch {
214254
+ }
214255
+ }
214256
+ const statusMatch = msg.match(/HTTP (\d+):/);
214257
+ if (statusMatch) {
214258
+ const code = parseInt(statusMatch[1], 10);
214259
+ if (code === 500) return "Internal server error \u2014 the API encountered an unexpected issue";
214260
+ if (code === 401) return "Authentication failed \u2014 check your API key";
214261
+ if (code === 403) return "Access denied \u2014 your API key lacks permission";
214262
+ if (code === 404) return "Not found \u2014 the resource does not exist";
214263
+ if (code === 429) return "Rate limited \u2014 too many requests, try again later";
214264
+ return `Server error (HTTP ${code})`;
214265
+ }
214266
+ return msg;
214267
+ }
214233
214268
  function request(method, path, body) {
214234
214269
  return new Promise((resolve7, reject) => {
214235
214270
  const baseUrl = getBaseUrl();
@@ -214264,7 +214299,7 @@ function request(method, path, body) {
214264
214299
  });
214265
214300
  }
214266
214301
  );
214267
- req.on("error", reject);
214302
+ req.on("error", (e) => reject(new Error(`Connection failed: ${e.message}`)));
214268
214303
  if (body) req.write(JSON.stringify(body));
214269
214304
  req.end();
214270
214305
  });
@@ -214282,6 +214317,9 @@ function getApiUrl() {
214282
214317
  function getApiKey() {
214283
214318
  return loadApiKey();
214284
214319
  }
214320
+ function formatError(e) {
214321
+ return parseError(e);
214322
+ }
214285
214323
 
214286
214324
  // src/git.ts
214287
214325
  var import_node_child_process = require("node:child_process");
@@ -214516,6 +214554,15 @@ function generateSmartRules(repoId, patterns) {
214516
214554
  scope: { paths: ["apps/", "libs/"] },
214517
214555
  pattern: { type: "FORBIDDEN_IMPORT", params: { imports: ["@modelcontextprotocol", "mcp"] } },
214518
214556
  repo_id: repoId
214557
+ },
214558
+ {
214559
+ name: "Controllers must be thin",
214560
+ description: "Controllers should only handle routing and validation. Move business logic, token manipulation, and debug code to services.",
214561
+ type: "STRUCTURAL",
214562
+ severity: "high",
214563
+ scope: { file_patterns: ["*.controller.ts"] },
214564
+ pattern: { type: "CONTROLLER_THICK", params: {} },
214565
+ repo_id: repoId
214519
214566
  }
214520
214567
  );
214521
214568
  return rules;
@@ -214677,12 +214724,24 @@ async function initCommand(args) {
214677
214724
  (sum, component) => sum + (component.relations?.length || 0),
214678
214725
  0
214679
214726
  );
214680
- console.log(` Framework: ${summary.framework} | Endpoints: ${summary.endpoints} | Services: ${summary.services}`);
214681
- console.log(` Modules: ${summary.modules} | Guards: ${summary.guards}`);
214727
+ const actualEndpoints = components.filter((c) => c.type === "endpoint").length;
214728
+ const actualServices = components.filter((c) => c.type === "service").length;
214729
+ const actualModules = components.filter((c) => c.type === "module").length;
214730
+ const actualGuards = components.filter((c) => c.type === "guard").length;
214731
+ const actualControllers = components.filter((c) => c.type === "controller").length;
214732
+ const actualDtos = components.filter((c) => c.type === "dto").length;
214733
+ console.log(` Framework: ${summary.framework} | Endpoints: ${actualEndpoints} | Services: ${actualServices}`);
214734
+ console.log(` Modules: ${actualModules} | Guards: ${actualGuards}`);
214682
214735
  console.log(` Relations: ${relationCount}`);
214736
+ if (actualControllers > 0) console.log(` Controllers: ${actualControllers}`);
214737
+ if (actualDtos > 0) console.log(` DTOs: ${actualDtos}`);
214683
214738
  if (summary.auth_patterns.length > 0) {
214684
214739
  console.log(` Auth: ${summary.auth_patterns.join(", ")}`);
214685
214740
  }
214741
+ if (scanResult.telemetry) {
214742
+ const t = scanResult.telemetry;
214743
+ console.log(` Scan: ${t.duration_ms}ms, ${t.memory_mb}MB heap, ${t.files_parsed} files`);
214744
+ }
214686
214745
  let repo;
214687
214746
  try {
214688
214747
  const repos = await api.get(`/workspaces/${workspaceSlug}/repos`);
@@ -214787,7 +214846,7 @@ Creating ${smartRules.length} architectural rules...`);
214787
214846
  }
214788
214847
  console.log(` Docs anchored: ${docsAnchored}`);
214789
214848
  console.log("\n---");
214790
- console.log(`Architecture: ${summary.modules} modules, ${summary.endpoints} endpoints, ${summary.services} services`);
214849
+ console.log(`Architecture: ${actualModules} modules, ${actualEndpoints} endpoints, ${actualServices} services`);
214791
214850
  console.log(`Relations: ${relationCount} cross-component dependencies mapped`);
214792
214851
  console.log(`Rules: ${rulesCreated} architectural rules created from code analysis`);
214793
214852
  console.log(`Watchers: ${watchersCreated} context watchers auto-generated`);
@@ -214832,6 +214891,7 @@ async function scanCommand(args) {
214832
214891
  const onlyDiff = args.includes("--diff");
214833
214892
  let diff = "";
214834
214893
  let source = "";
214894
+ let changedFiles = [];
214835
214895
  if (onlyDiff) {
214836
214896
  diff = getUncommittedDiff();
214837
214897
  source = "uncommitted changes";
@@ -214839,6 +214899,7 @@ async function scanCommand(args) {
214839
214899
  console.log("No uncommitted changes. Clean.");
214840
214900
  process.exit(0);
214841
214901
  }
214902
+ changedFiles = diff.split("\n").filter((line) => line.startsWith("+++ b/") || line.startsWith("--- a/")).map((line) => line.replace(/^\+\+\+ b\//, "").replace(/^--- a\//, "")).filter((f, i, arr) => arr.indexOf(f) === i);
214842
214903
  } else {
214843
214904
  const staged = getStagedDiff();
214844
214905
  const unstaged = getUncommittedDiff();
@@ -214852,6 +214913,9 @@ async function scanCommand(args) {
214852
214913
  const commitHash = getLastCommitHash();
214853
214914
  const author = getAuthorName();
214854
214915
  console.log(`Scanning ${source}...`);
214916
+ if (changedFiles.length > 0) {
214917
+ console.log(` Files: ${changedFiles.join(", ")}`);
214918
+ }
214855
214919
  const result = await api.post("/scan", {
214856
214920
  workspace_id: workspace.workspace_id,
214857
214921
  repo_id: repo.id,
@@ -214859,12 +214923,13 @@ async function scanCommand(args) {
214859
214923
  commit_hash: commitHash,
214860
214924
  author
214861
214925
  });
214926
+ const rulesEvaluated = result.rules_evaluated || 0;
214862
214927
  if (result.status === "clean") {
214863
- console.log("Clean \u2014 no violations detected.");
214928
+ console.log(`Clean \u2014 ${rulesEvaluated} rule(s) evaluated, no violations.`);
214864
214929
  process.exit(0);
214865
214930
  }
214866
214931
  if (!result.violations || result.violations.length === 0) {
214867
- console.log("Clean \u2014 no violations detected.");
214932
+ console.log(`Clean \u2014 ${rulesEvaluated} rule(s) evaluated, no violations.`);
214868
214933
  process.exit(0);
214869
214934
  }
214870
214935
  console.log(`
@@ -215066,12 +215131,16 @@ async function contextCommand(args) {
215066
215131
  try {
215067
215132
  const summaries = await api.get(`/workspaces/${workspaceSlug}/watchers${qs}`);
215068
215133
  if (isHuman) {
215069
- renderSummaryHuman(summaries);
215134
+ if (summaries.length === 0) {
215135
+ console.log("No context topics yet. Run `driftless init` to auto-generate watchers from your codebase.");
215136
+ } else {
215137
+ renderSummaryHuman(summaries);
215138
+ }
215070
215139
  } else {
215071
215140
  emitJSON(summaries);
215072
215141
  }
215073
215142
  } catch (e) {
215074
- console.error(`List failed: ${e.message}`);
215143
+ console.error(`List failed: ${formatError(e)}`);
215075
215144
  process.exit(1);
215076
215145
  }
215077
215146
  return;
@@ -215105,12 +215174,16 @@ async function contextCommand(args) {
215105
215174
  { files }
215106
215175
  );
215107
215176
  if (isHuman) {
215108
- renderMatchFilesHuman(results, files);
215177
+ if (results.length === 0) {
215178
+ console.log(`No context topics match these files. No watcher covers the changed paths.`);
215179
+ } else {
215180
+ renderMatchFilesHuman(results, files);
215181
+ }
215109
215182
  } else {
215110
215183
  emitJSON(results);
215111
215184
  }
215112
215185
  } catch (e) {
215113
- console.error(`Match failed: ${e.message}`);
215186
+ console.error(`Match failed: ${formatError(e)}`);
215114
215187
  process.exit(1);
215115
215188
  }
215116
215189
  return;
@@ -215145,12 +215218,16 @@ async function contextCommand(args) {
215145
215218
  `/workspaces/${workspaceSlug}/watchers/search?q=${encodeURIComponent(query)}`
215146
215219
  );
215147
215220
  if (isHuman) {
215148
- renderSummaryHuman(results);
215221
+ if (results.length === 0) {
215222
+ console.log(`No context topics matching "${query}".`);
215223
+ } else {
215224
+ renderSummaryHuman(results);
215225
+ }
215149
215226
  } else {
215150
215227
  emitJSON(results);
215151
215228
  }
215152
215229
  } catch (e) {
215153
- console.error(`Search failed: ${e.message}`);
215230
+ console.error(`Search failed: ${formatError(e)}`);
215154
215231
  process.exit(1);
215155
215232
  }
215156
215233
  return;
@@ -215349,12 +215426,16 @@ async function contextCommand(args) {
215349
215426
  { files }
215350
215427
  );
215351
215428
  if (isHuman) {
215352
- renderMatchFilesHuman(results, files);
215429
+ if (results.length === 0) {
215430
+ console.log(`No context topics match these files. No watcher covers the changed paths.`);
215431
+ } else {
215432
+ renderMatchFilesHuman(results, files);
215433
+ }
215353
215434
  } else {
215354
215435
  emitJSON(results);
215355
215436
  }
215356
215437
  } catch (e) {
215357
- console.error(`Push failed: ${e.message}`);
215438
+ console.error(`Push failed: ${formatError(e)}`);
215358
215439
  process.exit(1);
215359
215440
  }
215360
215441
  return;
@@ -215722,7 +215803,7 @@ function pad2(s, n) {
215722
215803
  }
215723
215804
 
215724
215805
  // src/index.ts
215725
- var VERSION = "0.1.18";
215806
+ var VERSION = "0.1.20";
215726
215807
  var HELP_TEXT = `Driftless CLI v${VERSION} \u2014 Context integrity for AI engineering teams
215727
215808
 
215728
215809
  Install: npm install -g @driftless-sh/cli