token-pilot 0.31.0 → 0.33.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.
Files changed (53) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/agents/tp-api-surface-tracker.md +1 -1
  4. package/agents/tp-audit-scanner.md +1 -1
  5. package/agents/tp-commit-writer.md +1 -1
  6. package/agents/tp-context-engineer.md +1 -1
  7. package/agents/tp-dead-code-finder.md +1 -1
  8. package/agents/tp-debugger.md +1 -1
  9. package/agents/tp-dep-health.md +1 -1
  10. package/agents/tp-doc-writer.md +1 -1
  11. package/agents/tp-history-explorer.md +1 -1
  12. package/agents/tp-impact-analyzer.md +1 -1
  13. package/agents/tp-incident-timeline.md +1 -1
  14. package/agents/tp-incremental-builder.md +1 -1
  15. package/agents/tp-migration-scout.md +1 -1
  16. package/agents/tp-onboard.md +1 -1
  17. package/agents/tp-performance-profiler.md +1 -1
  18. package/agents/tp-pr-reviewer.md +1 -1
  19. package/agents/tp-refactor-planner.md +1 -1
  20. package/agents/tp-review-impact.md +1 -1
  21. package/agents/tp-run.md +1 -1
  22. package/agents/tp-session-restorer.md +1 -1
  23. package/agents/tp-ship-coordinator.md +1 -1
  24. package/agents/tp-spec-writer.md +1 -1
  25. package/agents/tp-test-coverage-gapper.md +1 -1
  26. package/agents/tp-test-triage.md +1 -1
  27. package/agents/tp-test-writer.md +1 -1
  28. package/dist/ast-index/client.js +17 -1
  29. package/dist/cli/install-agents.d.ts +18 -0
  30. package/dist/cli/install-agents.js +88 -1
  31. package/dist/cli/stats.js +9 -2
  32. package/dist/core/error-log.d.ts +86 -0
  33. package/dist/core/error-log.js +228 -0
  34. package/dist/core/event-log.d.ts +49 -1
  35. package/dist/core/event-log.js +114 -0
  36. package/dist/core/validation.d.ts +25 -9
  37. package/dist/core/validation.js +212 -136
  38. package/dist/handlers/call-tree.d.ts +35 -0
  39. package/dist/handlers/call-tree.js +70 -0
  40. package/dist/handlers/smart-log.js +7 -2
  41. package/dist/hooks/installer.d.ts +40 -0
  42. package/dist/hooks/installer.js +145 -2
  43. package/dist/hooks/pre-task.js +44 -10
  44. package/dist/hooks/safe-runner.d.ts +48 -0
  45. package/dist/hooks/safe-runner.js +73 -0
  46. package/dist/hooks/session-start.d.ts +2 -0
  47. package/dist/hooks/session-start.js +49 -0
  48. package/dist/index.d.ts +11 -0
  49. package/dist/index.js +284 -63
  50. package/dist/server/tool-definitions.d.ts +65 -0
  51. package/dist/server/tool-definitions.js +18 -0
  52. package/dist/server.js +36 -1
  53. package/package.json +1 -1
@@ -1,4 +1,34 @@
1
- import { resolve, relative } from 'node:path';
1
+ import { resolve, relative } from "node:path";
2
+ /**
3
+ * v0.33.0 (B9) — coerce an `unknown` argument value to an integer.
4
+ *
5
+ * MCP transports frequently round-trip numeric arguments through
6
+ * JSON or environment variables and re-emit them as strings (e.g.
7
+ * `"42"`). Accept that case and reject everything else, including
8
+ * non-finite numbers, decimals, and strings that don't parse cleanly.
9
+ *
10
+ * Returns the integer value or `null` when the input cannot be
11
+ * interpreted as one.
12
+ */
13
+ export function coerceIntFromAny(value) {
14
+ if (typeof value === "number") {
15
+ if (!Number.isFinite(value) || !Number.isInteger(value))
16
+ return null;
17
+ return value;
18
+ }
19
+ if (typeof value === "string") {
20
+ const trimmed = value.trim();
21
+ if (trimmed.length === 0)
22
+ return null;
23
+ if (!/^-?\d+$/.test(trimmed))
24
+ return null;
25
+ const n = Number(trimmed);
26
+ if (!Number.isFinite(n) || !Number.isInteger(n))
27
+ return null;
28
+ return n;
29
+ }
30
+ return null;
31
+ }
2
32
  /**
3
33
  * Resolve a user-provided path and validate it stays within projectRoot.
4
34
  * Prevents path traversal attacks (e.g. ../../etc/passwd).
@@ -6,7 +36,7 @@ import { resolve, relative } from 'node:path';
6
36
  export function resolveSafePath(projectRoot, userPath) {
7
37
  const absPath = resolve(projectRoot, userPath);
8
38
  const rel = relative(projectRoot, absPath);
9
- if (rel.startsWith('..') || resolve(projectRoot, rel) !== absPath) {
39
+ if (rel.startsWith("..") || resolve(projectRoot, rel) !== absPath) {
10
40
  throw new Error(`Path "${userPath}" resolves outside project root.`);
11
41
  }
12
42
  return absPath;
@@ -15,40 +45,40 @@ export function resolveSafePath(projectRoot, userPath) {
15
45
  * Validate smart_read arguments.
16
46
  */
17
47
  export function validateSmartReadArgs(args) {
18
- if (!args || typeof args !== 'object') {
19
- throw new Error('Arguments must be an object.');
48
+ if (!args || typeof args !== "object") {
49
+ throw new Error("Arguments must be an object.");
20
50
  }
21
51
  const a = args;
22
- if (typeof a.path !== 'string' || a.path.length === 0) {
52
+ if (typeof a.path !== "string" || a.path.length === 0) {
23
53
  throw new Error('Required parameter "path" must be a non-empty string.');
24
54
  }
25
55
  return {
26
56
  path: a.path,
27
- show_imports: optionalBool(a.show_imports, 'show_imports'),
28
- show_docs: optionalBool(a.show_docs, 'show_docs'),
29
- show_references: optionalBool(a.show_references, 'show_references'),
30
- depth: optionalNumber(a.depth, 'depth'),
31
- max_tokens: optionalNumber(a.max_tokens, 'max_tokens'),
57
+ show_imports: optionalBool(a.show_imports, "show_imports"),
58
+ show_docs: optionalBool(a.show_docs, "show_docs"),
59
+ show_references: optionalBool(a.show_references, "show_references"),
60
+ depth: optionalNumber(a.depth, "depth"),
61
+ max_tokens: optionalNumber(a.max_tokens, "max_tokens"),
32
62
  };
33
63
  }
34
64
  /**
35
65
  * Validate read_symbol arguments.
36
66
  */
37
67
  export function validateReadSymbolArgs(args) {
38
- if (!args || typeof args !== 'object') {
39
- throw new Error('Arguments must be an object.');
68
+ if (!args || typeof args !== "object") {
69
+ throw new Error("Arguments must be an object.");
40
70
  }
41
71
  const a = args;
42
- if (typeof a.path !== 'string' || a.path.length === 0) {
72
+ if (typeof a.path !== "string" || a.path.length === 0) {
43
73
  throw new Error('Required parameter "path" must be a non-empty string.');
44
74
  }
45
- if (typeof a.symbol !== 'string' || a.symbol.length === 0) {
75
+ if (typeof a.symbol !== "string" || a.symbol.length === 0) {
46
76
  throw new Error('Required parameter "symbol" must be a non-empty string.');
47
77
  }
48
78
  let show;
49
79
  if (a.show !== undefined && a.show !== null) {
50
- const valid = ['full', 'head', 'tail', 'outline'];
51
- if (typeof a.show !== 'string' || !valid.includes(a.show)) {
80
+ const valid = ["full", "head", "tail", "outline"];
81
+ if (typeof a.show !== "string" || !valid.includes(a.show)) {
52
82
  throw new Error('"show" must be one of: full, head, tail, outline.');
53
83
  }
54
84
  show = a.show;
@@ -56,8 +86,8 @@ export function validateReadSymbolArgs(args) {
56
86
  return {
57
87
  path: a.path,
58
88
  symbol: a.symbol,
59
- context_before: optionalNumber(a.context_before, 'context_before'),
60
- context_after: optionalNumber(a.context_after, 'context_after'),
89
+ context_before: optionalNumber(a.context_before, "context_before"),
90
+ context_after: optionalNumber(a.context_after, "context_after"),
61
91
  show,
62
92
  };
63
93
  }
@@ -65,11 +95,11 @@ export function validateReadSymbolArgs(args) {
65
95
  * Validate read_symbols arguments (batch multi-symbol read).
66
96
  */
67
97
  export function validateReadSymbolsArgs(args) {
68
- if (!args || typeof args !== 'object') {
69
- throw new Error('Arguments must be an object.');
98
+ if (!args || typeof args !== "object") {
99
+ throw new Error("Arguments must be an object.");
70
100
  }
71
101
  const a = args;
72
- if (typeof a.path !== 'string' || a.path.length === 0) {
102
+ if (typeof a.path !== "string" || a.path.length === 0) {
73
103
  throw new Error('Required parameter "path" must be a non-empty string.');
74
104
  }
75
105
  if (!Array.isArray(a.symbols) || a.symbols.length === 0) {
@@ -79,14 +109,14 @@ export function validateReadSymbolsArgs(args) {
79
109
  throw new Error('"symbols" can contain at most 10 symbols.');
80
110
  }
81
111
  for (const s of a.symbols) {
82
- if (typeof s !== 'string' || s.length === 0) {
112
+ if (typeof s !== "string" || s.length === 0) {
83
113
  throw new Error('Each symbol in "symbols" must be a non-empty string.');
84
114
  }
85
115
  }
86
116
  let show;
87
117
  if (a.show !== undefined && a.show !== null) {
88
- const valid = ['full', 'head', 'tail', 'outline'];
89
- if (typeof a.show !== 'string' || !valid.includes(a.show)) {
118
+ const valid = ["full", "head", "tail", "outline"];
119
+ if (typeof a.show !== "string" || !valid.includes(a.show)) {
90
120
  throw new Error('"show" must be one of: full, head, tail, outline.');
91
121
  }
92
122
  show = a.show;
@@ -94,8 +124,8 @@ export function validateReadSymbolsArgs(args) {
94
124
  return {
95
125
  path: a.path,
96
126
  symbols: a.symbols,
97
- context_before: optionalNumber(a.context_before, 'context_before'),
98
- context_after: optionalNumber(a.context_after, 'context_after'),
127
+ context_before: optionalNumber(a.context_before, "context_before"),
128
+ context_after: optionalNumber(a.context_after, "context_after"),
99
129
  show,
100
130
  };
101
131
  }
@@ -103,78 +133,83 @@ export function validateReadSymbolsArgs(args) {
103
133
  * Validate read_range arguments.
104
134
  */
105
135
  export function validateReadRangeArgs(args) {
106
- if (!args || typeof args !== 'object') {
107
- throw new Error('Arguments must be an object.');
136
+ if (!args || typeof args !== "object") {
137
+ throw new Error("Arguments must be an object.");
108
138
  }
109
139
  const a = args;
110
- if (typeof a.path !== 'string' || a.path.length === 0) {
140
+ if (typeof a.path !== "string" || a.path.length === 0) {
111
141
  throw new Error('Required parameter "path" must be a non-empty string.');
112
142
  }
113
- if (typeof a.start_line !== 'number' || !Number.isInteger(a.start_line) || a.start_line < 1) {
143
+ // v0.33.0 (B9) some MCP clients serialise numbers as strings;
144
+ // accept "10" the same as 10. Reject non-numeric strings.
145
+ const start = coerceIntFromAny(a.start_line);
146
+ if (start === null || start < 1) {
114
147
  throw new Error('Required parameter "start_line" must be a positive integer.');
115
148
  }
116
- if (typeof a.end_line !== 'number' || !Number.isInteger(a.end_line) || a.end_line < 1) {
149
+ const end = coerceIntFromAny(a.end_line);
150
+ if (end === null || end < 1) {
117
151
  throw new Error('Required parameter "end_line" must be a positive integer.');
118
152
  }
119
- if (a.end_line < a.start_line) {
153
+ if (end < start) {
120
154
  throw new Error('"end_line" must be >= "start_line".');
121
155
  }
122
- return { path: a.path, start_line: a.start_line, end_line: a.end_line };
156
+ return { path: a.path, start_line: start, end_line: end };
123
157
  }
124
158
  /**
125
159
  * Validate read_diff arguments.
126
160
  */
127
161
  export function validateReadDiffArgs(args) {
128
- if (!args || typeof args !== 'object') {
129
- throw new Error('Arguments must be an object.');
162
+ if (!args || typeof args !== "object") {
163
+ throw new Error("Arguments must be an object.");
130
164
  }
131
165
  const a = args;
132
- if (typeof a.path !== 'string' || a.path.length === 0) {
166
+ if (typeof a.path !== "string" || a.path.length === 0) {
133
167
  throw new Error('Required parameter "path" must be a non-empty string.');
134
168
  }
135
169
  return {
136
170
  path: a.path,
137
- context_lines: optionalNumber(a.context_lines, 'context_lines'),
171
+ context_lines: optionalNumber(a.context_lines, "context_lines"),
138
172
  };
139
173
  }
140
174
  export function validateFindUsagesArgs(args) {
141
- if (!args || typeof args !== 'object') {
142
- throw new Error('Arguments must be an object.');
175
+ if (!args || typeof args !== "object") {
176
+ throw new Error("Arguments must be an object.");
143
177
  }
144
178
  const a = args;
145
- if (typeof a.symbol !== 'string' || a.symbol.length === 0) {
179
+ if (typeof a.symbol !== "string" || a.symbol.length === 0) {
146
180
  throw new Error('Required parameter "symbol" must be a non-empty string.');
147
181
  }
148
182
  let kind;
149
183
  if (a.kind !== undefined && a.kind !== null) {
150
- const validKinds = ['definitions', 'imports', 'usages', 'all'];
151
- if (typeof a.kind !== 'string' || !validKinds.includes(a.kind)) {
152
- throw new Error(`"kind" must be one of: ${validKinds.join(', ')}`);
184
+ const validKinds = ["definitions", "imports", "usages", "all"];
185
+ if (typeof a.kind !== "string" || !validKinds.includes(a.kind)) {
186
+ throw new Error(`"kind" must be one of: ${validKinds.join(", ")}`);
153
187
  }
154
188
  kind = a.kind;
155
189
  }
156
- const limit = optionalNumber(a.limit, 'limit');
190
+ const limit = optionalNumber(a.limit, "limit");
157
191
  if (limit !== undefined && (limit < 1 || limit > 500)) {
158
192
  throw new Error('"limit" must be between 1 and 500.');
159
193
  }
160
- const context_lines = optionalNumber(a.context_lines, 'context_lines');
161
- if (context_lines !== undefined && (context_lines < 0 || context_lines > 10)) {
194
+ const context_lines = optionalNumber(a.context_lines, "context_lines");
195
+ if (context_lines !== undefined &&
196
+ (context_lines < 0 || context_lines > 10)) {
162
197
  throw new Error('"context_lines" must be between 0 and 10.');
163
198
  }
164
199
  let mode;
165
200
  if (a.mode !== undefined && a.mode !== null) {
166
- const validModes = ['full', 'list'];
167
- if (typeof a.mode !== 'string' || !validModes.includes(a.mode)) {
168
- throw new Error(`"mode" must be one of: ${validModes.join(', ')}`);
201
+ const validModes = ["full", "list"];
202
+ if (typeof a.mode !== "string" || !validModes.includes(a.mode)) {
203
+ throw new Error(`"mode" must be one of: ${validModes.join(", ")}`);
169
204
  }
170
205
  mode = a.mode;
171
206
  }
172
207
  return {
173
208
  symbol: a.symbol,
174
- scope: optionalString(a.scope, 'scope'),
209
+ scope: optionalString(a.scope, "scope"),
175
210
  kind,
176
211
  limit,
177
- lang: optionalString(a.lang, 'lang'),
212
+ lang: optionalString(a.lang, "lang"),
178
213
  context_lines,
179
214
  mode,
180
215
  };
@@ -183,38 +218,41 @@ export function validateFindUsagesArgs(args) {
183
218
  * Validate smart_read_many arguments.
184
219
  */
185
220
  export function validateSmartReadManyArgs(args) {
186
- if (!args || typeof args !== 'object') {
187
- throw new Error('Arguments must be an object.');
221
+ if (!args || typeof args !== "object") {
222
+ throw new Error("Arguments must be an object.");
188
223
  }
189
224
  const a = args;
190
225
  if (!Array.isArray(a.paths)) {
191
226
  throw new Error('Required parameter "paths" must be an array of strings.');
192
227
  }
193
228
  for (const p of a.paths) {
194
- if (typeof p !== 'string' || p.length === 0) {
229
+ if (typeof p !== "string" || p.length === 0) {
195
230
  throw new Error('Each path in "paths" must be a non-empty string.');
196
231
  }
197
232
  }
198
- return { paths: a.paths, max_tokens: optionalNumber(a.max_tokens, 'max_tokens') };
233
+ return {
234
+ paths: a.paths,
235
+ max_tokens: optionalNumber(a.max_tokens, "max_tokens"),
236
+ };
199
237
  }
200
238
  function optionalString(val, name) {
201
239
  if (val === undefined || val === null)
202
240
  return undefined;
203
- if (typeof val !== 'string')
241
+ if (typeof val !== "string")
204
242
  throw new Error(`"${name}" must be a string.`);
205
243
  return val;
206
244
  }
207
245
  function optionalBool(val, name) {
208
246
  if (val === undefined || val === null)
209
247
  return undefined;
210
- if (typeof val !== 'boolean')
248
+ if (typeof val !== "boolean")
211
249
  throw new Error(`"${name}" must be a boolean.`);
212
250
  return val;
213
251
  }
214
252
  function optionalNumber(val, name) {
215
253
  if (val === undefined || val === null)
216
254
  return undefined;
217
- if (typeof val !== 'number' || !Number.isFinite(val))
255
+ if (typeof val !== "number" || !Number.isFinite(val))
218
256
  throw new Error(`"${name}" must be a finite number.`);
219
257
  return val;
220
258
  }
@@ -222,14 +260,17 @@ function optionalNumber(val, name) {
222
260
  * Validate read_for_edit arguments.
223
261
  */
224
262
  export function validateReadForEditArgs(args) {
225
- if (!args || typeof args !== 'object') {
226
- throw new Error('Arguments must be an object.');
263
+ if (!args || typeof args !== "object") {
264
+ throw new Error("Arguments must be an object.");
227
265
  }
228
266
  const a = args;
229
- if (typeof a.path !== 'string' || a.path.length === 0) {
267
+ if (typeof a.path !== "string" || a.path.length === 0) {
230
268
  throw new Error('Required parameter "path" must be a non-empty string.');
231
269
  }
232
- if (!a.symbol && !a.line && (!Array.isArray(a.symbols) || a.symbols.length === 0) && !a.section) {
270
+ if (!a.symbol &&
271
+ !a.line &&
272
+ (!Array.isArray(a.symbols) || a.symbols.length === 0) &&
273
+ !a.section) {
233
274
  throw new Error('Either "symbol", "symbols", "line", or "section" must be provided.');
234
275
  }
235
276
  // Validate symbols array (batch mode)
@@ -242,7 +283,7 @@ export function validateReadForEditArgs(args) {
242
283
  throw new Error('"symbols" can contain at most 10 symbols.');
243
284
  }
244
285
  for (const s of a.symbols) {
245
- if (typeof s !== 'string' || s.length === 0) {
286
+ if (typeof s !== "string" || s.length === 0) {
246
287
  throw new Error('Each symbol in "symbols" must be a non-empty string.');
247
288
  }
248
289
  }
@@ -250,87 +291,106 @@ export function validateReadForEditArgs(args) {
250
291
  }
251
292
  return {
252
293
  path: a.path,
253
- symbol: optionalString(a.symbol, 'symbol'),
294
+ symbol: optionalString(a.symbol, "symbol"),
254
295
  symbols,
255
- line: optionalNumber(a.line, 'line'),
256
- context: optionalNumber(a.context, 'context'),
257
- include_callers: optionalBool(a.include_callers, 'include_callers'),
258
- include_tests: optionalBool(a.include_tests, 'include_tests'),
259
- include_changes: optionalBool(a.include_changes, 'include_changes'),
260
- section: optionalString(a.section, 'section'),
296
+ line: optionalNumber(a.line, "line"),
297
+ context: optionalNumber(a.context, "context"),
298
+ include_callers: optionalBool(a.include_callers, "include_callers"),
299
+ include_tests: optionalBool(a.include_tests, "include_tests"),
300
+ include_changes: optionalBool(a.include_changes, "include_changes"),
301
+ section: optionalString(a.section, "section"),
261
302
  };
262
303
  }
263
304
  /**
264
305
  * Validate related_files arguments.
265
306
  */
266
307
  export function validateRelatedFilesArgs(args) {
267
- if (!args || typeof args !== 'object') {
268
- throw new Error('Arguments must be an object.');
308
+ if (!args || typeof args !== "object") {
309
+ throw new Error("Arguments must be an object.");
269
310
  }
270
311
  const a = args;
271
- if (typeof a.path !== 'string' || a.path.length === 0) {
312
+ if (typeof a.path !== "string" || a.path.length === 0) {
272
313
  throw new Error('Required parameter "path" must be a non-empty string.');
273
314
  }
274
315
  return { path: a.path };
275
316
  }
276
317
  export function validateOutlineArgs(args) {
277
- if (!args || typeof args !== 'object') {
278
- throw new Error('Arguments must be an object.');
318
+ if (!args || typeof args !== "object") {
319
+ throw new Error("Arguments must be an object.");
279
320
  }
280
321
  const a = args;
281
- if (typeof a.path !== 'string' || a.path.length === 0) {
322
+ if (typeof a.path !== "string" || a.path.length === 0) {
282
323
  throw new Error('Required parameter "path" must be a non-empty string.');
283
324
  }
284
- const maxDepth = optionalNumber(a.max_depth, 'max_depth');
325
+ const maxDepth = optionalNumber(a.max_depth, "max_depth");
285
326
  if (maxDepth !== undefined && (maxDepth < 1 || maxDepth > 5)) {
286
327
  throw new Error('"max_depth" must be between 1 and 5.');
287
328
  }
288
329
  return {
289
330
  path: a.path,
290
- recursive: optionalBool(a.recursive, 'recursive'),
331
+ recursive: optionalBool(a.recursive, "recursive"),
291
332
  max_depth: maxDepth,
292
333
  };
293
334
  }
294
335
  export function validateFindUnusedArgs(args) {
295
- if (!args || typeof args !== 'object')
336
+ if (!args || typeof args !== "object")
296
337
  return {};
297
338
  const a = args;
298
339
  return {
299
- module: optionalString(a.module, 'module'),
300
- export_only: optionalBool(a.export_only, 'export_only'),
301
- limit: optionalNumber(a.limit, 'limit'),
340
+ module: optionalString(a.module, "module"),
341
+ export_only: optionalBool(a.export_only, "export_only"),
342
+ limit: optionalNumber(a.limit, "limit"),
343
+ };
344
+ }
345
+ export function validateCallTreeArgs(args) {
346
+ if (!args || typeof args !== "object") {
347
+ throw new Error("call_tree requires { symbol: string }");
348
+ }
349
+ const a = args;
350
+ const symbol = optionalString(a.symbol, "symbol");
351
+ if (!symbol) {
352
+ throw new Error("call_tree: `symbol` is required and must be a non-empty string.");
353
+ }
354
+ return {
355
+ symbol,
356
+ depth: optionalNumber(a.depth, "depth"),
302
357
  };
303
358
  }
304
359
  export function validateCodeAuditArgs(args) {
305
- if (!args || typeof args !== 'object') {
360
+ if (!args || typeof args !== "object") {
306
361
  throw new Error('Arguments must be an object with a "check" parameter.');
307
362
  }
308
363
  const a = args;
309
- const validChecks = ['pattern', 'todo', 'deprecated', 'annotations', 'all'];
310
- if (typeof a.check !== 'string' || !validChecks.includes(a.check)) {
311
- throw new Error(`Required parameter "check" must be one of: ${validChecks.join(', ')}`);
364
+ const validChecks = ["pattern", "todo", "deprecated", "annotations", "all"];
365
+ if (typeof a.check !== "string" || !validChecks.includes(a.check)) {
366
+ throw new Error(`Required parameter "check" must be one of: ${validChecks.join(", ")}`);
312
367
  }
313
- if (a.check === 'pattern') {
314
- if (typeof a.pattern !== 'string' || a.pattern.length === 0) {
368
+ if (a.check === "pattern") {
369
+ if (typeof a.pattern !== "string" || a.pattern.length === 0) {
315
370
  throw new Error('Parameter "pattern" is required when check="pattern". Example: "except:" or "print($$$ARGS)"');
316
371
  }
317
372
  }
318
- if (a.check === 'annotations') {
319
- if (typeof a.name !== 'string' || a.name.length === 0) {
373
+ if (a.check === "annotations") {
374
+ if (typeof a.name !== "string" || a.name.length === 0) {
320
375
  throw new Error('Parameter "name" is required when check="annotations". Example: "Deprecated" or "Controller"');
321
376
  }
322
377
  }
323
378
  return {
324
379
  check: a.check,
325
- pattern: optionalString(a.pattern, 'pattern'),
326
- name: optionalString(a.name, 'name'),
327
- lang: optionalString(a.lang, 'lang'),
328
- limit: optionalNumber(a.limit, 'limit'),
380
+ pattern: optionalString(a.pattern, "pattern"),
381
+ name: optionalString(a.name, "name"),
382
+ lang: optionalString(a.lang, "lang"),
383
+ limit: optionalNumber(a.limit, "limit"),
329
384
  };
330
385
  }
331
- const VALID_INCLUDE_SECTIONS = ['stack', 'ci', 'quality', 'architecture'];
386
+ const VALID_INCLUDE_SECTIONS = [
387
+ "stack",
388
+ "ci",
389
+ "quality",
390
+ "architecture",
391
+ ];
332
392
  export function validateProjectOverviewArgs(args) {
333
- if (!args || typeof args !== 'object')
393
+ if (!args || typeof args !== "object")
334
394
  return {};
335
395
  const a = args;
336
396
  if (a.include !== undefined && a.include !== null) {
@@ -338,8 +398,9 @@ export function validateProjectOverviewArgs(args) {
338
398
  throw new Error('"include" must be an array of section names.');
339
399
  }
340
400
  for (const item of a.include) {
341
- if (typeof item !== 'string' || !VALID_INCLUDE_SECTIONS.includes(item)) {
342
- throw new Error(`Each element of "include" must be one of: ${VALID_INCLUDE_SECTIONS.join(', ')}. Got: "${item}"`);
401
+ if (typeof item !== "string" ||
402
+ !VALID_INCLUDE_SECTIONS.includes(item)) {
403
+ throw new Error(`Each element of "include" must be one of: ${VALID_INCLUDE_SECTIONS.join(", ")}. Got: "${item}"`);
343
404
  }
344
405
  }
345
406
  return { include: a.include };
@@ -347,55 +408,60 @@ export function validateProjectOverviewArgs(args) {
347
408
  return {};
348
409
  }
349
410
  export function validateModuleInfoArgs(args) {
350
- if (!args || typeof args !== 'object') {
411
+ if (!args || typeof args !== "object") {
351
412
  throw new Error('Arguments must be an object with a "module" parameter.');
352
413
  }
353
414
  const a = args;
354
- if (typeof a.module !== 'string' || a.module.length === 0) {
415
+ if (typeof a.module !== "string" || a.module.length === 0) {
355
416
  throw new Error('Required parameter "module" must be a non-empty string.');
356
417
  }
357
418
  let check;
358
419
  if (a.check !== undefined && a.check !== null) {
359
- const validChecks = ['deps', 'dependents', 'api', 'unused-deps', 'all'];
360
- if (typeof a.check !== 'string' || !validChecks.includes(a.check)) {
361
- throw new Error(`"check" must be one of: ${validChecks.join(', ')}`);
420
+ const validChecks = ["deps", "dependents", "api", "unused-deps", "all"];
421
+ if (typeof a.check !== "string" || !validChecks.includes(a.check)) {
422
+ throw new Error(`"check" must be one of: ${validChecks.join(", ")}`);
362
423
  }
363
424
  check = a.check;
364
425
  }
365
426
  return {
366
427
  module: a.module,
367
- check: check ?? 'all',
428
+ check: check ?? "all",
368
429
  };
369
430
  }
370
431
  export function validateSmartDiffArgs(args) {
371
- if (!args || typeof args !== 'object')
372
- return { scope: 'unstaged' };
432
+ if (!args || typeof args !== "object")
433
+ return { scope: "unstaged" };
373
434
  const a = args;
374
435
  let scope;
375
436
  if (a.scope !== undefined && a.scope !== null) {
376
- const validScopes = ['unstaged', 'staged', 'commit', 'branch'];
377
- if (typeof a.scope !== 'string' || !validScopes.includes(a.scope)) {
378
- throw new Error(`"scope" must be one of: ${validScopes.join(', ')}`);
437
+ const validScopes = ["unstaged", "staged", "commit", "branch"];
438
+ if (typeof a.scope !== "string" || !validScopes.includes(a.scope)) {
439
+ throw new Error(`"scope" must be one of: ${validScopes.join(", ")}`);
379
440
  }
380
441
  scope = a.scope;
381
442
  }
382
- const ref = optionalString(a.ref, 'ref');
383
- if ((scope === 'commit' || scope === 'branch') && !ref) {
443
+ const ref = optionalString(a.ref, "ref");
444
+ if ((scope === "commit" || scope === "branch") && !ref) {
384
445
  throw new Error(`"ref" is required when scope="${scope}".`);
385
446
  }
386
447
  return {
387
- scope: scope ?? 'unstaged',
388
- path: optionalString(a.path, 'path'),
448
+ scope: scope ?? "unstaged",
449
+ path: optionalString(a.path, "path"),
389
450
  ref,
390
451
  };
391
452
  }
392
- const VALID_EXPLORE_SECTIONS = ['outline', 'imports', 'tests', 'changes'];
453
+ const VALID_EXPLORE_SECTIONS = [
454
+ "outline",
455
+ "imports",
456
+ "tests",
457
+ "changes",
458
+ ];
393
459
  export function validateExploreAreaArgs(args) {
394
- if (!args || typeof args !== 'object') {
460
+ if (!args || typeof args !== "object") {
395
461
  throw new Error('Arguments must be an object with a "path" parameter.');
396
462
  }
397
463
  const a = args;
398
- if (typeof a.path !== 'string' || a.path.length === 0) {
464
+ if (typeof a.path !== "string" || a.path.length === 0) {
399
465
  throw new Error('Required parameter "path" must be a non-empty string.');
400
466
  }
401
467
  if (a.include !== undefined && a.include !== null) {
@@ -403,8 +469,9 @@ export function validateExploreAreaArgs(args) {
403
469
  throw new Error('"include" must be an array of section names.');
404
470
  }
405
471
  for (const item of a.include) {
406
- if (typeof item !== 'string' || !VALID_EXPLORE_SECTIONS.includes(item)) {
407
- throw new Error(`Each element of "include" must be one of: ${VALID_EXPLORE_SECTIONS.join(', ')}. Got: "${item}"`);
472
+ if (typeof item !== "string" ||
473
+ !VALID_EXPLORE_SECTIONS.includes(item)) {
474
+ throw new Error(`Each element of "include" must be one of: ${VALID_EXPLORE_SECTIONS.join(", ")}. Got: "${item}"`);
408
475
  }
409
476
  }
410
477
  return { path: a.path, include: a.include };
@@ -412,42 +479,51 @@ export function validateExploreAreaArgs(args) {
412
479
  return { path: a.path };
413
480
  }
414
481
  export function validateSmartLogArgs(args) {
415
- if (!args || typeof args !== 'object')
482
+ if (!args || typeof args !== "object")
416
483
  return {};
417
484
  const a = args;
418
- const path = optionalString(a.path, 'path');
485
+ const path = optionalString(a.path, "path");
419
486
  if (path !== undefined && path.length === 0) {
420
487
  throw new Error('"path" must be a non-empty string.');
421
488
  }
422
- const count = optionalNumber(a.count, 'count');
489
+ const count = optionalNumber(a.count, "count");
423
490
  if (count !== undefined) {
424
491
  if (!Number.isInteger(count) || count < 1 || count > 50) {
425
492
  throw new Error('"count" must be an integer between 1 and 50.');
426
493
  }
427
494
  }
428
- const ref = optionalString(a.ref, 'ref');
495
+ const ref = optionalString(a.ref, "ref");
429
496
  if (ref !== undefined && ref.length === 0) {
430
497
  throw new Error('"ref" must be a non-empty string.');
431
498
  }
432
499
  return { path, count, ref };
433
500
  }
434
- const VALID_RUNNERS = ['vitest', 'jest', 'pytest', 'phpunit', 'go', 'cargo', 'rspec', 'mocha'];
501
+ const VALID_RUNNERS = [
502
+ "vitest",
503
+ "jest",
504
+ "pytest",
505
+ "phpunit",
506
+ "go",
507
+ "cargo",
508
+ "rspec",
509
+ "mocha",
510
+ ];
435
511
  export function validateTestSummaryArgs(args) {
436
- if (!args || typeof args !== 'object') {
512
+ if (!args || typeof args !== "object") {
437
513
  throw new Error('Arguments must be an object with a "command" parameter.');
438
514
  }
439
515
  const a = args;
440
- if (typeof a.command !== 'string' || a.command.length === 0) {
516
+ if (typeof a.command !== "string" || a.command.length === 0) {
441
517
  throw new Error('Required parameter "command" must be a non-empty string.');
442
518
  }
443
519
  let runner;
444
520
  if (a.runner !== undefined && a.runner !== null) {
445
- if (typeof a.runner !== 'string' || !VALID_RUNNERS.includes(a.runner)) {
446
- throw new Error(`"runner" must be one of: ${VALID_RUNNERS.join(', ')}`);
521
+ if (typeof a.runner !== "string" || !VALID_RUNNERS.includes(a.runner)) {
522
+ throw new Error(`"runner" must be one of: ${VALID_RUNNERS.join(", ")}`);
447
523
  }
448
524
  runner = a.runner;
449
525
  }
450
- const timeout = optionalNumber(a.timeout, 'timeout');
526
+ const timeout = optionalNumber(a.timeout, "timeout");
451
527
  if (timeout !== undefined) {
452
528
  if (!Number.isInteger(timeout) || timeout < 1000 || timeout > 300000) {
453
529
  throw new Error('"timeout" must be an integer between 1000 and 300000 (ms).');
@@ -456,27 +532,27 @@ export function validateTestSummaryArgs(args) {
456
532
  return { command: a.command, runner, timeout };
457
533
  }
458
534
  export function validateReadSectionArgs(args) {
459
- if (!args || typeof args !== 'object') {
460
- throw new Error('Arguments must be an object.');
535
+ if (!args || typeof args !== "object") {
536
+ throw new Error("Arguments must be an object.");
461
537
  }
462
538
  const a = args;
463
- if (typeof a.path !== 'string' || a.path.length === 0) {
539
+ if (typeof a.path !== "string" || a.path.length === 0) {
464
540
  throw new Error('Required parameter "path" must be a non-empty string.');
465
541
  }
466
- if (typeof a.heading !== 'string' || a.heading.length === 0) {
542
+ if (typeof a.heading !== "string" || a.heading.length === 0) {
467
543
  throw new Error('Required parameter "heading" must be a non-empty string.');
468
544
  }
469
545
  return { path: a.path, heading: a.heading };
470
546
  }
471
547
  /** Detect roots that would cause ast-index to scan the entire filesystem */
472
548
  export function isDangerousRoot(root) {
473
- const normalized = root.replace(/\/+$/, '') || '/';
549
+ const normalized = root.replace(/\/+$/, "") || "/";
474
550
  // System roots
475
- if (normalized === '/' || normalized === '/tmp' || normalized === '/var')
551
+ if (normalized === "/" || normalized === "/tmp" || normalized === "/var")
476
552
  return true;
477
553
  // Home directories (macOS, Linux)
478
- const home = process.env.HOME || process.env.USERPROFILE || '';
479
- if (home && normalized === home.replace(/\/+$/, ''))
554
+ const home = process.env.HOME || process.env.USERPROFILE || "";
555
+ if (home && normalized === home.replace(/\/+$/, ""))
480
556
  return true;
481
557
  // Common dangerous patterns: /Users, /home, /root, C:\, C:\Users
482
558
  if (/^\/(?:Users|home|root)$/.test(normalized))