@yawlabs/ctxlint 0.22.0 → 0.24.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 +1 -1
- package/dist/index.js +176 -13
- package/package.json +2 -1
package/.pre-commit-hooks.yaml
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# Version-pinned so a checkout at `rev: vX.Y.Z` runs exactly that release
|
|
5
5
|
# of ctxlint — matches the pinning done by `ctxlint init`. release.sh keeps
|
|
6
6
|
# this in sync with package.json on each bump.
|
|
7
|
-
entry: npx @yawlabs/ctxlint@0.
|
|
7
|
+
entry: npx @yawlabs/ctxlint@0.24.0 --strict
|
|
8
8
|
language: node
|
|
9
9
|
always_run: true
|
|
10
10
|
pass_filenames: false
|
package/dist/index.js
CHANGED
|
@@ -24050,6 +24050,80 @@ var init_cli_subcommands = __esm({
|
|
|
24050
24050
|
}
|
|
24051
24051
|
});
|
|
24052
24052
|
|
|
24053
|
+
// src/utils/markdown.ts
|
|
24054
|
+
function inlineCodeSpans(line) {
|
|
24055
|
+
const spans = [];
|
|
24056
|
+
const re2 = /`([^`]+)`/g;
|
|
24057
|
+
let m;
|
|
24058
|
+
while ((m = re2.exec(line)) !== null) {
|
|
24059
|
+
spans.push({ start: m.index, end: m.index + m[0].length, content: m[1] });
|
|
24060
|
+
}
|
|
24061
|
+
return spans;
|
|
24062
|
+
}
|
|
24063
|
+
function maskCodeSpans(line, spans) {
|
|
24064
|
+
let out = line;
|
|
24065
|
+
for (const s of spans) {
|
|
24066
|
+
out = out.slice(0, s.start) + "#".repeat(s.end - s.start) + out.slice(s.end);
|
|
24067
|
+
}
|
|
24068
|
+
return out;
|
|
24069
|
+
}
|
|
24070
|
+
function nonProseLineMask(lines) {
|
|
24071
|
+
return classifyLines(lines).map((c3) => c3.fence || c3.comment);
|
|
24072
|
+
}
|
|
24073
|
+
function htmlCommentLineMask(lines) {
|
|
24074
|
+
return classifyLines(lines).map((c3) => c3.comment);
|
|
24075
|
+
}
|
|
24076
|
+
function classifyLines(lines) {
|
|
24077
|
+
const out = lines.map(() => ({ fence: false, comment: false }));
|
|
24078
|
+
let inFence = false;
|
|
24079
|
+
let inHtmlComment = false;
|
|
24080
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
24081
|
+
const raw = lines[i2];
|
|
24082
|
+
const trimmed2 = raw.trimStart();
|
|
24083
|
+
if (trimmed2.startsWith("```")) {
|
|
24084
|
+
out[i2].fence = true;
|
|
24085
|
+
inFence = !inFence;
|
|
24086
|
+
continue;
|
|
24087
|
+
}
|
|
24088
|
+
if (inFence) {
|
|
24089
|
+
out[i2].fence = true;
|
|
24090
|
+
continue;
|
|
24091
|
+
}
|
|
24092
|
+
const probe = maskCodeSpans(trimmed2, inlineCodeSpans(trimmed2));
|
|
24093
|
+
if (inHtmlComment) {
|
|
24094
|
+
out[i2].comment = true;
|
|
24095
|
+
if (probe.includes("-->")) inHtmlComment = false;
|
|
24096
|
+
continue;
|
|
24097
|
+
}
|
|
24098
|
+
if (HTML_COMMENT.test(probe.replace(HTML_COMMENT_PAIR, ""))) {
|
|
24099
|
+
out[i2].comment = true;
|
|
24100
|
+
inHtmlComment = true;
|
|
24101
|
+
}
|
|
24102
|
+
}
|
|
24103
|
+
return out;
|
|
24104
|
+
}
|
|
24105
|
+
function stripInlineHtmlComments(line) {
|
|
24106
|
+
return line.replace(HTML_COMMENT_PAIR, (m) => "#".repeat(m.length));
|
|
24107
|
+
}
|
|
24108
|
+
function htmlCommentSpans(line) {
|
|
24109
|
+
const re2 = /<!--[\s\S]*?-->/g;
|
|
24110
|
+
const spans = [];
|
|
24111
|
+
let m;
|
|
24112
|
+
while ((m = re2.exec(line)) !== null) {
|
|
24113
|
+
spans.push({ start: m.index, end: m.index + m[0].length });
|
|
24114
|
+
}
|
|
24115
|
+
return spans;
|
|
24116
|
+
}
|
|
24117
|
+
var HTML_COMMENT_PAIR, HTML_COMMENT;
|
|
24118
|
+
var init_markdown = __esm({
|
|
24119
|
+
"src/utils/markdown.ts"() {
|
|
24120
|
+
"use strict";
|
|
24121
|
+
init_define_WEB_FIRST_SEGMENTS();
|
|
24122
|
+
HTML_COMMENT_PAIR = /<!--[\s\S]*?-->/g;
|
|
24123
|
+
HTML_COMMENT = /<!--/;
|
|
24124
|
+
}
|
|
24125
|
+
});
|
|
24126
|
+
|
|
24053
24127
|
// src/core/checks/commands.ts
|
|
24054
24128
|
import * as fs6 from "node:fs";
|
|
24055
24129
|
import * as path7 from "node:path";
|
|
@@ -24112,13 +24186,48 @@ function loadDeniedCommandPrefixes(projectRoot) {
|
|
|
24112
24186
|
function isDeniedCommand(cmd, deniedPrefixes) {
|
|
24113
24187
|
return deniedPrefixes.some((p2) => cmd === p2 || cmd.startsWith(`${p2} `));
|
|
24114
24188
|
}
|
|
24189
|
+
function narrowAtContrastComma(clause) {
|
|
24190
|
+
const idx = clause.lastIndexOf(",");
|
|
24191
|
+
if (idx === -1) return clause;
|
|
24192
|
+
const tail = clause.slice(idx + 1);
|
|
24193
|
+
return RECOMMENDATION_VERB.test(tail) ? tail : clause;
|
|
24194
|
+
}
|
|
24195
|
+
function isProhibitedAt(rawLine, offset) {
|
|
24196
|
+
const line = stripInlineHtmlComments(rawLine);
|
|
24197
|
+
const masked = maskCodeSpans(line, inlineCodeSpans(line));
|
|
24198
|
+
const prefix = masked.slice(0, Math.max(0, offset));
|
|
24199
|
+
const clause = narrowAtContrastComma(prefix.split(CLAUSE_TERMINATOR).pop() ?? "");
|
|
24200
|
+
return PROHIBITION_TOKEN.test(clause);
|
|
24201
|
+
}
|
|
24202
|
+
function isProhibitedRef(lines, ref) {
|
|
24203
|
+
return isProhibitedAt(lines[ref.line - 1] ?? "", ref.column - 1);
|
|
24204
|
+
}
|
|
24205
|
+
function isProhibitedInvocation(lines, inv) {
|
|
24206
|
+
const line = lines[inv.line - 1] ?? "";
|
|
24207
|
+
for (const span of inlineCodeSpans(line)) {
|
|
24208
|
+
if (span.content.trim() === inv.cmd) return isProhibitedAt(line, span.start);
|
|
24209
|
+
}
|
|
24210
|
+
const idx = line.indexOf(inv.cmd);
|
|
24211
|
+
return idx < 0 ? false : isProhibitedAt(line, idx);
|
|
24212
|
+
}
|
|
24115
24213
|
async function checkCommands(file2, projectRoot) {
|
|
24116
24214
|
const issues = [];
|
|
24117
24215
|
const pkgJson = loadPackageJson(projectRoot);
|
|
24118
24216
|
const makefile = loadMakefile(projectRoot);
|
|
24119
24217
|
const deniedPrefixes = loadDeniedCommandPrefixes(projectRoot);
|
|
24218
|
+
const contentLines = file2.content.split("\n");
|
|
24219
|
+
const commentLines = htmlCommentLineMask(contentLines);
|
|
24220
|
+
const inHtmlComment = (line, column) => {
|
|
24221
|
+
if (commentLines[line - 1] === true) return true;
|
|
24222
|
+
const offset = column - 1;
|
|
24223
|
+
return htmlCommentSpans(contentLines[line - 1] ?? "").some(
|
|
24224
|
+
(s) => offset >= s.start && offset < s.end
|
|
24225
|
+
);
|
|
24226
|
+
};
|
|
24120
24227
|
if (!pkgJson) {
|
|
24121
|
-
const skipped = file2.references.commands.find(
|
|
24228
|
+
const skipped = file2.references.commands.find(
|
|
24229
|
+
(ref) => wouldNeedPackageJson(ref.value) && !inHtmlComment(ref.line, ref.column) && !isDeniedCommand(ref.value, deniedPrefixes) && !isProhibitedRef(contentLines, ref)
|
|
24230
|
+
);
|
|
24122
24231
|
if (skipped) {
|
|
24123
24232
|
issues.push({
|
|
24124
24233
|
severity: "info",
|
|
@@ -24130,8 +24239,11 @@ async function checkCommands(file2, projectRoot) {
|
|
|
24130
24239
|
});
|
|
24131
24240
|
}
|
|
24132
24241
|
}
|
|
24133
|
-
issues.push(
|
|
24242
|
+
issues.push(
|
|
24243
|
+
...checkUnknownSubcommand(file2, projectRoot, pkgJson, deniedPrefixes, contentLines)
|
|
24244
|
+
);
|
|
24134
24245
|
for (const ref of file2.references.commands) {
|
|
24246
|
+
if (inHtmlComment(ref.line, ref.column)) continue;
|
|
24135
24247
|
const cmd = ref.value;
|
|
24136
24248
|
const masked = analyzeMaskedExitStatus(cmd, pkgJson?.scripts);
|
|
24137
24249
|
if (masked && !pipefailInScope(file2.content, ref.line)) {
|
|
@@ -24145,6 +24257,7 @@ async function checkCommands(file2, projectRoot) {
|
|
|
24145
24257
|
suggestion: "Add `set -o pipefail` before the pipeline, drop the filter, or read `${PIPESTATUS[0]}` instead of `$?`."
|
|
24146
24258
|
});
|
|
24147
24259
|
}
|
|
24260
|
+
if (isDeniedCommand(cmd, deniedPrefixes) || isProhibitedRef(contentLines, ref)) continue;
|
|
24148
24261
|
const scriptMatch = cmd.match(NPM_SCRIPT_PATTERN);
|
|
24149
24262
|
if (scriptMatch && pkgJson) {
|
|
24150
24263
|
const scriptName = scriptNameFromMatch(scriptMatch);
|
|
@@ -24187,7 +24300,6 @@ async function checkCommands(file2, projectRoot) {
|
|
|
24187
24300
|
...pkgJson.optionalDependencies
|
|
24188
24301
|
};
|
|
24189
24302
|
if (!(pkgName in allDeps)) {
|
|
24190
|
-
if (isDeniedCommand(cmd, deniedPrefixes)) continue;
|
|
24191
24303
|
const binPath = path7.join(projectRoot, "node_modules", ".bin", pkgName);
|
|
24192
24304
|
try {
|
|
24193
24305
|
fs6.accessSync(binPath);
|
|
@@ -24255,7 +24367,7 @@ async function checkCommands(file2, projectRoot) {
|
|
|
24255
24367
|
}
|
|
24256
24368
|
return issues;
|
|
24257
24369
|
}
|
|
24258
|
-
function checkUnknownSubcommand(file2, projectRoot, pkgJson) {
|
|
24370
|
+
function checkUnknownSubcommand(file2, projectRoot, pkgJson, deniedPrefixes, contentLines) {
|
|
24259
24371
|
const bins = ownedBins(pkgJson);
|
|
24260
24372
|
if (bins.length === 0) return [];
|
|
24261
24373
|
const invocations = findBinInvocations(file2.content, new Set(bins.map((b2) => b2.name)));
|
|
@@ -24263,6 +24375,8 @@ function checkUnknownSubcommand(file2, projectRoot, pkgJson) {
|
|
|
24263
24375
|
const resolved = /* @__PURE__ */ new Map();
|
|
24264
24376
|
const issues = [];
|
|
24265
24377
|
for (const inv of invocations) {
|
|
24378
|
+
if (isDeniedCommand(inv.cmd, deniedPrefixes)) continue;
|
|
24379
|
+
if (isProhibitedInvocation(contentLines, inv)) continue;
|
|
24266
24380
|
if (!resolved.has(inv.bin)) {
|
|
24267
24381
|
const bin = bins.find((b2) => b2.name === inv.bin);
|
|
24268
24382
|
resolved.set(inv.bin, bin ? knownSubcommands(projectRoot, bin.entry) : null);
|
|
@@ -24302,7 +24416,7 @@ function hasMakeTarget(makefile, target) {
|
|
|
24302
24416
|
const pattern = new RegExp(`^${escaped}\\s*:(?!:?=)`, "m");
|
|
24303
24417
|
return pattern.test(makefile);
|
|
24304
24418
|
}
|
|
24305
|
-
var NPM_SCRIPT_PATTERN, MAKE_PATTERN, PM_BUILTIN_SUBCOMMANDS, PM_MANAGER_BUILTINS, PKG_DEPENDENT_TOOL_PATTERN, BIN_TO_PACKAGE, PKG_SHORTHAND_PATTERN;
|
|
24419
|
+
var NPM_SCRIPT_PATTERN, MAKE_PATTERN, PM_BUILTIN_SUBCOMMANDS, PM_MANAGER_BUILTINS, PKG_DEPENDENT_TOOL_PATTERN, BIN_TO_PACKAGE, PKG_SHORTHAND_PATTERN, PROHIBITION_TOKEN, CLAUSE_TERMINATOR, RECOMMENDATION_VERB;
|
|
24306
24420
|
var init_commands = __esm({
|
|
24307
24421
|
"src/core/checks/commands.ts"() {
|
|
24308
24422
|
"use strict";
|
|
@@ -24311,6 +24425,7 @@ var init_commands = __esm({
|
|
|
24311
24425
|
init_fs();
|
|
24312
24426
|
init_exit_status();
|
|
24313
24427
|
init_cli_subcommands();
|
|
24428
|
+
init_markdown();
|
|
24314
24429
|
NPM_SCRIPT_PATTERN = /^(?:npm\s+run|(pnpm|yarn|bun)(?:\s+(run))?)\s+(\S+)/;
|
|
24315
24430
|
MAKE_PATTERN = /^make\s+\S/;
|
|
24316
24431
|
PM_BUILTIN_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
@@ -24380,6 +24495,9 @@ var init_commands = __esm({
|
|
|
24380
24495
|
tsc: "typescript"
|
|
24381
24496
|
};
|
|
24382
24497
|
PKG_SHORTHAND_PATTERN = /^(npm|pnpm|yarn|bun)\s+(test|start|build|dev|lint|format|check|typecheck|clean|serve|preview|e2e)\b/;
|
|
24498
|
+
PROHIBITION_TOKEN = /\b(?:never|don['’]?t|do\s+not|must\s+not|avoid)\b/i;
|
|
24499
|
+
CLAUSE_TERMINATOR = /[.!?;—]|\s--(?=\s|$)/;
|
|
24500
|
+
RECOMMENDATION_VERB = /\b(?:run|use|call|invoke|execute|prefer|deploy|install)\b/i;
|
|
24383
24501
|
}
|
|
24384
24502
|
});
|
|
24385
24503
|
|
|
@@ -24650,6 +24768,24 @@ function computeSectionCosts(file2) {
|
|
|
24650
24768
|
return { title: s.title, line: s.startLine, tokens: countTokens(body) };
|
|
24651
24769
|
}).sort((a, b2) => b2.tokens - a.tokens);
|
|
24652
24770
|
}
|
|
24771
|
+
function findInviolableCommand(line) {
|
|
24772
|
+
const spans = inlineCodeSpans(line);
|
|
24773
|
+
if (spans.length === 0) return null;
|
|
24774
|
+
const masked = maskCodeSpans(line, spans);
|
|
24775
|
+
FRAMING_TOKEN.lastIndex = 0;
|
|
24776
|
+
let m;
|
|
24777
|
+
while ((m = FRAMING_TOKEN.exec(masked)) !== null) {
|
|
24778
|
+
const token = m[1];
|
|
24779
|
+
if (token === token.toLowerCase()) continue;
|
|
24780
|
+
const afterIdx = m.index + m[0].length;
|
|
24781
|
+
const span = spans.find((s) => s.start >= afterIdx);
|
|
24782
|
+
if (!span) continue;
|
|
24783
|
+
const gap = masked.slice(afterIdx, span.start);
|
|
24784
|
+
if (gap.length > FRAMING_COMMAND_GAP || /[.!?]/.test(gap)) continue;
|
|
24785
|
+
return { framing: token, command: span.content };
|
|
24786
|
+
}
|
|
24787
|
+
return null;
|
|
24788
|
+
}
|
|
24653
24789
|
function fingerprintFiles(paths) {
|
|
24654
24790
|
return paths.map((p2) => {
|
|
24655
24791
|
try {
|
|
@@ -24725,14 +24861,16 @@ function commandIsEnforced(cmd, settings) {
|
|
|
24725
24861
|
function checkHardEnforcement(file2, settings) {
|
|
24726
24862
|
const issues = [];
|
|
24727
24863
|
const lines = file2.content.split("\n");
|
|
24864
|
+
const nonProse = nonProseLineMask(lines);
|
|
24728
24865
|
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
24729
|
-
|
|
24730
|
-
const
|
|
24866
|
+
if (nonProse[i2]) continue;
|
|
24867
|
+
const line = stripInlineHtmlComments(lines[i2]);
|
|
24868
|
+
const match = findInviolableCommand(line);
|
|
24731
24869
|
if (!match) continue;
|
|
24732
|
-
const cmd = canonicalizeCommand(match
|
|
24870
|
+
const cmd = canonicalizeCommand(match.command);
|
|
24733
24871
|
if (!cmd) continue;
|
|
24734
24872
|
if (commandIsEnforced(cmd, settings)) continue;
|
|
24735
|
-
const suggestion = match
|
|
24873
|
+
const suggestion = match.framing.toUpperCase() === "ALWAYS" ? `Rules in always-loaded files are advisory. For \`${cmd}\`, add a hook in .claude/settings.json (e.g. a PreToolUse or Stop hook that runs or verifies \`${cmd}\`) so the requirement doesn't depend on the agent remembering.` : `Rules in always-loaded files are advisory. For \`${cmd}\`, add a PreToolUse hook (or permissions.deny entry) in .claude/settings.json so the command is physically blocked.`;
|
|
24736
24874
|
issues.push({
|
|
24737
24875
|
severity: "info",
|
|
24738
24876
|
check: "tier-tokens",
|
|
@@ -24787,7 +24925,7 @@ function checkAggregateTierTokens(files, thresholds = DEFAULT_TOKEN_THRESHOLDS)
|
|
|
24787
24925
|
suggestion: "Consider moving the largest files or their heaviest sections to on-demand tiers (skills, subagents, memory)."
|
|
24788
24926
|
};
|
|
24789
24927
|
}
|
|
24790
|
-
var ALWAYS_LOADED_NAMES, TOP_SECTIONS_TO_REPORT,
|
|
24928
|
+
var ALWAYS_LOADED_NAMES, TOP_SECTIONS_TO_REPORT, FRAMING_TOKEN, FRAMING_COMMAND_GAP, settingsCache;
|
|
24791
24929
|
var init_tier_tokens = __esm({
|
|
24792
24930
|
"src/core/checks/tier-tokens.ts"() {
|
|
24793
24931
|
"use strict";
|
|
@@ -24795,6 +24933,7 @@ var init_tier_tokens = __esm({
|
|
|
24795
24933
|
init_main3();
|
|
24796
24934
|
init_tokens();
|
|
24797
24935
|
init_fs();
|
|
24936
|
+
init_markdown();
|
|
24798
24937
|
init_tokens2();
|
|
24799
24938
|
ALWAYS_LOADED_NAMES = [
|
|
24800
24939
|
"CLAUDE.md",
|
|
@@ -24817,7 +24956,8 @@ var init_tier_tokens = __esm({
|
|
|
24817
24956
|
".goose/instructions.md"
|
|
24818
24957
|
];
|
|
24819
24958
|
TOP_SECTIONS_TO_REPORT = 3;
|
|
24820
|
-
|
|
24959
|
+
FRAMING_TOKEN = /(?<![\w'.-])(never|always|don'?t|do\s+not|must\s+not)(?![\w'.-])/gi;
|
|
24960
|
+
FRAMING_COMMAND_GAP = 80;
|
|
24821
24961
|
settingsCache = null;
|
|
24822
24962
|
}
|
|
24823
24963
|
});
|
|
@@ -28968,7 +29108,7 @@ import { readFileSync as readFileSync8 } from "node:fs";
|
|
|
28968
29108
|
import { resolve as resolve15, dirname as dirname7 } from "node:path";
|
|
28969
29109
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
28970
29110
|
function loadVersion() {
|
|
28971
|
-
if (true) return "0.
|
|
29111
|
+
if (true) return "0.24.0";
|
|
28972
29112
|
try {
|
|
28973
29113
|
const __dir = dirname7(fileURLToPath2(import.meta.url));
|
|
28974
29114
|
const pkgPath = resolve15(__dir, "../package.json");
|
|
@@ -70060,6 +70200,13 @@ __export(cli_exports, {
|
|
|
70060
70200
|
});
|
|
70061
70201
|
import * as fs15 from "node:fs";
|
|
70062
70202
|
import * as path20 from "node:path";
|
|
70203
|
+
function existsAsDirectory(p2) {
|
|
70204
|
+
try {
|
|
70205
|
+
return fs15.statSync(p2).isDirectory();
|
|
70206
|
+
} catch {
|
|
70207
|
+
return false;
|
|
70208
|
+
}
|
|
70209
|
+
}
|
|
70063
70210
|
function validateCheckNames(names, source) {
|
|
70064
70211
|
const invalid = names.filter((n7) => n7 && !VALID_CHECKS.has(n7));
|
|
70065
70212
|
if (invalid.length > 0) {
|
|
@@ -70083,6 +70230,12 @@ async function runCli() {
|
|
|
70083
70230
|
false
|
|
70084
70231
|
).option("--no-ignore-file", "Disable .ctxlintignore suppression (see all findings)").option("--watch", "Re-lint on context file changes", false).action(async (projectPath, opts) => {
|
|
70085
70232
|
const resolvedPath = path20.resolve(projectPath);
|
|
70233
|
+
if (!existsAsDirectory(resolvedPath)) {
|
|
70234
|
+
console.error(
|
|
70235
|
+
`Error: ${resolvedPath} is not an existing directory. Check the path argument.`
|
|
70236
|
+
);
|
|
70237
|
+
process.exit(2);
|
|
70238
|
+
}
|
|
70086
70239
|
const { config: config2, options, activeChecks } = resolveSession(resolvedPath, opts);
|
|
70087
70240
|
const spinner = options.format === "text" && !options.quiet ? ora("Scanning for context files...").start() : void 0;
|
|
70088
70241
|
try {
|
|
@@ -70401,7 +70554,7 @@ function resolveDepth(raw) {
|
|
|
70401
70554
|
}
|
|
70402
70555
|
function resolveSession(resolvedPath, opts, throwOnConfigError = false) {
|
|
70403
70556
|
const configPath = opts.config ? path20.resolve(opts.config) : void 0;
|
|
70404
|
-
const config2 = configPath ? loadConfigFromPath(configPath, throwOnConfigError) :
|
|
70557
|
+
const config2 = configPath ? loadConfigFromPath(configPath, throwOnConfigError) : loadDiscoveredConfig(resolvedPath, throwOnConfigError);
|
|
70405
70558
|
const mcpGlobal = opts.mcpGlobal || config2?.mcpGlobal || false;
|
|
70406
70559
|
const mcpOnly = opts.mcpOnly || config2?.mcpOnly || false;
|
|
70407
70560
|
const mcpFlag = opts.mcp || mcpGlobal || mcpOnly || config2?.mcp || false;
|
|
@@ -70469,6 +70622,16 @@ function resolveSession(resolvedPath, opts, throwOnConfigError = false) {
|
|
|
70469
70622
|
const activeChecks = options.checks.filter((c3) => !options.ignore.includes(c3));
|
|
70470
70623
|
return { config: config2, options, activeChecks };
|
|
70471
70624
|
}
|
|
70625
|
+
function loadDiscoveredConfig(resolvedPath, throwOnError = false) {
|
|
70626
|
+
try {
|
|
70627
|
+
return loadConfig(resolvedPath);
|
|
70628
|
+
} catch (err) {
|
|
70629
|
+
if (throwOnError) throw err;
|
|
70630
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
70631
|
+
console.error(`Error: ${detail}`);
|
|
70632
|
+
process.exit(2);
|
|
70633
|
+
}
|
|
70634
|
+
}
|
|
70472
70635
|
function loadConfigFromPath(configPath, throwOnError = false) {
|
|
70473
70636
|
try {
|
|
70474
70637
|
return loadConfigFromExplicitPath(configPath);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yawlabs/ctxlint",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"mcpName": "io.github.YawLabs/ctxlint",
|
|
5
5
|
"description": "Lint your AI agent context files, MCP server configs, and session data against your actual codebase",
|
|
6
6
|
"bin": {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"scripts": {
|
|
14
14
|
"build": "node build.mjs",
|
|
15
15
|
"dev": "node build.mjs",
|
|
16
|
+
"prepublishOnly": "node build.mjs",
|
|
16
17
|
"generate": "node scripts/generate-catalog-prose.mjs",
|
|
17
18
|
"generate:check": "node scripts/generate-catalog-prose.mjs --check",
|
|
18
19
|
"pretest": "node build.mjs",
|