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/mcp.js CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { n as runSubprocess, t as isToolInstalled } from "./subprocess-CCnnN_oQ.js";
3
3
  import { createRequire, isBuiltin } from "node:module";
4
+ import { performance } from "node:perf_hooks";
4
5
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
7
  import path from "node:path";
@@ -9,11 +10,11 @@ import { z } from "zod";
9
10
  import fs from "node:fs";
10
11
  import YAML from "yaml";
11
12
  import { z as z$1 } from "zod/v4";
12
- import { performance } from "node:perf_hooks";
13
13
  import micromatch from "micromatch";
14
14
  import { fileURLToPath } from "node:url";
15
15
  import os from "node:os";
16
16
  import "typescript";
17
+ import { randomUUID } from "node:crypto";
17
18
 
18
19
  //#region src/config/defaults.ts
19
20
  const DEFAULT_CONFIG = {
@@ -1253,6 +1254,27 @@ const collectJsDeps = (rootDir, jsDeps) => {
1253
1254
  collectNestedManifests(rootDir, jsDeps);
1254
1255
  return true;
1255
1256
  };
1257
+ const TS_CONFIG_FILES = ["tsconfig.json", "jsconfig.json"];
1258
+ const buildAliasMatcher = (key) => {
1259
+ const starIdx = key.indexOf("*");
1260
+ if (starIdx === -1) return (spec) => spec === key;
1261
+ const before = key.slice(0, starIdx);
1262
+ const after = key.slice(starIdx + 1);
1263
+ return (spec) => spec.length >= before.length + after.length && spec.startsWith(before) && spec.endsWith(after);
1264
+ };
1265
+ const collectAliasMatchersFromConfig = (configPath, matchers) => {
1266
+ const opts = readJson(configPath)?.compilerOptions;
1267
+ if (!opts || typeof opts !== "object") return;
1268
+ const paths = opts.paths;
1269
+ if (!paths || typeof paths !== "object") return;
1270
+ for (const key of Object.keys(paths)) matchers.push(buildAliasMatcher(key));
1271
+ };
1272
+ const collectTsPathAliases = (rootDir) => {
1273
+ const matchers = [];
1274
+ const dirs = [rootDir, ...expandWorkspaceDirs(rootDir, readWorkspaceGlobs(rootDir, readJson(path.join(rootDir, "package.json"))))];
1275
+ for (const dir of dirs) for (const fname of TS_CONFIG_FILES) collectAliasMatchersFromConfig(path.join(dir, fname), matchers);
1276
+ return matchers;
1277
+ };
1256
1278
  const addPyDep = (pyDeps, name) => {
1257
1279
  const normalized = name.toLowerCase().replace(/_/g, "-");
1258
1280
  pyDeps.add(normalized);
@@ -1410,10 +1432,11 @@ const extractPyImports = (content) => {
1410
1432
  }
1411
1433
  return results;
1412
1434
  };
1413
- const checkJsImport = (spec, manifest) => {
1435
+ const checkJsImport = (spec, manifest, tsAliasMatchers) => {
1414
1436
  if (isJsRelativeOrAbsolute(spec)) return null;
1415
1437
  if (isJsBuiltin(spec)) return null;
1416
1438
  if (isJsVirtualModule(spec)) return null;
1439
+ if (tsAliasMatchers.some((m) => m(spec))) return null;
1417
1440
  const pkg = packageNameFromImport(spec);
1418
1441
  if (manifest.jsDeps.has(pkg)) return null;
1419
1442
  if (pkg.startsWith("@types/")) {
@@ -1434,6 +1457,7 @@ const checkPyImport = (spec, manifest) => {
1434
1457
  const detectHallucinatedImports = async (context) => {
1435
1458
  const manifest = loadManifest(context.rootDirectory);
1436
1459
  if (!manifest.hasJsManifest && !manifest.hasPyManifest) return [];
1460
+ const tsAliasMatchers = manifest.hasJsManifest ? collectTsPathAliases(context.rootDirectory) : [];
1437
1461
  const diagnostics = [];
1438
1462
  const files = getSourceFiles(context);
1439
1463
  for (const filePath of files) {
@@ -1453,7 +1477,7 @@ const detectHallucinatedImports = async (context) => {
1453
1477
  const relPath = path.relative(context.rootDirectory, filePath);
1454
1478
  const imports = isJs ? extractJsImports(content) : extractPyImports(content);
1455
1479
  for (const { spec, line } of imports) {
1456
- const hallucinated = isJs ? checkJsImport(spec, manifest) : checkPyImport(spec, manifest);
1480
+ const hallucinated = isJs ? checkJsImport(spec, manifest, tsAliasMatchers) : checkPyImport(spec, manifest);
1457
1481
  if (!hallucinated) continue;
1458
1482
  const manifestLabel = isJs ? "package.json" : "requirements.txt / pyproject.toml / Pipfile";
1459
1483
  diagnostics.push({
@@ -5071,7 +5095,225 @@ const handleAislopBaseline = (input) => {
5071
5095
 
5072
5096
  //#endregion
5073
5097
  //#region src/version.ts
5074
- const APP_VERSION = "0.8.2";
5098
+ const APP_VERSION = "0.9.0";
5099
+
5100
+ //#endregion
5101
+ //#region src/telemetry/env.ts
5102
+ const detectPackageManager = (env = process.env) => {
5103
+ const execPath = env.npm_execpath ?? "";
5104
+ if (execPath.includes("npx")) return "npx";
5105
+ const userAgent = env.npm_config_user_agent ?? "";
5106
+ if (userAgent.startsWith("pnpm/")) return "pnpm";
5107
+ if (userAgent.startsWith("yarn/")) return "yarn";
5108
+ if (userAgent.startsWith("bun/")) return "bun";
5109
+ if (userAgent.startsWith("npm/")) return "npm";
5110
+ if (execPath.includes("pnpm")) return "pnpm";
5111
+ if (execPath.includes("yarn")) return "yarn";
5112
+ if (execPath.includes("bun")) return "bun";
5113
+ if (execPath.includes("npm")) return "npm";
5114
+ return "unknown";
5115
+ };
5116
+ const CI_ENV_KEYS = [
5117
+ "CI",
5118
+ "GITHUB_ACTIONS",
5119
+ "GITLAB_CI",
5120
+ "CIRCLECI",
5121
+ "TRAVIS",
5122
+ "BUILDKITE",
5123
+ "DRONE",
5124
+ "TEAMCITY_VERSION",
5125
+ "TF_BUILD"
5126
+ ];
5127
+ const isCiEnv = (env = process.env) => CI_ENV_KEYS.some((k) => {
5128
+ const v = env[k];
5129
+ return v === "true" || v === "1" || v != null && v.length > 0 && k !== "CI";
5130
+ }) || env.CI === "true" || env.CI === "1";
5131
+
5132
+ //#endregion
5133
+ //#region src/telemetry/identity.ts
5134
+ const FILE_BASENAME = "install_id";
5135
+ const resolveInstallIdPath = (homedir = os.homedir(), env = process.env) => {
5136
+ if (process.platform === "linux" && env.XDG_STATE_HOME) return path.join(env.XDG_STATE_HOME, "aislop", FILE_BASENAME);
5137
+ return path.join(homedir, ".aislop", FILE_BASENAME);
5138
+ };
5139
+ const ensureInstallId = (idPath = resolveInstallIdPath()) => {
5140
+ if (fs.existsSync(idPath)) {
5141
+ const existing = fs.readFileSync(idPath, "utf-8").trim();
5142
+ if (existing.length > 0) return {
5143
+ installId: existing,
5144
+ created: false
5145
+ };
5146
+ }
5147
+ const dir = path.dirname(idPath);
5148
+ fs.mkdirSync(dir, { recursive: true });
5149
+ const installId = randomUUID();
5150
+ const tmpPath = `${idPath}.${process.pid}.tmp`;
5151
+ fs.writeFileSync(tmpPath, `${installId}\n`, { mode: 384 });
5152
+ try {
5153
+ fs.renameSync(tmpPath, idPath);
5154
+ return {
5155
+ installId,
5156
+ created: true
5157
+ };
5158
+ } catch {
5159
+ fs.rmSync(tmpPath, { force: true });
5160
+ return {
5161
+ installId: fs.readFileSync(idPath, "utf-8").trim(),
5162
+ created: false
5163
+ };
5164
+ }
5165
+ };
5166
+
5167
+ //#endregion
5168
+ //#region src/telemetry/redaction.ts
5169
+ const SAFE_PROPERTY_NAMES = new Set([
5170
+ "aislop_version",
5171
+ "node_version",
5172
+ "os",
5173
+ "arch",
5174
+ "schema_version",
5175
+ "anonymous_install_id",
5176
+ "package_manager",
5177
+ "is_ci",
5178
+ "command",
5179
+ "language_summary",
5180
+ "lang_typescript",
5181
+ "lang_javascript",
5182
+ "lang_python",
5183
+ "lang_java",
5184
+ "file_count_bucket",
5185
+ "exit_code",
5186
+ "duration_ms",
5187
+ "error_kind",
5188
+ "score",
5189
+ "score_bucket",
5190
+ "finding_count",
5191
+ "error_count",
5192
+ "warning_count",
5193
+ "fixable_count",
5194
+ "fix_steps",
5195
+ "fix_resolved",
5196
+ "fix_score_delta",
5197
+ "engine_format_issues",
5198
+ "engine_format_ms",
5199
+ "engine_lint_issues",
5200
+ "engine_lint_ms",
5201
+ "engine_code_quality_issues",
5202
+ "engine_code_quality_ms",
5203
+ "engine_ai_slop_issues",
5204
+ "engine_ai_slop_ms",
5205
+ "engine_architecture_issues",
5206
+ "engine_architecture_ms",
5207
+ "engine_security_issues",
5208
+ "engine_security_ms",
5209
+ "tool",
5210
+ "ok",
5211
+ "agent",
5212
+ "score_delta"
5213
+ ]);
5214
+ const redactProperties = (props) => {
5215
+ const clean = {};
5216
+ const dropped = [];
5217
+ for (const [key, value] of Object.entries(props)) {
5218
+ if (value === void 0) continue;
5219
+ if (SAFE_PROPERTY_NAMES.has(key)) clean[key] = value;
5220
+ else dropped.push(key);
5221
+ }
5222
+ return {
5223
+ clean,
5224
+ dropped
5225
+ };
5226
+ };
5227
+
5228
+ //#endregion
5229
+ //#region src/telemetry/client.ts
5230
+ const POSTHOG_HOST = "https://eu.i.posthog.com";
5231
+ const POSTHOG_KEY = "phc_eY2cOMFva9q24GrWeOuvuVIOhCIdjOALxeAR3ItrqbJ";
5232
+ const SCHEMA_VERSION = "v2";
5233
+ const REQUEST_TIMEOUT_MS = 3e3;
5234
+ const isTelemetryDisabled = (config) => {
5235
+ const env = process.env;
5236
+ if (env.AISLOP_NO_TELEMETRY === "1" || env.DO_NOT_TRACK === "1") return true;
5237
+ if (config?.enabled === false) return true;
5238
+ if (config?.enabled === true) return false;
5239
+ if (env.CI === "true" || env.CI === "1") return true;
5240
+ return false;
5241
+ };
5242
+ const isDebug = () => process.env.AISLOP_TELEMETRY_DEBUG === "1";
5243
+ const pendingRequests = /* @__PURE__ */ new Set();
5244
+ let cachedInstallId = null;
5245
+ let installCreated = false;
5246
+ const baseProperties = (installId) => ({
5247
+ aislop_version: APP_VERSION,
5248
+ node_version: process.version,
5249
+ os: os.platform(),
5250
+ arch: os.arch(),
5251
+ schema_version: SCHEMA_VERSION,
5252
+ anonymous_install_id: installId,
5253
+ package_manager: detectPackageManager(),
5254
+ is_ci: isCiEnv()
5255
+ });
5256
+ const track = (input) => {
5257
+ if (isTelemetryDisabled(input.config)) return { installCreated: false };
5258
+ if (cachedInstallId == null) {
5259
+ const ensured = ensureInstallId(resolveInstallIdPath());
5260
+ cachedInstallId = ensured.installId;
5261
+ installCreated = ensured.created;
5262
+ }
5263
+ const { clean, dropped } = redactProperties({
5264
+ ...baseProperties(cachedInstallId),
5265
+ ...input.properties
5266
+ });
5267
+ if (isDebug()) {
5268
+ const compact = JSON.stringify({
5269
+ event: input.event,
5270
+ properties: clean
5271
+ });
5272
+ process.stderr.write(`[telemetry] ${compact}\n`);
5273
+ if (dropped.length > 0) for (const key of dropped) process.stderr.write(`[telemetry] dropped non-allowlisted property: ${key}\n`);
5274
+ }
5275
+ if (process.env.AISLOP_TELEMETRY_DRY_RUN === "1") return { installCreated };
5276
+ const payload = {
5277
+ api_key: POSTHOG_KEY,
5278
+ event: input.event,
5279
+ distinct_id: cachedInstallId,
5280
+ properties: clean,
5281
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
5282
+ };
5283
+ const request = fetch(`${POSTHOG_HOST}/capture/`, {
5284
+ method: "POST",
5285
+ headers: { "Content-Type": "application/json" },
5286
+ body: JSON.stringify(payload),
5287
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
5288
+ }).then(() => {}).catch(() => {}).finally(() => {
5289
+ pendingRequests.delete(request);
5290
+ });
5291
+ pendingRequests.add(request);
5292
+ return { installCreated };
5293
+ };
5294
+ const flushTelemetry = async () => {
5295
+ if (pendingRequests.size === 0) return;
5296
+ await Promise.all(pendingRequests);
5297
+ };
5298
+
5299
+ //#endregion
5300
+ //#region src/telemetry/events.ts
5301
+ const buildMcpToolCalledProps = (input) => {
5302
+ const props = {
5303
+ tool: input.tool,
5304
+ duration_ms: Math.round(input.durationMs),
5305
+ ok: input.ok
5306
+ };
5307
+ if (input.errorKind) props.error_kind = input.errorKind;
5308
+ return props;
5309
+ };
5310
+ const errorKindFromException = (error) => {
5311
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
5312
+ if (message.includes("timeout") || message.includes("timed out")) return "timeout";
5313
+ if (message.includes("invalid config") || message.includes("config_invalid")) return "config_invalid";
5314
+ if (message.includes("engine") && message.includes("crash")) return "engine_crash";
5315
+ return "unknown";
5316
+ };
5075
5317
 
5076
5318
  //#endregion
5077
5319
  //#region src/mcp.ts
@@ -5086,10 +5328,29 @@ const err = (message) => ({
5086
5328
  }],
5087
5329
  isError: true
5088
5330
  });
5089
- const tryHandle = async (fn) => {
5331
+ const instrument = async (tool, fn) => {
5332
+ const startedAt = performance.now();
5090
5333
  try {
5091
- return ok(await fn());
5334
+ const value = await fn();
5335
+ track({
5336
+ event: "mcp_tool_called",
5337
+ properties: buildMcpToolCalledProps({
5338
+ tool,
5339
+ durationMs: performance.now() - startedAt,
5340
+ ok: true
5341
+ })
5342
+ });
5343
+ return ok(value);
5092
5344
  } catch (e) {
5345
+ track({
5346
+ event: "mcp_tool_called",
5347
+ properties: buildMcpToolCalledProps({
5348
+ tool,
5349
+ durationMs: performance.now() - startedAt,
5350
+ ok: false,
5351
+ errorKind: errorKindFromException(e)
5352
+ })
5353
+ });
5093
5354
  return err(e instanceof Error ? e.message : String(e));
5094
5355
  }
5095
5356
  };
@@ -5101,25 +5362,27 @@ const buildServer = () => {
5101
5362
  server.registerTool(aislopScanTool.name, {
5102
5363
  description: aislopScanTool.description,
5103
5364
  inputSchema: aislopScanInputSchema.shape
5104
- }, (input) => tryHandle(() => handleAislopScan(input)));
5365
+ }, (input) => instrument("aislop_scan", () => handleAislopScan(input)));
5105
5366
  server.registerTool(aislopFixTool.name, {
5106
5367
  description: aislopFixTool.description,
5107
5368
  inputSchema: aislopFixInputSchema.shape
5108
- }, (input) => tryHandle(() => handleAislopFix(input)));
5369
+ }, (input) => instrument("aislop_fix", () => handleAislopFix(input)));
5109
5370
  server.registerTool(aislopWhyTool.name, {
5110
5371
  description: aislopWhyTool.description,
5111
5372
  inputSchema: aislopWhyInputSchema.shape
5112
- }, (input) => tryHandle(() => handleAislopWhy(input)));
5373
+ }, (input) => instrument("aislop_why", () => handleAislopWhy(input)));
5113
5374
  server.registerTool(aislopBaselineTool.name, {
5114
5375
  description: aislopBaselineTool.description,
5115
5376
  inputSchema: aislopBaselineInputSchema.shape
5116
- }, (input) => tryHandle(() => handleAislopBaseline(input)));
5377
+ }, (input) => instrument("aislop_baseline", () => handleAislopBaseline(input)));
5117
5378
  return server;
5118
5379
  };
5119
5380
  const main = async () => {
5120
5381
  const server = buildServer();
5121
5382
  const transport = new StdioServerTransport();
5122
5383
  await server.connect(transport);
5384
+ track({ event: "mcp_server_started" });
5385
+ await flushTelemetry();
5123
5386
  };
5124
5387
  main().catch((e) => {
5125
5388
  process.stderr.write(`aislop-mcp failed to start: ${e instanceof Error ? e.message : String(e)}\n`);
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { r as runSubprocess } from "./cli.js";
2
+ import { n as runSubprocess } from "./cli.js";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
 
@@ -29,7 +29,7 @@ const getEngineLabel = (engine) => ENGINE_INFO[engine].label;
29
29
 
30
30
  //#endregion
31
31
  //#region src/version.ts
32
- const APP_VERSION = "0.8.2";
32
+ const APP_VERSION = "0.9.0";
33
33
 
34
34
  //#endregion
35
35
  export { ENGINE_INFO as n, getEngineLabel as r, APP_VERSION as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aislop",
3
- "version": "0.8.2",
3
+ "version": "0.9.0",
4
4
  "description": "The engineering standards layer and quality gate for AI-written code. Define your standard once. Every agent — Claude Code, Cursor, Codex — is held to it automatically, on every edit and every PR. Catches the slop they leave behind, enforces the rules your team sets. 8+ languages. Deterministic.",
5
5
  "type": "module",
6
6
  "bin": {