Skip to content
This repository was archived by the owner on Jun 16, 2026. It is now read-only.

Commit e4e2f40

Browse files
mcp-tool-shopclaude
andcommitted
audit: add validate CLI command, fix JSON parse error handling
- Add 'validate' CLI command to surface validateIndex() from command line - Fix loadIndex() to emit structured error on malformed JSON instead of raw stack - Bump to v1.4.3 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 39d1b88 commit e4e2f40

3 files changed

Lines changed: 63 additions & 2 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# Changelog
22

3+
## 1.4.3 — 2026-03-25
4+
5+
- **`validate` CLI command**: validate index structure from the command line (`ai-loadout validate <index>`)
6+
- **Fix**: `loadIndex()` now emits structured error on malformed JSON instead of raw stack trace
7+
38
## 1.4.2 — 2026-03-06
49

510
- **README overhaul**: Document full API surface — agent runtime (`planLoad`, `recordLoad`, `manualLookup`), resolver, observability, merge, CLI commands

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@mcptoolshop/ai-loadout",
3-
"version": "1.4.2",
3+
"version": "1.4.3",
44
"description": "Context-aware knowledge router for AI agents. Dispatch table, matcher, hierarchical resolver, agent runtime contract, CLI.",
55
"type": "module",
66
"bin": {

src/cli.ts

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { readUsage, summarizeUsage } from "./usage.js";
1818
import { findDeadEntries, findKeywordOverlaps, analyzeBudget } from "./analysis.js";
1919
import { resolveLoadout, explainEntry } from "./resolve.js";
2020
import type { ResolveOptions } from "./resolve.js";
21+
import { validateIndex } from "./validate.js";
2122

2223
// ── Colors ────────────────────────────────────────────────────
2324
const BOLD = "\x1b[1m";
@@ -73,7 +74,11 @@ function loadIndex(path: string): LoadoutIndex {
7374
if (!existsSync(path)) {
7475
fail("FILE_NOT_FOUND", `Index not found: ${path}`);
7576
}
76-
return JSON.parse(readFileSync(path, "utf-8")) as LoadoutIndex;
77+
try {
78+
return JSON.parse(readFileSync(path, "utf-8")) as LoadoutIndex;
79+
} catch (e) {
80+
fail("PARSE_ERROR", `Failed to parse index: ${path}`, (e as Error).message);
81+
}
7782
}
7883

7984
function getVersion(): string {
@@ -98,6 +103,7 @@ ${BOLD}Usage:${RESET}
98103
ai-loadout dead <index> <jsonl> Find entries never loaded
99104
ai-loadout overlaps <index> Find keyword routing ambiguities
100105
ai-loadout budget <index> [jsonl] Token budget breakdown
106+
ai-loadout validate <index> Validate index structure
101107
102108
${BOLD}Options:${RESET}
103109
--json Output as JSON
@@ -115,6 +121,7 @@ ${BOLD}Examples:${RESET}
115121
ai-loadout usage .claude/loadout-usage.jsonl
116122
ai-loadout dead .claude/rules/index.json .claude/loadout-usage.jsonl
117123
ai-loadout overlaps .claude/rules/index.json
124+
ai-loadout validate .claude/rules/index.json
118125
ai-loadout budget .claude/rules/index.json .claude/loadout-usage.jsonl
119126
`);
120127
}
@@ -266,6 +273,52 @@ function cmdBudget(args: string[]) {
266273
log("");
267274
}
268275

276+
function cmdValidate(args: string[]) {
277+
const positional = positionalArgs(args);
278+
if (positional.length < 1) {
279+
fail("MISSING_ARG", "Usage: ai-loadout validate <index>");
280+
}
281+
282+
const index = loadIndex(resolve(positional[0]));
283+
const issues = validateIndex(index);
284+
const json = hasFlag(args, "json");
285+
286+
if (json) {
287+
log(JSON.stringify({
288+
valid: issues.filter((i) => i.severity === "error").length === 0,
289+
errors: issues.filter((i) => i.severity === "error").length,
290+
warnings: issues.filter((i) => i.severity === "warning").length,
291+
issues,
292+
}, null, 2));
293+
return;
294+
}
295+
296+
const errors = issues.filter((i) => i.severity === "error");
297+
const warnings = issues.filter((i) => i.severity === "warning");
298+
299+
if (issues.length === 0) {
300+
ok(`Index is valid (${index.entries.length} entries)`);
301+
return;
302+
}
303+
304+
log(`\n${BOLD}Validation Results${RESET}\n`);
305+
306+
for (const issue of errors) {
307+
log(` ${RED}✗ [${issue.code}]${RESET} ${issue.message}`);
308+
if (issue.hint) log(` ${DIM}${issue.hint}${RESET}`);
309+
}
310+
311+
for (const issue of warnings) {
312+
warn(`[${issue.code}] ${issue.message}`);
313+
}
314+
315+
log(`\n ${errors.length} error(s), ${warnings.length} warning(s)`);
316+
317+
if (errors.length > 0) {
318+
process.exit(1);
319+
}
320+
}
321+
269322
function cmdResolve(args: string[]) {
270323
const opts = getResolveOpts(args);
271324
const json = hasFlag(args, "json");
@@ -411,6 +464,9 @@ switch (cmd) {
411464
case "budget":
412465
cmdBudget(cmdArgs);
413466
break;
467+
case "validate":
468+
cmdValidate(cmdArgs);
469+
break;
414470
default:
415471
fail("UNKNOWN_COMMAND", `Unknown command: ${cmd}`, "Run ai-loadout --help for usage");
416472
}

0 commit comments

Comments
 (0)