aislop 0.8.2 → 0.9.0

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
@@ -1,4 +1,4 @@
1
- import { n as ENGINE_INFO, r as getEngineLabel, t as APP_VERSION } from "./version-G3ekYjY1.js";
1
+ import { n as ENGINE_INFO, r as getEngineLabel, t as APP_VERSION } from "./version-D_rqBdyj.js";
2
2
  import { n as runSubprocess, t as isToolInstalled } from "./subprocess-CQUJDGgn.js";
3
3
  import { r as runGenericLinter, t as fixRubyLint } from "./generic-BrcWMW7E.js";
4
4
  import { n as runExpoDoctor } from "./expo-doctor-Bz0LZhQ6.js";
@@ -15,6 +15,7 @@ import { fileURLToPath } from "node:url";
15
15
  import { performance } from "node:perf_hooks";
16
16
  import os from "node:os";
17
17
  import ts from "typescript";
18
+ import { randomUUID } from "node:crypto";
18
19
  import { isCancel, multiselect, select, text } from "@clack/prompts";
19
20
 
20
21
  //#region src/config/defaults.ts
@@ -1994,6 +1995,27 @@ const collectJsDeps = (rootDir, jsDeps) => {
1994
1995
  collectNestedManifests(rootDir, jsDeps);
1995
1996
  return true;
1996
1997
  };
1998
+ const TS_CONFIG_FILES = ["tsconfig.json", "jsconfig.json"];
1999
+ const buildAliasMatcher = (key) => {
2000
+ const starIdx = key.indexOf("*");
2001
+ if (starIdx === -1) return (spec) => spec === key;
2002
+ const before = key.slice(0, starIdx);
2003
+ const after = key.slice(starIdx + 1);
2004
+ return (spec) => spec.length >= before.length + after.length && spec.startsWith(before) && spec.endsWith(after);
2005
+ };
2006
+ const collectAliasMatchersFromConfig = (configPath, matchers) => {
2007
+ const opts = readJson(configPath)?.compilerOptions;
2008
+ if (!opts || typeof opts !== "object") return;
2009
+ const paths = opts.paths;
2010
+ if (!paths || typeof paths !== "object") return;
2011
+ for (const key of Object.keys(paths)) matchers.push(buildAliasMatcher(key));
2012
+ };
2013
+ const collectTsPathAliases = (rootDir) => {
2014
+ const matchers = [];
2015
+ const dirs = [rootDir, ...expandWorkspaceDirs(rootDir, readWorkspaceGlobs(rootDir, readJson(path.join(rootDir, "package.json"))))];
2016
+ for (const dir of dirs) for (const fname of TS_CONFIG_FILES) collectAliasMatchersFromConfig(path.join(dir, fname), matchers);
2017
+ return matchers;
2018
+ };
1997
2019
  const addPyDep = (pyDeps, name) => {
1998
2020
  const normalized = name.toLowerCase().replace(/_/g, "-");
1999
2021
  pyDeps.add(normalized);
@@ -2151,10 +2173,11 @@ const extractPyImports = (content) => {
2151
2173
  }
2152
2174
  return results;
2153
2175
  };
2154
- const checkJsImport = (spec, manifest) => {
2176
+ const checkJsImport = (spec, manifest, tsAliasMatchers) => {
2155
2177
  if (isJsRelativeOrAbsolute(spec)) return null;
2156
2178
  if (isJsBuiltin(spec)) return null;
2157
2179
  if (isJsVirtualModule(spec)) return null;
2180
+ if (tsAliasMatchers.some((m) => m(spec))) return null;
2158
2181
  const pkg = packageNameFromImport(spec);
2159
2182
  if (manifest.jsDeps.has(pkg)) return null;
2160
2183
  if (pkg.startsWith("@types/")) {
@@ -2175,6 +2198,7 @@ const checkPyImport = (spec, manifest) => {
2175
2198
  const detectHallucinatedImports = async (context) => {
2176
2199
  const manifest = loadManifest(context.rootDirectory);
2177
2200
  if (!manifest.hasJsManifest && !manifest.hasPyManifest) return [];
2201
+ const tsAliasMatchers = manifest.hasJsManifest ? collectTsPathAliases(context.rootDirectory) : [];
2178
2202
  const diagnostics = [];
2179
2203
  const files = getSourceFiles(context);
2180
2204
  for (const filePath of files) {
@@ -2194,7 +2218,7 @@ const detectHallucinatedImports = async (context) => {
2194
2218
  const relPath = path.relative(context.rootDirectory, filePath);
2195
2219
  const imports = isJs ? extractJsImports(content) : extractPyImports(content);
2196
2220
  for (const { spec, line } of imports) {
2197
- const hallucinated = isJs ? checkJsImport(spec, manifest) : checkPyImport(spec, manifest);
2221
+ const hallucinated = isJs ? checkJsImport(spec, manifest, tsAliasMatchers) : checkPyImport(spec, manifest);
2198
2222
  if (!hallucinated) continue;
2199
2223
  const manifestLabel = isJs ? "package.json" : "requirements.txt / pyproject.toml / Pipfile";
2200
2224
  diagnostics.push({
@@ -5851,60 +5875,348 @@ var LiveRail = class {
5851
5875
  };
5852
5876
 
5853
5877
  //#endregion
5854
- //#region src/utils/telemetry.ts
5855
- const POSTHOG_HOST = "https://eu.i.posthog.com";
5856
- const POSTHOG_KEY = "phc_eY2cOMFva9q24GrWeOuvuVIOhCIdjOALxeAR3ItrqbJ";
5857
- /**
5858
- * Returns true if telemetry should be disabled.
5859
- * Telemetry is opt-out: it runs unless explicitly disabled.
5860
- */
5861
- const isTelemetryDisabled = (configEnabled) => {
5862
- if (process.env.AISLOP_NO_TELEMETRY === "1" || process.env.DO_NOT_TRACK === "1") return true;
5863
- if (process.env.CI === "true" || process.env.CI === "1") return true;
5864
- if (configEnabled === false) return true;
5865
- return false;
5866
- };
5867
- const getScoreBucket = (score) => {
5878
+ //#region src/telemetry/env.ts
5879
+ const detectPackageManager$1 = (env = process.env) => {
5880
+ const execPath = env.npm_execpath ?? "";
5881
+ if (execPath.includes("npx")) return "npx";
5882
+ const userAgent = env.npm_config_user_agent ?? "";
5883
+ if (userAgent.startsWith("pnpm/")) return "pnpm";
5884
+ if (userAgent.startsWith("yarn/")) return "yarn";
5885
+ if (userAgent.startsWith("bun/")) return "bun";
5886
+ if (userAgent.startsWith("npm/")) return "npm";
5887
+ if (execPath.includes("pnpm")) return "pnpm";
5888
+ if (execPath.includes("yarn")) return "yarn";
5889
+ if (execPath.includes("bun")) return "bun";
5890
+ if (execPath.includes("npm")) return "npm";
5891
+ return "unknown";
5892
+ };
5893
+ const CI_ENV_KEYS = [
5894
+ "CI",
5895
+ "GITHUB_ACTIONS",
5896
+ "GITLAB_CI",
5897
+ "CIRCLECI",
5898
+ "TRAVIS",
5899
+ "BUILDKITE",
5900
+ "DRONE",
5901
+ "TEAMCITY_VERSION",
5902
+ "TF_BUILD"
5903
+ ];
5904
+ const isCiEnv = (env = process.env) => CI_ENV_KEYS.some((k) => {
5905
+ const v = env[k];
5906
+ return v === "true" || v === "1" || v != null && v.length > 0 && k !== "CI";
5907
+ }) || env.CI === "true" || env.CI === "1";
5908
+ const fileCountBucket = (count) => {
5909
+ if (count < 10) return "0-10";
5910
+ if (count < 50) return "10-50";
5911
+ if (count < 100) return "50-100";
5912
+ if (count < 500) return "100-500";
5913
+ if (count < 1e3) return "500-1000";
5914
+ return "1000+";
5915
+ };
5916
+ const scoreBucket = (score) => {
5868
5917
  if (score >= 75) return "75-100";
5869
5918
  if (score >= 50) return "50-75";
5870
5919
  if (score >= 25) return "25-50";
5871
5920
  return "0-25";
5872
5921
  };
5873
- const getAnonymousId = () => {
5874
- const raw = `${os.hostname()}-${os.platform()}-${os.arch()}`;
5875
- let hash = 5381;
5876
- for (let i = 0; i < raw.length; i++) hash = hash * 33 ^ raw.charCodeAt(i);
5877
- return `aislop_${(hash >>> 0).toString(36)}`;
5922
+
5923
+ //#endregion
5924
+ //#region src/telemetry/identity.ts
5925
+ const FILE_BASENAME = "install_id";
5926
+ const resolveInstallIdPath = (homedir = os.homedir(), env = process.env) => {
5927
+ if (process.platform === "linux" && env.XDG_STATE_HOME) return path.join(env.XDG_STATE_HOME, "aislop", FILE_BASENAME);
5928
+ return path.join(homedir, ".aislop", FILE_BASENAME);
5929
+ };
5930
+ const ensureInstallId = (idPath = resolveInstallIdPath()) => {
5931
+ if (fs.existsSync(idPath)) {
5932
+ const existing = fs.readFileSync(idPath, "utf-8").trim();
5933
+ if (existing.length > 0) return {
5934
+ installId: existing,
5935
+ created: false
5936
+ };
5937
+ }
5938
+ const dir = path.dirname(idPath);
5939
+ fs.mkdirSync(dir, { recursive: true });
5940
+ const installId = randomUUID();
5941
+ const tmpPath = `${idPath}.${process.pid}.tmp`;
5942
+ fs.writeFileSync(tmpPath, `${installId}\n`, { mode: 384 });
5943
+ try {
5944
+ fs.renameSync(tmpPath, idPath);
5945
+ return {
5946
+ installId,
5947
+ created: true
5948
+ };
5949
+ } catch {
5950
+ fs.rmSync(tmpPath, { force: true });
5951
+ return {
5952
+ installId: fs.readFileSync(idPath, "utf-8").trim(),
5953
+ created: false
5954
+ };
5955
+ }
5956
+ };
5957
+
5958
+ //#endregion
5959
+ //#region src/telemetry/redaction.ts
5960
+ const SAFE_PROPERTY_NAMES = new Set([
5961
+ "aislop_version",
5962
+ "node_version",
5963
+ "os",
5964
+ "arch",
5965
+ "schema_version",
5966
+ "anonymous_install_id",
5967
+ "package_manager",
5968
+ "is_ci",
5969
+ "command",
5970
+ "language_summary",
5971
+ "lang_typescript",
5972
+ "lang_javascript",
5973
+ "lang_python",
5974
+ "lang_java",
5975
+ "file_count_bucket",
5976
+ "exit_code",
5977
+ "duration_ms",
5978
+ "error_kind",
5979
+ "score",
5980
+ "score_bucket",
5981
+ "finding_count",
5982
+ "error_count",
5983
+ "warning_count",
5984
+ "fixable_count",
5985
+ "fix_steps",
5986
+ "fix_resolved",
5987
+ "fix_score_delta",
5988
+ "engine_format_issues",
5989
+ "engine_format_ms",
5990
+ "engine_lint_issues",
5991
+ "engine_lint_ms",
5992
+ "engine_code_quality_issues",
5993
+ "engine_code_quality_ms",
5994
+ "engine_ai_slop_issues",
5995
+ "engine_ai_slop_ms",
5996
+ "engine_architecture_issues",
5997
+ "engine_architecture_ms",
5998
+ "engine_security_issues",
5999
+ "engine_security_ms",
6000
+ "tool",
6001
+ "ok",
6002
+ "agent",
6003
+ "score_delta"
6004
+ ]);
6005
+ const redactProperties = (props) => {
6006
+ const clean = {};
6007
+ const dropped = [];
6008
+ for (const [key, value] of Object.entries(props)) {
6009
+ if (value === void 0) continue;
6010
+ if (SAFE_PROPERTY_NAMES.has(key)) clean[key] = value;
6011
+ else dropped.push(key);
6012
+ }
6013
+ return {
6014
+ clean,
6015
+ dropped
6016
+ };
6017
+ };
6018
+
6019
+ //#endregion
6020
+ //#region src/telemetry/client.ts
6021
+ const POSTHOG_HOST = "https://eu.i.posthog.com";
6022
+ const POSTHOG_KEY = "phc_eY2cOMFva9q24GrWeOuvuVIOhCIdjOALxeAR3ItrqbJ";
6023
+ const SCHEMA_VERSION = "v2";
6024
+ const REQUEST_TIMEOUT_MS = 3e3;
6025
+ const isTelemetryDisabled = (config) => {
6026
+ const env = process.env;
6027
+ if (env.AISLOP_NO_TELEMETRY === "1" || env.DO_NOT_TRACK === "1") return true;
6028
+ if (config?.enabled === false) return true;
6029
+ if (config?.enabled === true) return false;
6030
+ if (env.CI === "true" || env.CI === "1") return true;
6031
+ return false;
5878
6032
  };
5879
- /** Pending telemetry request kept alive so Node doesn't exit before it completes. */
5880
- let pendingRequest = null;
5881
- const trackEvent = (event) => {
6033
+ const isDebug = () => process.env.AISLOP_TELEMETRY_DEBUG === "1";
6034
+ const pendingRequests = /* @__PURE__ */ new Set();
6035
+ let cachedInstallId = null;
6036
+ let installCreated = false;
6037
+ const baseProperties = (installId) => ({
6038
+ aislop_version: APP_VERSION,
6039
+ node_version: process.version,
6040
+ os: os.platform(),
6041
+ arch: os.arch(),
6042
+ schema_version: SCHEMA_VERSION,
6043
+ anonymous_install_id: installId,
6044
+ package_manager: detectPackageManager$1(),
6045
+ is_ci: isCiEnv()
6046
+ });
6047
+ const track = (input) => {
6048
+ if (isTelemetryDisabled(input.config)) return { installCreated: false };
6049
+ if (cachedInstallId == null) {
6050
+ const ensured = ensureInstallId(resolveInstallIdPath());
6051
+ cachedInstallId = ensured.installId;
6052
+ installCreated = ensured.created;
6053
+ }
6054
+ const { clean, dropped } = redactProperties({
6055
+ ...baseProperties(cachedInstallId),
6056
+ ...input.properties
6057
+ });
6058
+ if (isDebug()) {
6059
+ const compact = JSON.stringify({
6060
+ event: input.event,
6061
+ properties: clean
6062
+ });
6063
+ process.stderr.write(`[telemetry] ${compact}\n`);
6064
+ if (dropped.length > 0) for (const key of dropped) process.stderr.write(`[telemetry] dropped non-allowlisted property: ${key}\n`);
6065
+ }
6066
+ if (process.env.AISLOP_TELEMETRY_DRY_RUN === "1") return { installCreated };
5882
6067
  const payload = {
5883
6068
  api_key: POSTHOG_KEY,
5884
- event: `cli_${event.command}`,
5885
- distinct_id: getAnonymousId(),
5886
- properties: {
5887
- version: APP_VERSION,
5888
- node_version: process.version,
5889
- os: os.platform(),
5890
- arch: os.arch(),
5891
- languages: event.languages,
5892
- score_bucket: event.scoreBucket,
5893
- engine_issues: event.engineIssues,
5894
- engine_timings: event.engineTimings,
5895
- elapsed_ms: event.elapsedMs,
5896
- file_count: event.fileCount,
5897
- fix_steps: event.fixSteps,
5898
- fix_resolved: event.fixResolved
5899
- },
6069
+ event: input.event,
6070
+ distinct_id: cachedInstallId,
6071
+ properties: clean,
5900
6072
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
5901
6073
  };
5902
- pendingRequest = fetch(`${POSTHOG_HOST}/capture/`, {
6074
+ const request = fetch(`${POSTHOG_HOST}/capture/`, {
5903
6075
  method: "POST",
5904
6076
  headers: { "Content-Type": "application/json" },
5905
6077
  body: JSON.stringify(payload),
5906
- signal: AbortSignal.timeout(3e3)
5907
- }).then(() => {}).catch(() => {});
6078
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
6079
+ }).then(() => {}).catch(() => {}).finally(() => {
6080
+ pendingRequests.delete(request);
6081
+ });
6082
+ pendingRequests.add(request);
6083
+ return { installCreated };
6084
+ };
6085
+ const flushTelemetry = async () => {
6086
+ if (pendingRequests.size === 0) return;
6087
+ await Promise.all(pendingRequests);
6088
+ };
6089
+
6090
+ //#endregion
6091
+ //#region src/telemetry/language.ts
6092
+ const ALL_LANGUAGES = [
6093
+ "typescript",
6094
+ "javascript",
6095
+ "python",
6096
+ "java"
6097
+ ];
6098
+ const buildLanguageProperties = (detected) => {
6099
+ const present = new Set(detected);
6100
+ const summary = [...present].filter((l) => ALL_LANGUAGES.includes(l));
6101
+ summary.sort();
6102
+ return {
6103
+ language_summary: summary.join(","),
6104
+ lang_typescript: present.has("typescript"),
6105
+ lang_javascript: present.has("javascript"),
6106
+ lang_python: present.has("python"),
6107
+ lang_java: present.has("java")
6108
+ };
6109
+ };
6110
+
6111
+ //#endregion
6112
+ //#region src/telemetry/events.ts
6113
+ const buildCommandStartedProps = (input) => {
6114
+ const props = { command: input.command };
6115
+ if (input.languages) Object.assign(props, buildLanguageProperties(input.languages));
6116
+ if (typeof input.fileCount === "number") props.file_count_bucket = fileCountBucket(input.fileCount);
6117
+ return props;
6118
+ };
6119
+ const ENGINE_KEY_MAP = {
6120
+ format: "engine_format",
6121
+ lint: "engine_lint",
6122
+ "code-quality": "engine_code_quality",
6123
+ "ai-slop": "engine_ai_slop",
6124
+ architecture: "engine_architecture",
6125
+ security: "engine_security"
6126
+ };
6127
+ const flattenEngineStats = (issues, timings) => {
6128
+ const out = {};
6129
+ for (const [engine, count] of Object.entries(issues)) {
6130
+ const key = ENGINE_KEY_MAP[engine];
6131
+ if (key != null && typeof count === "number") out[`${key}_issues`] = count;
6132
+ }
6133
+ for (const [engine, ms] of Object.entries(timings)) {
6134
+ const key = ENGINE_KEY_MAP[engine];
6135
+ if (key != null && typeof ms === "number") out[`${key}_ms`] = Math.round(ms);
6136
+ }
6137
+ return out;
6138
+ };
6139
+ const buildCommandCompletedProps = (input) => {
6140
+ const props = {
6141
+ ...input.startProps,
6142
+ exit_code: input.exitCode,
6143
+ duration_ms: Math.round(input.durationMs)
6144
+ };
6145
+ if (input.errorKind) props.error_kind = input.errorKind;
6146
+ if (typeof input.score === "number") {
6147
+ props.score = input.score;
6148
+ props.score_bucket = scoreBucket(input.score);
6149
+ }
6150
+ if (typeof input.findingCount === "number") props.finding_count = input.findingCount;
6151
+ if (typeof input.errorCount === "number") props.error_count = input.errorCount;
6152
+ if (typeof input.warningCount === "number") props.warning_count = input.warningCount;
6153
+ if (typeof input.fixableCount === "number") props.fixable_count = input.fixableCount;
6154
+ if (input.engineIssues && input.engineTimings) Object.assign(props, flattenEngineStats(input.engineIssues, input.engineTimings));
6155
+ if (typeof input.fixSteps === "number") props.fix_steps = input.fixSteps;
6156
+ if (typeof input.fixResolved === "number") props.fix_resolved = input.fixResolved;
6157
+ if (typeof input.fixScoreDelta === "number") props.fix_score_delta = input.fixScoreDelta;
6158
+ return props;
6159
+ };
6160
+ const errorKindFromException = (error) => {
6161
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
6162
+ if (message.includes("timeout") || message.includes("timed out")) return "timeout";
6163
+ if (message.includes("invalid config") || message.includes("config_invalid")) return "config_invalid";
6164
+ if (message.includes("engine") && message.includes("crash")) return "engine_crash";
6165
+ return "unknown";
6166
+ };
6167
+
6168
+ //#endregion
6169
+ //#region src/telemetry/lifecycle.ts
6170
+ const withCommandLifecycle = async (start, run) => {
6171
+ const startProps = buildCommandStartedProps({
6172
+ command: start.command,
6173
+ languages: start.languages,
6174
+ fileCount: start.fileCount
6175
+ });
6176
+ track({
6177
+ event: "cli_command_started",
6178
+ properties: startProps,
6179
+ config: start.config
6180
+ });
6181
+ const startedAt = performance.now();
6182
+ try {
6183
+ const result = await run();
6184
+ const durationMs = performance.now() - startedAt;
6185
+ track({
6186
+ event: "cli_command_completed",
6187
+ properties: buildCommandCompletedProps({
6188
+ startProps,
6189
+ exitCode: result.exitCode,
6190
+ durationMs,
6191
+ score: result.score,
6192
+ findingCount: result.findingCount,
6193
+ errorCount: result.errorCount,
6194
+ warningCount: result.warningCount,
6195
+ fixableCount: result.fixableCount,
6196
+ engineIssues: result.engineIssues,
6197
+ engineTimings: result.engineTimings,
6198
+ fixSteps: result.fixSteps,
6199
+ fixResolved: result.fixResolved,
6200
+ fixScoreDelta: result.fixScoreDelta
6201
+ }),
6202
+ config: start.config
6203
+ });
6204
+ await flushTelemetry();
6205
+ return result;
6206
+ } catch (error) {
6207
+ track({
6208
+ event: "cli_command_completed",
6209
+ properties: buildCommandCompletedProps({
6210
+ startProps,
6211
+ exitCode: 1,
6212
+ durationMs: performance.now() - startedAt,
6213
+ errorKind: errorKindFromException(error)
6214
+ }),
6215
+ config: start.config
6216
+ });
6217
+ await flushTelemetry();
6218
+ throw error;
6219
+ }
5908
6220
  };
5909
6221
 
5910
6222
  //#endregion
@@ -6275,7 +6587,6 @@ const buildScanRender = (input) => {
6275
6587
  }, deps)}`;
6276
6588
  };
6277
6589
  const scanCommand = async (directory, config, options) => {
6278
- const startTime = performance.now();
6279
6590
  const resolvedDir = path.resolve(directory);
6280
6591
  if (!fs.existsSync(resolvedDir)) {
6281
6592
  const msg = `Path does not exist: ${resolvedDir}`;
@@ -6289,9 +6600,18 @@ const scanCommand = async (directory, config, options) => {
6289
6600
  else log.error(msg);
6290
6601
  return { exitCode: 1 };
6291
6602
  }
6603
+ const projectInfo = await discoverProject(resolvedDir);
6604
+ return withCommandLifecycle({
6605
+ command: options.command ?? "scan",
6606
+ config: config.telemetry,
6607
+ languages: projectInfo.languages,
6608
+ fileCount: projectInfo.sourceFileCount
6609
+ }, () => runScanBody(resolvedDir, config, options, projectInfo));
6610
+ };
6611
+ const runScanBody = async (resolvedDir, config, options, projectInfo) => {
6612
+ const startTime = performance.now();
6292
6613
  const showHeader = options.showHeader !== false;
6293
6614
  const useLiveProgress = !options.json && shouldUseSpinner();
6294
- const projectInfo = await discoverProject(resolvedDir);
6295
6615
  let files;
6296
6616
  if (options.staged) {
6297
6617
  files = filterProjectFiles(resolvedDir, getStagedFiles(resolvedDir), [], config.exclude);
@@ -6358,28 +6678,27 @@ const scanCommand = async (directory, config, options) => {
6358
6678
  const elapsedMs = performance.now() - startTime;
6359
6679
  const scoreResult = calculateScore(allDiagnostics, config.scoring.weights, config.scoring.thresholds, projectInfo.sourceFileCount, config.scoring.smoothing);
6360
6680
  const exitCode = allDiagnostics.some((d) => d.severity === "error") || scoreResult.score < config.ci.failBelow ? 1 : 0;
6361
- if (!isTelemetryDisabled(config.telemetry?.enabled)) {
6362
- const engineIssues = {};
6363
- const engineTimings = {};
6364
- for (const r of results) {
6365
- engineIssues[r.engine] = r.diagnostics.length;
6366
- engineTimings[r.engine] = Math.round(r.elapsed);
6367
- }
6368
- trackEvent({
6369
- command: options.command ?? "scan",
6370
- languages: projectInfo.languages,
6371
- scoreBucket: getScoreBucket(scoreResult.score),
6372
- engineIssues,
6373
- engineTimings,
6374
- elapsedMs: Math.round(elapsedMs),
6375
- fileCount: projectInfo.sourceFileCount
6376
- });
6377
- }
6681
+ const engineIssues = {};
6682
+ const engineTimings = {};
6683
+ for (const r of results) {
6684
+ engineIssues[r.engine] = r.diagnostics.length;
6685
+ engineTimings[r.engine] = Math.round(r.elapsed);
6686
+ }
6687
+ const completion = {
6688
+ exitCode,
6689
+ score: scoreResult.score,
6690
+ findingCount: allDiagnostics.length,
6691
+ errorCount: allDiagnostics.filter((d) => d.severity === "error").length,
6692
+ warningCount: allDiagnostics.filter((d) => d.severity === "warning").length,
6693
+ fixableCount: allDiagnostics.filter((d) => d.fixable).length,
6694
+ engineIssues,
6695
+ engineTimings
6696
+ };
6378
6697
  if (options.json) {
6379
- const { buildJsonOutput } = await import("./json-DxLkV8n2.js");
6698
+ const { buildJsonOutput } = await import("./json-DZfGz2xa.js");
6380
6699
  const jsonOut = buildJsonOutput(results, scoreResult, projectInfo.sourceFileCount, elapsedMs);
6381
6700
  console.log(JSON.stringify(jsonOut, null, 2));
6382
- return { exitCode };
6701
+ return completion;
6383
6702
  }
6384
6703
  const projectName = projectInfo.projectName ?? "project";
6385
6704
  const language = projectInfo.languages[0] ?? "unknown";
@@ -6396,7 +6715,7 @@ const scanCommand = async (directory, config, options) => {
6396
6715
  includeHeader: showHeader,
6397
6716
  printBrand: options.printBrand
6398
6717
  }));
6399
- return { exitCode };
6718
+ return completion;
6400
6719
  };
6401
6720
 
6402
6721
  //#endregion
@@ -7741,15 +8060,23 @@ const fixCommand = async (directory, config, options = {
7741
8060
  verbose: false,
7742
8061
  showHeader: true
7743
8062
  }) => {
7744
- const startTime = performance.now();
7745
8063
  const resolvedDir = path.resolve(directory);
7746
8064
  if (!fs.existsSync(resolvedDir) || !fs.statSync(resolvedDir).isDirectory()) {
7747
8065
  const msg = !fs.existsSync(resolvedDir) ? `Path does not exist: ${resolvedDir}` : `Not a directory: ${resolvedDir}`;
7748
8066
  log.error(msg);
7749
8067
  return;
7750
8068
  }
7751
- const showHeader = options.showHeader !== false;
7752
8069
  const projectInfo = await discoverProject(resolvedDir);
8070
+ await withCommandLifecycle({
8071
+ command: "fix",
8072
+ config: config.telemetry,
8073
+ languages: projectInfo.languages,
8074
+ fileCount: projectInfo.sourceFileCount
8075
+ }, () => runFixBody(resolvedDir, config, options, projectInfo));
8076
+ };
8077
+ const runFixBody = async (resolvedDir, config, options, projectInfo) => {
8078
+ const startTime = performance.now();
8079
+ const showHeader = options.showHeader !== false;
7753
8080
  const projectName = projectInfo.projectName ?? "project";
7754
8081
  if (showHeader) process.stdout.write(renderHeader({
7755
8082
  version: APP_VERSION,
@@ -7786,12 +8113,6 @@ const fixCommand = async (directory, config, options = {
7786
8113
  await runFormattingStep(pipelineDeps);
7787
8114
  await runForceSteps(pipelineDeps);
7788
8115
  const totalResolved = steps.reduce((sum, s) => sum + s.resolvedIssues, 0);
7789
- if (!isTelemetryDisabled(config.telemetry?.enabled)) trackEvent({
7790
- command: "fix",
7791
- languages: projectInfo.languages,
7792
- fixSteps: steps.length,
7793
- fixResolved: totalResolved
7794
- });
7795
8116
  const configDir = findConfigDir(resolvedDir);
7796
8117
  const rulesPath = configDir ? path.join(configDir, RULES_FILE) : void 0;
7797
8118
  const engineConfig = {
@@ -7814,7 +8135,9 @@ const fixCommand = async (directory, config, options = {
7814
8135
  });
7815
8136
  const allDiagnostics = scanResults.flatMap((r) => r.diagnostics);
7816
8137
  const scoreResult = calculateScore(allDiagnostics, config.scoring.weights, config.scoring.thresholds, projectInfo.sourceFileCount, config.scoring.smoothing);
7817
- const remaining = allDiagnostics.filter((d) => d.severity === "error").length + allDiagnostics.filter((d) => d.severity === "warning").length;
8138
+ const errors = allDiagnostics.filter((d) => d.severity === "error").length;
8139
+ const warnings = allDiagnostics.filter((d) => d.severity === "warning").length;
8140
+ const remaining = errors + warnings;
7818
8141
  if (steps.length === 0) rail.complete({
7819
8142
  status: "skipped",
7820
8143
  label: "No applicable auto-fixers found"
@@ -7843,12 +8166,31 @@ const fixCommand = async (directory, config, options = {
7843
8166
  }
7844
8167
  if (options.agent) {
7845
8168
  launchAgent(options.agent, resolvedDir, allDiagnostics, scoreResult.score);
7846
- return;
8169
+ return {
8170
+ exitCode: 0,
8171
+ score: scoreResult.score,
8172
+ fixSteps: steps.length,
8173
+ fixResolved: totalResolved
8174
+ };
7847
8175
  }
7848
8176
  if (options.prompt) {
7849
8177
  printPrompt(resolvedDir, allDiagnostics, scoreResult.score);
7850
- return;
8178
+ return {
8179
+ exitCode: 0,
8180
+ score: scoreResult.score,
8181
+ fixSteps: steps.length,
8182
+ fixResolved: totalResolved
8183
+ };
7851
8184
  }
8185
+ return {
8186
+ exitCode: 0,
8187
+ score: scoreResult.score,
8188
+ findingCount: allDiagnostics.length,
8189
+ errorCount: errors,
8190
+ warningCount: warnings,
8191
+ fixSteps: steps.length,
8192
+ fixResolved: totalResolved
8193
+ };
7852
8194
  };
7853
8195
 
7854
8196
  //#endregion
@@ -1,4 +1,4 @@
1
- import { n as ENGINE_INFO, t as APP_VERSION } from "./version-G3ekYjY1.js";
1
+ import { n as ENGINE_INFO, t as APP_VERSION } from "./version-D_rqBdyj.js";
2
2
 
3
3
  //#region src/output/json.ts
4
4
  const buildJsonOutput = (results, scoreResult, fileCount, elapsedMs) => {
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as ENGINE_INFO, t as APP_VERSION } from "./cli.js";
2
+ import { r as APP_VERSION, t as ENGINE_INFO } from "./cli.js";
3
3
 
4
4
  //#region src/output/json.ts
5
5
  const buildJsonOutput = (results, scoreResult, fileCount, elapsedMs) => {