@yawlabs/ctxlint 0.7.0 → 0.8.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/.pre-commit-hooks.yaml +0 -1
- package/README.md +37 -4
- package/action.yml +27 -0
- package/dist/index.js +51232 -3
- package/package.json +16 -17
- package/dist/chunk-DYPYGTPV.js +0 -3082
- package/dist/chunk-ZXMDA7VB.js +0 -16
- package/dist/cli-7DNRBGDO.js +0 -463
- package/dist/mcp/chunk-MCKGQKYU.js +0 -15
- package/dist/mcp/server.js +0 -3330
- package/dist/mcp/tiktoken-MWTCLHI2.js +0 -465
- package/dist/server-ADUSCPPU.js +0 -288
- package/dist/tiktoken-CCNRJMFQ.js +0 -466
package/dist/chunk-ZXMDA7VB.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
4
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
5
|
-
}) : x)(function(x) {
|
|
6
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
7
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
|
-
});
|
|
9
|
-
var __commonJS = (cb, mod) => function __require2() {
|
|
10
|
-
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
export {
|
|
14
|
-
__require,
|
|
15
|
-
__commonJS
|
|
16
|
-
};
|
package/dist/cli-7DNRBGDO.js
DELETED
|
@@ -1,463 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
ALL_CHECKS,
|
|
4
|
-
ALL_MCP_CHECKS,
|
|
5
|
-
ALL_SESSION_CHECKS,
|
|
6
|
-
VERSION,
|
|
7
|
-
applyFixes,
|
|
8
|
-
freeEncoder,
|
|
9
|
-
resetGit,
|
|
10
|
-
resetPathsCache,
|
|
11
|
-
resetTokenThresholds,
|
|
12
|
-
runAudit,
|
|
13
|
-
setTokenThresholds
|
|
14
|
-
} from "./chunk-DYPYGTPV.js";
|
|
15
|
-
import "./chunk-ZXMDA7VB.js";
|
|
16
|
-
|
|
17
|
-
// src/cli.ts
|
|
18
|
-
import * as fs2 from "fs";
|
|
19
|
-
import { Command } from "commander";
|
|
20
|
-
import ora from "ora";
|
|
21
|
-
|
|
22
|
-
// src/core/reporter.ts
|
|
23
|
-
import chalk from "chalk";
|
|
24
|
-
function formatText(result, verbose = false) {
|
|
25
|
-
const lines = [];
|
|
26
|
-
lines.push("");
|
|
27
|
-
lines.push(chalk.bold(`ctxlint v${result.version}`));
|
|
28
|
-
lines.push("");
|
|
29
|
-
lines.push(`Scanning ${result.projectRoot}...`);
|
|
30
|
-
lines.push("");
|
|
31
|
-
const isMcpFile = (f) => f.issues.some((i) => i.check.startsWith("mcp-"));
|
|
32
|
-
const contextFiles = result.files.filter((f) => !isMcpFile(f));
|
|
33
|
-
const mcpFiles = result.files.filter((f) => isMcpFile(f));
|
|
34
|
-
const totalTokens = result.summary.totalTokens;
|
|
35
|
-
if (contextFiles.length > 0) {
|
|
36
|
-
lines.push(
|
|
37
|
-
`Found ${contextFiles.length} context file${contextFiles.length !== 1 ? "s" : ""} (${totalTokens.toLocaleString()} tokens total)`
|
|
38
|
-
);
|
|
39
|
-
for (const file of contextFiles) {
|
|
40
|
-
let desc = ` ${file.path} (${file.tokens.toLocaleString()} tokens, ${file.lines} lines)`;
|
|
41
|
-
if (file.isSymlink && file.symlinkTarget) {
|
|
42
|
-
desc = ` ${file.path} ${chalk.dim(`\u2192 ${file.symlinkTarget} (symlink)`)}`;
|
|
43
|
-
}
|
|
44
|
-
lines.push(desc);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
if (mcpFiles.length > 0) {
|
|
48
|
-
if (contextFiles.length > 0) lines.push("");
|
|
49
|
-
lines.push(`Found ${mcpFiles.length} MCP config${mcpFiles.length !== 1 ? "s" : ""}`);
|
|
50
|
-
for (const file of mcpFiles) {
|
|
51
|
-
lines.push(` ${file.path}`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
if (contextFiles.length === 0 && mcpFiles.length === 0) {
|
|
55
|
-
lines.push(`Found ${result.files.length} file${result.files.length !== 1 ? "s" : ""}`);
|
|
56
|
-
}
|
|
57
|
-
lines.push("");
|
|
58
|
-
const renderFileGroup = (files) => {
|
|
59
|
-
for (const file of files) {
|
|
60
|
-
const fileIssues = file.issues;
|
|
61
|
-
if (fileIssues.length === 0 && !verbose) continue;
|
|
62
|
-
lines.push(chalk.underline(file.path));
|
|
63
|
-
if (fileIssues.length === 0) {
|
|
64
|
-
lines.push(chalk.green(" \u2713 All checks passed"));
|
|
65
|
-
} else {
|
|
66
|
-
for (const issue of fileIssues) {
|
|
67
|
-
lines.push(formatIssue(issue));
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
lines.push("");
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
if (contextFiles.length > 0 && mcpFiles.length > 0) {
|
|
74
|
-
const contextWithIssues = contextFiles.filter((f) => f.issues.length > 0 || verbose);
|
|
75
|
-
const mcpWithIssues = mcpFiles.filter((f) => f.issues.length > 0 || verbose);
|
|
76
|
-
if (contextWithIssues.length > 0) {
|
|
77
|
-
lines.push(chalk.bold("Context Files"));
|
|
78
|
-
renderFileGroup(contextFiles);
|
|
79
|
-
}
|
|
80
|
-
if (mcpWithIssues.length > 0) {
|
|
81
|
-
lines.push(chalk.bold("MCP Configs"));
|
|
82
|
-
renderFileGroup(mcpFiles);
|
|
83
|
-
}
|
|
84
|
-
} else {
|
|
85
|
-
renderFileGroup(result.files);
|
|
86
|
-
}
|
|
87
|
-
const { errors, warnings, info } = result.summary;
|
|
88
|
-
const parts = [];
|
|
89
|
-
if (errors > 0) parts.push(chalk.red(`${errors} error${errors !== 1 ? "s" : ""}`));
|
|
90
|
-
if (warnings > 0) parts.push(chalk.yellow(`${warnings} warning${warnings !== 1 ? "s" : ""}`));
|
|
91
|
-
if (info > 0) parts.push(chalk.blue(`${info} info`));
|
|
92
|
-
if (parts.length > 0) {
|
|
93
|
-
lines.push(`Summary: ${parts.join(", ")}`);
|
|
94
|
-
} else {
|
|
95
|
-
lines.push(chalk.green("No issues found!"));
|
|
96
|
-
}
|
|
97
|
-
lines.push(` Token usage: ${totalTokens.toLocaleString()} tokens per agent session`);
|
|
98
|
-
if (result.summary.estimatedWaste > 0) {
|
|
99
|
-
lines.push(` Estimated waste: ~${result.summary.estimatedWaste} tokens (redundant content)`);
|
|
100
|
-
}
|
|
101
|
-
lines.push("");
|
|
102
|
-
return lines.join("\n");
|
|
103
|
-
}
|
|
104
|
-
function formatJson(result) {
|
|
105
|
-
return JSON.stringify(result, null, 2);
|
|
106
|
-
}
|
|
107
|
-
function formatTokenReport(result) {
|
|
108
|
-
const lines = [];
|
|
109
|
-
lines.push("");
|
|
110
|
-
lines.push(chalk.bold("Token Usage Report"));
|
|
111
|
-
lines.push(
|
|
112
|
-
chalk.dim(" (counts use GPT-4 cl100k_base tokenizer \u2014 Claude counts may vary slightly)")
|
|
113
|
-
);
|
|
114
|
-
lines.push("");
|
|
115
|
-
const maxPathLen = Math.max(...result.files.map((f) => f.path.length), 4);
|
|
116
|
-
lines.push(
|
|
117
|
-
` ${chalk.dim("File".padEnd(maxPathLen))} ${chalk.dim("Tokens".padStart(8))} ${chalk.dim("Lines".padStart(6))}`
|
|
118
|
-
);
|
|
119
|
-
lines.push(` ${"\u2500".repeat(maxPathLen)} ${"\u2500".repeat(8)} ${"\u2500".repeat(6)}`);
|
|
120
|
-
for (const file of result.files) {
|
|
121
|
-
const tokenStr = file.tokens.toLocaleString().padStart(8);
|
|
122
|
-
const lineStr = file.lines.toString().padStart(6);
|
|
123
|
-
lines.push(` ${file.path.padEnd(maxPathLen)} ${tokenStr} ${lineStr}`);
|
|
124
|
-
}
|
|
125
|
-
lines.push(` ${"\u2500".repeat(maxPathLen)} ${"\u2500".repeat(8)} ${"\u2500".repeat(6)}`);
|
|
126
|
-
lines.push(
|
|
127
|
-
` ${"Total".padEnd(maxPathLen)} ${result.summary.totalTokens.toLocaleString().padStart(8)}`
|
|
128
|
-
);
|
|
129
|
-
if (result.summary.estimatedWaste > 0) {
|
|
130
|
-
lines.push("");
|
|
131
|
-
lines.push(
|
|
132
|
-
chalk.yellow(
|
|
133
|
-
` ~${result.summary.estimatedWaste} tokens estimated waste from redundant content`
|
|
134
|
-
)
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
lines.push("");
|
|
138
|
-
return lines.join("\n");
|
|
139
|
-
}
|
|
140
|
-
function formatSarif(result) {
|
|
141
|
-
const severityToLevel = {
|
|
142
|
-
error: "error",
|
|
143
|
-
warning: "warning",
|
|
144
|
-
info: "note"
|
|
145
|
-
};
|
|
146
|
-
const results = [];
|
|
147
|
-
for (const file of result.files) {
|
|
148
|
-
for (const issue of file.issues) {
|
|
149
|
-
const sarifResult = {
|
|
150
|
-
ruleId: issue.ruleId ? `ctxlint/${issue.check}/${issue.ruleId}` : `ctxlint/${issue.check}`,
|
|
151
|
-
level: severityToLevel[issue.severity] || "note",
|
|
152
|
-
message: {
|
|
153
|
-
text: issue.message + (issue.suggestion ? ` (${issue.suggestion})` : "")
|
|
154
|
-
},
|
|
155
|
-
locations: [
|
|
156
|
-
{
|
|
157
|
-
physicalLocation: {
|
|
158
|
-
artifactLocation: {
|
|
159
|
-
uri: file.path,
|
|
160
|
-
uriBaseId: "%SRCROOT%"
|
|
161
|
-
},
|
|
162
|
-
region: {
|
|
163
|
-
startLine: Math.max(issue.line, 1)
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
]
|
|
168
|
-
};
|
|
169
|
-
if (issue.detail) {
|
|
170
|
-
sarifResult.message.text += `
|
|
171
|
-
${issue.detail}`;
|
|
172
|
-
}
|
|
173
|
-
results.push(sarifResult);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
const sarif = {
|
|
177
|
-
version: "2.1.0",
|
|
178
|
-
$schema: "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json",
|
|
179
|
-
runs: [
|
|
180
|
-
{
|
|
181
|
-
tool: {
|
|
182
|
-
driver: {
|
|
183
|
-
name: "ctxlint",
|
|
184
|
-
version: result.version,
|
|
185
|
-
informationUri: "https://github.com/yawlabs/ctxlint",
|
|
186
|
-
rules: buildRuleDescriptors()
|
|
187
|
-
}
|
|
188
|
-
},
|
|
189
|
-
results
|
|
190
|
-
}
|
|
191
|
-
]
|
|
192
|
-
};
|
|
193
|
-
return JSON.stringify(sarif, null, 2);
|
|
194
|
-
}
|
|
195
|
-
function buildRuleDescriptors() {
|
|
196
|
-
return [
|
|
197
|
-
{
|
|
198
|
-
id: "ctxlint/paths",
|
|
199
|
-
shortDescription: { text: "File path does not exist in project" },
|
|
200
|
-
helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
|
|
201
|
-
},
|
|
202
|
-
{
|
|
203
|
-
id: "ctxlint/commands",
|
|
204
|
-
shortDescription: { text: "Command does not match project scripts" },
|
|
205
|
-
helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
|
|
206
|
-
},
|
|
207
|
-
{
|
|
208
|
-
id: "ctxlint/staleness",
|
|
209
|
-
shortDescription: { text: "Context file is stale relative to referenced code" },
|
|
210
|
-
helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
id: "ctxlint/tokens",
|
|
214
|
-
shortDescription: { text: "Context file token usage" },
|
|
215
|
-
helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
|
|
216
|
-
},
|
|
217
|
-
{
|
|
218
|
-
id: "ctxlint/redundancy",
|
|
219
|
-
shortDescription: { text: "Content is redundant or inferable" },
|
|
220
|
-
helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
|
|
221
|
-
},
|
|
222
|
-
{
|
|
223
|
-
id: "ctxlint/contradictions",
|
|
224
|
-
shortDescription: { text: "Conflicting directives across context files" },
|
|
225
|
-
helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
|
|
226
|
-
},
|
|
227
|
-
{
|
|
228
|
-
id: "ctxlint/frontmatter",
|
|
229
|
-
shortDescription: { text: "Invalid or missing frontmatter" },
|
|
230
|
-
helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
id: "ctxlint/mcp-schema",
|
|
234
|
-
shortDescription: { text: "MCP config structural validation error" },
|
|
235
|
-
helpUri: "https://github.com/yawlabs/ctxlint#mcp-config-linting"
|
|
236
|
-
},
|
|
237
|
-
{
|
|
238
|
-
id: "ctxlint/mcp-security",
|
|
239
|
-
shortDescription: { text: "Hardcoded secret in MCP config" },
|
|
240
|
-
helpUri: "https://github.com/yawlabs/ctxlint#mcp-config-linting"
|
|
241
|
-
},
|
|
242
|
-
{
|
|
243
|
-
id: "ctxlint/mcp-commands",
|
|
244
|
-
shortDescription: { text: "MCP stdio command validation issue" },
|
|
245
|
-
helpUri: "https://github.com/yawlabs/ctxlint#mcp-config-linting"
|
|
246
|
-
},
|
|
247
|
-
{
|
|
248
|
-
id: "ctxlint/mcp-deprecated",
|
|
249
|
-
shortDescription: { text: "Deprecated MCP transport or pattern" },
|
|
250
|
-
helpUri: "https://github.com/yawlabs/ctxlint#mcp-config-linting"
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
id: "ctxlint/mcp-env",
|
|
254
|
-
shortDescription: { text: "MCP environment variable syntax issue" },
|
|
255
|
-
helpUri: "https://github.com/yawlabs/ctxlint#mcp-config-linting"
|
|
256
|
-
},
|
|
257
|
-
{
|
|
258
|
-
id: "ctxlint/mcp-urls",
|
|
259
|
-
shortDescription: { text: "MCP URL validation issue" },
|
|
260
|
-
helpUri: "https://github.com/yawlabs/ctxlint#mcp-config-linting"
|
|
261
|
-
},
|
|
262
|
-
{
|
|
263
|
-
id: "ctxlint/mcp-consistency",
|
|
264
|
-
shortDescription: { text: "MCP config inconsistency across clients" },
|
|
265
|
-
helpUri: "https://github.com/yawlabs/ctxlint#mcp-config-linting"
|
|
266
|
-
},
|
|
267
|
-
{
|
|
268
|
-
id: "ctxlint/mcp-redundancy",
|
|
269
|
-
shortDescription: { text: "Redundant MCP config entry" },
|
|
270
|
-
helpUri: "https://github.com/yawlabs/ctxlint#mcp-config-linting"
|
|
271
|
-
}
|
|
272
|
-
];
|
|
273
|
-
}
|
|
274
|
-
function formatIssue(issue) {
|
|
275
|
-
const icon = issue.severity === "error" ? chalk.red("\u2717") : issue.severity === "warning" ? chalk.yellow("\u26A0") : chalk.blue("\u2139");
|
|
276
|
-
const lineRef = issue.line > 0 ? `Line ${issue.line}: ` : "";
|
|
277
|
-
let line = ` ${icon} ${lineRef}${issue.message}`;
|
|
278
|
-
if (issue.suggestion) {
|
|
279
|
-
line += `
|
|
280
|
-
${chalk.dim("\u2192")} ${chalk.dim(issue.suggestion)}`;
|
|
281
|
-
}
|
|
282
|
-
if (issue.detail) {
|
|
283
|
-
line += `
|
|
284
|
-
${chalk.dim(issue.detail)}`;
|
|
285
|
-
}
|
|
286
|
-
return line;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// src/core/config.ts
|
|
290
|
-
import * as fs from "fs";
|
|
291
|
-
import * as path from "path";
|
|
292
|
-
var CONFIG_FILENAMES = [".ctxlintrc", ".ctxlintrc.json"];
|
|
293
|
-
function loadConfig(projectRoot) {
|
|
294
|
-
for (const filename of CONFIG_FILENAMES) {
|
|
295
|
-
const filePath = path.join(projectRoot, filename);
|
|
296
|
-
try {
|
|
297
|
-
const content = fs.readFileSync(filePath, "utf-8");
|
|
298
|
-
return JSON.parse(content);
|
|
299
|
-
} catch {
|
|
300
|
-
continue;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
return null;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// src/cli.ts
|
|
307
|
-
import * as path2 from "path";
|
|
308
|
-
async function runCli() {
|
|
309
|
-
const program = new Command();
|
|
310
|
-
program.name("ctxlint").description(
|
|
311
|
-
"Lint your AI agent context files and MCP server configs against your actual codebase"
|
|
312
|
-
).version(VERSION).argument("[path]", "Project directory to scan", ".").option("--strict", "Exit code 1 on any warning or error (for CI)", false).option("--checks <checks>", "Comma-separated list of checks to run", "").option("--format <format>", "Output format: text, json, or sarif", "text").option("--tokens", "Show token breakdown per file", false).option("--verbose", "Show passing checks too", false).option("--fix", "Auto-fix broken paths using git history and fuzzy matching", false).option("--ignore <checks>", "Comma-separated list of checks to ignore", "").option("--quiet", "Suppress all output except errors (exit code only)", false).option("--config <path>", "Path to config file (default: .ctxlintrc in project root)").option("--depth <n>", "Max subdirectory depth to scan (default: 2)", "2").option("--mcp", "Enable MCP config linting alongside context file checks", false).option("--mcp-only", "Run only MCP config checks, skip context file checks", false).option("--mcp-global", "Also scan user/global MCP config files (implies --mcp)", false).option("--mcp-server", "Start the MCP server (for IDE/agent integration)").option("--session", "Run session audit checks (cross-project consistency)", false).option("--session-only", "Run only session checks, skip context and MCP checks", false).action(async (projectPath, opts) => {
|
|
313
|
-
const resolvedPath = path2.resolve(projectPath);
|
|
314
|
-
const configPath = opts.config ? path2.resolve(opts.config) : void 0;
|
|
315
|
-
const config = configPath ? loadConfigFromPath(configPath) : loadConfig(resolvedPath);
|
|
316
|
-
const mcpGlobal = opts.mcpGlobal || false;
|
|
317
|
-
const mcpOnly = opts.mcpOnly || false;
|
|
318
|
-
const mcpFlag = opts.mcp || mcpGlobal || mcpOnly || config?.mcp || false;
|
|
319
|
-
const sessionFlag = opts.session || false;
|
|
320
|
-
const sessionOnly = opts.sessionOnly || false;
|
|
321
|
-
const explicitChecks = opts.checks ? opts.checks.split(",").map((c) => c.trim()) : null;
|
|
322
|
-
const hasMcpInChecks = explicitChecks?.some((c) => c.startsWith("mcp-")) || false;
|
|
323
|
-
const hasSessionInChecks = explicitChecks?.some((c) => c.startsWith("session-")) || false;
|
|
324
|
-
const effectiveMcp = mcpFlag || hasMcpInChecks;
|
|
325
|
-
const effectiveSession = sessionFlag || sessionOnly || hasSessionInChecks;
|
|
326
|
-
let checks;
|
|
327
|
-
if (explicitChecks) {
|
|
328
|
-
checks = explicitChecks;
|
|
329
|
-
} else if (sessionOnly) {
|
|
330
|
-
checks = ALL_SESSION_CHECKS;
|
|
331
|
-
} else if (mcpOnly) {
|
|
332
|
-
checks = ALL_MCP_CHECKS;
|
|
333
|
-
} else {
|
|
334
|
-
const base = config?.checks || ALL_CHECKS;
|
|
335
|
-
checks = [
|
|
336
|
-
...base,
|
|
337
|
-
...effectiveMcp ? ALL_MCP_CHECKS : [],
|
|
338
|
-
...effectiveSession ? ALL_SESSION_CHECKS : []
|
|
339
|
-
];
|
|
340
|
-
}
|
|
341
|
-
const options = {
|
|
342
|
-
projectPath: resolvedPath,
|
|
343
|
-
checks,
|
|
344
|
-
strict: opts.strict || config?.strict || false,
|
|
345
|
-
format: opts.format,
|
|
346
|
-
verbose: opts.verbose,
|
|
347
|
-
fix: opts.fix,
|
|
348
|
-
ignore: opts.ignore ? opts.ignore.split(",").map((c) => c.trim()) : config?.ignore || [],
|
|
349
|
-
tokensOnly: opts.tokens,
|
|
350
|
-
quiet: opts.quiet,
|
|
351
|
-
depth: parseInt(opts.depth, 10) || 2,
|
|
352
|
-
mcp: effectiveMcp,
|
|
353
|
-
mcpOnly,
|
|
354
|
-
mcpGlobal: mcpGlobal || config?.mcpGlobal || false,
|
|
355
|
-
session: effectiveSession,
|
|
356
|
-
sessionOnly
|
|
357
|
-
};
|
|
358
|
-
if (config?.tokenThresholds) {
|
|
359
|
-
setTokenThresholds(config.tokenThresholds);
|
|
360
|
-
}
|
|
361
|
-
const activeChecks = options.checks.filter((c) => !options.ignore.includes(c));
|
|
362
|
-
const spinner = options.format === "text" && !options.quiet ? ora("Scanning for context files...").start() : void 0;
|
|
363
|
-
try {
|
|
364
|
-
if (spinner) spinner.text = "Running checks...";
|
|
365
|
-
const result = await runAudit(resolvedPath, activeChecks, {
|
|
366
|
-
depth: options.depth,
|
|
367
|
-
extraPatterns: config?.contextFiles,
|
|
368
|
-
mcp: options.mcp,
|
|
369
|
-
mcpGlobal: options.mcpGlobal,
|
|
370
|
-
mcpOnly: options.mcpOnly,
|
|
371
|
-
session: options.session,
|
|
372
|
-
sessionOnly: options.sessionOnly
|
|
373
|
-
});
|
|
374
|
-
spinner?.stop();
|
|
375
|
-
if (result.files.length === 0) {
|
|
376
|
-
if (!options.quiet) {
|
|
377
|
-
if (options.format === "json") {
|
|
378
|
-
console.log(JSON.stringify(result));
|
|
379
|
-
} else if (options.format === "sarif") {
|
|
380
|
-
console.log(formatSarif(result));
|
|
381
|
-
} else {
|
|
382
|
-
console.log("\nNo context files found.\n");
|
|
383
|
-
}
|
|
384
|
-
}
|
|
385
|
-
process.exit(0);
|
|
386
|
-
}
|
|
387
|
-
if (options.fix) {
|
|
388
|
-
const fixSummary = applyFixes(result);
|
|
389
|
-
if (fixSummary.totalFixes > 0 && !options.quiet) {
|
|
390
|
-
console.log(
|
|
391
|
-
`
|
|
392
|
-
Fixed ${fixSummary.totalFixes} issue${fixSummary.totalFixes !== 1 ? "s" : ""} in ${fixSummary.filesModified.length} file${fixSummary.filesModified.length !== 1 ? "s" : ""}.
|
|
393
|
-
`
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
|
-
}
|
|
397
|
-
if (!options.quiet) {
|
|
398
|
-
if (options.tokensOnly) {
|
|
399
|
-
console.log(formatTokenReport(result));
|
|
400
|
-
} else if (options.format === "json") {
|
|
401
|
-
console.log(formatJson(result));
|
|
402
|
-
} else if (options.format === "sarif") {
|
|
403
|
-
console.log(formatSarif(result));
|
|
404
|
-
} else {
|
|
405
|
-
console.log(formatText(result, options.verbose));
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
if (options.strict && (result.summary.errors > 0 || result.summary.warnings > 0)) {
|
|
409
|
-
process.exit(1);
|
|
410
|
-
}
|
|
411
|
-
} catch (err) {
|
|
412
|
-
spinner?.stop();
|
|
413
|
-
console.error("Error:", err instanceof Error ? err.message : err);
|
|
414
|
-
process.exit(2);
|
|
415
|
-
} finally {
|
|
416
|
-
freeEncoder();
|
|
417
|
-
resetGit();
|
|
418
|
-
resetPathsCache();
|
|
419
|
-
resetTokenThresholds();
|
|
420
|
-
}
|
|
421
|
-
});
|
|
422
|
-
program.command("init").description("Set up a git pre-commit hook that runs ctxlint --strict").action(async () => {
|
|
423
|
-
const hooksDir = path2.resolve(".git", "hooks");
|
|
424
|
-
if (!fs2.existsSync(path2.resolve(".git"))) {
|
|
425
|
-
console.error('Error: not a git repository. Run "git init" first.');
|
|
426
|
-
process.exit(1);
|
|
427
|
-
}
|
|
428
|
-
if (!fs2.existsSync(hooksDir)) {
|
|
429
|
-
fs2.mkdirSync(hooksDir, { recursive: true });
|
|
430
|
-
}
|
|
431
|
-
const hookPath = path2.join(hooksDir, "pre-commit");
|
|
432
|
-
const hookContent = `#!/bin/sh
|
|
433
|
-
# ctxlint pre-commit hook
|
|
434
|
-
npx @yawlabs/ctxlint --strict
|
|
435
|
-
`;
|
|
436
|
-
if (fs2.existsSync(hookPath)) {
|
|
437
|
-
const existing = fs2.readFileSync(hookPath, "utf-8");
|
|
438
|
-
if (existing.includes("ctxlint")) {
|
|
439
|
-
console.log("Pre-commit hook already includes ctxlint.");
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
fs2.appendFileSync(hookPath, "\n" + hookContent);
|
|
443
|
-
console.log("Added ctxlint to existing pre-commit hook.");
|
|
444
|
-
} else {
|
|
445
|
-
fs2.writeFileSync(hookPath, hookContent, { mode: 493 });
|
|
446
|
-
console.log("Created pre-commit hook at .git/hooks/pre-commit");
|
|
447
|
-
}
|
|
448
|
-
console.log("ctxlint will now run automatically before each commit.");
|
|
449
|
-
});
|
|
450
|
-
program.parse();
|
|
451
|
-
}
|
|
452
|
-
function loadConfigFromPath(configPath) {
|
|
453
|
-
try {
|
|
454
|
-
const content = fs2.readFileSync(configPath, "utf-8");
|
|
455
|
-
return JSON.parse(content);
|
|
456
|
-
} catch {
|
|
457
|
-
console.error(`Error: could not load config from ${configPath}`);
|
|
458
|
-
process.exit(2);
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
export {
|
|
462
|
-
runCli
|
|
463
|
-
};
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
2
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
3
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
4
|
-
}) : x)(function(x) {
|
|
5
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
6
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
-
});
|
|
8
|
-
var __commonJS = (cb, mod) => function __require2() {
|
|
9
|
-
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export {
|
|
13
|
-
__require,
|
|
14
|
-
__commonJS
|
|
15
|
-
};
|