@yawlabs/ctxlint 0.9.3 → 0.9.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.
- package/context-lint-rules.json +11 -0
- package/dist/index.js +135 -4
- package/package.json +3 -1
package/context-lint-rules.json
CHANGED
|
@@ -148,6 +148,17 @@
|
|
|
148
148
|
"fixable": false,
|
|
149
149
|
"stability": "stable"
|
|
150
150
|
},
|
|
151
|
+
{
|
|
152
|
+
"id": "commands/npm-auth-trap",
|
|
153
|
+
"category": "commands",
|
|
154
|
+
"severity": "info",
|
|
155
|
+
"description": "Context file documents an npm write operation (deprecate, unpublish, dist-tag, access, owner) as a local CLI command. Under WebAuthn-only 2FA (no TOTP authenticator), these commands return 422 because npm treats CLI web-auth tokens as not-2FA-authenticated for writes. The npm website settings UI is the only path that works; CI-driven workflows are the scalable alternative.",
|
|
156
|
+
"trigger": "Context file references `npm deprecate`, `npm unpublish`, `npm dist-tag add/rm/set`, `npm access grant/revoke/2fa`, or `npm owner add/rm` as a command.",
|
|
157
|
+
"message": "\"{cmd}\" — npm {op} requires a 2FA-authenticated session; CLI returns 422 under WebAuthn-only auth",
|
|
158
|
+
"fixable": false,
|
|
159
|
+
"stability": "experimental",
|
|
160
|
+
"source": "https://docs.npmjs.com/requiring-2fa-for-package-publishing-and-settings-modification"
|
|
161
|
+
},
|
|
151
162
|
{
|
|
152
163
|
"id": "staleness/stale",
|
|
153
164
|
"category": "staleness",
|
package/dist/index.js
CHANGED
|
@@ -35178,7 +35178,7 @@ var init_parser = __esm({
|
|
|
35178
35178
|
PATH_PATTERN = /(?:^|[\s`"'(])((\.{0,2}\/)?(?:[\w@.-]+\/)+[\w.*-]+(?:\.\w+)?)(?=[\s`"'),;:]|$)/gm;
|
|
35179
35179
|
PATH_EXCLUDE = /^(https?:\/\/|ftp:\/\/|mailto:|n\/a|w\/o|I\/O|i\/o|e\.g\.|N\/A|\.deb\/|\.rpm[.\/]|\.tar[.\/]|\.zip[.\/])/i;
|
|
35180
35180
|
COMMAND_PREFIXES = /^\s*[\$>]\s+(.+)$/;
|
|
35181
|
-
COMMON_COMMANDS = /^(npm
|
|
35181
|
+
COMMON_COMMANDS = /^(npm|npx|pnpm|yarn|make|cargo|go\s+(run|build|test)|python|pytest|vitest|jest|bun|deno)\b/;
|
|
35182
35182
|
}
|
|
35183
35183
|
});
|
|
35184
35184
|
|
|
@@ -35645,6 +35645,121 @@ var require_browser = __commonJS({
|
|
|
35645
35645
|
}
|
|
35646
35646
|
});
|
|
35647
35647
|
|
|
35648
|
+
// node_modules/.pnpm/has-flag@4.0.0/node_modules/has-flag/index.js
|
|
35649
|
+
var require_has_flag = __commonJS({
|
|
35650
|
+
"node_modules/.pnpm/has-flag@4.0.0/node_modules/has-flag/index.js"(exports, module) {
|
|
35651
|
+
"use strict";
|
|
35652
|
+
module.exports = (flag, argv = process.argv) => {
|
|
35653
|
+
const prefix = flag.startsWith("-") ? "" : flag.length === 1 ? "-" : "--";
|
|
35654
|
+
const position = argv.indexOf(prefix + flag);
|
|
35655
|
+
const terminatorPosition = argv.indexOf("--");
|
|
35656
|
+
return position !== -1 && (terminatorPosition === -1 || position < terminatorPosition);
|
|
35657
|
+
};
|
|
35658
|
+
}
|
|
35659
|
+
});
|
|
35660
|
+
|
|
35661
|
+
// node_modules/.pnpm/supports-color@7.2.0/node_modules/supports-color/index.js
|
|
35662
|
+
var require_supports_color = __commonJS({
|
|
35663
|
+
"node_modules/.pnpm/supports-color@7.2.0/node_modules/supports-color/index.js"(exports, module) {
|
|
35664
|
+
"use strict";
|
|
35665
|
+
var os3 = __require("os");
|
|
35666
|
+
var tty3 = __require("tty");
|
|
35667
|
+
var hasFlag2 = require_has_flag();
|
|
35668
|
+
var { env: env2 } = process;
|
|
35669
|
+
var forceColor;
|
|
35670
|
+
if (hasFlag2("no-color") || hasFlag2("no-colors") || hasFlag2("color=false") || hasFlag2("color=never")) {
|
|
35671
|
+
forceColor = 0;
|
|
35672
|
+
} else if (hasFlag2("color") || hasFlag2("colors") || hasFlag2("color=true") || hasFlag2("color=always")) {
|
|
35673
|
+
forceColor = 1;
|
|
35674
|
+
}
|
|
35675
|
+
if ("FORCE_COLOR" in env2) {
|
|
35676
|
+
if (env2.FORCE_COLOR === "true") {
|
|
35677
|
+
forceColor = 1;
|
|
35678
|
+
} else if (env2.FORCE_COLOR === "false") {
|
|
35679
|
+
forceColor = 0;
|
|
35680
|
+
} else {
|
|
35681
|
+
forceColor = env2.FORCE_COLOR.length === 0 ? 1 : Math.min(parseInt(env2.FORCE_COLOR, 10), 3);
|
|
35682
|
+
}
|
|
35683
|
+
}
|
|
35684
|
+
function translateLevel2(level) {
|
|
35685
|
+
if (level === 0) {
|
|
35686
|
+
return false;
|
|
35687
|
+
}
|
|
35688
|
+
return {
|
|
35689
|
+
level,
|
|
35690
|
+
hasBasic: true,
|
|
35691
|
+
has256: level >= 2,
|
|
35692
|
+
has16m: level >= 3
|
|
35693
|
+
};
|
|
35694
|
+
}
|
|
35695
|
+
function supportsColor2(haveStream, streamIsTTY) {
|
|
35696
|
+
if (forceColor === 0) {
|
|
35697
|
+
return 0;
|
|
35698
|
+
}
|
|
35699
|
+
if (hasFlag2("color=16m") || hasFlag2("color=full") || hasFlag2("color=truecolor")) {
|
|
35700
|
+
return 3;
|
|
35701
|
+
}
|
|
35702
|
+
if (hasFlag2("color=256")) {
|
|
35703
|
+
return 2;
|
|
35704
|
+
}
|
|
35705
|
+
if (haveStream && !streamIsTTY && forceColor === void 0) {
|
|
35706
|
+
return 0;
|
|
35707
|
+
}
|
|
35708
|
+
const min = forceColor || 0;
|
|
35709
|
+
if (env2.TERM === "dumb") {
|
|
35710
|
+
return min;
|
|
35711
|
+
}
|
|
35712
|
+
if (process.platform === "win32") {
|
|
35713
|
+
const osRelease = os3.release().split(".");
|
|
35714
|
+
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
35715
|
+
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
35716
|
+
}
|
|
35717
|
+
return 1;
|
|
35718
|
+
}
|
|
35719
|
+
if ("CI" in env2) {
|
|
35720
|
+
if (["TRAVIS", "CIRCLECI", "APPVEYOR", "GITLAB_CI", "GITHUB_ACTIONS", "BUILDKITE"].some((sign) => sign in env2) || env2.CI_NAME === "codeship") {
|
|
35721
|
+
return 1;
|
|
35722
|
+
}
|
|
35723
|
+
return min;
|
|
35724
|
+
}
|
|
35725
|
+
if ("TEAMCITY_VERSION" in env2) {
|
|
35726
|
+
return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env2.TEAMCITY_VERSION) ? 1 : 0;
|
|
35727
|
+
}
|
|
35728
|
+
if (env2.COLORTERM === "truecolor") {
|
|
35729
|
+
return 3;
|
|
35730
|
+
}
|
|
35731
|
+
if ("TERM_PROGRAM" in env2) {
|
|
35732
|
+
const version2 = parseInt((env2.TERM_PROGRAM_VERSION || "").split(".")[0], 10);
|
|
35733
|
+
switch (env2.TERM_PROGRAM) {
|
|
35734
|
+
case "iTerm.app":
|
|
35735
|
+
return version2 >= 3 ? 3 : 2;
|
|
35736
|
+
case "Apple_Terminal":
|
|
35737
|
+
return 2;
|
|
35738
|
+
}
|
|
35739
|
+
}
|
|
35740
|
+
if (/-256(color)?$/i.test(env2.TERM)) {
|
|
35741
|
+
return 2;
|
|
35742
|
+
}
|
|
35743
|
+
if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env2.TERM)) {
|
|
35744
|
+
return 1;
|
|
35745
|
+
}
|
|
35746
|
+
if ("COLORTERM" in env2) {
|
|
35747
|
+
return 1;
|
|
35748
|
+
}
|
|
35749
|
+
return min;
|
|
35750
|
+
}
|
|
35751
|
+
function getSupportLevel(stream) {
|
|
35752
|
+
const level = supportsColor2(stream, stream && stream.isTTY);
|
|
35753
|
+
return translateLevel2(level);
|
|
35754
|
+
}
|
|
35755
|
+
module.exports = {
|
|
35756
|
+
supportsColor: getSupportLevel,
|
|
35757
|
+
stdout: translateLevel2(supportsColor2(true, tty3.isatty(1))),
|
|
35758
|
+
stderr: translateLevel2(supportsColor2(true, tty3.isatty(2)))
|
|
35759
|
+
};
|
|
35760
|
+
}
|
|
35761
|
+
});
|
|
35762
|
+
|
|
35648
35763
|
// node_modules/.pnpm/debug@4.4.3/node_modules/debug/src/node.js
|
|
35649
35764
|
var require_node = __commonJS({
|
|
35650
35765
|
"node_modules/.pnpm/debug@4.4.3/node_modules/debug/src/node.js"(exports, module) {
|
|
@@ -35663,7 +35778,7 @@ var require_node = __commonJS({
|
|
|
35663
35778
|
);
|
|
35664
35779
|
exports.colors = [6, 2, 3, 4, 5, 1];
|
|
35665
35780
|
try {
|
|
35666
|
-
const supportsColor2 =
|
|
35781
|
+
const supportsColor2 = require_supports_color();
|
|
35667
35782
|
if (supportsColor2 && (supportsColor2.stderr || supportsColor2).level >= 2) {
|
|
35668
35783
|
exports.colors = [
|
|
35669
35784
|
20,
|
|
@@ -41373,6 +41488,21 @@ async function checkCommands(file2, projectRoot) {
|
|
|
41373
41488
|
const makefile = loadMakefile(projectRoot);
|
|
41374
41489
|
for (const ref of file2.references.commands) {
|
|
41375
41490
|
const cmd = ref.value;
|
|
41491
|
+
const writeMatch = cmd.match(NPM_WRITE_OP_PATTERN);
|
|
41492
|
+
if (writeMatch) {
|
|
41493
|
+
const op = writeMatch[1].split(/\s+/)[0];
|
|
41494
|
+
if (op !== "publish") {
|
|
41495
|
+
issues.push({
|
|
41496
|
+
severity: "info",
|
|
41497
|
+
check: "commands",
|
|
41498
|
+
ruleId: "commands/npm-auth-trap",
|
|
41499
|
+
line: ref.line,
|
|
41500
|
+
message: `"${cmd}" \u2014 npm ${op} requires a 2FA-authenticated session; CLI returns 422 under WebAuthn-only auth`,
|
|
41501
|
+
suggestion: "Under WebAuthn 2FA without a TOTP fallback, the npm website UI (npmjs.com/package/<pkg>/settings) is the only path that works. For recurring writes, prefer a CI-driven workflow using a granular NPM_TOKEN secret.",
|
|
41502
|
+
detail: "npm treats CLI web-auth tokens as not-2FA-authenticated for writes; WebAuthn yields no TOTP code to satisfy the check."
|
|
41503
|
+
});
|
|
41504
|
+
}
|
|
41505
|
+
}
|
|
41376
41506
|
const scriptMatch = cmd.match(NPM_SCRIPT_PATTERN);
|
|
41377
41507
|
if (scriptMatch && pkgJson) {
|
|
41378
41508
|
const scriptName = scriptMatch[1];
|
|
@@ -41492,7 +41622,7 @@ function hasMakeTarget(makefile, target) {
|
|
|
41492
41622
|
const pattern = new RegExp(`^${target.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*:`, "m");
|
|
41493
41623
|
return pattern.test(makefile);
|
|
41494
41624
|
}
|
|
41495
|
-
var NPM_SCRIPT_PATTERN, MAKE_PATTERN, NPX_PATTERN;
|
|
41625
|
+
var NPM_SCRIPT_PATTERN, MAKE_PATTERN, NPX_PATTERN, NPM_WRITE_OP_PATTERN;
|
|
41496
41626
|
var init_commands = __esm({
|
|
41497
41627
|
"src/core/checks/commands.ts"() {
|
|
41498
41628
|
"use strict";
|
|
@@ -41500,6 +41630,7 @@ var init_commands = __esm({
|
|
|
41500
41630
|
NPM_SCRIPT_PATTERN = /^(?:npm\s+run|pnpm(?:\s+run)?|yarn(?:\s+run)?|bun(?:\s+run)?)\s+(\S+)/;
|
|
41501
41631
|
MAKE_PATTERN = /^make\s+(\S+)/;
|
|
41502
41632
|
NPX_PATTERN = /^npx\s+(\S+)/;
|
|
41633
|
+
NPM_WRITE_OP_PATTERN = /^npm\s+(publish|deprecate|unpublish|access\s+(grant|revoke|2fa)|dist-tag\s+(add|rm|set)|owner\s+(add|rm))\b/;
|
|
41503
41634
|
}
|
|
41504
41635
|
});
|
|
41505
41636
|
|
|
@@ -44022,7 +44153,7 @@ import { readFileSync as readFileSync4 } from "node:fs";
|
|
|
44022
44153
|
import { resolve as resolve8, dirname as dirname3 } from "node:path";
|
|
44023
44154
|
import { fileURLToPath } from "node:url";
|
|
44024
44155
|
function loadVersion() {
|
|
44025
|
-
if (true) return "0.9.
|
|
44156
|
+
if (true) return "0.9.4";
|
|
44026
44157
|
const __dir = dirname3(fileURLToPath(import.meta.url));
|
|
44027
44158
|
const pkgPath = resolve8(__dir, "../package.json");
|
|
44028
44159
|
const pkg = JSON.parse(readFileSync4(pkgPath, "utf-8"));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yawlabs/ctxlint",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4",
|
|
4
4
|
"description": "Lint your AI agent context files, MCP server configs, and session data against your actual codebase",
|
|
5
5
|
"bin": {
|
|
6
6
|
"ctxlint": "dist/index.js"
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"dev": "node build.mjs",
|
|
12
12
|
"test": "vitest",
|
|
13
13
|
"test:run": "vitest run",
|
|
14
|
+
"test:coverage": "vitest run --coverage --coverage.reporter=text-summary --coverage.reporter=html --coverage.exclude='**/__tests__/**' --coverage.exclude='**/fixtures/**' --coverage.exclude='dist/**' --coverage.exclude='build.mjs' --coverage.exclude='coverage-report.mjs'",
|
|
14
15
|
"lint": "eslint src/",
|
|
15
16
|
"format": "prettier --write src/",
|
|
16
17
|
"mcp": "node dist/index.js --mcp-server"
|
|
@@ -79,6 +80,7 @@
|
|
|
79
80
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
80
81
|
"@types/fast-levenshtein": "^0.0.4",
|
|
81
82
|
"@types/node": "^25.6.0",
|
|
83
|
+
"@vitest/coverage-v8": "^4.1.4",
|
|
82
84
|
"chalk": "^5.6.2",
|
|
83
85
|
"commander": "^14.0.3",
|
|
84
86
|
"esbuild": "^0.28.0",
|