@whenlabs/when 0.9.2 → 0.10.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,14 +1,14 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ findBin
4
+ } from "./chunk-JOMP6AU5.js";
2
5
 
3
- // src/mcp.ts
6
+ // src/mcp/index.ts
4
7
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
8
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
- import { z } from "zod";
7
- import { spawn } from "child_process";
8
- import { resolve, dirname, join } from "path";
9
- import { existsSync, mkdirSync, writeFileSync, readFileSync } from "fs";
9
+ import { readFileSync as readFileSync2 } from "fs";
10
+ import { resolve, dirname } from "path";
10
11
  import { fileURLToPath } from "url";
11
- import { homedir } from "os";
12
12
  import {
13
13
  initDb,
14
14
  TaskQueries,
@@ -18,15 +18,15 @@ import {
18
18
  registerStats,
19
19
  registerHistory
20
20
  } from "@whenlabs/velocity-mcp/lib";
21
- var __dirname = dirname(fileURLToPath(import.meta.url));
22
- function findBin(name) {
23
- const pkgRoot = resolve(__dirname, "..");
24
- const localBin = resolve(pkgRoot, "node_modules", ".bin", name);
25
- if (existsSync(localBin)) return localBin;
26
- const directCli = resolve(pkgRoot, "node_modules", "@whenlabs", name, "dist", "cli.js");
27
- if (existsSync(directCli)) return directCli;
28
- return name;
29
- }
21
+
22
+ // src/mcp/stale.ts
23
+ import { z } from "zod";
24
+
25
+ // src/mcp/run-cli.ts
26
+ import { spawn } from "child_process";
27
+ import { join } from "path";
28
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync } from "fs";
29
+ import { homedir } from "os";
30
30
  function runCli(bin, args, cwd) {
31
31
  return new Promise((res) => {
32
32
  const child = spawn(findBin(bin), args, {
@@ -64,6 +64,12 @@ function readAwareProjectName(path) {
64
64
  return null;
65
65
  }
66
66
  }
67
+ function formatOutput(result) {
68
+ const parts = [];
69
+ if (result.stdout.trim()) parts.push(result.stdout.trim());
70
+ if (result.stderr.trim()) parts.push(result.stderr.trim());
71
+ return parts.join("\n") || "No output";
72
+ }
67
73
  async function checkTriggers(toolName, result, path) {
68
74
  const output = result.stdout || result.stderr || "";
69
75
  const extras = [];
@@ -94,13 +100,743 @@ ${staleOutput}`);
94
100
  extras.push(`
95
101
  Note: Conflicts found in project "${projectName}".`);
96
102
  }
103
+ try {
104
+ const cacheFiles = readdirSync(CACHE_DIR).filter((f) => f.startsWith("stale_"));
105
+ for (const cacheFile of cacheFiles) {
106
+ const cached = JSON.parse(readFileSync(join(CACHE_DIR, cacheFile), "utf8"));
107
+ if (/\b\d{4,5}\b/.test(cached.output || "")) {
108
+ extras.push("\nTip: Port references found in documentation \u2014 stale_scan may need re-run after resolving conflicts.");
109
+ break;
110
+ }
111
+ }
112
+ } catch {
113
+ }
114
+ }
115
+ }
116
+ if (toolName === "envalid_detect") {
117
+ const serviceUrlMatches = output.match(/\b[A-Z_]*(?:HOST|PORT|URL|URI)[A-Z_]*\b/g);
118
+ if (serviceUrlMatches && serviceUrlMatches.length > 0) {
119
+ const examples = [...new Set(serviceUrlMatches)].slice(0, 3).join(", ");
120
+ extras.push(`
121
+ Tip: Service URLs detected (${examples}, etc.) \u2014 run berth_register to track their ports for conflict detection.`);
122
+ }
123
+ }
124
+ if (toolName === "velocity_end_task") {
125
+ const largeChange = /actual_files["\s:]+([1-9]\d)/i.test(output) || /\b([6-9]|\d{2,})\s+files?\b/i.test(output);
126
+ if (largeChange) {
127
+ extras.push("\nTip: Large change detected \u2014 consider running stale_scan to check for documentation drift.");
128
+ }
129
+ }
130
+ if (toolName === "vow_scan") {
131
+ const cacheFile = join(CACHE_DIR, `vow_scan_${deriveProject(path)}.json`);
132
+ const isFirstScan = !existsSync(cacheFile);
133
+ const hasNewPackages = /new package|added|installed/i.test(output);
134
+ if (isFirstScan || hasNewPackages) {
135
+ extras.push("\nTip: Dependency changes detected \u2014 run aware_sync to update AI context files with new library info.");
97
136
  }
98
137
  }
99
138
  return extras;
100
139
  }
140
+
141
+ // src/mcp/stale.ts
142
+ function registerStaleTools(server2) {
143
+ server2.tool(
144
+ "stale_scan",
145
+ "Scan for documentation drift \u2014 detect when docs say one thing and code says another",
146
+ {
147
+ path: z.string().optional().describe("Project directory to scan (defaults to cwd)"),
148
+ deep: z.coerce.boolean().optional().describe("Enable AI-powered deep analysis"),
149
+ git: z.coerce.boolean().optional().describe("Enable git history staleness checks"),
150
+ format: z.enum(["terminal", "json", "markdown", "sarif"]).optional().describe("Output format")
151
+ },
152
+ async ({ path, deep, git, format }) => {
153
+ const args = ["scan"];
154
+ if (deep) args.push("--deep");
155
+ if (git) args.push("--git");
156
+ if (format) args.push("--format", format);
157
+ const result = await runCli("stale", args, path);
158
+ const output = formatOutput(result);
159
+ writeCache("stale", deriveProject(path), output, result.code);
160
+ const extras = await checkTriggers("stale_scan", result, path);
161
+ return { content: [{ type: "text", text: output + extras.join("") }] };
162
+ }
163
+ );
164
+ server2.tool(
165
+ "stale_fix",
166
+ "Auto-fix documentation drift \u2014 generate fixes for wrong file paths, dead links, phantom env vars, outdated scripts",
167
+ {
168
+ path: z.string().optional().describe("Project directory to scan (defaults to cwd)"),
169
+ format: z.enum(["terminal", "diff"]).optional().describe("Output format (default: terminal)"),
170
+ apply: z.coerce.boolean().optional().describe("Apply high-confidence fixes directly"),
171
+ dryRun: z.coerce.boolean().optional().describe("Show what --apply would do without writing")
172
+ },
173
+ async ({ path, format, apply, dryRun }) => {
174
+ const args = ["fix"];
175
+ if (format) args.push("--format", format);
176
+ if (apply) args.push("--apply");
177
+ if (dryRun) args.push("--dry-run");
178
+ const result = await runCli("stale", args, path);
179
+ const output = formatOutput(result);
180
+ return { content: [{ type: "text", text: output }] };
181
+ }
182
+ );
183
+ server2.tool(
184
+ "stale_init",
185
+ "Generate a .stale.yml config file for customizing documentation drift detection",
186
+ { path: z.string().optional().describe("Project directory (defaults to cwd)") },
187
+ async ({ path }) => {
188
+ const result = await runCli("stale", ["init"], path);
189
+ const output = formatOutput(result);
190
+ return { content: [{ type: "text", text: output }] };
191
+ }
192
+ );
193
+ server2.tool(
194
+ "stale_auto_fix",
195
+ "Scan for documentation drift and auto-fix high-confidence issues in one step",
196
+ {
197
+ path: z.string().optional().describe("Project directory to scan (defaults to cwd)"),
198
+ deep: z.coerce.boolean().optional().describe("Enable AI-powered deep analysis")
199
+ },
200
+ async ({ path, deep }) => {
201
+ const scanArgs = ["scan"];
202
+ if (deep) scanArgs.push("--deep");
203
+ const scanResult = await runCli("stale", scanArgs, path);
204
+ const scanOutput = formatOutput(scanResult);
205
+ writeCache("stale_scan", deriveProject(path), scanOutput, scanResult.code);
206
+ writeCache("stale_auto_fix", deriveProject(path), scanOutput, scanResult.code);
207
+ if (scanResult.code !== 0) {
208
+ const fixResult = await runCli("stale", ["fix", "--apply"], path);
209
+ const fixOutput = formatOutput(fixResult);
210
+ const combined = `${scanOutput}
211
+ --- Auto-fix applied ---
212
+ ${fixOutput}`;
213
+ writeCache("stale_auto_fix", deriveProject(path), combined, fixResult.code);
214
+ return { content: [{ type: "text", text: combined }] };
215
+ }
216
+ return { content: [{ type: "text", text: scanOutput }] };
217
+ }
218
+ );
219
+ }
220
+
221
+ // src/mcp/envalid.ts
222
+ import { z as z2 } from "zod";
223
+ function registerEnvalidTools(server2) {
224
+ server2.tool(
225
+ "envalid_validate",
226
+ "Validate .env files against their schema \u2014 catch missing or invalid environment variables",
227
+ {
228
+ path: z2.string().optional().describe("Project directory (defaults to cwd)"),
229
+ environment: z2.string().optional().describe("Target environment (e.g. production, staging)"),
230
+ format: z2.enum(["terminal", "json", "markdown"]).optional().describe("Output format")
231
+ },
232
+ async ({ path, environment, format }) => {
233
+ const args = ["validate"];
234
+ if (environment) args.push("--environment", environment);
235
+ if (format) args.push("--format", format);
236
+ const result = await runCli("envalid", args, path);
237
+ const output = formatOutput(result);
238
+ writeCache("envalid_validate", deriveProject(path), output, result.code);
239
+ const extras = await checkTriggers("envalid_validate", result, path);
240
+ return { content: [{ type: "text", text: output + extras.join("") }] };
241
+ }
242
+ );
243
+ server2.tool(
244
+ "envalid_detect",
245
+ "Scan codebase for env var usage and compare with schema \u2014 find undocumented env vars",
246
+ {
247
+ path: z2.string().optional().describe("Project directory (defaults to cwd)"),
248
+ format: z2.enum(["terminal", "json"]).optional().describe("Output format")
249
+ },
250
+ async ({ path, format }) => {
251
+ const args = ["detect"];
252
+ if (format) args.push("--format", format);
253
+ const result = await runCli("envalid", args, path);
254
+ const output = formatOutput(result);
255
+ writeCache("envalid_detect", deriveProject(path), output, result.code);
256
+ const extras = await checkTriggers("envalid_detect", result, path);
257
+ return { content: [{ type: "text", text: output + extras.join("") }] };
258
+ }
259
+ );
260
+ server2.tool(
261
+ "envalid_init",
262
+ "Generate .env.schema from an existing .env file \u2014 bootstrap type-safe env validation",
263
+ {
264
+ path: z2.string().optional().describe("Project directory (defaults to cwd)"),
265
+ force: z2.coerce.boolean().optional().describe("Overwrite existing schema")
266
+ },
267
+ async ({ path, force }) => {
268
+ const args = ["init"];
269
+ if (force) args.push("--force");
270
+ const result = await runCli("envalid", args, path);
271
+ const output = formatOutput(result);
272
+ return { content: [{ type: "text", text: output }] };
273
+ }
274
+ );
275
+ server2.tool(
276
+ "envalid_diff",
277
+ "Compare two .env files \u2014 show added, removed, and changed variables",
278
+ {
279
+ source: z2.string().describe("Path to source .env file"),
280
+ target: z2.string().describe("Path to target .env file"),
281
+ schema: z2.string().optional().describe("Path to .env.schema for sensitivity info"),
282
+ format: z2.enum(["terminal", "json", "markdown"]).optional().describe("Output format")
283
+ },
284
+ async ({ source, target, schema, format }) => {
285
+ const args = ["diff", source, target];
286
+ if (schema) args.push("--schema", schema);
287
+ if (format) args.push("--format", format);
288
+ const result = await runCli("envalid", args);
289
+ const output = formatOutput(result);
290
+ return { content: [{ type: "text", text: output }] };
291
+ }
292
+ );
293
+ server2.tool(
294
+ "envalid_sync",
295
+ "Check multiple environment files against schema \u2014 ensure all envs are in sync",
296
+ {
297
+ environments: z2.string().describe('Comma-separated env file paths (e.g. ".env,.env.staging,.env.production")'),
298
+ path: z2.string().optional().describe("Project directory (defaults to cwd)"),
299
+ format: z2.enum(["terminal", "json", "markdown"]).optional().describe("Output format")
300
+ },
301
+ async ({ environments, path, format }) => {
302
+ const args = ["sync", "--environments", environments];
303
+ if (format) args.push("--format", format);
304
+ const result = await runCli("envalid", args, path);
305
+ const output = formatOutput(result);
306
+ return { content: [{ type: "text", text: output }] };
307
+ }
308
+ );
309
+ server2.tool(
310
+ "envalid_generate",
311
+ "Generate .env.example from schema \u2014 create a safe template without secrets",
312
+ {
313
+ path: z2.string().optional().describe("Project directory (defaults to cwd)"),
314
+ output: z2.string().optional().describe("Output file path (default: .env.example)")
315
+ },
316
+ async ({ path, output }) => {
317
+ const args = ["generate-example"];
318
+ if (output) args.push("--output", output);
319
+ const result = await runCli("envalid", args, path);
320
+ const outputText = formatOutput(result);
321
+ return { content: [{ type: "text", text: outputText }] };
322
+ }
323
+ );
324
+ server2.tool(
325
+ "envalid_secrets",
326
+ "Scan committed files for leaked secrets \u2014 detect API keys, tokens, passwords in code",
327
+ {
328
+ path: z2.string().optional().describe("Project directory (defaults to cwd)"),
329
+ format: z2.enum(["terminal", "json"]).optional().describe("Output format")
330
+ },
331
+ async ({ path, format }) => {
332
+ const args = ["secrets"];
333
+ if (format) args.push("--format", format);
334
+ const result = await runCli("envalid", args, path);
335
+ const output = formatOutput(result);
336
+ writeCache("envalid_secrets", deriveProject(path), output, result.code);
337
+ return { content: [{ type: "text", text: output }] };
338
+ }
339
+ );
340
+ server2.tool(
341
+ "envalid_generate_schema",
342
+ "Generate .env.schema from code analysis \u2014 infer types, required-ness, and sensitivity from usage patterns",
343
+ {
344
+ path: z2.string().optional().describe("Project directory (defaults to cwd)"),
345
+ output: z2.string().optional().describe("Output file path (default: .env.schema)")
346
+ },
347
+ async ({ path, output }) => {
348
+ const args = ["detect", "--generate"];
349
+ if (output) args.push("-o", output);
350
+ const result = await runCli("envalid", args, path);
351
+ const outputText = formatOutput(result);
352
+ return { content: [{ type: "text", text: outputText }] };
353
+ }
354
+ );
355
+ server2.tool(
356
+ "envalid_hook_status",
357
+ "Check if the envalid pre-commit git hook is installed",
358
+ { path: z2.string().optional().describe("Project directory (defaults to cwd)") },
359
+ async ({ path }) => {
360
+ const result = await runCli("envalid", ["hook", "status"], path);
361
+ const output = formatOutput(result);
362
+ return { content: [{ type: "text", text: output }] };
363
+ }
364
+ );
365
+ server2.tool(
366
+ "envalid_auto_fix",
367
+ "Detect undocumented env vars and auto-generate schema entries",
368
+ {
369
+ path: z2.string().optional().describe("Project directory (defaults to cwd)")
370
+ },
371
+ async ({ path }) => {
372
+ const detectResult = await runCli("envalid", ["detect"], path);
373
+ const detectOutput = formatOutput(detectResult);
374
+ writeCache("envalid_detect", deriveProject(path), detectOutput, detectResult.code);
375
+ const hasUndocumented = /undocumented|missing from schema/i.test(detectOutput);
376
+ if (hasUndocumented) {
377
+ const generateResult = await runCli("envalid", ["detect", "--generate"], path);
378
+ const generateOutput = formatOutput(generateResult);
379
+ const combined = `${detectOutput}
380
+ --- Auto-generated schema entries ---
381
+ ${generateOutput}`;
382
+ return { content: [{ type: "text", text: combined }] };
383
+ }
384
+ return { content: [{ type: "text", text: detectOutput }] };
385
+ }
386
+ );
387
+ }
388
+
389
+ // src/mcp/berth.ts
390
+ import { z as z3 } from "zod";
391
+ function registerBerthTools(server2) {
392
+ server2.tool(
393
+ "berth_status",
394
+ "Show all active ports, Docker ports, and configured ports \u2014 diagnose port conflicts",
395
+ {},
396
+ async () => {
397
+ const result = await runCli("berth", ["status", "--json"]);
398
+ const output = formatOutput(result);
399
+ writeCache("berth_status", deriveProject(), output, result.code);
400
+ const extras = await checkTriggers("berth_status", result);
401
+ return { content: [{ type: "text", text: output + extras.join("") }] };
402
+ }
403
+ );
404
+ server2.tool(
405
+ "berth_check",
406
+ "Scan a project directory for port conflicts before starting dev servers",
407
+ { path: z3.string().optional().describe("Project directory to check (defaults to cwd)") },
408
+ async ({ path }) => {
409
+ const result = await runCli("berth", ["check", path || "."]);
410
+ const output = formatOutput(result);
411
+ writeCache("berth_check", deriveProject(path), output, result.code);
412
+ const extras = await checkTriggers("berth_check", result, path);
413
+ return { content: [{ type: "text", text: output + extras.join("") }] };
414
+ }
415
+ );
416
+ server2.tool(
417
+ "berth_kill",
418
+ "Kill processes on a specific port \u2014 free up a port for your dev server",
419
+ {
420
+ port: z3.coerce.number().optional().describe("Port number to free"),
421
+ dev: z3.coerce.boolean().optional().describe("Kill all dev processes (node, python, ruby, etc.)")
422
+ },
423
+ async ({ port, dev }) => {
424
+ const args = ["kill"];
425
+ if (port) args.push(String(port));
426
+ if (dev) args.push("--dev");
427
+ args.push("--force");
428
+ const result = await runCli("berth", args);
429
+ const output = formatOutput(result);
430
+ return { content: [{ type: "text", text: output }] };
431
+ }
432
+ );
433
+ server2.tool(
434
+ "berth_free",
435
+ "Free all ports for a registered project \u2014 kill every process blocking the project",
436
+ { project: z3.string().describe("Registered project name") },
437
+ async ({ project }) => {
438
+ const result = await runCli("berth", ["free", project]);
439
+ const output = formatOutput(result);
440
+ return { content: [{ type: "text", text: output }] };
441
+ }
442
+ );
443
+ server2.tool(
444
+ "berth_register",
445
+ "Register a project directory's port requirements for conflict tracking",
446
+ {
447
+ path: z3.string().optional().describe("Project directory to register (defaults to cwd)")
448
+ },
449
+ async ({ path }) => {
450
+ const args = ["register", "--yes"];
451
+ if (path) args.push("--dir", path);
452
+ const result = await runCli("berth", args);
453
+ const output = formatOutput(result);
454
+ return { content: [{ type: "text", text: output }] };
455
+ }
456
+ );
457
+ server2.tool(
458
+ "berth_list",
459
+ "List all registered projects and their port statuses",
460
+ {},
461
+ async () => {
462
+ const result = await runCli("berth", ["list", "--json"]);
463
+ const output = formatOutput(result);
464
+ return { content: [{ type: "text", text: output }] };
465
+ }
466
+ );
467
+ server2.tool(
468
+ "berth_reassign",
469
+ "Change a port assignment in project config files (docker-compose, .env, etc.)",
470
+ {
471
+ oldPort: z3.number().describe("Current port number"),
472
+ newPort: z3.number().describe("New port number"),
473
+ project: z3.string().optional().describe("Project name from registry")
474
+ },
475
+ async ({ oldPort, newPort, project }) => {
476
+ const args = ["reassign", String(oldPort), String(newPort)];
477
+ if (project) args.push("--project", project);
478
+ const result = await runCli("berth", args);
479
+ const output = formatOutput(result);
480
+ return { content: [{ type: "text", text: output }] };
481
+ }
482
+ );
483
+ server2.tool(
484
+ "berth_start",
485
+ "Auto-resolve all port conflicts and prepare a project to start cleanly",
486
+ {
487
+ project: z3.string().describe("Registered project name"),
488
+ dryRun: z3.coerce.boolean().optional().describe("Show what would be done without making changes")
489
+ },
490
+ async ({ project, dryRun }) => {
491
+ const args = ["start", project];
492
+ if (dryRun) args.push("--dry-run");
493
+ const result = await runCli("berth", args);
494
+ const output = formatOutput(result);
495
+ return { content: [{ type: "text", text: output }] };
496
+ }
497
+ );
498
+ server2.tool(
499
+ "berth_resolve",
500
+ "Auto-resolve port conflicts \u2014 detect conflicts and fix via kill or reassign strategy",
501
+ {
502
+ path: z3.string().optional().describe("Project directory (defaults to cwd)"),
503
+ strategy: z3.enum(["kill", "reassign", "auto"]).optional().describe("Resolution strategy (default: auto)"),
504
+ kill: z3.coerce.boolean().optional().describe("Allow killing processes (required for kill/auto strategies)"),
505
+ dryRun: z3.coerce.boolean().optional().describe("Show what would be done without making changes")
506
+ },
507
+ async ({ path, strategy, kill, dryRun }) => {
508
+ const args = ["resolve"];
509
+ if (strategy) args.push("--strategy", strategy);
510
+ if (kill) args.push("--kill");
511
+ if (dryRun) args.push("--dry-run");
512
+ const result = await runCli("berth", args, path);
513
+ const output = formatOutput(result);
514
+ return { content: [{ type: "text", text: output }] };
515
+ }
516
+ );
517
+ server2.tool(
518
+ "berth_predict",
519
+ "Predict port conflicts from project config files before starting \u2014 dry-run conflict check",
520
+ { path: z3.string().optional().describe("Project directory (defaults to cwd)") },
521
+ async ({ path }) => {
522
+ const result = await runCli("berth", ["predict", path || "."]);
523
+ const output = formatOutput(result);
524
+ return { content: [{ type: "text", text: output }] };
525
+ }
526
+ );
527
+ server2.tool(
528
+ "berth_auto_resolve",
529
+ "Check for port conflicts and auto-resolve them",
530
+ {
531
+ path: z3.string().optional().describe("Project directory (defaults to cwd)"),
532
+ strategy: z3.enum(["kill", "reassign", "auto"]).optional().describe("Resolution strategy (default: auto)")
533
+ },
534
+ async ({ path, strategy }) => {
535
+ const checkResult = await runCli("berth", ["check", path || "."]);
536
+ const checkOutput = formatOutput(checkResult);
537
+ writeCache("berth_check", deriveProject(path), checkOutput, checkResult.code);
538
+ const hasConflicts = /conflict/i.test(checkOutput);
539
+ if (hasConflicts) {
540
+ const resolveArgs = ["resolve", "--strategy", strategy || "auto", "--kill"];
541
+ const resolveResult = await runCli("berth", resolveArgs, path);
542
+ const resolveOutput = formatOutput(resolveResult);
543
+ const combined = `${checkOutput}
544
+ --- Auto-resolve applied ---
545
+ ${resolveOutput}`;
546
+ return { content: [{ type: "text", text: combined }] };
547
+ }
548
+ return { content: [{ type: "text", text: checkOutput }] };
549
+ }
550
+ );
551
+ }
552
+
553
+ // src/mcp/aware.ts
554
+ import { z as z4 } from "zod";
555
+ function registerAwareTools(server2) {
556
+ server2.tool(
557
+ "aware_init",
558
+ "Auto-detect project stack and generate AI context files (CLAUDE.md, .cursorrules, etc.)",
559
+ {
560
+ path: z4.string().optional().describe("Project directory (defaults to cwd)"),
561
+ targets: z4.string().optional().describe("Comma-separated targets: claude,cursor,copilot,agents,all"),
562
+ force: z4.coerce.boolean().optional().describe("Overwrite existing files without prompting")
563
+ },
564
+ async ({ path, targets, force }) => {
565
+ const args = ["init"];
566
+ if (targets) args.push("--targets", targets);
567
+ if (force) args.push("--force");
568
+ const result = await runCli("aware", args, path);
569
+ const output = formatOutput(result);
570
+ writeCache("aware_init", deriveProject(path), output, result.code);
571
+ const extras = await checkTriggers("aware_init", result, path);
572
+ return { content: [{ type: "text", text: output + extras.join("") }] };
573
+ }
574
+ );
575
+ server2.tool(
576
+ "aware_sync",
577
+ "Regenerate AI context files from .aware.json \u2014 update CLAUDE.md, .cursorrules, etc.",
578
+ {
579
+ path: z4.string().optional().describe("Project directory (defaults to cwd)"),
580
+ dryRun: z4.coerce.boolean().optional().describe("Show what would change without writing files")
581
+ },
582
+ async ({ path, dryRun }) => {
583
+ const args = ["sync"];
584
+ if (dryRun) args.push("--dry-run");
585
+ const result = await runCli("aware", args, path);
586
+ const output = formatOutput(result);
587
+ writeCache("aware_sync", deriveProject(path), output, result.code);
588
+ return { content: [{ type: "text", text: output }] };
589
+ }
590
+ );
591
+ server2.tool(
592
+ "aware_diff",
593
+ "Show project changes since last sync \u2014 see what drifted in your codebase",
594
+ {
595
+ path: z4.string().optional().describe("Project directory (defaults to cwd)"),
596
+ exitCode: z4.coerce.boolean().optional().describe("Return exit code 1 if changes detected (useful for CI)")
597
+ },
598
+ async ({ path, exitCode }) => {
599
+ const args = ["diff"];
600
+ if (exitCode) args.push("--exit-code");
601
+ const result = await runCli("aware", args, path);
602
+ const output = formatOutput(result);
603
+ return { content: [{ type: "text", text: output }] };
604
+ }
605
+ );
606
+ server2.tool(
607
+ "aware_validate",
608
+ "Validate .aware.json schema and content \u2014 check for config errors",
609
+ { path: z4.string().optional().describe("Project directory (defaults to cwd)") },
610
+ async ({ path }) => {
611
+ const result = await runCli("aware", ["validate"], path);
612
+ const output = formatOutput(result);
613
+ return { content: [{ type: "text", text: output }] };
614
+ }
615
+ );
616
+ server2.tool(
617
+ "aware_doctor",
618
+ "Diagnose project health \u2014 check config issues, stack drift, stale AI context files",
619
+ { path: z4.string().optional().describe("Project directory (defaults to cwd)") },
620
+ async ({ path }) => {
621
+ const result = await runCli("aware", ["doctor"], path);
622
+ const output = formatOutput(result);
623
+ writeCache("aware_doctor", deriveProject(path), output, result.code);
624
+ const extras = await checkTriggers("aware_doctor", result, path);
625
+ return { content: [{ type: "text", text: output + extras.join("") }] };
626
+ }
627
+ );
628
+ server2.tool(
629
+ "aware_add",
630
+ "Add a rule, convention, or structure entry to .aware.json",
631
+ {
632
+ path: z4.string().optional().describe("Project directory (defaults to cwd)"),
633
+ type: z4.enum(["rule", "convention", "structure"]).describe("Type to add")
634
+ },
635
+ async ({ path, type }) => {
636
+ const args = ["add", "--type", type];
637
+ const result = await runCli("aware", args, path);
638
+ const output = formatOutput(result);
639
+ return { content: [{ type: "text", text: output }] };
640
+ }
641
+ );
642
+ server2.tool(
643
+ "aware_auto_sync",
644
+ "Diagnose project health and auto-sync stale AI context files",
645
+ {
646
+ path: z4.string().optional().describe("Project directory (defaults to cwd)")
647
+ },
648
+ async ({ path }) => {
649
+ const doctorResult = await runCli("aware", ["doctor"], path);
650
+ const doctorOutput = formatOutput(doctorResult);
651
+ writeCache("aware_doctor", deriveProject(path), doctorOutput, doctorResult.code);
652
+ const needsSync = /stale|outdated|drift/i.test(doctorOutput);
653
+ if (needsSync) {
654
+ const syncResult = await runCli("aware", ["sync"], path);
655
+ const syncOutput = formatOutput(syncResult);
656
+ const combined = `${doctorOutput}
657
+ --- Auto-sync applied ---
658
+ ${syncOutput}`;
659
+ return { content: [{ type: "text", text: combined }] };
660
+ }
661
+ return { content: [{ type: "text", text: doctorOutput }] };
662
+ }
663
+ );
664
+ }
665
+
666
+ // src/mcp/vow.ts
667
+ import { z as z5 } from "zod";
668
+ function registerVowTools(server2) {
669
+ server2.tool(
670
+ "vow_scan",
671
+ "Scan dependency licenses \u2014 summarize all licenses in the project",
672
+ {
673
+ path: z5.string().optional().describe("Project directory (defaults to cwd)"),
674
+ production: z5.coerce.boolean().optional().describe("Skip devDependencies"),
675
+ format: z5.enum(["terminal", "json"]).optional().describe("Output format")
676
+ },
677
+ async ({ path, production, format }) => {
678
+ const args = ["scan"];
679
+ if (production) args.push("--production");
680
+ if (format) args.push("--format", format);
681
+ const result = await runCli("vow", args, path);
682
+ const output = formatOutput(result);
683
+ writeCache("vow_scan", deriveProject(path), output, result.code);
684
+ const extras = await checkTriggers("vow_scan", result, path);
685
+ return { content: [{ type: "text", text: output + extras.join("") }] };
686
+ }
687
+ );
688
+ server2.tool(
689
+ "vow_check",
690
+ "Validate dependency licenses against policy \u2014 flag violations before release",
691
+ {
692
+ path: z5.string().optional().describe("Project directory (defaults to cwd)"),
693
+ production: z5.coerce.boolean().optional().describe("Skip devDependencies")
694
+ },
695
+ async ({ path, production }) => {
696
+ const args = ["check"];
697
+ if (production) args.push("--production");
698
+ const result = await runCli("vow", args, path);
699
+ const output = formatOutput(result);
700
+ writeCache("vow_check", deriveProject(path), output, result.code);
701
+ const extras = await checkTriggers("vow_check", result, path);
702
+ return { content: [{ type: "text", text: output + extras.join("") }] };
703
+ }
704
+ );
705
+ server2.tool(
706
+ "vow_init",
707
+ "Generate a license policy file (.vow.json) \u2014 choose from commercial, opensource, or strict templates",
708
+ {
709
+ path: z5.string().optional().describe("Project directory (defaults to cwd)"),
710
+ template: z5.enum(["commercial", "opensource", "strict"]).optional().describe("Policy template")
711
+ },
712
+ async ({ path, template }) => {
713
+ const args = ["init"];
714
+ if (template) args.push("--template", template);
715
+ const result = await runCli("vow", args, path);
716
+ const output = formatOutput(result);
717
+ return { content: [{ type: "text", text: output }] };
718
+ }
719
+ );
720
+ server2.tool(
721
+ "vow_tree",
722
+ "Display dependency tree with license annotations \u2014 trace license inheritance",
723
+ {
724
+ path: z5.string().optional().describe("Project directory (defaults to cwd)"),
725
+ filter: z5.string().optional().describe('Show only subtrees containing this license (e.g. "GPL")'),
726
+ depth: z5.coerce.number().optional().describe("Max tree depth"),
727
+ production: z5.coerce.boolean().optional().describe("Skip devDependencies")
728
+ },
729
+ async ({ path, filter, depth, production }) => {
730
+ const args = ["tree"];
731
+ if (path) args.push("--path", path);
732
+ if (filter) args.push("--filter", filter);
733
+ if (depth) args.push("--depth", String(depth));
734
+ if (production) args.push("--production");
735
+ const result = await runCli("vow", args, path);
736
+ const output = formatOutput(result);
737
+ return { content: [{ type: "text", text: output }] };
738
+ }
739
+ );
740
+ server2.tool(
741
+ "vow_fix",
742
+ "Suggest alternative packages for license policy violations \u2014 find compliant replacements",
743
+ {
744
+ path: z5.string().optional().describe("Project directory (defaults to cwd)"),
745
+ production: z5.coerce.boolean().optional().describe("Skip devDependencies"),
746
+ limit: z5.coerce.number().optional().describe("Max alternatives per package")
747
+ },
748
+ async ({ path, production, limit }) => {
749
+ const args = ["fix"];
750
+ if (path) args.push("--path", path);
751
+ if (production) args.push("--production");
752
+ if (limit) args.push("--limit", String(limit));
753
+ const result = await runCli("vow", args, path);
754
+ const output = formatOutput(result);
755
+ return { content: [{ type: "text", text: output }] };
756
+ }
757
+ );
758
+ server2.tool(
759
+ "vow_export",
760
+ "Export full license report as JSON, CSV, or Markdown",
761
+ {
762
+ path: z5.string().optional().describe("Project directory (defaults to cwd)"),
763
+ format: z5.enum(["json", "csv", "markdown"]).optional().describe("Export format (default: json)"),
764
+ output: z5.string().optional().describe("Output file path"),
765
+ production: z5.coerce.boolean().optional().describe("Skip devDependencies")
766
+ },
767
+ async ({ path, format, output, production }) => {
768
+ const args = ["export"];
769
+ if (path) args.push("--path", path);
770
+ if (format) args.push("--format", format);
771
+ if (output) args.push("--output", output);
772
+ if (production) args.push("--production");
773
+ const result = await runCli("vow", args, path);
774
+ const outputText = formatOutput(result);
775
+ return { content: [{ type: "text", text: outputText }] };
776
+ }
777
+ );
778
+ server2.tool(
779
+ "vow_hook_install",
780
+ "Install a pre-commit git hook that checks dependency licenses before each commit",
781
+ {
782
+ path: z5.string().optional().describe("Project directory (defaults to cwd)")
783
+ },
784
+ async ({ path }) => {
785
+ const result = await runCli("vow", ["hook", "install"], path);
786
+ const output = formatOutput(result);
787
+ return { content: [{ type: "text", text: output }] };
788
+ }
789
+ );
790
+ server2.tool(
791
+ "vow_hook_uninstall",
792
+ "Remove the vow pre-commit license check hook",
793
+ {
794
+ path: z5.string().optional().describe("Project directory (defaults to cwd)")
795
+ },
796
+ async ({ path }) => {
797
+ const result = await runCli("vow", ["hook", "uninstall"], path);
798
+ const output = formatOutput(result);
799
+ return { content: [{ type: "text", text: output }] };
800
+ }
801
+ );
802
+ server2.tool(
803
+ "vow_hook_status",
804
+ "Check if the vow pre-commit license check hook is installed",
805
+ {
806
+ path: z5.string().optional().describe("Project directory (defaults to cwd)")
807
+ },
808
+ async ({ path }) => {
809
+ const result = await runCli("vow", ["hook", "status"], path);
810
+ const output = formatOutput(result);
811
+ return { content: [{ type: "text", text: output }] };
812
+ }
813
+ );
814
+ server2.tool(
815
+ "vow_attribution",
816
+ "Generate THIRD_PARTY_LICENSES.md \u2014 list all dependencies with their licenses for compliance",
817
+ {
818
+ path: z5.string().optional().describe("Project directory (defaults to cwd)"),
819
+ output: z5.string().optional().describe("Output file (default: THIRD_PARTY_LICENSES.md)"),
820
+ production: z5.coerce.boolean().optional().describe("Skip devDependencies")
821
+ },
822
+ async ({ path, output, production }) => {
823
+ const args = ["attribution"];
824
+ if (path) args.push("--path", path);
825
+ if (output) args.push("--output", output);
826
+ if (production) args.push("--production");
827
+ const result = await runCli("vow", args, path);
828
+ const outputText = formatOutput(result);
829
+ return { content: [{ type: "text", text: outputText }] };
830
+ }
831
+ );
832
+ }
833
+
834
+ // src/mcp/index.ts
835
+ var __dirname = dirname(fileURLToPath(import.meta.url));
836
+ var { version } = JSON.parse(readFileSync2(resolve(__dirname, "..", "package.json"), "utf8"));
101
837
  var server = new McpServer({
102
838
  name: "whenlabs",
103
- version: "0.3.0"
839
+ version
104
840
  });
105
841
  var velocityDb = initDb();
106
842
  var velocityQueries = new TaskQueries(velocityDb);
@@ -110,587 +846,11 @@ registerEndTask(s, velocityQueries);
110
846
  registerEstimate(s, velocityQueries);
111
847
  registerStats(s, velocityQueries);
112
848
  registerHistory(s, velocityQueries);
113
- function formatOutput(result) {
114
- const parts = [];
115
- if (result.stdout.trim()) parts.push(result.stdout.trim());
116
- if (result.stderr.trim()) parts.push(result.stderr.trim());
117
- return parts.join("\n") || "No output";
118
- }
119
- server.tool(
120
- "stale_scan",
121
- "Scan for documentation drift \u2014 detect when docs say one thing and code says another",
122
- {
123
- path: z.string().optional().describe("Project directory to scan (defaults to cwd)"),
124
- deep: z.coerce.boolean().optional().describe("Enable AI-powered deep analysis"),
125
- git: z.coerce.boolean().optional().describe("Enable git history staleness checks"),
126
- format: z.enum(["terminal", "json", "markdown", "sarif"]).optional().describe("Output format")
127
- },
128
- async ({ path, deep, git, format }) => {
129
- const args = ["scan"];
130
- if (deep) args.push("--deep");
131
- if (git) args.push("--git");
132
- if (format) args.push("--format", format);
133
- const result = await runCli("stale", args, path);
134
- const output = formatOutput(result);
135
- writeCache("stale", deriveProject(path), output, result.code);
136
- const extras = await checkTriggers("stale_scan", result, path);
137
- return { content: [{ type: "text", text: output + extras.join("") }] };
138
- }
139
- );
140
- server.tool(
141
- "stale_fix",
142
- "Auto-fix documentation drift \u2014 generate fixes for wrong file paths, dead links, phantom env vars, outdated scripts",
143
- {
144
- path: z.string().optional().describe("Project directory to scan (defaults to cwd)"),
145
- format: z.enum(["terminal", "diff"]).optional().describe("Output format (default: terminal)"),
146
- apply: z.coerce.boolean().optional().describe("Apply high-confidence fixes directly"),
147
- dryRun: z.coerce.boolean().optional().describe("Show what --apply would do without writing")
148
- },
149
- async ({ path, format, apply, dryRun }) => {
150
- const args = ["fix"];
151
- if (format) args.push("--format", format);
152
- if (apply) args.push("--apply");
153
- if (dryRun) args.push("--dry-run");
154
- const result = await runCli("stale", args, path);
155
- const output = formatOutput(result);
156
- return { content: [{ type: "text", text: output }] };
157
- }
158
- );
159
- server.tool(
160
- "stale_init",
161
- "Generate a .stale.yml config file for customizing documentation drift detection",
162
- { path: z.string().optional().describe("Project directory (defaults to cwd)") },
163
- async ({ path }) => {
164
- const result = await runCli("stale", ["init"], path);
165
- const output = formatOutput(result);
166
- return { content: [{ type: "text", text: output }] };
167
- }
168
- );
169
- server.tool(
170
- "envalid_validate",
171
- "Validate .env files against their schema \u2014 catch missing or invalid environment variables",
172
- {
173
- path: z.string().optional().describe("Project directory (defaults to cwd)"),
174
- environment: z.string().optional().describe("Target environment (e.g. production, staging)"),
175
- format: z.enum(["terminal", "json", "markdown"]).optional().describe("Output format")
176
- },
177
- async ({ path, environment, format }) => {
178
- const args = ["validate"];
179
- if (environment) args.push("--environment", environment);
180
- if (format) args.push("--format", format);
181
- const result = await runCli("envalid", args, path);
182
- const output = formatOutput(result);
183
- writeCache("envalid_validate", deriveProject(path), output, result.code);
184
- const extras = await checkTriggers("envalid_validate", result, path);
185
- return { content: [{ type: "text", text: output + extras.join("") }] };
186
- }
187
- );
188
- server.tool(
189
- "envalid_detect",
190
- "Scan codebase for env var usage and compare with schema \u2014 find undocumented env vars",
191
- {
192
- path: z.string().optional().describe("Project directory (defaults to cwd)"),
193
- format: z.enum(["terminal", "json"]).optional().describe("Output format")
194
- },
195
- async ({ path, format }) => {
196
- const args = ["detect"];
197
- if (format) args.push("--format", format);
198
- const result = await runCli("envalid", args, path);
199
- const output = formatOutput(result);
200
- writeCache("envalid_detect", deriveProject(path), output, result.code);
201
- const extras = await checkTriggers("envalid_detect", result, path);
202
- return { content: [{ type: "text", text: output + extras.join("") }] };
203
- }
204
- );
205
- server.tool(
206
- "envalid_init",
207
- "Generate .env.schema from an existing .env file \u2014 bootstrap type-safe env validation",
208
- {
209
- path: z.string().optional().describe("Project directory (defaults to cwd)"),
210
- force: z.coerce.boolean().optional().describe("Overwrite existing schema")
211
- },
212
- async ({ path, force }) => {
213
- const args = ["init"];
214
- if (force) args.push("--force");
215
- const result = await runCli("envalid", args, path);
216
- const output = formatOutput(result);
217
- return { content: [{ type: "text", text: output }] };
218
- }
219
- );
220
- server.tool(
221
- "envalid_diff",
222
- "Compare two .env files \u2014 show added, removed, and changed variables",
223
- {
224
- source: z.string().describe("Path to source .env file"),
225
- target: z.string().describe("Path to target .env file"),
226
- schema: z.string().optional().describe("Path to .env.schema for sensitivity info"),
227
- format: z.enum(["terminal", "json", "markdown"]).optional().describe("Output format")
228
- },
229
- async ({ source, target, schema, format }) => {
230
- const args = ["diff", source, target];
231
- if (schema) args.push("--schema", schema);
232
- if (format) args.push("--format", format);
233
- const result = await runCli("envalid", args);
234
- const output = formatOutput(result);
235
- return { content: [{ type: "text", text: output }] };
236
- }
237
- );
238
- server.tool(
239
- "envalid_sync",
240
- "Check multiple environment files against schema \u2014 ensure all envs are in sync",
241
- {
242
- environments: z.string().describe('Comma-separated env file paths (e.g. ".env,.env.staging,.env.production")'),
243
- path: z.string().optional().describe("Project directory (defaults to cwd)"),
244
- format: z.enum(["terminal", "json", "markdown"]).optional().describe("Output format")
245
- },
246
- async ({ environments, path, format }) => {
247
- const args = ["sync", "--environments", environments];
248
- if (format) args.push("--format", format);
249
- const result = await runCli("envalid", args, path);
250
- const output = formatOutput(result);
251
- return { content: [{ type: "text", text: output }] };
252
- }
253
- );
254
- server.tool(
255
- "envalid_generate",
256
- "Generate .env.example from schema \u2014 create a safe template without secrets",
257
- {
258
- path: z.string().optional().describe("Project directory (defaults to cwd)"),
259
- output: z.string().optional().describe("Output file path (default: .env.example)")
260
- },
261
- async ({ path, output }) => {
262
- const args = ["generate-example"];
263
- if (output) args.push("--output", output);
264
- const result = await runCli("envalid", args, path);
265
- const outputText = formatOutput(result);
266
- return { content: [{ type: "text", text: outputText }] };
267
- }
268
- );
269
- server.tool(
270
- "envalid_secrets",
271
- "Scan committed files for leaked secrets \u2014 detect API keys, tokens, passwords in code",
272
- {
273
- path: z.string().optional().describe("Project directory (defaults to cwd)"),
274
- format: z.enum(["terminal", "json"]).optional().describe("Output format")
275
- },
276
- async ({ path, format }) => {
277
- const args = ["secrets"];
278
- if (format) args.push("--format", format);
279
- const result = await runCli("envalid", args, path);
280
- const output = formatOutput(result);
281
- writeCache("envalid_secrets", deriveProject(path), output, result.code);
282
- return { content: [{ type: "text", text: output }] };
283
- }
284
- );
285
- server.tool(
286
- "envalid_generate_schema",
287
- "Generate .env.schema from code analysis \u2014 infer types, required-ness, and sensitivity from usage patterns",
288
- {
289
- path: z.string().optional().describe("Project directory (defaults to cwd)"),
290
- output: z.string().optional().describe("Output file path (default: .env.schema)")
291
- },
292
- async ({ path, output }) => {
293
- const args = ["detect", "--generate"];
294
- if (output) args.push("-o", output);
295
- const result = await runCli("envalid", args, path);
296
- const outputText = formatOutput(result);
297
- return { content: [{ type: "text", text: outputText }] };
298
- }
299
- );
300
- server.tool(
301
- "envalid_hook_status",
302
- "Check if the envalid pre-commit git hook is installed",
303
- { path: z.string().optional().describe("Project directory (defaults to cwd)") },
304
- async ({ path }) => {
305
- const result = await runCli("envalid", ["hook", "status"], path);
306
- const output = formatOutput(result);
307
- return { content: [{ type: "text", text: output }] };
308
- }
309
- );
310
- server.tool(
311
- "berth_status",
312
- "Show all active ports, Docker ports, and configured ports \u2014 diagnose port conflicts",
313
- {},
314
- async () => {
315
- const result = await runCli("berth", ["status", "--json"]);
316
- const output = formatOutput(result);
317
- writeCache("berth_status", deriveProject(), output, result.code);
318
- const extras = await checkTriggers("berth_status", result);
319
- return { content: [{ type: "text", text: output + extras.join("") }] };
320
- }
321
- );
322
- server.tool(
323
- "berth_check",
324
- "Scan a project directory for port conflicts before starting dev servers",
325
- { path: z.string().optional().describe("Project directory to check (defaults to cwd)") },
326
- async ({ path }) => {
327
- const result = await runCli("berth", ["check", path || "."]);
328
- const output = formatOutput(result);
329
- writeCache("berth_check", deriveProject(path), output, result.code);
330
- const extras = await checkTriggers("berth_check", result, path);
331
- return { content: [{ type: "text", text: output + extras.join("") }] };
332
- }
333
- );
334
- server.tool(
335
- "berth_kill",
336
- "Kill processes on a specific port \u2014 free up a port for your dev server",
337
- {
338
- port: z.coerce.number().optional().describe("Port number to free"),
339
- dev: z.coerce.boolean().optional().describe("Kill all dev processes (node, python, ruby, etc.)")
340
- },
341
- async ({ port, dev }) => {
342
- const args = ["kill"];
343
- if (port) args.push(String(port));
344
- if (dev) args.push("--dev");
345
- args.push("--force");
346
- const result = await runCli("berth", args);
347
- const output = formatOutput(result);
348
- return { content: [{ type: "text", text: output }] };
349
- }
350
- );
351
- server.tool(
352
- "berth_free",
353
- "Free all ports for a registered project \u2014 kill every process blocking the project",
354
- { project: z.string().describe("Registered project name") },
355
- async ({ project }) => {
356
- const result = await runCli("berth", ["free", project]);
357
- const output = formatOutput(result);
358
- return { content: [{ type: "text", text: output }] };
359
- }
360
- );
361
- server.tool(
362
- "berth_register",
363
- "Register a project directory's port requirements for conflict tracking",
364
- {
365
- path: z.string().optional().describe("Project directory to register (defaults to cwd)")
366
- },
367
- async ({ path }) => {
368
- const args = ["register", "--yes"];
369
- if (path) args.push("--dir", path);
370
- const result = await runCli("berth", args);
371
- const output = formatOutput(result);
372
- return { content: [{ type: "text", text: output }] };
373
- }
374
- );
375
- server.tool(
376
- "berth_list",
377
- "List all registered projects and their port statuses",
378
- {},
379
- async () => {
380
- const result = await runCli("berth", ["list", "--json"]);
381
- const output = formatOutput(result);
382
- return { content: [{ type: "text", text: output }] };
383
- }
384
- );
385
- server.tool(
386
- "berth_reassign",
387
- "Change a port assignment in project config files (docker-compose, .env, etc.)",
388
- {
389
- oldPort: z.number().describe("Current port number"),
390
- newPort: z.number().describe("New port number"),
391
- project: z.string().optional().describe("Project name from registry")
392
- },
393
- async ({ oldPort, newPort, project }) => {
394
- const args = ["reassign", String(oldPort), String(newPort)];
395
- if (project) args.push("--project", project);
396
- const result = await runCli("berth", args);
397
- const output = formatOutput(result);
398
- return { content: [{ type: "text", text: output }] };
399
- }
400
- );
401
- server.tool(
402
- "berth_start",
403
- "Auto-resolve all port conflicts and prepare a project to start cleanly",
404
- {
405
- project: z.string().describe("Registered project name"),
406
- dryRun: z.coerce.boolean().optional().describe("Show what would be done without making changes")
407
- },
408
- async ({ project, dryRun }) => {
409
- const args = ["start", project];
410
- if (dryRun) args.push("--dry-run");
411
- const result = await runCli("berth", args);
412
- const output = formatOutput(result);
413
- return { content: [{ type: "text", text: output }] };
414
- }
415
- );
416
- server.tool(
417
- "berth_resolve",
418
- "Auto-resolve port conflicts \u2014 detect conflicts and fix via kill or reassign strategy",
419
- {
420
- path: z.string().optional().describe("Project directory (defaults to cwd)"),
421
- strategy: z.enum(["kill", "reassign", "auto"]).optional().describe("Resolution strategy (default: auto)"),
422
- kill: z.coerce.boolean().optional().describe("Allow killing processes (required for kill/auto strategies)"),
423
- dryRun: z.coerce.boolean().optional().describe("Show what would be done without making changes")
424
- },
425
- async ({ path, strategy, kill, dryRun }) => {
426
- const args = ["resolve"];
427
- if (strategy) args.push("--strategy", strategy);
428
- if (kill) args.push("--kill");
429
- if (dryRun) args.push("--dry-run");
430
- const result = await runCli("berth", args, path);
431
- const output = formatOutput(result);
432
- return { content: [{ type: "text", text: output }] };
433
- }
434
- );
435
- server.tool(
436
- "berth_predict",
437
- "Predict port conflicts from project config files before starting \u2014 dry-run conflict check",
438
- { path: z.string().optional().describe("Project directory (defaults to cwd)") },
439
- async ({ path }) => {
440
- const result = await runCli("berth", ["predict", path || "."]);
441
- const output = formatOutput(result);
442
- return { content: [{ type: "text", text: output }] };
443
- }
444
- );
445
- server.tool(
446
- "aware_init",
447
- "Auto-detect project stack and generate AI context files (CLAUDE.md, .cursorrules, etc.)",
448
- {
449
- path: z.string().optional().describe("Project directory (defaults to cwd)"),
450
- targets: z.string().optional().describe("Comma-separated targets: claude,cursor,copilot,agents,all"),
451
- force: z.coerce.boolean().optional().describe("Overwrite existing files without prompting")
452
- },
453
- async ({ path, targets, force }) => {
454
- const args = ["init"];
455
- if (targets) args.push("--targets", targets);
456
- if (force) args.push("--force");
457
- const result = await runCli("aware", args, path);
458
- const output = formatOutput(result);
459
- writeCache("aware_init", deriveProject(path), output, result.code);
460
- const extras = await checkTriggers("aware_init", result, path);
461
- return { content: [{ type: "text", text: output + extras.join("") }] };
462
- }
463
- );
464
- server.tool(
465
- "aware_sync",
466
- "Regenerate AI context files from .aware.json \u2014 update CLAUDE.md, .cursorrules, etc.",
467
- {
468
- path: z.string().optional().describe("Project directory (defaults to cwd)"),
469
- dryRun: z.coerce.boolean().optional().describe("Show what would change without writing files")
470
- },
471
- async ({ path, dryRun }) => {
472
- const args = ["sync"];
473
- if (dryRun) args.push("--dry-run");
474
- const result = await runCli("aware", args, path);
475
- const output = formatOutput(result);
476
- writeCache("aware_sync", deriveProject(path), output, result.code);
477
- return { content: [{ type: "text", text: output }] };
478
- }
479
- );
480
- server.tool(
481
- "aware_diff",
482
- "Show project changes since last sync \u2014 see what drifted in your codebase",
483
- {
484
- path: z.string().optional().describe("Project directory (defaults to cwd)"),
485
- exitCode: z.coerce.boolean().optional().describe("Return exit code 1 if changes detected (useful for CI)")
486
- },
487
- async ({ path, exitCode }) => {
488
- const args = ["diff"];
489
- if (exitCode) args.push("--exit-code");
490
- const result = await runCli("aware", args, path);
491
- const output = formatOutput(result);
492
- return { content: [{ type: "text", text: output }] };
493
- }
494
- );
495
- server.tool(
496
- "aware_validate",
497
- "Validate .aware.json schema and content \u2014 check for config errors",
498
- { path: z.string().optional().describe("Project directory (defaults to cwd)") },
499
- async ({ path }) => {
500
- const result = await runCli("aware", ["validate"], path);
501
- const output = formatOutput(result);
502
- return { content: [{ type: "text", text: output }] };
503
- }
504
- );
505
- server.tool(
506
- "aware_doctor",
507
- "Diagnose project health \u2014 check config issues, stack drift, stale AI context files",
508
- { path: z.string().optional().describe("Project directory (defaults to cwd)") },
509
- async ({ path }) => {
510
- const result = await runCli("aware", ["doctor"], path);
511
- const output = formatOutput(result);
512
- writeCache("aware_doctor", deriveProject(path), output, result.code);
513
- const extras = await checkTriggers("aware_doctor", result, path);
514
- return { content: [{ type: "text", text: output + extras.join("") }] };
515
- }
516
- );
517
- server.tool(
518
- "aware_add",
519
- "Add a rule, convention, or structure entry to .aware.json",
520
- {
521
- path: z.string().optional().describe("Project directory (defaults to cwd)"),
522
- type: z.enum(["rule", "convention", "structure"]).describe("Type to add")
523
- },
524
- async ({ path, type }) => {
525
- const args = ["add", "--type", type];
526
- const result = await runCli("aware", args, path);
527
- const output = formatOutput(result);
528
- return { content: [{ type: "text", text: output }] };
529
- }
530
- );
531
- server.tool(
532
- "vow_scan",
533
- "Scan dependency licenses \u2014 summarize all licenses in the project",
534
- {
535
- path: z.string().optional().describe("Project directory (defaults to cwd)"),
536
- production: z.coerce.boolean().optional().describe("Skip devDependencies"),
537
- format: z.enum(["terminal", "json"]).optional().describe("Output format")
538
- },
539
- async ({ path, production, format }) => {
540
- const args = ["scan"];
541
- if (production) args.push("--production");
542
- if (format) args.push("--format", format);
543
- const result = await runCli("vow", args, path);
544
- const output = formatOutput(result);
545
- writeCache("vow_scan", deriveProject(path), output, result.code);
546
- const extras = await checkTriggers("vow_scan", result, path);
547
- return { content: [{ type: "text", text: output + extras.join("") }] };
548
- }
549
- );
550
- server.tool(
551
- "vow_check",
552
- "Validate dependency licenses against policy \u2014 flag violations before release",
553
- {
554
- path: z.string().optional().describe("Project directory (defaults to cwd)"),
555
- production: z.coerce.boolean().optional().describe("Skip devDependencies")
556
- },
557
- async ({ path, production }) => {
558
- const args = ["check"];
559
- if (production) args.push("--production");
560
- const result = await runCli("vow", args, path);
561
- const output = formatOutput(result);
562
- writeCache("vow_check", deriveProject(path), output, result.code);
563
- const extras = await checkTriggers("vow_check", result, path);
564
- return { content: [{ type: "text", text: output + extras.join("") }] };
565
- }
566
- );
567
- server.tool(
568
- "vow_init",
569
- "Generate a license policy file (.vow.json) \u2014 choose from commercial, opensource, or strict templates",
570
- {
571
- path: z.string().optional().describe("Project directory (defaults to cwd)"),
572
- template: z.enum(["commercial", "opensource", "strict"]).optional().describe("Policy template")
573
- },
574
- async ({ path, template }) => {
575
- const args = ["init"];
576
- if (template) args.push("--template", template);
577
- const result = await runCli("vow", args, path);
578
- const output = formatOutput(result);
579
- return { content: [{ type: "text", text: output }] };
580
- }
581
- );
582
- server.tool(
583
- "vow_tree",
584
- "Display dependency tree with license annotations \u2014 trace license inheritance",
585
- {
586
- path: z.string().optional().describe("Project directory (defaults to cwd)"),
587
- filter: z.string().optional().describe('Show only subtrees containing this license (e.g. "GPL")'),
588
- depth: z.coerce.number().optional().describe("Max tree depth"),
589
- production: z.coerce.boolean().optional().describe("Skip devDependencies")
590
- },
591
- async ({ path, filter, depth, production }) => {
592
- const args = ["tree"];
593
- if (path) args.push("--path", path);
594
- if (filter) args.push("--filter", filter);
595
- if (depth) args.push("--depth", String(depth));
596
- if (production) args.push("--production");
597
- const result = await runCli("vow", args, path);
598
- const output = formatOutput(result);
599
- return { content: [{ type: "text", text: output }] };
600
- }
601
- );
602
- server.tool(
603
- "vow_fix",
604
- "Suggest alternative packages for license policy violations \u2014 find compliant replacements",
605
- {
606
- path: z.string().optional().describe("Project directory (defaults to cwd)"),
607
- production: z.coerce.boolean().optional().describe("Skip devDependencies"),
608
- limit: z.coerce.number().optional().describe("Max alternatives per package")
609
- },
610
- async ({ path, production, limit }) => {
611
- const args = ["fix"];
612
- if (path) args.push("--path", path);
613
- if (production) args.push("--production");
614
- if (limit) args.push("--limit", String(limit));
615
- const result = await runCli("vow", args, path);
616
- const output = formatOutput(result);
617
- return { content: [{ type: "text", text: output }] };
618
- }
619
- );
620
- server.tool(
621
- "vow_export",
622
- "Export full license report as JSON, CSV, or Markdown",
623
- {
624
- path: z.string().optional().describe("Project directory (defaults to cwd)"),
625
- format: z.enum(["json", "csv", "markdown"]).optional().describe("Export format (default: json)"),
626
- output: z.string().optional().describe("Output file path"),
627
- production: z.coerce.boolean().optional().describe("Skip devDependencies")
628
- },
629
- async ({ path, format, output, production }) => {
630
- const args = ["export"];
631
- if (path) args.push("--path", path);
632
- if (format) args.push("--format", format);
633
- if (output) args.push("--output", output);
634
- if (production) args.push("--production");
635
- const result = await runCli("vow", args, path);
636
- const outputText = formatOutput(result);
637
- return { content: [{ type: "text", text: outputText }] };
638
- }
639
- );
640
- server.tool(
641
- "vow_hook_install",
642
- "Install a pre-commit git hook that checks dependency licenses before each commit",
643
- {
644
- path: z.string().optional().describe("Project directory (defaults to cwd)")
645
- },
646
- async ({ path }) => {
647
- const result = await runCli("vow", ["hook", "install"], path);
648
- const output = formatOutput(result);
649
- return { content: [{ type: "text", text: output }] };
650
- }
651
- );
652
- server.tool(
653
- "vow_hook_uninstall",
654
- "Remove the vow pre-commit license check hook",
655
- {
656
- path: z.string().optional().describe("Project directory (defaults to cwd)")
657
- },
658
- async ({ path }) => {
659
- const result = await runCli("vow", ["hook", "uninstall"], path);
660
- const output = formatOutput(result);
661
- return { content: [{ type: "text", text: output }] };
662
- }
663
- );
664
- server.tool(
665
- "vow_hook_status",
666
- "Check if the vow pre-commit license check hook is installed",
667
- {
668
- path: z.string().optional().describe("Project directory (defaults to cwd)")
669
- },
670
- async ({ path }) => {
671
- const result = await runCli("vow", ["hook", "status"], path);
672
- const output = formatOutput(result);
673
- return { content: [{ type: "text", text: output }] };
674
- }
675
- );
676
- server.tool(
677
- "vow_attribution",
678
- "Generate THIRD_PARTY_LICENSES.md \u2014 list all dependencies with their licenses for compliance",
679
- {
680
- path: z.string().optional().describe("Project directory (defaults to cwd)"),
681
- output: z.string().optional().describe("Output file (default: THIRD_PARTY_LICENSES.md)"),
682
- production: z.coerce.boolean().optional().describe("Skip devDependencies")
683
- },
684
- async ({ path, output, production }) => {
685
- const args = ["attribution"];
686
- if (path) args.push("--path", path);
687
- if (output) args.push("--output", output);
688
- if (production) args.push("--production");
689
- const result = await runCli("vow", args, path);
690
- const outputText = formatOutput(result);
691
- return { content: [{ type: "text", text: outputText }] };
692
- }
693
- );
849
+ registerStaleTools(server);
850
+ registerEnvalidTools(server);
851
+ registerBerthTools(server);
852
+ registerAwareTools(server);
853
+ registerVowTools(server);
694
854
  process.on("SIGINT", () => {
695
855
  velocityDb.close();
696
856
  process.exit(0);