@yawlabs/ctxlint 0.13.0 → 0.13.2
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 +185 -108
- package/package.json +1 -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.13.
|
|
7
|
+
entry: npx @yawlabs/ctxlint@0.13.2 --strict
|
|
8
8
|
language: node
|
|
9
9
|
always_run: true
|
|
10
10
|
pass_filenames: false
|
package/dist/index.js
CHANGED
|
@@ -22404,7 +22404,7 @@ var init_protocol = __esm({
|
|
|
22404
22404
|
return;
|
|
22405
22405
|
}
|
|
22406
22406
|
const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
|
|
22407
|
-
await new Promise((
|
|
22407
|
+
await new Promise((resolve15) => setTimeout(resolve15, pollInterval));
|
|
22408
22408
|
options?.signal?.throwIfAborted();
|
|
22409
22409
|
}
|
|
22410
22410
|
} catch (error49) {
|
|
@@ -22421,7 +22421,7 @@ var init_protocol = __esm({
|
|
|
22421
22421
|
*/
|
|
22422
22422
|
request(request, resultSchema, options) {
|
|
22423
22423
|
const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
|
|
22424
|
-
return new Promise((
|
|
22424
|
+
return new Promise((resolve15, reject) => {
|
|
22425
22425
|
const earlyReject = (error49) => {
|
|
22426
22426
|
reject(error49);
|
|
22427
22427
|
};
|
|
@@ -22499,7 +22499,7 @@ var init_protocol = __esm({
|
|
|
22499
22499
|
if (!parseResult.success) {
|
|
22500
22500
|
reject(parseResult.error);
|
|
22501
22501
|
} else {
|
|
22502
|
-
|
|
22502
|
+
resolve15(parseResult.data);
|
|
22503
22503
|
}
|
|
22504
22504
|
} catch (error49) {
|
|
22505
22505
|
reject(error49);
|
|
@@ -22760,12 +22760,12 @@ var init_protocol = __esm({
|
|
|
22760
22760
|
}
|
|
22761
22761
|
} catch {
|
|
22762
22762
|
}
|
|
22763
|
-
return new Promise((
|
|
22763
|
+
return new Promise((resolve15, reject) => {
|
|
22764
22764
|
if (signal.aborted) {
|
|
22765
22765
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
22766
22766
|
return;
|
|
22767
22767
|
}
|
|
22768
|
-
const timeoutId = setTimeout(
|
|
22768
|
+
const timeoutId = setTimeout(resolve15, interval);
|
|
22769
22769
|
signal.addEventListener("abort", () => {
|
|
22770
22770
|
clearTimeout(timeoutId);
|
|
22771
22771
|
reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
|
|
@@ -25813,7 +25813,7 @@ var require_compile = __commonJS({
|
|
|
25813
25813
|
const schOrFunc = root.refs[ref];
|
|
25814
25814
|
if (schOrFunc)
|
|
25815
25815
|
return schOrFunc;
|
|
25816
|
-
let _sch =
|
|
25816
|
+
let _sch = resolve15.call(this, root, ref);
|
|
25817
25817
|
if (_sch === void 0) {
|
|
25818
25818
|
const schema = (_a3 = root.localRefs) === null || _a3 === void 0 ? void 0 : _a3[ref];
|
|
25819
25819
|
const { schemaId } = this.opts;
|
|
@@ -25840,7 +25840,7 @@ var require_compile = __commonJS({
|
|
|
25840
25840
|
function sameSchemaEnv(s1, s2) {
|
|
25841
25841
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
25842
25842
|
}
|
|
25843
|
-
function
|
|
25843
|
+
function resolve15(root, ref) {
|
|
25844
25844
|
let sch;
|
|
25845
25845
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
25846
25846
|
ref = sch;
|
|
@@ -26418,7 +26418,7 @@ var require_fast_uri = __commonJS({
|
|
|
26418
26418
|
}
|
|
26419
26419
|
return uri;
|
|
26420
26420
|
}
|
|
26421
|
-
function
|
|
26421
|
+
function resolve15(baseURI, relativeURI, options) {
|
|
26422
26422
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
26423
26423
|
const resolved = resolveComponent(parse4(baseURI, schemelessOptions), parse4(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
26424
26424
|
schemelessOptions.skipEscape = true;
|
|
@@ -26645,7 +26645,7 @@ var require_fast_uri = __commonJS({
|
|
|
26645
26645
|
var fastUri = {
|
|
26646
26646
|
SCHEMES,
|
|
26647
26647
|
normalize: normalize3,
|
|
26648
|
-
resolve:
|
|
26648
|
+
resolve: resolve15,
|
|
26649
26649
|
resolveComponent,
|
|
26650
26650
|
equal,
|
|
26651
26651
|
serialize,
|
|
@@ -30827,7 +30827,7 @@ var init_mcp = __esm({
|
|
|
30827
30827
|
let task = createTaskResult.task;
|
|
30828
30828
|
const pollInterval = task.pollInterval ?? 5e3;
|
|
30829
30829
|
while (task.status !== "completed" && task.status !== "failed" && task.status !== "cancelled") {
|
|
30830
|
-
await new Promise((
|
|
30830
|
+
await new Promise((resolve15) => setTimeout(resolve15, pollInterval));
|
|
30831
30831
|
const updatedTask = await extra.taskStore.getTask(taskId);
|
|
30832
30832
|
if (!updatedTask) {
|
|
30833
30833
|
throw new McpError(ErrorCode.InternalError, `Task ${taskId} not found during polling`);
|
|
@@ -31423,12 +31423,12 @@ var init_stdio2 = __esm({
|
|
|
31423
31423
|
this.onclose?.();
|
|
31424
31424
|
}
|
|
31425
31425
|
send(message) {
|
|
31426
|
-
return new Promise((
|
|
31426
|
+
return new Promise((resolve15) => {
|
|
31427
31427
|
const json2 = serializeMessage(message);
|
|
31428
31428
|
if (this._stdout.write(json2)) {
|
|
31429
|
-
|
|
31429
|
+
resolve15();
|
|
31430
31430
|
} else {
|
|
31431
|
-
this._stdout.once("drain",
|
|
31431
|
+
this._stdout.once("drain", resolve15);
|
|
31432
31432
|
}
|
|
31433
31433
|
});
|
|
31434
31434
|
}
|
|
@@ -34433,19 +34433,28 @@ function stripBom(content) {
|
|
|
34433
34433
|
return content.charCodeAt(0) === 65279 ? content.slice(1) : content;
|
|
34434
34434
|
}
|
|
34435
34435
|
function loadPackageJson(projectRoot) {
|
|
34436
|
-
if (pkgJsonCache
|
|
34436
|
+
if (pkgJsonCache.has(projectRoot)) {
|
|
34437
|
+
const cached2 = pkgJsonCache.get(projectRoot) ?? null;
|
|
34438
|
+
pkgJsonCache.delete(projectRoot);
|
|
34439
|
+
pkgJsonCache.set(projectRoot, cached2);
|
|
34440
|
+
return cached2;
|
|
34441
|
+
}
|
|
34442
|
+
let data;
|
|
34437
34443
|
try {
|
|
34438
34444
|
const content = stripBom(fs2.readFileSync(path.join(projectRoot, "package.json"), "utf-8"));
|
|
34439
|
-
|
|
34440
|
-
pkgJsonCache = { root: projectRoot, data };
|
|
34441
|
-
return data;
|
|
34445
|
+
data = JSON.parse(content);
|
|
34442
34446
|
} catch {
|
|
34443
|
-
|
|
34444
|
-
|
|
34447
|
+
data = null;
|
|
34448
|
+
}
|
|
34449
|
+
pkgJsonCache.set(projectRoot, data);
|
|
34450
|
+
if (pkgJsonCache.size > PKG_CACHE_MAX_ENTRIES) {
|
|
34451
|
+
const oldest = pkgJsonCache.keys().next().value;
|
|
34452
|
+
if (oldest !== void 0) pkgJsonCache.delete(oldest);
|
|
34445
34453
|
}
|
|
34454
|
+
return data;
|
|
34446
34455
|
}
|
|
34447
34456
|
function resetPackageJsonCache() {
|
|
34448
|
-
pkgJsonCache
|
|
34457
|
+
pkgJsonCache.clear();
|
|
34449
34458
|
}
|
|
34450
34459
|
function fileExists(filePath) {
|
|
34451
34460
|
try {
|
|
@@ -34511,12 +34520,13 @@ function getAllProjectFiles(projectRoot) {
|
|
|
34511
34520
|
walk(projectRoot, 0);
|
|
34512
34521
|
return files;
|
|
34513
34522
|
}
|
|
34514
|
-
var pkgJsonCache, IGNORED_DIRS;
|
|
34523
|
+
var PKG_CACHE_MAX_ENTRIES, pkgJsonCache, IGNORED_DIRS;
|
|
34515
34524
|
var init_fs = __esm({
|
|
34516
34525
|
"src/utils/fs.ts"() {
|
|
34517
34526
|
"use strict";
|
|
34518
34527
|
init_define_WEB_FIRST_SEGMENTS();
|
|
34519
|
-
|
|
34528
|
+
PKG_CACHE_MAX_ENTRIES = 256;
|
|
34529
|
+
pkgJsonCache = /* @__PURE__ */ new Map();
|
|
34520
34530
|
IGNORED_DIRS = /* @__PURE__ */ new Set([
|
|
34521
34531
|
"node_modules",
|
|
34522
34532
|
".git",
|
|
@@ -40905,6 +40915,45 @@ async function getCommitsSinceBatch(projectRoot, paths, since) {
|
|
|
40905
40915
|
}
|
|
40906
40916
|
return out;
|
|
40907
40917
|
}
|
|
40918
|
+
function normalizeRenamePath(p2) {
|
|
40919
|
+
return p2.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
40920
|
+
}
|
|
40921
|
+
function parseRenameLog(result, targetPath) {
|
|
40922
|
+
if (!result.trim()) return null;
|
|
40923
|
+
const want = targetPath ? normalizeRenamePath(targetPath) : void 0;
|
|
40924
|
+
const wantBase = want ? want.split("/").pop() ?? want : void 0;
|
|
40925
|
+
let currentHash = "unknown";
|
|
40926
|
+
let currentDateStr;
|
|
40927
|
+
let basenameFallback = null;
|
|
40928
|
+
const lines = result.trim().split("\n");
|
|
40929
|
+
for (const line of lines) {
|
|
40930
|
+
const headerMatch = line.match(/^([a-f0-9]{7,40})\s+(\d{4}-\d\d-\d\d\b.*)$/);
|
|
40931
|
+
if (headerMatch) {
|
|
40932
|
+
currentHash = headerMatch[1].substring(0, 7);
|
|
40933
|
+
currentDateStr = headerMatch[2];
|
|
40934
|
+
continue;
|
|
40935
|
+
}
|
|
40936
|
+
if (line.startsWith("R")) {
|
|
40937
|
+
const parts = line.split(" ");
|
|
40938
|
+
if (parts.length >= 3) {
|
|
40939
|
+
const daysAgo = currentDateStr ? Math.floor((Date.now() - new Date(currentDateStr).getTime()) / (1e3 * 60 * 60 * 24)) : 0;
|
|
40940
|
+
const info2 = {
|
|
40941
|
+
oldPath: parts[1],
|
|
40942
|
+
newPath: parts[2],
|
|
40943
|
+
commitHash: currentHash,
|
|
40944
|
+
daysAgo
|
|
40945
|
+
};
|
|
40946
|
+
if (!want) return info2;
|
|
40947
|
+
const oldNorm = normalizeRenamePath(info2.oldPath);
|
|
40948
|
+
if (oldNorm === want) return info2;
|
|
40949
|
+
if (!basenameFallback && wantBase && (oldNorm.split("/").pop() ?? oldNorm) === wantBase) {
|
|
40950
|
+
basenameFallback = info2;
|
|
40951
|
+
}
|
|
40952
|
+
}
|
|
40953
|
+
}
|
|
40954
|
+
}
|
|
40955
|
+
return basenameFallback;
|
|
40956
|
+
}
|
|
40908
40957
|
async function findRenames(projectRoot, filePath) {
|
|
40909
40958
|
try {
|
|
40910
40959
|
const git = getGit(projectRoot);
|
|
@@ -40914,35 +40963,9 @@ async function findRenames(projectRoot, filePath) {
|
|
|
40914
40963
|
"--find-renames",
|
|
40915
40964
|
"--name-status",
|
|
40916
40965
|
"--format=%H %ai",
|
|
40917
|
-
"-
|
|
40918
|
-
"--",
|
|
40919
|
-
filePath
|
|
40966
|
+
"-50"
|
|
40920
40967
|
]);
|
|
40921
|
-
|
|
40922
|
-
let currentHash = "unknown";
|
|
40923
|
-
let currentDateStr;
|
|
40924
|
-
const lines = result.trim().split("\n");
|
|
40925
|
-
for (const line of lines) {
|
|
40926
|
-
const headerMatch = line.match(/^([a-f0-9]{7,40})\s+(.+)$/);
|
|
40927
|
-
if (headerMatch) {
|
|
40928
|
-
currentHash = headerMatch[1].substring(0, 7);
|
|
40929
|
-
currentDateStr = headerMatch[2];
|
|
40930
|
-
continue;
|
|
40931
|
-
}
|
|
40932
|
-
if (line.startsWith("R")) {
|
|
40933
|
-
const parts = line.split(" ");
|
|
40934
|
-
if (parts.length >= 3) {
|
|
40935
|
-
const daysAgo = currentDateStr ? Math.floor((Date.now() - new Date(currentDateStr).getTime()) / (1e3 * 60 * 60 * 24)) : 0;
|
|
40936
|
-
return {
|
|
40937
|
-
oldPath: parts[1],
|
|
40938
|
-
newPath: parts[2],
|
|
40939
|
-
commitHash: currentHash,
|
|
40940
|
-
daysAgo
|
|
40941
|
-
};
|
|
40942
|
-
}
|
|
40943
|
-
}
|
|
40944
|
-
}
|
|
40945
|
-
return null;
|
|
40968
|
+
return parseRenameLog(result, filePath);
|
|
40946
40969
|
} catch {
|
|
40947
40970
|
return null;
|
|
40948
40971
|
}
|
|
@@ -42590,10 +42613,26 @@ function extractNpxPackage(cmd) {
|
|
|
42590
42613
|
}
|
|
42591
42614
|
return null;
|
|
42592
42615
|
}
|
|
42616
|
+
function wouldNeedPackageJson(cmd) {
|
|
42617
|
+
return NPM_SCRIPT_PATTERN.test(cmd) || PKG_SHORTHAND_PATTERN.test(cmd) || /^npx\b/.test(cmd) || PKG_DEPENDENT_TOOL_PATTERN.test(cmd);
|
|
42618
|
+
}
|
|
42593
42619
|
async function checkCommands(file2, projectRoot) {
|
|
42594
42620
|
const issues = [];
|
|
42595
42621
|
const pkgJson = loadPackageJson(projectRoot);
|
|
42596
42622
|
const makefile = loadMakefile(projectRoot);
|
|
42623
|
+
if (!pkgJson) {
|
|
42624
|
+
const skipped = file2.references.commands.find((ref) => wouldNeedPackageJson(ref.value));
|
|
42625
|
+
if (skipped) {
|
|
42626
|
+
issues.push({
|
|
42627
|
+
severity: "info",
|
|
42628
|
+
check: "commands",
|
|
42629
|
+
ruleId: "commands/package-json-missing",
|
|
42630
|
+
line: skipped.line,
|
|
42631
|
+
message: "package.json missing or unparseable \u2014 command checks skipped",
|
|
42632
|
+
suggestion: "Add a parseable package.json at the project root so script, npx, and tool references can be validated."
|
|
42633
|
+
});
|
|
42634
|
+
}
|
|
42635
|
+
}
|
|
42597
42636
|
for (const ref of file2.references.commands) {
|
|
42598
42637
|
const cmd = ref.value;
|
|
42599
42638
|
const scriptMatch = cmd.match(NPM_SCRIPT_PATTERN);
|
|
@@ -42612,9 +42651,7 @@ async function checkCommands(file2, projectRoot) {
|
|
|
42612
42651
|
}
|
|
42613
42652
|
continue;
|
|
42614
42653
|
}
|
|
42615
|
-
const shorthandMatch = cmd.match(
|
|
42616
|
-
/^(npm|pnpm|yarn|bun)\s+(test|start|build|dev|lint|format|check|typecheck|clean|serve|preview|e2e)\b/
|
|
42617
|
-
);
|
|
42654
|
+
const shorthandMatch = cmd.match(PKG_SHORTHAND_PATTERN);
|
|
42618
42655
|
if (shorthandMatch && pkgJson) {
|
|
42619
42656
|
const scriptName = shorthandMatch[2];
|
|
42620
42657
|
if (pkgJson.scripts && !(scriptName in pkgJson.scripts)) {
|
|
@@ -42676,7 +42713,7 @@ async function checkCommands(file2, projectRoot) {
|
|
|
42676
42713
|
}
|
|
42677
42714
|
continue;
|
|
42678
42715
|
}
|
|
42679
|
-
const toolMatch = cmd.match(
|
|
42716
|
+
const toolMatch = cmd.match(PKG_DEPENDENT_TOOL_PATTERN);
|
|
42680
42717
|
if (toolMatch && pkgJson) {
|
|
42681
42718
|
const tool = toolMatch[1];
|
|
42682
42719
|
const allDeps = {
|
|
@@ -42714,7 +42751,7 @@ function hasMakeTarget(makefile, target) {
|
|
|
42714
42751
|
const pattern = new RegExp(`^${target.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*:`, "m");
|
|
42715
42752
|
return pattern.test(makefile);
|
|
42716
42753
|
}
|
|
42717
|
-
var NPM_SCRIPT_PATTERN, MAKE_PATTERN;
|
|
42754
|
+
var NPM_SCRIPT_PATTERN, MAKE_PATTERN, PKG_DEPENDENT_TOOL_PATTERN, PKG_SHORTHAND_PATTERN;
|
|
42718
42755
|
var init_commands = __esm({
|
|
42719
42756
|
"src/core/checks/commands.ts"() {
|
|
42720
42757
|
"use strict";
|
|
@@ -42722,6 +42759,8 @@ var init_commands = __esm({
|
|
|
42722
42759
|
init_fs();
|
|
42723
42760
|
NPM_SCRIPT_PATTERN = /^(?:npm\s+run|pnpm(?:\s+run)?|yarn(?:\s+run)?|bun(?:\s+run)?)\s+(\S+)/;
|
|
42724
42761
|
MAKE_PATTERN = /^make\s+(\S+)/;
|
|
42762
|
+
PKG_DEPENDENT_TOOL_PATTERN = /^(vitest|jest|pytest|mocha|eslint|prettier|tsc)\b/;
|
|
42763
|
+
PKG_SHORTHAND_PATTERN = /^(npm|pnpm|yarn|bun)\s+(test|start|build|dev|lint|format|check|typecheck|clean|serve|preview|e2e)\b/;
|
|
42725
42764
|
}
|
|
42726
42765
|
});
|
|
42727
42766
|
|
|
@@ -42912,6 +42951,7 @@ function computeSectionCosts(file2) {
|
|
|
42912
42951
|
}).sort((a, b2) => b2.tokens - a.tokens);
|
|
42913
42952
|
}
|
|
42914
42953
|
function loadSettingsSources(projectRoot) {
|
|
42954
|
+
if (settingsCache?.root === projectRoot) return settingsCache.data;
|
|
42915
42955
|
const sources = [];
|
|
42916
42956
|
const candidates = [
|
|
42917
42957
|
path7.join(projectRoot, ".claude", "settings.json"),
|
|
@@ -42931,6 +42971,7 @@ function loadSettingsSources(projectRoot) {
|
|
|
42931
42971
|
console.warn(`ctxlint: could not parse ${p2}: ${err.message}`);
|
|
42932
42972
|
}
|
|
42933
42973
|
}
|
|
42974
|
+
settingsCache = { root: projectRoot, data: sources };
|
|
42934
42975
|
return sources;
|
|
42935
42976
|
}
|
|
42936
42977
|
function canonicalizeCommand(backticked) {
|
|
@@ -43025,7 +43066,7 @@ function checkAggregateTierTokens(files, thresholds = DEFAULT_TOKEN_THRESHOLDS)
|
|
|
43025
43066
|
suggestion: "Consider moving the largest files or their heaviest sections to on-demand tiers (skills, subagents, memory)."
|
|
43026
43067
|
};
|
|
43027
43068
|
}
|
|
43028
|
-
var ALWAYS_LOADED_NAMES, TOP_SECTIONS_TO_REPORT, INVIOLABLE_WITH_COMMAND;
|
|
43069
|
+
var ALWAYS_LOADED_NAMES, TOP_SECTIONS_TO_REPORT, INVIOLABLE_WITH_COMMAND, settingsCache;
|
|
43029
43070
|
var init_tier_tokens = __esm({
|
|
43030
43071
|
"src/core/checks/tier-tokens.ts"() {
|
|
43031
43072
|
"use strict";
|
|
@@ -43055,6 +43096,7 @@ var init_tier_tokens = __esm({
|
|
|
43055
43096
|
];
|
|
43056
43097
|
TOP_SECTIONS_TO_REPORT = 3;
|
|
43057
43098
|
INVIOLABLE_WITH_COMMAND = /\b(NEVER|ALWAYS|DON'?T|DO NOT|MUST NOT)\b[^.!?`]{0,80}`([^`]+)`/i;
|
|
43099
|
+
settingsCache = null;
|
|
43058
43100
|
}
|
|
43059
43101
|
});
|
|
43060
43102
|
|
|
@@ -43151,7 +43193,8 @@ async function checkRedundancy(file2, projectRoot) {
|
|
|
43151
43193
|
ruleId: "redundancy/tech-mention",
|
|
43152
43194
|
line: i2 + 1,
|
|
43153
43195
|
message: `"${mention}" is in package.json ${pkgJson.dependencies?.[pkg] ? "dependencies" : pkgJson.devDependencies?.[pkg] ? "devDependencies" : pkgJson.peerDependencies?.[pkg] ? "peerDependencies" : "optionalDependencies"} \u2014 agent can infer this`,
|
|
43154
|
-
suggestion: `~${wastedTokens} tokens could be saved
|
|
43196
|
+
suggestion: `~${wastedTokens} tokens could be saved`,
|
|
43197
|
+
wastedTokens
|
|
43155
43198
|
});
|
|
43156
43199
|
}
|
|
43157
43200
|
}
|
|
@@ -43165,7 +43208,8 @@ async function checkRedundancy(file2, projectRoot) {
|
|
|
43165
43208
|
);
|
|
43166
43209
|
if (dirMatch) {
|
|
43167
43210
|
const dir = dirMatch[1].replace(/[`"]/g, "");
|
|
43168
|
-
const
|
|
43211
|
+
const baseDir = dir.startsWith("./") || dir.startsWith("../") ? path8.dirname(file2.filePath) : projectRoot;
|
|
43212
|
+
const fullPath = path8.resolve(baseDir, dir);
|
|
43169
43213
|
if (isDirectory(fullPath)) {
|
|
43170
43214
|
issues.push({
|
|
43171
43215
|
severity: "info",
|
|
@@ -43397,19 +43441,23 @@ var init_contradictions = __esm({
|
|
|
43397
43441
|
options: [
|
|
43398
43442
|
{
|
|
43399
43443
|
label: "Jest",
|
|
43400
|
-
|
|
43444
|
+
// The third pattern bounds the gap between `test` and `with <fw>` to a
|
|
43445
|
+
// few intra-clause chars so prose like "run our test suite and then
|
|
43446
|
+
// deploy with jest" no longer matches the framework directive — only
|
|
43447
|
+
// tight phrasings like "test with jest" / "tests run with jest" do.
|
|
43448
|
+
patterns: [/\buse\s+jest\b/i, /\bjest\s+for\s+test/i, /\btest\w*\s+with\s+jest\b/i]
|
|
43401
43449
|
},
|
|
43402
43450
|
{
|
|
43403
43451
|
label: "Vitest",
|
|
43404
|
-
patterns: [/\buse\s+vitest\b/i, /\bvitest\s+for\s+test/i, /\btest
|
|
43452
|
+
patterns: [/\buse\s+vitest\b/i, /\bvitest\s+for\s+test/i, /\btest\w*\s+with\s+vitest\b/i]
|
|
43405
43453
|
},
|
|
43406
43454
|
{
|
|
43407
43455
|
label: "Mocha",
|
|
43408
|
-
patterns: [/\buse\s+mocha\b/i, /\bmocha\s+for\s+test/i, /\btest
|
|
43456
|
+
patterns: [/\buse\s+mocha\b/i, /\bmocha\s+for\s+test/i, /\btest\w*\s+with\s+mocha\b/i]
|
|
43409
43457
|
},
|
|
43410
43458
|
{
|
|
43411
43459
|
label: "pytest",
|
|
43412
|
-
patterns: [/\buse\s+pytest\b/i, /\bpytest\s+for\s+test/i, /\btest
|
|
43460
|
+
patterns: [/\buse\s+pytest\b/i, /\bpytest\s+for\s+test/i, /\btest\w*\s+with\s+pytest\b/i]
|
|
43413
43461
|
},
|
|
43414
43462
|
{
|
|
43415
43463
|
label: "Playwright",
|
|
@@ -43464,19 +43512,11 @@ var init_contradictions = __esm({
|
|
|
43464
43512
|
},
|
|
43465
43513
|
{
|
|
43466
43514
|
label: "2 spaces",
|
|
43467
|
-
patterns: [
|
|
43468
|
-
/\b2[\s-]?space\s+indent/i,
|
|
43469
|
-
/\bindent\s+with\s+2\s+spaces/i,
|
|
43470
|
-
/\b2[\s-]?space\s+tabs?\b/i
|
|
43471
|
-
]
|
|
43515
|
+
patterns: [/\b2[\s-]?space\s+indent/i, /\bindent\s+with\s+2\s+spaces/i]
|
|
43472
43516
|
},
|
|
43473
43517
|
{
|
|
43474
43518
|
label: "4 spaces",
|
|
43475
|
-
patterns: [
|
|
43476
|
-
/\b4[\s-]?space\s+indent/i,
|
|
43477
|
-
/\bindent\s+with\s+4\s+spaces/i,
|
|
43478
|
-
/\b4[\s-]?space\s+tabs?\b/i
|
|
43479
|
-
]
|
|
43519
|
+
patterns: [/\b4[\s-]?space\s+indent/i, /\bindent\s+with\s+4\s+spaces/i]
|
|
43480
43520
|
}
|
|
43481
43521
|
]
|
|
43482
43522
|
},
|
|
@@ -43638,10 +43678,11 @@ function hasUnbalancedBracketsOrQuotes(val) {
|
|
|
43638
43678
|
if (square < 0 || curly < 0) return true;
|
|
43639
43679
|
}
|
|
43640
43680
|
if (square !== 0 || curly !== 0) return true;
|
|
43641
|
-
const
|
|
43642
|
-
|
|
43643
|
-
|
|
43644
|
-
|
|
43681
|
+
const quoteChar = val[0];
|
|
43682
|
+
if (quoteChar === '"' || quoteChar === "'") {
|
|
43683
|
+
const count = (val.match(quoteChar === '"' ? /"/g : /'/g) || []).length;
|
|
43684
|
+
if (count % 2 !== 0) return true;
|
|
43685
|
+
}
|
|
43645
43686
|
return false;
|
|
43646
43687
|
}
|
|
43647
43688
|
async function checkFrontmatter(file2, _projectRoot) {
|
|
@@ -44851,13 +44892,13 @@ var init_gitignore = __esm({
|
|
|
44851
44892
|
// src/core/session-parser.ts
|
|
44852
44893
|
import { readFile } from "node:fs/promises";
|
|
44853
44894
|
import { readFileSync as readFileSync4 } from "node:fs";
|
|
44854
|
-
import { dirname as
|
|
44895
|
+
import { dirname as dirname3, resolve as resolve4 } from "node:path";
|
|
44855
44896
|
import { fileURLToPath } from "node:url";
|
|
44856
44897
|
function loadWebFirstSegments() {
|
|
44857
44898
|
if (typeof define_WEB_FIRST_SEGMENTS_default !== "undefined") {
|
|
44858
44899
|
return new Set(define_WEB_FIRST_SEGMENTS_default);
|
|
44859
44900
|
}
|
|
44860
|
-
const __dir =
|
|
44901
|
+
const __dir = dirname3(fileURLToPath(import.meta.url));
|
|
44861
44902
|
const fixturePath = resolve4(__dir, "../../tests/fixtures/web-first-segments.json");
|
|
44862
44903
|
const raw = JSON.parse(readFileSync4(fixturePath, "utf-8"));
|
|
44863
44904
|
const list = Array.isArray(raw) ? raw : raw.segments;
|
|
@@ -44968,7 +45009,7 @@ var init_session_parser = __esm({
|
|
|
44968
45009
|
import { readdir, stat } from "node:fs/promises";
|
|
44969
45010
|
import { createReadStream } from "node:fs";
|
|
44970
45011
|
import { createInterface } from "node:readline";
|
|
44971
|
-
import { dirname as
|
|
45012
|
+
import { dirname as dirname4, join as join5, resolve as resolve5 } from "node:path";
|
|
44972
45013
|
import { existsSync } from "node:fs";
|
|
44973
45014
|
function detectProviders() {
|
|
44974
45015
|
if (!home) return [];
|
|
@@ -45063,7 +45104,7 @@ function detectAiderInSiblings(siblings) {
|
|
|
45063
45104
|
return null;
|
|
45064
45105
|
}
|
|
45065
45106
|
async function detectSiblings(projectRoot) {
|
|
45066
|
-
const parentDir =
|
|
45107
|
+
const parentDir = dirname4(resolve5(projectRoot));
|
|
45067
45108
|
let entries;
|
|
45068
45109
|
try {
|
|
45069
45110
|
entries = await readdir(parentDir);
|
|
@@ -45180,11 +45221,12 @@ async function checkMissingSecret(ctx) {
|
|
|
45180
45221
|
const issues = [];
|
|
45181
45222
|
const secrets = [];
|
|
45182
45223
|
for (const entry of ctx.history) {
|
|
45183
|
-
const match = entry.display.match(
|
|
45224
|
+
const match = entry.display.match(SECRET_SET_SIMPLE);
|
|
45184
45225
|
if (!match) continue;
|
|
45226
|
+
const repoMatch = entry.display.match(REPO_FLAG_PATTERN);
|
|
45185
45227
|
secrets.push({
|
|
45186
45228
|
name: match[1],
|
|
45187
|
-
repo:
|
|
45229
|
+
repo: repoMatch ? repoMatch[1] : void 0,
|
|
45188
45230
|
project: entry.project
|
|
45189
45231
|
});
|
|
45190
45232
|
}
|
|
@@ -45229,19 +45271,19 @@ async function checkMissingSecret(ctx) {
|
|
|
45229
45271
|
}
|
|
45230
45272
|
return issues;
|
|
45231
45273
|
}
|
|
45232
|
-
var
|
|
45274
|
+
var SECRET_SET_SIMPLE, REPO_FLAG_PATTERN;
|
|
45233
45275
|
var init_missing_secret = __esm({
|
|
45234
45276
|
"src/core/checks/session/missing-secret.ts"() {
|
|
45235
45277
|
"use strict";
|
|
45236
45278
|
init_define_WEB_FIRST_SEGMENTS();
|
|
45237
|
-
SECRET_SET_PATTERN = /gh\s+secret\s+set\s+(\S+)\s+--repo\s+(\S+)/;
|
|
45238
45279
|
SECRET_SET_SIMPLE = /gh\s+secret\s+set\s+(\S+)/;
|
|
45280
|
+
REPO_FLAG_PATTERN = /--repo\s+(\S+)/;
|
|
45239
45281
|
}
|
|
45240
45282
|
});
|
|
45241
45283
|
|
|
45242
45284
|
// src/core/checks/session/diverged-file.ts
|
|
45243
45285
|
import { readFile as readFile2, stat as stat2 } from "node:fs/promises";
|
|
45244
|
-
import { join as join6 } from "node:path";
|
|
45286
|
+
import { join as join6, resolve as resolve7 } from "node:path";
|
|
45245
45287
|
import { existsSync as existsSync2 } from "node:fs";
|
|
45246
45288
|
async function loadLineSet(absPath) {
|
|
45247
45289
|
let mtimeMs;
|
|
@@ -45290,11 +45332,16 @@ async function checkDivergedFile(ctx) {
|
|
|
45290
45332
|
bothEmptyIsIdentical: true
|
|
45291
45333
|
});
|
|
45292
45334
|
if (overlap >= 0.2 && overlap < 0.9) {
|
|
45293
|
-
diverged.push({
|
|
45335
|
+
diverged.push({
|
|
45336
|
+
sibling: sib.name,
|
|
45337
|
+
overlap: Math.round(overlap * 100),
|
|
45338
|
+
path: resolve7(sibPath)
|
|
45339
|
+
});
|
|
45294
45340
|
}
|
|
45295
45341
|
}
|
|
45296
45342
|
if (diverged.length > 0) {
|
|
45297
45343
|
const details = diverged.map((d) => `${d.sibling} (${d.overlap}% overlap)`).join(", ");
|
|
45344
|
+
const sibPaths = diverged.map((d) => ` ${d.sibling}: ${d.path}`).join("\n");
|
|
45298
45345
|
issues.push({
|
|
45299
45346
|
severity: "warning",
|
|
45300
45347
|
check: "session-diverged-file",
|
|
@@ -45302,7 +45349,9 @@ async function checkDivergedFile(ctx) {
|
|
|
45302
45349
|
line: 0,
|
|
45303
45350
|
message: `${fileName} has diverged from sibling repos: ${details}`,
|
|
45304
45351
|
suggestion: `Compare with sibling versions to identify unintentional drift`,
|
|
45305
|
-
detail: `
|
|
45352
|
+
detail: `File: ${resolve7(currentPath)}
|
|
45353
|
+
Diverged siblings:
|
|
45354
|
+
${sibPaths}`
|
|
45306
45355
|
});
|
|
45307
45356
|
}
|
|
45308
45357
|
}
|
|
@@ -45389,15 +45438,15 @@ var init_missing_workflow = __esm({
|
|
|
45389
45438
|
|
|
45390
45439
|
// src/core/checks/session/stale-memory.ts
|
|
45391
45440
|
import { existsSync as existsSync4 } from "node:fs";
|
|
45392
|
-
import { resolve as
|
|
45441
|
+
import { resolve as resolve8, isAbsolute as isAbsolute2 } from "node:path";
|
|
45393
45442
|
function resolveRef2(ref, projectRoot) {
|
|
45394
45443
|
if (ref.startsWith("~/") || ref === "~") {
|
|
45395
45444
|
const home2 = process.env.HOME || process.env.USERPROFILE;
|
|
45396
45445
|
if (!home2) return null;
|
|
45397
|
-
return ref === "~" ? home2 :
|
|
45446
|
+
return ref === "~" ? home2 : resolve8(home2, ref.slice(2));
|
|
45398
45447
|
}
|
|
45399
45448
|
if (isAbsolute2(ref)) return ref;
|
|
45400
|
-
return
|
|
45449
|
+
return resolve8(projectRoot, ref);
|
|
45401
45450
|
}
|
|
45402
45451
|
async function checkStaleMemory(ctx) {
|
|
45403
45452
|
const issues = [];
|
|
@@ -45440,7 +45489,15 @@ var init_stale_memory = __esm({
|
|
|
45440
45489
|
async function checkDuplicateMemory(ctx) {
|
|
45441
45490
|
const issues = [];
|
|
45442
45491
|
const reported = /* @__PURE__ */ new Set();
|
|
45443
|
-
const lineSets = ctx.memories.
|
|
45492
|
+
const lineSets = new Array(ctx.memories.length);
|
|
45493
|
+
const getLineSet = (idx) => {
|
|
45494
|
+
let ls2 = lineSets[idx];
|
|
45495
|
+
if (ls2 === void 0) {
|
|
45496
|
+
ls2 = toLineSet(ctx.memories[idx].content, MIN_TOKEN_LEN2);
|
|
45497
|
+
lineSets[idx] = ls2;
|
|
45498
|
+
}
|
|
45499
|
+
return ls2;
|
|
45500
|
+
};
|
|
45444
45501
|
for (let i2 = 0; i2 < ctx.memories.length; i2++) {
|
|
45445
45502
|
for (let j3 = i2 + 1; j3 < ctx.memories.length; j3++) {
|
|
45446
45503
|
const a = ctx.memories[i2];
|
|
@@ -45450,7 +45507,7 @@ async function checkDuplicateMemory(ctx) {
|
|
|
45450
45507
|
const bIsCurrent = projectDirMatchesPath(b2.projectDir, ctx.currentProject);
|
|
45451
45508
|
if (!aIsCurrent && !bIsCurrent) continue;
|
|
45452
45509
|
if (a.content.length < 50 || b2.content.length < 50) continue;
|
|
45453
|
-
const overlap = jaccardSimilarityFromSets(
|
|
45510
|
+
const overlap = jaccardSimilarityFromSets(getLineSet(i2), getLineSet(j3));
|
|
45454
45511
|
if (overlap < 0.6) continue;
|
|
45455
45512
|
const pairKey = [a.filePath, b2.filePath].sort().join("::");
|
|
45456
45513
|
if (reported.has(pairKey)) continue;
|
|
@@ -45484,9 +45541,9 @@ var init_duplicate_memory = __esm({
|
|
|
45484
45541
|
});
|
|
45485
45542
|
|
|
45486
45543
|
// src/core/checks/session/loop-detection.ts
|
|
45487
|
-
import { resolve as
|
|
45544
|
+
import { resolve as resolve9 } from "node:path";
|
|
45488
45545
|
function normalizeProject(p2) {
|
|
45489
|
-
return
|
|
45546
|
+
return resolve9(p2).replace(/\\/g, "/");
|
|
45490
45547
|
}
|
|
45491
45548
|
function findConsecutiveRepeats(displays) {
|
|
45492
45549
|
const results = [];
|
|
@@ -45496,13 +45553,13 @@ function findConsecutiveRepeats(displays) {
|
|
|
45496
45553
|
while (j3 < displays.length && displays[j3] === displays[i2]) j3++;
|
|
45497
45554
|
const count = j3 - i2;
|
|
45498
45555
|
if (count >= CONSECUTIVE_THRESHOLD) {
|
|
45499
|
-
results.push({ command: displays[i2], count });
|
|
45556
|
+
results.push({ command: displays[i2], count, startIdx: i2, endIdx: j3 });
|
|
45500
45557
|
}
|
|
45501
45558
|
i2 = j3;
|
|
45502
45559
|
}
|
|
45503
45560
|
return results;
|
|
45504
45561
|
}
|
|
45505
|
-
function findCyclicPatterns(displays) {
|
|
45562
|
+
function findCyclicPatterns(displays, consecutiveSpans) {
|
|
45506
45563
|
const results = [];
|
|
45507
45564
|
for (let cycleLen = 2; cycleLen <= MAX_CYCLE_LENGTH; cycleLen++) {
|
|
45508
45565
|
for (let start = 0; start <= displays.length - cycleLen * CYCLE_REPEAT_THRESHOLD; start++) {
|
|
@@ -45520,8 +45577,13 @@ function findCyclicPatterns(displays) {
|
|
|
45520
45577
|
}
|
|
45521
45578
|
}
|
|
45522
45579
|
if (repeats >= CYCLE_REPEAT_THRESHOLD) {
|
|
45580
|
+
const cycleEnd = start + cycleLen * repeats;
|
|
45581
|
+
const overlapsConsecutive = consecutiveSpans.some(
|
|
45582
|
+
(s) => s.startIdx < cycleEnd && start < s.endIdx
|
|
45583
|
+
);
|
|
45584
|
+
if (overlapsConsecutive) continue;
|
|
45523
45585
|
const alreadyCovered = results.some(
|
|
45524
|
-
(r2) => r2.startIdx <= start && r2.startIdx + r2.cycle.length * r2.repeats >=
|
|
45586
|
+
(r2) => r2.startIdx <= start && r2.startIdx + r2.cycle.length * r2.repeats >= cycleEnd
|
|
45525
45587
|
);
|
|
45526
45588
|
if (!alreadyCovered) {
|
|
45527
45589
|
results.push({ cycle, repeats, startIdx: start });
|
|
@@ -45550,7 +45612,8 @@ async function checkLoopDetection(ctx) {
|
|
|
45550
45612
|
suggestion: "An agent may be looping on this command. Check history.jsonl for context on what went wrong"
|
|
45551
45613
|
});
|
|
45552
45614
|
}
|
|
45553
|
-
const
|
|
45615
|
+
const consecutiveSpans = repeats.map((r2) => ({ startIdx: r2.startIdx, endIdx: r2.endIdx }));
|
|
45616
|
+
const cycles = findCyclicPatterns(displays, consecutiveSpans);
|
|
45554
45617
|
for (const { cycle, repeats: reps } of cycles) {
|
|
45555
45618
|
const cycleStr = cycle.map((c3) => c3.length > 40 ? c3.slice(0, 37) + "..." : c3).join(" -> ");
|
|
45556
45619
|
issues.push({
|
|
@@ -45655,9 +45718,12 @@ async function findReleaseWorkflows(projectRoot) {
|
|
|
45655
45718
|
}
|
|
45656
45719
|
try {
|
|
45657
45720
|
const content = stripBom(await readFile4(join9(workflowDir, f), "utf-8"));
|
|
45658
|
-
const nameMatch = content.match(
|
|
45659
|
-
if (nameMatch
|
|
45660
|
-
|
|
45721
|
+
const nameMatch = content.match(/^\s*name:\s*(.+?)\s*$/m);
|
|
45722
|
+
if (nameMatch) {
|
|
45723
|
+
const name = nameMatch[1].replace(/^(['"])(.*)\1$/, "$2");
|
|
45724
|
+
if (RELEASE_FILENAME_PATTERNS.some((p2) => p2.test(name))) {
|
|
45725
|
+
releaseWorkflows.push(f);
|
|
45726
|
+
}
|
|
45661
45727
|
}
|
|
45662
45728
|
} catch {
|
|
45663
45729
|
}
|
|
@@ -46511,6 +46577,7 @@ function extractPathsFromMessage(msg) {
|
|
|
46511
46577
|
function applyIgnoreRules(issues, rules) {
|
|
46512
46578
|
const compiled = compileRules(rules);
|
|
46513
46579
|
const kept = [];
|
|
46580
|
+
const keepMask = [];
|
|
46514
46581
|
let dropped = 0;
|
|
46515
46582
|
for (const issue2 of issues) {
|
|
46516
46583
|
let suppress = false;
|
|
@@ -46530,12 +46597,15 @@ function applyIgnoreRules(issues, rules) {
|
|
|
46530
46597
|
}
|
|
46531
46598
|
if (suppress) {
|
|
46532
46599
|
dropped++;
|
|
46600
|
+
keepMask.push(false);
|
|
46533
46601
|
} else {
|
|
46534
46602
|
kept.push(issue2);
|
|
46603
|
+
keepMask.push(true);
|
|
46535
46604
|
}
|
|
46536
46605
|
}
|
|
46537
46606
|
return {
|
|
46538
46607
|
kept,
|
|
46608
|
+
keepMask,
|
|
46539
46609
|
dropped,
|
|
46540
46610
|
unusedRules: compiled.filter((r2) => !r2.fired).map(stripCompiled),
|
|
46541
46611
|
rulesMissingReason: rules.filter((r2) => !r2.reason)
|
|
@@ -46570,12 +46640,12 @@ var init_types3 = __esm({
|
|
|
46570
46640
|
|
|
46571
46641
|
// src/version.ts
|
|
46572
46642
|
import { readFileSync as readFileSync7 } from "node:fs";
|
|
46573
|
-
import { resolve as
|
|
46643
|
+
import { resolve as resolve12, dirname as dirname6 } from "node:path";
|
|
46574
46644
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
46575
46645
|
function loadVersion() {
|
|
46576
|
-
if (true) return "0.
|
|
46577
|
-
const __dir =
|
|
46578
|
-
const pkgPath =
|
|
46646
|
+
if (true) return "0.13.1";
|
|
46647
|
+
const __dir = dirname6(fileURLToPath2(import.meta.url));
|
|
46648
|
+
const pkgPath = resolve12(__dir, "../package.json");
|
|
46579
46649
|
const pkg = JSON.parse(readFileSync7(pkgPath, "utf-8"));
|
|
46580
46650
|
return pkg.version;
|
|
46581
46651
|
}
|
|
@@ -46883,10 +46953,9 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
46883
46953
|
}
|
|
46884
46954
|
});
|
|
46885
46955
|
const applied = applyIgnoreRules(flat, options.ignoreRules);
|
|
46886
|
-
const keptSet = new Set(applied.kept);
|
|
46887
46956
|
const rebuilt = fileResults.map(() => []);
|
|
46888
46957
|
flat.forEach((issue2, i2) => {
|
|
46889
|
-
if (
|
|
46958
|
+
if (applied.keepMask[i2]) rebuilt[owners[i2]].push(issue2);
|
|
46890
46959
|
});
|
|
46891
46960
|
fileResults.forEach((fr, idx) => {
|
|
46892
46961
|
fr.issues = rebuilt[idx];
|
|
@@ -46900,7 +46969,10 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
46900
46969
|
let estimatedWaste = 0;
|
|
46901
46970
|
for (const fr of fileResults) {
|
|
46902
46971
|
for (const issue2 of fr.issues) {
|
|
46903
|
-
if (issue2.check
|
|
46972
|
+
if (issue2.check !== "redundancy") continue;
|
|
46973
|
+
if (typeof issue2.wastedTokens === "number") {
|
|
46974
|
+
estimatedWaste += issue2.wastedTokens;
|
|
46975
|
+
} else if (issue2.suggestion) {
|
|
46904
46976
|
const tokenMatch = issue2.suggestion.match(/~(\d+)\s+tokens/);
|
|
46905
46977
|
if (tokenMatch) estimatedWaste += parseInt(tokenMatch[1], 10);
|
|
46906
46978
|
}
|
|
@@ -54788,6 +54860,11 @@ function formatText(result, verbose = false) {
|
|
|
54788
54860
|
lines.push("Session audit scanned");
|
|
54789
54861
|
renderedAnySummary = true;
|
|
54790
54862
|
}
|
|
54863
|
+
if (groups.skill.length > 0) {
|
|
54864
|
+
if (renderedAnySummary) lines.push("");
|
|
54865
|
+
lines.push("Skill audit scanned");
|
|
54866
|
+
renderedAnySummary = true;
|
|
54867
|
+
}
|
|
54791
54868
|
if (!renderedAnySummary) {
|
|
54792
54869
|
lines.push(`Found ${result.files.length} file${result.files.length !== 1 ? "s" : ""}`);
|
|
54793
54870
|
}
|
package/package.json
CHANGED