agent-context-lint 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,114 @@
1
+ # agent-context-lint
2
+
3
+ Lint AI coding agent context files for staleness, broken paths, and semantic quality issues.
4
+
5
+ Supports **CLAUDE.md**, **AGENTS.md**, **.cursorrules**, and **copilot-instructions.md**.
6
+
7
+ ## Quickstart
8
+
9
+ ```bash
10
+ npx agent-context-lint
11
+ ```
12
+
13
+ Auto-discovers context files in the current directory and runs both structural and semantic checks.
14
+
15
+ ## The problem
16
+
17
+ Every engineering team using Claude Code, Cursor, Copilot, or Codex has context files that drift silently away from reality as the codebase changes. Broken paths, missing scripts, stale dates, and vague instructions degrade agent performance and inflate token costs.
18
+
19
+ [Research shows](https://github.blog/ai-and-ml/github-copilot/how-to-write-a-great-agents-md-lessons-from-over-2500-repositories/) most AGENTS.md files lack the sections that most improve agent task performance. [Studies confirm](https://devcenter.upsun.com/posts/agents-md-less-is-more/) poor context files inflate token costs by 20%+.
20
+
21
+ No existing tool does both structural validation (do referenced paths and scripts actually exist?) and semantic quality scoring (are instructions specific and actionable?).
22
+
23
+ ## What it checks
24
+
25
+ ### Layer 1 — Repo-state validation
26
+
27
+ | Rule | Severity | What it catches |
28
+ |------|----------|----------------|
29
+ | `check:paths` | error | File/directory paths mentioned in context file that don't exist on disk |
30
+ | `check:scripts` | error | npm/pnpm/yarn/bun scripts referenced that aren't in package.json |
31
+
32
+ ### Layer 2 — Semantic quality
33
+
34
+ | Rule | Severity | What it catches |
35
+ |------|----------|----------------|
36
+ | `check:token-budget` | warn/error | Files exceeding token thresholds (warn: 2,000, error: 5,000) |
37
+ | `check:vague` | warning | Vague instructions like "follow best practices" or "use good judgment" |
38
+ | `check:required-sections` | warning | Missing recommended sections (Setup, Testing, Build) |
39
+ | `check:stale-dates` | warning | Year references older than 2 years |
40
+ | `check:contradictions` | warning | Contradictory directives ("always use X" + "never use X") |
41
+
42
+ Each file gets a **0–100 quality score** based on findings.
43
+
44
+ ### Example output
45
+
46
+ ```
47
+ CLAUDE.md (score: 70/100)
48
+ 3:5 x Path does not exist: ./src/old-module.ts [check:paths]
49
+ 7:1 ! Vague instruction: "follow best practices" [check:vague]
50
+ 1:1 ! Missing recommended section: "Testing" [check:required-sections]
51
+
52
+ 1 problems (1 errors, 2 warnings)
53
+ ```
54
+
55
+ ## CLI options
56
+
57
+ ```
58
+ npx agent-context-lint # auto-discover and lint all context files
59
+ npx agent-context-lint CLAUDE.md # lint a specific file
60
+ npx agent-context-lint --format json # machine-readable output for CI
61
+ npx agent-context-lint --json # shorthand for --format json
62
+ npx agent-context-lint -V # show version
63
+ ```
64
+
65
+ Exit code 1 on any error (CI-compatible).
66
+
67
+ ## Configuration
68
+
69
+ Create `.agent-context-lint.json` in your project root, or add an `agentContextLint` key to `package.json`:
70
+
71
+ ```json
72
+ {
73
+ "tokenBudget": { "warn": 2000, "error": 5000 },
74
+ "requiredSections": ["Setup", "Testing", "Build"],
75
+ "staleDateYears": 2,
76
+ "vaguePatterns": ["follow best practices", "be careful", "use good judgment"],
77
+ "ignore": []
78
+ }
79
+ ```
80
+
81
+ ## Programmatic API
82
+
83
+ ```typescript
84
+ import { lint, lintFile } from 'agent-context-lint';
85
+
86
+ const result = lint(process.cwd());
87
+ // result.files[0].score → 85
88
+ // result.files[0].findings → [{ rule: 'check:paths', ... }]
89
+
90
+ const fileResult = lintFile('./CLAUDE.md', process.cwd());
91
+ ```
92
+
93
+ ## Prior art
94
+
95
+ - [agents-lint](https://github.com/giacomo/agents-lint) — structural checks only (paths, scripts, freshness score). No semantic quality scoring.
96
+ - [ai-context-kit](https://github.com/ofershap/ai-context-kit) — semantic checks only (token budget, vague instructions, contradictions). No repo-state validation.
97
+ - [@carlrannaberg/cclint](https://github.com/carlrannaberg/cclint) — Claude Code-specific. Not useful for AGENTS.md or .cursorrules.
98
+
99
+ agent-context-lint combines both structural and semantic checks in a single tool.
100
+
101
+ ## Contributing
102
+
103
+ Contributions welcome. Please open an issue first to discuss what you'd like to change.
104
+
105
+ ```bash
106
+ git clone https://github.com/mattschaller/agent-context-lint
107
+ cd agent-context-lint
108
+ npm install
109
+ npm test
110
+ ```
111
+
112
+ ## License
113
+
114
+ MIT
package/dist/cli.js ADDED
@@ -0,0 +1,525 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { resolve as resolve4 } from "path";
5
+
6
+ // src/checkers.ts
7
+ import { existsSync, readFileSync } from "fs";
8
+ import { dirname, resolve } from "path";
9
+ function checkPaths(parsed, filePath) {
10
+ const findings = [];
11
+ const baseDir = dirname(filePath);
12
+ for (const ref of parsed.paths) {
13
+ const resolved = resolve(baseDir, ref.value);
14
+ if (!existsSync(resolved)) {
15
+ findings.push({
16
+ file: filePath,
17
+ rule: "check:paths",
18
+ line: ref.line,
19
+ column: ref.column,
20
+ severity: "error",
21
+ message: `Path does not exist: ${ref.value}`
22
+ });
23
+ }
24
+ }
25
+ return findings;
26
+ }
27
+ function checkScripts(parsed, filePath) {
28
+ const findings = [];
29
+ const baseDir = dirname(filePath);
30
+ const pkgPath = resolve(baseDir, "package.json");
31
+ let scripts = {};
32
+ if (existsSync(pkgPath)) {
33
+ try {
34
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
35
+ scripts = pkg.scripts || {};
36
+ } catch {
37
+ return findings;
38
+ }
39
+ } else {
40
+ return findings;
41
+ }
42
+ for (const cmd of parsed.commands) {
43
+ const match = /(?:npm|pnpm|yarn|bun)\s+run\s+([\w:@./-]+)/.exec(cmd.value);
44
+ const directMatch = /(?:npm|pnpm|yarn|bun)\s+(test|start|build|lint)\b/.exec(
45
+ cmd.value
46
+ );
47
+ const scriptName = match?.[1] || directMatch?.[1];
48
+ if (scriptName && !(scriptName in scripts)) {
49
+ findings.push({
50
+ file: filePath,
51
+ rule: "check:scripts",
52
+ line: cmd.line,
53
+ column: cmd.column,
54
+ severity: "error",
55
+ message: `Script not found in package.json: "${scriptName}"`
56
+ });
57
+ }
58
+ }
59
+ return findings;
60
+ }
61
+ function checkTokenBudget(parsed, filePath, config) {
62
+ const findings = [];
63
+ const estimatedTokens = Math.ceil(parsed.content.length / 4);
64
+ if (estimatedTokens > config.tokenBudget.error) {
65
+ findings.push({
66
+ file: filePath,
67
+ rule: "check:token-budget",
68
+ line: 1,
69
+ column: 1,
70
+ severity: "error",
71
+ message: `File is ~${estimatedTokens} tokens (limit: ${config.tokenBudget.error}). Consider splitting or condensing.`
72
+ });
73
+ } else if (estimatedTokens > config.tokenBudget.warn) {
74
+ findings.push({
75
+ file: filePath,
76
+ rule: "check:token-budget",
77
+ line: 1,
78
+ column: 1,
79
+ severity: "warning",
80
+ message: `File is ~${estimatedTokens} tokens (warn threshold: ${config.tokenBudget.warn}). Consider condensing.`
81
+ });
82
+ }
83
+ return findings;
84
+ }
85
+ function checkVague(parsed, filePath, config) {
86
+ const findings = [];
87
+ for (let i = 0; i < parsed.lines.length; i++) {
88
+ const line = parsed.lines[i].toLowerCase();
89
+ for (const pattern of config.vaguePatterns) {
90
+ if (line.includes(pattern.toLowerCase())) {
91
+ findings.push({
92
+ file: filePath,
93
+ rule: "check:vague",
94
+ line: i + 1,
95
+ column: line.indexOf(pattern.toLowerCase()) + 1,
96
+ severity: "warning",
97
+ message: `Vague instruction: "${pattern}". Replace with specific, actionable guidance.`
98
+ });
99
+ }
100
+ }
101
+ }
102
+ return findings;
103
+ }
104
+ function checkRequiredSections(parsed, filePath, config) {
105
+ const findings = [];
106
+ const normalizedSections = parsed.sections.map((s) => s.toLowerCase());
107
+ for (const required of config.requiredSections) {
108
+ const found = normalizedSections.some(
109
+ (s) => s.includes(required.toLowerCase())
110
+ );
111
+ if (!found) {
112
+ findings.push({
113
+ file: filePath,
114
+ rule: "check:required-sections",
115
+ line: 1,
116
+ column: 1,
117
+ severity: "warning",
118
+ message: `Missing recommended section: "${required}"`
119
+ });
120
+ }
121
+ }
122
+ return findings;
123
+ }
124
+ function checkStaleDates(parsed, filePath, config) {
125
+ const findings = [];
126
+ const currentYear = (/* @__PURE__ */ new Date()).getFullYear();
127
+ const threshold = currentYear - config.staleDateYears;
128
+ const yearPattern = /\b(20[0-9]{2})\b/g;
129
+ for (let i = 0; i < parsed.lines.length; i++) {
130
+ let match;
131
+ yearPattern.lastIndex = 0;
132
+ while ((match = yearPattern.exec(parsed.lines[i])) !== null) {
133
+ const year = parseInt(match[1], 10);
134
+ if (year < threshold) {
135
+ findings.push({
136
+ file: filePath,
137
+ rule: "check:stale-dates",
138
+ line: i + 1,
139
+ column: match.index + 1,
140
+ severity: "warning",
141
+ message: `Possibly stale year reference: ${year} (older than ${config.staleDateYears} years)`
142
+ });
143
+ }
144
+ }
145
+ }
146
+ return findings;
147
+ }
148
+ function checkContradictions(parsed, filePath) {
149
+ const findings = [];
150
+ const contradictionPairs = [
151
+ [
152
+ /\balways use (\w+)/i,
153
+ /\bnever use (\w+)/i,
154
+ 'Contradictory "always use" and "never use" directives'
155
+ ],
156
+ [
157
+ /\bdo not (?:use|add|include) (comments|docstrings|type annotations)/i,
158
+ /\b(?:always|must) (?:add|include|write) \1/i,
159
+ "Contradictory directives about adding/not adding"
160
+ ],
161
+ [
162
+ /\bprefer (\w+) over (\w+)/i,
163
+ /\bprefer \2 over \1/i,
164
+ "Contradictory preference directives"
165
+ ]
166
+ ];
167
+ const lineTexts = parsed.lines;
168
+ for (const [patternA, patternB, message] of contradictionPairs) {
169
+ const matchesA = [];
170
+ const matchesB = [];
171
+ for (let i = 0; i < lineTexts.length; i++) {
172
+ const lineText = lineTexts[i];
173
+ const a = patternA.exec(lineText);
174
+ if (a) matchesA.push({ line: i + 1, match: a });
175
+ const b = patternB.exec(lineText);
176
+ if (b) matchesB.push({ line: i + 1, match: b });
177
+ }
178
+ if (matchesA.length > 0 && matchesB.length > 0) {
179
+ for (const a of matchesA) {
180
+ for (const b of matchesB) {
181
+ if (a.match[1] && b.match[1] && a.match[1].toLowerCase() === b.match[1].toLowerCase()) {
182
+ findings.push({
183
+ file: filePath,
184
+ rule: "check:contradictions",
185
+ line: b.line,
186
+ column: 1,
187
+ severity: "warning",
188
+ message: `${message} (conflicts with line ${a.line})`
189
+ });
190
+ }
191
+ }
192
+ }
193
+ }
194
+ }
195
+ return findings;
196
+ }
197
+
198
+ // src/config.ts
199
+ import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
200
+ import { resolve as resolve2 } from "path";
201
+
202
+ // src/types.ts
203
+ var DEFAULT_CONFIG = {
204
+ tokenBudget: { warn: 2e3, error: 5e3 },
205
+ requiredSections: ["Setup", "Testing", "Build"],
206
+ staleDateYears: 2,
207
+ vaguePatterns: [
208
+ "follow best practices",
209
+ "be careful",
210
+ "use good judgment",
211
+ "use common sense",
212
+ "as appropriate",
213
+ "when necessary",
214
+ "if needed",
215
+ "as needed",
216
+ "handle edge cases",
217
+ "write clean code",
218
+ "keep it simple",
219
+ "use proper",
220
+ "ensure quality"
221
+ ],
222
+ ignore: []
223
+ };
224
+ var CONTEXT_FILE_NAMES = [
225
+ "CLAUDE.md",
226
+ "AGENTS.md",
227
+ ".cursorrules",
228
+ "copilot-instructions.md",
229
+ ".github/copilot-instructions.md"
230
+ ];
231
+
232
+ // src/config.ts
233
+ function loadConfig(cwd) {
234
+ const configPath = resolve2(cwd, ".agent-context-lint.json");
235
+ if (existsSync2(configPath)) {
236
+ try {
237
+ const raw = JSON.parse(readFileSync2(configPath, "utf-8"));
238
+ return mergeConfig(raw);
239
+ } catch {
240
+ }
241
+ }
242
+ const pkgPath = resolve2(cwd, "package.json");
243
+ if (existsSync2(pkgPath)) {
244
+ try {
245
+ const pkg = JSON.parse(readFileSync2(pkgPath, "utf-8"));
246
+ if (pkg.agentContextLint) {
247
+ return mergeConfig(pkg.agentContextLint);
248
+ }
249
+ } catch {
250
+ }
251
+ }
252
+ return { ...DEFAULT_CONFIG };
253
+ }
254
+ function mergeConfig(overrides) {
255
+ return {
256
+ tokenBudget: {
257
+ ...DEFAULT_CONFIG.tokenBudget,
258
+ ...overrides.tokenBudget
259
+ },
260
+ requiredSections: overrides.requiredSections ?? DEFAULT_CONFIG.requiredSections,
261
+ staleDateYears: overrides.staleDateYears ?? DEFAULT_CONFIG.staleDateYears,
262
+ vaguePatterns: overrides.vaguePatterns ?? DEFAULT_CONFIG.vaguePatterns,
263
+ ignore: overrides.ignore ?? DEFAULT_CONFIG.ignore
264
+ };
265
+ }
266
+
267
+ // src/discovery.ts
268
+ import { existsSync as existsSync3 } from "fs";
269
+ import { resolve as resolve3 } from "path";
270
+ function discoverContextFiles(cwd) {
271
+ const found = [];
272
+ for (const name of CONTEXT_FILE_NAMES) {
273
+ const fullPath = resolve3(cwd, name);
274
+ if (existsSync3(fullPath)) {
275
+ found.push(fullPath);
276
+ }
277
+ }
278
+ return found;
279
+ }
280
+
281
+ // src/parser.ts
282
+ import { readFileSync as readFileSync3 } from "fs";
283
+ var PATH_PATTERN = /(?:^|\s|`)(\.?\.?\/[\w./@-]+[\w/@-])/g;
284
+ var COMMAND_PATTERN = /(?:npm|npx|pnpm|yarn|bun|bunx)\s+(?:run\s+)?[\w:@./-]+/g;
285
+ var HEADING_PATTERN = /^#{1,6}\s+(.+)$/;
286
+ var FENCED_BLOCK_START = /^```(\w*)/;
287
+ var FENCED_BLOCK_END = /^```\s*$/;
288
+ var INLINE_CODE_PATTERN = /`([^`]+)`/g;
289
+ function parseFile(filePath) {
290
+ const content = readFileSync3(filePath, "utf-8");
291
+ const lines = content.split("\n");
292
+ const paths = [];
293
+ const commands = [];
294
+ const sections = [];
295
+ const codeBlocks = [];
296
+ const inlineCode = [];
297
+ let inCodeBlock = false;
298
+ let codeBlockLang = "";
299
+ let codeBlockContent = "";
300
+ let codeBlockStart = 0;
301
+ for (let i = 0; i < lines.length; i++) {
302
+ const line = lines[i];
303
+ const lineNum = i + 1;
304
+ if (!inCodeBlock) {
305
+ const blockStart = FENCED_BLOCK_START.exec(line);
306
+ if (blockStart && line.trimStart().startsWith("```")) {
307
+ inCodeBlock = true;
308
+ codeBlockLang = blockStart[1] || "";
309
+ codeBlockContent = "";
310
+ codeBlockStart = lineNum;
311
+ continue;
312
+ }
313
+ } else {
314
+ if (FENCED_BLOCK_END.test(line) && line.trimStart() === "```") {
315
+ codeBlocks.push({
316
+ content: codeBlockContent,
317
+ lang: codeBlockLang,
318
+ line: codeBlockStart
319
+ });
320
+ inCodeBlock = false;
321
+ continue;
322
+ }
323
+ codeBlockContent += (codeBlockContent ? "\n" : "") + line;
324
+ }
325
+ const headingMatch = HEADING_PATTERN.exec(line);
326
+ if (headingMatch) {
327
+ sections.push(headingMatch[1].trim());
328
+ }
329
+ let pathMatch;
330
+ PATH_PATTERN.lastIndex = 0;
331
+ while ((pathMatch = PATH_PATTERN.exec(line)) !== null) {
332
+ const value = pathMatch[1];
333
+ if (value.includes("://")) continue;
334
+ paths.push({
335
+ value,
336
+ line: lineNum,
337
+ column: pathMatch.index + (pathMatch[0].length - value.length) + 1
338
+ });
339
+ }
340
+ let cmdMatch;
341
+ COMMAND_PATTERN.lastIndex = 0;
342
+ while ((cmdMatch = COMMAND_PATTERN.exec(line)) !== null) {
343
+ commands.push({
344
+ value: cmdMatch[0],
345
+ line: lineNum,
346
+ column: cmdMatch.index + 1
347
+ });
348
+ }
349
+ if (!inCodeBlock) {
350
+ let inlineMatch;
351
+ INLINE_CODE_PATTERN.lastIndex = 0;
352
+ while ((inlineMatch = INLINE_CODE_PATTERN.exec(line)) !== null) {
353
+ inlineCode.push({
354
+ content: inlineMatch[1],
355
+ line: lineNum,
356
+ column: inlineMatch.index + 2
357
+ });
358
+ }
359
+ }
360
+ }
361
+ return { content, lines, paths, commands, sections, codeBlocks, inlineCode };
362
+ }
363
+
364
+ // src/scorer.ts
365
+ function computeScore(findings) {
366
+ let score = 100;
367
+ for (const finding of findings) {
368
+ if (finding.severity === "error") {
369
+ score -= 15;
370
+ } else {
371
+ score -= 5;
372
+ }
373
+ }
374
+ return Math.max(0, score);
375
+ }
376
+
377
+ // src/reporter.ts
378
+ import { relative } from "path";
379
+ function formatText(result, cwd) {
380
+ const lines = [];
381
+ for (const file of result.files) {
382
+ const relPath = relative(cwd, file.file);
383
+ lines.push(`
384
+ ${relPath} (score: ${file.score}/100)`);
385
+ if (file.findings.length === 0) {
386
+ lines.push(" No issues found.");
387
+ continue;
388
+ }
389
+ for (const f of file.findings) {
390
+ const icon = f.severity === "error" ? "x" : "!";
391
+ lines.push(
392
+ ` ${f.line}:${f.column} ${icon} ${f.message} [${f.rule}]`
393
+ );
394
+ }
395
+ }
396
+ lines.push("");
397
+ lines.push(
398
+ ` ${result.totalFindings} problems (${result.errors} errors, ${result.warnings} warnings)`
399
+ );
400
+ lines.push("");
401
+ return lines.join("\n");
402
+ }
403
+ function formatJson(result, cwd) {
404
+ const output = {
405
+ ...result,
406
+ files: result.files.map((f) => ({
407
+ ...f,
408
+ file: relative(cwd, f.file)
409
+ }))
410
+ };
411
+ return JSON.stringify(output, null, 2);
412
+ }
413
+
414
+ // src/index.ts
415
+ function lintFile(filePath, cwd) {
416
+ const config = loadConfig(cwd);
417
+ const parsed = parseFile(filePath);
418
+ const findings = [
419
+ ...checkPaths(parsed, filePath),
420
+ ...checkScripts(parsed, filePath),
421
+ ...checkTokenBudget(parsed, filePath, config),
422
+ ...checkVague(parsed, filePath, config),
423
+ ...checkRequiredSections(parsed, filePath, config),
424
+ ...checkStaleDates(parsed, filePath, config),
425
+ ...checkContradictions(parsed, filePath)
426
+ ];
427
+ return {
428
+ file: filePath,
429
+ findings,
430
+ score: computeScore(findings)
431
+ };
432
+ }
433
+ function lint(cwd, files) {
434
+ const targetFiles = files && files.length > 0 ? files.map((f) => resolve4(cwd, f)) : discoverContextFiles(cwd);
435
+ if (targetFiles.length === 0) {
436
+ return { files: [], totalFindings: 0, errors: 0, warnings: 0 };
437
+ }
438
+ const results = targetFiles.map((f) => lintFile(f, cwd));
439
+ const totalFindings = results.reduce(
440
+ (sum, r) => sum + r.findings.length,
441
+ 0
442
+ );
443
+ const errors = results.reduce(
444
+ (sum, r) => sum + r.findings.filter((f) => f.severity === "error").length,
445
+ 0
446
+ );
447
+ const warnings = results.reduce(
448
+ (sum, r) => sum + r.findings.filter((f) => f.severity === "warning").length,
449
+ 0
450
+ );
451
+ return { files: results, totalFindings, errors, warnings };
452
+ }
453
+
454
+ // src/cli.ts
455
+ function parseArgs(argv) {
456
+ const args = argv.slice(2);
457
+ const options = {
458
+ files: [],
459
+ format: "text",
460
+ fix: false,
461
+ cwd: process.cwd()
462
+ };
463
+ for (let i = 0; i < args.length; i++) {
464
+ const arg = args[i];
465
+ if (arg === "--format" && args[i + 1]) {
466
+ const fmt = args[++i];
467
+ if (fmt === "json" || fmt === "text") {
468
+ options.format = fmt;
469
+ }
470
+ } else if (arg === "--json") {
471
+ options.format = "json";
472
+ } else if (arg === "--fix") {
473
+ options.fix = true;
474
+ } else if (arg === "--help" || arg === "-h") {
475
+ printHelp();
476
+ process.exit(0);
477
+ } else if (arg === "--version" || arg === "-V") {
478
+ console.log("0.1.0");
479
+ process.exit(0);
480
+ } else if (!arg.startsWith("-")) {
481
+ options.files.push(arg);
482
+ }
483
+ }
484
+ return options;
485
+ }
486
+ function printHelp() {
487
+ console.log(`
488
+ agent-context-lint \u2014 Lint AI coding agent context files
489
+
490
+ Usage:
491
+ npx agent-context-lint Auto-discover and lint all context files
492
+ npx agent-context-lint CLAUDE.md Lint a specific file
493
+ npx agent-context-lint --format json Machine-readable output for CI
494
+
495
+ Options:
496
+ --format <text|json> Output format (default: text)
497
+ --json Shorthand for --format json
498
+ --fix Auto-fix safe issues (not yet implemented)
499
+ -V, --version Show version
500
+ -h, --help Show this help
501
+
502
+ Context files detected:
503
+ CLAUDE.md, AGENTS.md, .cursorrules, copilot-instructions.md,
504
+ .github/copilot-instructions.md
505
+
506
+ Configuration:
507
+ .agent-context-lint.json or "agentContextLint" key in package.json
508
+ `);
509
+ }
510
+ function main() {
511
+ const options = parseArgs(process.argv);
512
+ const result = lint(
513
+ options.cwd,
514
+ options.files.length > 0 ? options.files : void 0
515
+ );
516
+ if (result.files.length === 0) {
517
+ console.log("No context files found.");
518
+ process.exit(0);
519
+ }
520
+ const output = options.format === "json" ? formatJson(result, options.cwd) : formatText(result, options.cwd);
521
+ console.log(output);
522
+ process.exit(result.errors > 0 ? 1 : 0);
523
+ }
524
+ main();
525
+ //# sourceMappingURL=cli.js.map