@khanglvm/outline-cli 0.1.2 → 0.1.4

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.
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
2
2
  import fsSync from "node:fs";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
+ import { CliError } from "./errors.js";
5
6
 
6
7
  export const CONFIG_VERSION = 1;
7
8
 
@@ -479,6 +480,45 @@ export function suggestProfiles(config, query, options = {}) {
479
480
  };
480
481
  }
481
482
 
483
+ function isHighConfidenceProfileMatch(top, second, options = {}) {
484
+ if (!top) {
485
+ return false;
486
+ }
487
+
488
+ const minScore = Number.isFinite(Number(options.minScore)) ? Number(options.minScore) : 1.7;
489
+ const minGap = Number.isFinite(Number(options.minGap)) ? Number(options.minGap) : 0.55;
490
+ const exactSignals = new Set(["id_exact", "name_exact", "keyword_exact", "host"]);
491
+ const hasExactSignal = Array.isArray(top.matchedOn) && top.matchedOn.some((item) => exactSignals.has(item));
492
+ const gap = top.score - Number(second?.score || 0);
493
+
494
+ if (top.score >= 3.2) {
495
+ return true;
496
+ }
497
+ if (top.score >= minScore && gap >= minGap) {
498
+ return true;
499
+ }
500
+ if (hasExactSignal && top.score >= 1.2 && gap >= 0.35) {
501
+ return true;
502
+ }
503
+
504
+ return false;
505
+ }
506
+
507
+ function formatProfileSelectionError(config, message, options = {}) {
508
+ const availableProfiles = Object.keys(config?.profiles || {});
509
+ const query = String(options.query || "").trim();
510
+ const suggestionLimit = Number.isFinite(Number(options.suggestionLimit))
511
+ ? Math.max(1, Number(options.suggestionLimit))
512
+ : 3;
513
+ const suggestions = query ? suggestProfiles(config, query, { limit: suggestionLimit }).matches : [];
514
+
515
+ return new CliError(message, {
516
+ code: "PROFILE_SELECTION_REQUIRED",
517
+ availableProfiles,
518
+ ...(query ? { query, suggestions } : {}),
519
+ });
520
+ }
521
+
482
522
  export function suggestProfileMetadata(input = {}, options = {}) {
483
523
  const id = String(input.id || "").trim();
484
524
  const name = String(input.name || id || "").trim();
@@ -566,13 +606,17 @@ export function listProfiles(config) {
566
606
  }));
567
607
  }
568
608
 
569
- export function getProfile(config, explicitId) {
609
+ export function getProfile(config, explicitId, options = {}) {
570
610
  const profiles = config?.profiles || {};
571
611
 
572
612
  if (explicitId) {
573
613
  const profile = profiles[explicitId];
574
614
  if (!profile) {
575
- throw new Error(`Profile not found: ${explicitId}`);
615
+ throw new CliError(`Profile not found: ${explicitId}`, {
616
+ code: "PROFILE_NOT_FOUND",
617
+ profileId: explicitId,
618
+ availableProfiles: Object.keys(profiles),
619
+ });
576
620
  }
577
621
  return {
578
622
  id: explicitId,
@@ -583,7 +627,11 @@ export function getProfile(config, explicitId) {
583
627
  if (config.defaultProfile) {
584
628
  const profile = profiles[config.defaultProfile];
585
629
  if (!profile) {
586
- throw new Error(`Profile not found: ${config.defaultProfile}`);
630
+ throw new CliError(`Profile not found: ${config.defaultProfile}`, {
631
+ code: "PROFILE_NOT_FOUND",
632
+ profileId: config.defaultProfile,
633
+ availableProfiles: Object.keys(profiles),
634
+ });
587
635
  }
588
636
  return {
589
637
  id: config.defaultProfile,
@@ -601,12 +649,44 @@ export function getProfile(config, explicitId) {
601
649
  }
602
650
 
603
651
  if (profileIds.length > 1) {
604
- throw new Error(
605
- "Profile selection required: multiple profiles are saved and no default profile is set. Use --profile <id> or `outline-cli profile use <id>`."
652
+ const query = String(options.query || "").trim();
653
+ const allowAutoSelect = options.allowAutoSelect !== false;
654
+ const suggestionLimit = Number.isFinite(Number(options.suggestionLimit))
655
+ ? Math.max(1, Number(options.suggestionLimit))
656
+ : 3;
657
+
658
+ if (allowAutoSelect && query) {
659
+ const suggestions = suggestProfiles(config, query, { limit: suggestionLimit }).matches;
660
+ const top = suggestions[0];
661
+ const second = suggestions[1];
662
+
663
+ if (isHighConfidenceProfileMatch(top, second, options)) {
664
+ return {
665
+ id: top.id,
666
+ ...profiles[top.id],
667
+ selection: {
668
+ autoSelected: true,
669
+ query,
670
+ score: top.score,
671
+ matchedOn: top.matchedOn,
672
+ },
673
+ };
674
+ }
675
+ }
676
+
677
+ throw formatProfileSelectionError(
678
+ config,
679
+ "Profile selection required: multiple profiles are saved and no default profile is set. Use --profile <id> or `outline-cli profile use <id>`.",
680
+ {
681
+ query,
682
+ suggestionLimit,
683
+ }
606
684
  );
607
685
  }
608
686
 
609
- throw new Error("No profiles configured. Use `outline-cli profile add <id> ...` first.");
687
+ throw new CliError("No profiles configured. Use `outline-cli profile add <id> ...` first.", {
688
+ code: "PROFILE_NOT_CONFIGURED",
689
+ });
610
690
  }
611
691
 
612
692
  export function redactProfile(profile) {
@@ -3,8 +3,8 @@ export const ENTRY_INTEGRITY_MANIFEST = Object.freeze({
3
3
  version: 1,
4
4
  algorithm: "sha256",
5
5
  signatureAlgorithm: "sha256-salted-manifest-v1",
6
- signature: "db2b8cd3d150b5be3b17246af7a254fc42ec500c3567a8be79ca2e4178f4657a",
7
- generatedAt: "2026-03-06T00:27:55.266Z",
6
+ signature: "c69d41948b78c8ae35c0223377149ece8df5588863a010adf92a0b6ad11876d3",
7
+ generatedAt: "2026-03-07T12:16:27.340Z",
8
8
  files: [
9
9
  {
10
10
  "path": "src/action-gate.js",
@@ -12,19 +12,19 @@ export const ENTRY_INTEGRITY_MANIFEST = Object.freeze({
12
12
  },
13
13
  {
14
14
  "path": "src/agent-skills.js",
15
- "sha256": "3ec649a1e4fb475de47882223d2ca0e5bb049720cad1b456ea3ecd6ff1b74ecc"
15
+ "sha256": "d40e94a4b61922d3ee8a43ed89d5e9ce1562a2cdffd37e2f396c2a18b9a481fc"
16
16
  },
17
17
  {
18
18
  "path": "src/cli.js",
19
- "sha256": "bf37837390eaedadc6f8a8a8e279e61c12d56792f83cb4c53173439009cf5422"
19
+ "sha256": "2b6b17a71c15954c2663397d57afde2b8825ed368bb7f71833f6191662eff079"
20
20
  },
21
21
  {
22
22
  "path": "src/config-store.js",
23
- "sha256": "e0d62083c694f84895a93752738accbbd98830157227f06380ccdf8e421508fd"
23
+ "sha256": "3773d59d74502c75b7375c194b4f0e2c39fb43e95f739e6e4878aa1b0957a511"
24
24
  },
25
25
  {
26
26
  "path": "src/entry-integrity.js",
27
- "sha256": "3463fc3a3c6d0aead8e889ecce7c323b64b09ef54c772c52ff8b7266eb8c0d69"
27
+ "sha256": "5f66c29e57f53f11fc1751c9a8bd41c318001e3fb6562ee6f3a593f31849e45e"
28
28
  },
29
29
  {
30
30
  "path": "src/errors.js",
@@ -42,25 +42,29 @@ export const ENTRY_INTEGRITY_MANIFEST = Object.freeze({
42
42
  "path": "src/secure-keyring.js",
43
43
  "sha256": "370d904265733f7d6b0f47b3213c7f026c95e59bc18d364dff96be90dbd7f479"
44
44
  },
45
+ {
46
+ "path": "src/summary-redaction.js",
47
+ "sha256": "ddd3562334e704de5d9f6068726e311a98564e7e46bcdb56d9aade7e63b65bd3"
48
+ },
45
49
  {
46
50
  "path": "src/tool-arg-schemas.js",
47
- "sha256": "de6f389349d6688296a8e66d2d612937f31b4fc2a0efdca085d36ec5b09cab26"
51
+ "sha256": "51f8727255e5b2b0a4cce78495cfaed9bb1869a7ef104b22dbc170604f878feb"
48
52
  },
49
53
  {
50
54
  "path": "src/tools.extended.js",
51
- "sha256": "8f29601301c7942cce8884c689916a78a22231e5f6691ecebfe09d9d4f87d6c3"
55
+ "sha256": "0c5317ca0b28a0b7110d9e6c81d8ca6ccd705e9b303de2f29e03f847b31df310"
52
56
  },
53
57
  {
54
58
  "path": "src/tools.js",
55
- "sha256": "bfcb36045abecc758237ce9441d7e5994a4467893b09d48557749a90de424401"
59
+ "sha256": "96cfbb14cac2baa5221dc4e9294a0520f81c3e22a0c69ee648cd6c2354e1512e"
56
60
  },
57
61
  {
58
62
  "path": "src/tools.mutation.js",
59
- "sha256": "013cee7d9815f868cca8990af39abe385239fa0f324054477d681e479605e903"
63
+ "sha256": "15fa3d146d99f55bc16d814ffd9360127b99eca2f9271cf751c6e5c7e4234317"
60
64
  },
61
65
  {
62
66
  "path": "src/tools.navigation.js",
63
- "sha256": "8cd6ad37db2ef4d8a2ddd2eea408c8ab50b5649b620680cef07bd8f3ecea7e89"
67
+ "sha256": "f7118e91c2edd0e6b57591d41fe7707c68eef51cde77918495bb4ee166139339"
64
68
  },
65
69
  {
66
70
  "path": "src/tools.platform.js",
@@ -52,6 +52,7 @@ export async function assertEntryIntegrity(rootDir = REPO_ROOT) {
52
52
  if (files.length === 0) {
53
53
  throw new CliError("Entry integrity manifest is empty", {
54
54
  code: "ENTRY_INTEGRITY_MANIFEST_EMPTY",
55
+ hint: "Run `npm run integrity:refresh` to regenerate local integrity metadata.",
55
56
  });
56
57
  }
57
58
 
@@ -62,6 +63,7 @@ export async function assertEntryIntegrity(rootDir = REPO_ROOT) {
62
63
  expected: manifest.signature,
63
64
  actual: computedSignature,
64
65
  keyId: ENTRY_INTEGRITY_BINDING.keyId,
66
+ hint: "Run `npm run integrity:refresh` after local source edits, or set `OUTLINE_CLI_SKIP_INTEGRITY_CHECK=1` for local smoke runs.",
65
67
  });
66
68
  }
67
69
 
@@ -93,6 +95,7 @@ export async function assertEntryIntegrity(rootDir = REPO_ROOT) {
93
95
  code: "ENTRY_SUBMODULE_INTEGRITY_FAILED",
94
96
  mismatchCount: mismatches.length,
95
97
  mismatches,
98
+ hint: "Run `npm run integrity:refresh` after local source edits, or set `OUTLINE_CLI_SKIP_INTEGRITY_CHECK=1` for local smoke runs.",
96
99
  });
97
100
  }
98
101
 
@@ -0,0 +1,37 @@
1
+ const SUMMARY_SECRET_LINE_PATTERNS = [
2
+ /(^|\n)([ \t>*+\-]*(?:[*_`~]+\s*)*(?:user(?:name)?\s*\/\s*pass(?:word)?|username\s*\/\s*password|credentials?|api[ _-]*key|access[ _-]*token|refresh[ _-]*token|client[ _-]*secret|secret|password|pass|pwd|authorization|bearer)(?:\s*[*_`~]+)*\s*:\s*)([^\n]+)/gi,
3
+ /(^|\n)([ \t>*+\-]*(?:[*_`~]+\s*)*(?:user(?:name)?|email|login)(?:\s*[*_`~]+)*\s*:\s*)([^\n]+)(\n[ \t>*+\-]*(?:[*_`~]+\s*)*(?:pass(?:word)?|pwd)(?:\s*[*_`~]+)*\s*:\s*)([^\n]+)/gi,
4
+ ];
5
+
6
+ export function redactSensitiveSummaryText(text) {
7
+ if (typeof text !== "string" || text.length === 0) {
8
+ return text;
9
+ }
10
+
11
+ let redacted = text;
12
+ for (const pattern of SUMMARY_SECRET_LINE_PATTERNS) {
13
+ redacted = redacted.replace(pattern, (...parts) => {
14
+ const leading = parts[1] || "";
15
+ const prefix = parts[2] || "";
16
+ const secondPrefix = parts[4];
17
+ if (typeof secondPrefix === "string") {
18
+ return `${leading}${prefix}[REDACTED]${secondPrefix}[REDACTED]`;
19
+ }
20
+ return `${leading}${prefix}[REDACTED]`;
21
+ });
22
+ }
23
+
24
+ redacted = redacted.replace(/\bol_api_[A-Za-z0-9]+\b/g, "ol_api_[REDACTED]");
25
+ redacted = redacted.replace(/\b(?:ghp|gho|ghu|ghs|github_pat|sk|pk)_[A-Za-z0-9_]+\b/g, "[REDACTED_TOKEN]");
26
+ redacted = redacted.replace(/(https?:\/\/)([^\s:@/]+):([^\s@/]+)@/gi, "$1[REDACTED]@");
27
+
28
+ return redacted;
29
+ }
30
+
31
+ export function summarizeSafeText(text, maxChars) {
32
+ const redacted = redactSensitiveSummaryText(text);
33
+ if (typeof redacted !== "string") {
34
+ return redacted;
35
+ }
36
+ return redacted.length > maxChars ? `${redacted.slice(0, maxChars)}...` : redacted;
37
+ }
@@ -11,12 +11,8 @@ const TYPES = {
11
11
  "string|string[]": (v) => typeof v === "string" || (Array.isArray(v) && v.every((x) => typeof x === "string")),
12
12
  };
13
13
 
14
- function fail(tool, issues) {
15
- throw new CliError(`Invalid args for ${tool}`, {
16
- code: "ARG_VALIDATION_FAILED",
17
- tool,
18
- issues,
19
- });
14
+ function fail(tool, issues, spec = {}, args = undefined) {
15
+ throw new CliError(`Invalid args for ${tool}`, buildValidationDetails(tool, spec, issues, args));
20
16
  }
21
17
 
22
18
  function ensureObject(tool, args) {
@@ -25,7 +21,248 @@ function ensureObject(tool, args) {
25
21
  }
26
22
  }
27
23
 
28
- function validateSpec(tool, args, spec) {
24
+ function levenshteinDistance(a, b) {
25
+ const left = String(a || "");
26
+ const right = String(b || "");
27
+ const rows = left.length + 1;
28
+ const cols = right.length + 1;
29
+ const matrix = Array.from({ length: rows }, () => Array(cols).fill(0));
30
+
31
+ for (let i = 0; i < rows; i += 1) {
32
+ matrix[i][0] = i;
33
+ }
34
+ for (let j = 0; j < cols; j += 1) {
35
+ matrix[0][j] = j;
36
+ }
37
+
38
+ for (let i = 1; i < rows; i += 1) {
39
+ for (let j = 1; j < cols; j += 1) {
40
+ const cost = left[i - 1] === right[j - 1] ? 0 : 1;
41
+ matrix[i][j] = Math.min(
42
+ matrix[i - 1][j] + 1,
43
+ matrix[i][j - 1] + 1,
44
+ matrix[i - 1][j - 1] + cost
45
+ );
46
+ }
47
+ }
48
+
49
+ return matrix[rows - 1][cols - 1];
50
+ }
51
+
52
+ function buildAcceptedArgList(spec = {}) {
53
+ const accepted = new Set(Object.keys(spec.properties || {}));
54
+ accepted.add("compact");
55
+ return [...accepted].sort();
56
+ }
57
+
58
+ function suggestClosestArgNames(key, acceptedArgs) {
59
+ const raw = String(key || "").trim();
60
+ if (!raw) {
61
+ return [];
62
+ }
63
+
64
+ return acceptedArgs
65
+ .map((candidate) => ({
66
+ candidate,
67
+ distance: levenshteinDistance(raw.toLowerCase(), String(candidate).toLowerCase()),
68
+ }))
69
+ .filter((row) => row.distance <= 3)
70
+ .sort((a, b) => a.distance - b.distance || a.candidate.localeCompare(b.candidate))
71
+ .slice(0, 3)
72
+ .map((row) => row.candidate);
73
+ }
74
+
75
+ function enrichIssuesWithArgSuggestions(issues, acceptedArgs) {
76
+ return issues.map((issue) => {
77
+ if (!issue || issue.message !== "is not allowed" || typeof issue.path !== "string") {
78
+ return issue;
79
+ }
80
+ const key = issue.path.startsWith("args.") ? issue.path.slice(5) : issue.path;
81
+ const suggestions = suggestClosestArgNames(key, acceptedArgs);
82
+ return suggestions.length > 0 ? { ...issue, suggestions } : issue;
83
+ });
84
+ }
85
+
86
+ function extractArgKeyFromIssue(issue) {
87
+ if (!issue || typeof issue.path !== "string") {
88
+ return null;
89
+ }
90
+ return issue.path.startsWith("args.") ? issue.path.slice(5) : issue.path;
91
+ }
92
+
93
+ function applySuggestedArgFixes(args, issues) {
94
+ if (!args || typeof args !== "object" || Array.isArray(args)) {
95
+ return undefined;
96
+ }
97
+
98
+ const next = { ...args };
99
+ let changed = false;
100
+
101
+ for (const issue of issues) {
102
+ if (!issue || issue.message !== "is not allowed") {
103
+ continue;
104
+ }
105
+ const key = extractArgKeyFromIssue(issue);
106
+ const target = Array.isArray(issue.suggestions) && issue.suggestions.length > 0
107
+ ? issue.suggestions[0]
108
+ : null;
109
+ if (!key || !target || !(key in next) || key === target) {
110
+ continue;
111
+ }
112
+ if (target in next && next[target] !== next[key]) {
113
+ continue;
114
+ }
115
+ if (!(target in next)) {
116
+ next[target] = next[key];
117
+ }
118
+ delete next[key];
119
+ changed = true;
120
+ }
121
+
122
+ return changed ? next : undefined;
123
+ }
124
+
125
+ function buildSuggestedArgs(spec, args, enrichedIssues) {
126
+ const candidate = applySuggestedArgFixes(args, enrichedIssues);
127
+ if (!candidate) {
128
+ return undefined;
129
+ }
130
+
131
+ const normalized = normalizeArgsForSpec(candidate, spec);
132
+ const remainingIssues = collectValidationIssues(normalized, spec);
133
+ return remainingIssues.length === 0 ? normalized : undefined;
134
+ }
135
+
136
+ function buildValidationDetails(tool, spec, issues, args = undefined) {
137
+ const acceptedArgs = buildAcceptedArgList(spec);
138
+ const enrichedIssues = enrichIssuesWithArgSuggestions(issues, acceptedArgs);
139
+ const requiredArgs = [...new Set(spec.required || [])].sort();
140
+ const unknownArgs = enrichedIssues
141
+ .filter((issue) => issue?.message === "is not allowed" && typeof issue.path === "string")
142
+ .map((issue) => issue.path.replace(/^args\./, ""));
143
+ const suggestedArgs = buildSuggestedArgs(spec, args, enrichedIssues);
144
+
145
+ return {
146
+ code: "ARG_VALIDATION_FAILED",
147
+ tool,
148
+ issues: enrichedIssues,
149
+ acceptedArgs,
150
+ requiredArgs,
151
+ unknownArgs,
152
+ suggestedArgs,
153
+ validationHint:
154
+ acceptedArgs.length > 0 ? `Accepted args: ${acceptedArgs.join(", ")}` : undefined,
155
+ };
156
+ }
157
+
158
+ function looksNumeric(value) {
159
+ return /^-?(?:\d+|\d+\.\d+)$/.test(String(value || "").trim());
160
+ }
161
+
162
+ function coerceScalarValue(types, value) {
163
+ if (typeof value !== "string") {
164
+ return value;
165
+ }
166
+
167
+ const trimmed = value.trim();
168
+ if (!trimmed) {
169
+ return value;
170
+ }
171
+
172
+ if (types.includes("boolean") && /^(true|false)$/i.test(trimmed)) {
173
+ return trimmed.toLowerCase() === "true";
174
+ }
175
+ if (types.includes("null") && /^null$/i.test(trimmed)) {
176
+ return null;
177
+ }
178
+ if (types.includes("number") && looksNumeric(trimmed)) {
179
+ return Number(trimmed);
180
+ }
181
+ if (types.includes("object") && /^[{]/.test(trimmed)) {
182
+ try {
183
+ return JSON.parse(trimmed);
184
+ } catch {
185
+ return value;
186
+ }
187
+ }
188
+ if (types.includes("array") && /^[[]/.test(trimmed)) {
189
+ try {
190
+ return JSON.parse(trimmed);
191
+ } catch {
192
+ return value;
193
+ }
194
+ }
195
+
196
+ return value;
197
+ }
198
+
199
+ function coerceArrayValue(types, value) {
200
+ if (!Array.isArray(value)) {
201
+ if (types.includes("string[]") && typeof value === "string") {
202
+ return [value];
203
+ }
204
+ return value;
205
+ }
206
+
207
+ if (types.includes("string[]")) {
208
+ return value.map((item) => (typeof item === "string" ? item : String(item)));
209
+ }
210
+
211
+ return value;
212
+ }
213
+
214
+ function normalizeCrossFieldAliases(args, properties) {
215
+ const next = { ...args };
216
+
217
+ if (Array.isArray(next.query) && properties.queries && next.queries === undefined) {
218
+ next.queries = next.query.map((item) => String(item));
219
+ delete next.query;
220
+ }
221
+ if (Array.isArray(next.question) && properties.questions && next.questions === undefined) {
222
+ next.questions = next.question.map((item) => String(item));
223
+ delete next.question;
224
+ }
225
+ if (Array.isArray(next.id) && properties.ids && next.ids === undefined) {
226
+ next.ids = next.id.map((item) => String(item));
227
+ delete next.id;
228
+ }
229
+ if (typeof next.ids === "string" && properties.id && !properties.ids && next.id === undefined) {
230
+ next.id = next.ids;
231
+ delete next.ids;
232
+ }
233
+ if (typeof next.queries === "string" && properties.query && !properties.queries && next.query === undefined) {
234
+ next.query = next.queries;
235
+ delete next.queries;
236
+ }
237
+
238
+ return next;
239
+ }
240
+
241
+ function normalizeArgsForSpec(args, spec) {
242
+ const properties = spec.properties || {};
243
+ const next = normalizeCrossFieldAliases(args, properties);
244
+
245
+ for (const [key, rule] of Object.entries(properties)) {
246
+ if (!(key in next)) {
247
+ continue;
248
+ }
249
+
250
+ const types = Array.isArray(rule.type) ? rule.type : [rule.type];
251
+ let value = next[key];
252
+ value = coerceArrayValue(types, value);
253
+ value = coerceScalarValue(types, value);
254
+
255
+ if (Array.isArray(value) && types.includes("string[]")) {
256
+ value = value.map((item) => (typeof item === "string" ? item : String(item)));
257
+ }
258
+
259
+ next[key] = value;
260
+ }
261
+
262
+ return next;
263
+ }
264
+
265
+ function collectValidationIssues(args, spec) {
29
266
  const issues = [];
30
267
  const properties = spec.properties || {};
31
268
 
@@ -81,8 +318,14 @@ function validateSpec(tool, args, spec) {
81
318
  spec.custom(args, issues);
82
319
  }
83
320
 
321
+ return issues;
322
+ }
323
+
324
+ function validateSpec(tool, args, spec) {
325
+ const issues = collectValidationIssues(args, spec);
326
+
84
327
  if (issues.length > 0) {
85
- fail(tool, issues);
328
+ fail(tool, issues, spec, args);
86
329
  }
87
330
  }
88
331
 
@@ -151,10 +394,19 @@ export const TOOL_ARG_SCHEMAS = {
151
394
  sort: { type: "string" },
152
395
  direction: { type: "string", enum: ["ASC", "DESC"] },
153
396
  parentDocumentId: { type: ["string", "null"] },
397
+ rootOnly: { type: "boolean" },
154
398
  backlinkDocumentId: { type: "string" },
155
399
  includePolicies: { type: "boolean" },
156
400
  ...SHARED_DOC_COMMON,
157
401
  },
402
+ custom(args, issues) {
403
+ if (args.rootOnly === true && Object.prototype.hasOwnProperty.call(args, "parentDocumentId") && args.parentDocumentId !== null) {
404
+ issues.push({
405
+ path: "args.rootOnly",
406
+ message: "cannot be combined with a non-null args.parentDocumentId",
407
+ });
408
+ }
409
+ },
158
410
  },
159
411
  "documents.backlinks": {
160
412
  required: ["id"],
@@ -2245,6 +2497,7 @@ export const TOOL_ARG_SCHEMAS = {
2245
2497
  dateFilter: { type: "string", enum: ["day", "week", "month", "year"] },
2246
2498
  includePolicies: { type: "boolean" },
2247
2499
  includeEvidenceDocs: { type: "boolean" },
2500
+ limit: { type: "number", min: 1 },
2248
2501
  view: { type: "string", enum: ["summary", "full"] },
2249
2502
  maxAttempts: { type: "number", min: 1 },
2250
2503
  },
@@ -2267,6 +2520,7 @@ export const TOOL_ARG_SCHEMAS = {
2267
2520
  dateFilter: { type: "string", enum: ["day", "week", "month", "year"] },
2268
2521
  includePolicies: { type: "boolean" },
2269
2522
  includeEvidenceDocs: { type: "boolean" },
2523
+ limit: { type: "number", min: 1 },
2270
2524
  view: { type: "string", enum: ["summary", "full"] },
2271
2525
  concurrency: { type: "number", min: 1 },
2272
2526
  maxAttempts: { type: "number", min: 1 },
@@ -2338,9 +2592,11 @@ export const TOOL_ARG_SCHEMAS = {
2338
2592
  export function validateToolArgs(toolName, args = {}) {
2339
2593
  const spec = TOOL_ARG_SCHEMAS[toolName];
2340
2594
  if (!spec) {
2341
- return;
2595
+ return args;
2342
2596
  }
2343
2597
 
2344
2598
  ensureObject(toolName, args);
2345
- validateSpec(toolName, args, spec);
2599
+ const normalizedArgs = normalizeArgsForSpec(args, spec);
2600
+ validateSpec(toolName, normalizedArgs, spec);
2601
+ return normalizedArgs;
2346
2602
  }