aislop 0.8.3 → 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 = {
@@ -5094,7 +5095,225 @@ const handleAislopBaseline = (input) => {
5094
5095
 
5095
5096
  //#endregion
5096
5097
  //#region src/version.ts
5097
- const APP_VERSION = "0.8.3";
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
+ };
5098
5317
 
5099
5318
  //#endregion
5100
5319
  //#region src/mcp.ts
@@ -5109,10 +5328,29 @@ const err = (message) => ({
5109
5328
  }],
5110
5329
  isError: true
5111
5330
  });
5112
- const tryHandle = async (fn) => {
5331
+ const instrument = async (tool, fn) => {
5332
+ const startedAt = performance.now();
5113
5333
  try {
5114
- 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);
5115
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
+ });
5116
5354
  return err(e instanceof Error ? e.message : String(e));
5117
5355
  }
5118
5356
  };
@@ -5124,25 +5362,27 @@ const buildServer = () => {
5124
5362
  server.registerTool(aislopScanTool.name, {
5125
5363
  description: aislopScanTool.description,
5126
5364
  inputSchema: aislopScanInputSchema.shape
5127
- }, (input) => tryHandle(() => handleAislopScan(input)));
5365
+ }, (input) => instrument("aislop_scan", () => handleAislopScan(input)));
5128
5366
  server.registerTool(aislopFixTool.name, {
5129
5367
  description: aislopFixTool.description,
5130
5368
  inputSchema: aislopFixInputSchema.shape
5131
- }, (input) => tryHandle(() => handleAislopFix(input)));
5369
+ }, (input) => instrument("aislop_fix", () => handleAislopFix(input)));
5132
5370
  server.registerTool(aislopWhyTool.name, {
5133
5371
  description: aislopWhyTool.description,
5134
5372
  inputSchema: aislopWhyInputSchema.shape
5135
- }, (input) => tryHandle(() => handleAislopWhy(input)));
5373
+ }, (input) => instrument("aislop_why", () => handleAislopWhy(input)));
5136
5374
  server.registerTool(aislopBaselineTool.name, {
5137
5375
  description: aislopBaselineTool.description,
5138
5376
  inputSchema: aislopBaselineInputSchema.shape
5139
- }, (input) => tryHandle(() => handleAislopBaseline(input)));
5377
+ }, (input) => instrument("aislop_baseline", () => handleAislopBaseline(input)));
5140
5378
  return server;
5141
5379
  };
5142
5380
  const main = async () => {
5143
5381
  const server = buildServer();
5144
5382
  const transport = new StdioServerTransport();
5145
5383
  await server.connect(transport);
5384
+ track({ event: "mcp_server_started" });
5385
+ await flushTelemetry();
5146
5386
  };
5147
5387
  main().catch((e) => {
5148
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.3";
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.3",
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": {