@yawlabs/ctxlint 0.18.6 → 0.19.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/AGENT_SESSION_LINT_SPEC.md +548 -450
- package/CONTEXT_LINT_SPEC.md +11 -0
- package/README.md +1 -1
- package/agent-session-lint-rules.json +223 -193
- package/context-lint-rules.json +734 -734
- package/dist/index.js +1044 -457
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13770,10 +13770,16 @@ function extractPathReferences(lines, sections) {
|
|
|
13770
13770
|
if (inCodeBlock && codeBlockLang === "" && /^\s/.test(line) && /^[\w@.-]+\/$/.test(cleanValue)) {
|
|
13771
13771
|
continue;
|
|
13772
13772
|
}
|
|
13773
|
-
const
|
|
13774
|
-
if (
|
|
13773
|
+
const twoSegmentProse = /^([A-Za-z][\w-]*)\/[A-Za-z][\w-]*$/.exec(cleanValue);
|
|
13774
|
+
if (twoSegmentProse && !PATH_FIRST_SEGMENTS.has(twoSegmentProse[1].toLowerCase())) {
|
|
13775
13775
|
continue;
|
|
13776
13776
|
}
|
|
13777
|
+
if (/^[a-z][\w-]*(?:\/[a-z][\w-]*){2,}$/.test(cleanValue)) continue;
|
|
13778
|
+
if (/([A-Z])\1{2,}/.test(cleanValue)) continue;
|
|
13779
|
+
if (cleanValue.includes("*")) {
|
|
13780
|
+
const anchored = /^\.{0,2}\//.test(cleanValue) || cleanValue.includes("**") || PATH_FIRST_SEGMENTS.has(cleanValue.split("/")[0]);
|
|
13781
|
+
if (!anchored) continue;
|
|
13782
|
+
}
|
|
13777
13783
|
if (/(^|\/)\.git\//.test(cleanValue) || /\.app\//.test(cleanValue)) continue;
|
|
13778
13784
|
const column = match.index + match[0].length - match[1].length + 1;
|
|
13779
13785
|
paths.push({
|
|
@@ -19960,8 +19966,8 @@ async function findRenames(projectRoot, filePath) {
|
|
|
19960
19966
|
async function findRenamesBatch(projectRoot, relPaths) {
|
|
19961
19967
|
const result = /* @__PURE__ */ new Map();
|
|
19962
19968
|
if (relPaths.length === 0) return result;
|
|
19963
|
-
let
|
|
19964
|
-
if (!
|
|
19969
|
+
let cache2 = renameCache.get(projectRoot);
|
|
19970
|
+
if (!cache2) {
|
|
19965
19971
|
try {
|
|
19966
19972
|
const git = getGit(projectRoot);
|
|
19967
19973
|
const rawOutput = await git.raw([
|
|
@@ -19979,14 +19985,14 @@ async function findRenamesBatch(projectRoot, relPaths) {
|
|
|
19979
19985
|
} catch {
|
|
19980
19986
|
}
|
|
19981
19987
|
const { allRenames: allRenames2, basenameBuckets: basenameBuckets2 } = parseRenameLogAll(rawOutput);
|
|
19982
|
-
|
|
19983
|
-
renameCache.set(projectRoot,
|
|
19988
|
+
cache2 = { allRenames: allRenames2, basenameBuckets: basenameBuckets2, prefix: prefix2 };
|
|
19989
|
+
renameCache.set(projectRoot, cache2);
|
|
19984
19990
|
} catch {
|
|
19985
19991
|
for (const p2 of relPaths) result.set(p2, null);
|
|
19986
19992
|
return result;
|
|
19987
19993
|
}
|
|
19988
19994
|
}
|
|
19989
|
-
const { allRenames, basenameBuckets, prefix } =
|
|
19995
|
+
const { allRenames, basenameBuckets, prefix } = cache2;
|
|
19990
19996
|
for (const relPath of relPaths) {
|
|
19991
19997
|
const rel = relPath.replace(/\\/g, "/");
|
|
19992
19998
|
const target = prefix + rel;
|
|
@@ -20505,6 +20511,7 @@ async function checkPaths(file2, projectRoot) {
|
|
|
20505
20511
|
const projectFiles = getProjectFiles(projectRoot);
|
|
20506
20512
|
const basenameIndex = getBasenameIndex(projectRoot, projectFiles);
|
|
20507
20513
|
const contextDir = path4.dirname(file2.filePath);
|
|
20514
|
+
const inRootDotConfigDir = path4.dirname(contextDir) === projectRoot && path4.basename(contextDir).startsWith(".");
|
|
20508
20515
|
const pending = [];
|
|
20509
20516
|
for (const ref of file2.references.paths) {
|
|
20510
20517
|
const rawNormalized = ref.value.replace(/\\/g, "/");
|
|
@@ -20515,7 +20522,7 @@ async function checkPaths(file2, projectRoot) {
|
|
|
20515
20522
|
continue;
|
|
20516
20523
|
}
|
|
20517
20524
|
const explicitRel = /^\.\.?\//.test(normalizedRef);
|
|
20518
|
-
const baseDirs = explicitRel
|
|
20525
|
+
const baseDirs = isImport ? [contextDir] : explicitRel ? inRootDotConfigDir ? [contextDir, projectRoot] : [contextDir] : [projectRoot, contextDir];
|
|
20519
20526
|
const primaryBase = baseDirs[0];
|
|
20520
20527
|
if (normalizedRef.includes("*")) {
|
|
20521
20528
|
let hasMatch = false;
|
|
@@ -20676,423 +20683,6 @@ var init_paths = __esm({
|
|
|
20676
20683
|
}
|
|
20677
20684
|
});
|
|
20678
20685
|
|
|
20679
|
-
// src/core/checks/commands.ts
|
|
20680
|
-
import * as fs4 from "node:fs";
|
|
20681
|
-
import * as path5 from "node:path";
|
|
20682
|
-
function isManagerBuiltin(manager, sub) {
|
|
20683
|
-
return PM_MANAGER_BUILTINS[manager]?.has(sub) ?? false;
|
|
20684
|
-
}
|
|
20685
|
-
function scriptNameFromMatch(match) {
|
|
20686
|
-
const [, manager, explicitRun, name] = match;
|
|
20687
|
-
if (name.startsWith("-")) return null;
|
|
20688
|
-
if (manager && !explicitRun && PM_BUILTIN_SUBCOMMANDS.has(name)) return null;
|
|
20689
|
-
if (manager && !explicitRun && isManagerBuiltin(manager, name)) return null;
|
|
20690
|
-
return name;
|
|
20691
|
-
}
|
|
20692
|
-
function extractNpxPackage(cmd) {
|
|
20693
|
-
if (!/^npx\b/.test(cmd)) return null;
|
|
20694
|
-
const tokens = cmd.split(/\s+/).slice(1);
|
|
20695
|
-
for (let i2 = 0; i2 < tokens.length; i2++) {
|
|
20696
|
-
const t2 = tokens[i2];
|
|
20697
|
-
if (t2 === "-p" || t2 === "--package") {
|
|
20698
|
-
const v2 = tokens[i2 + 1];
|
|
20699
|
-
if (v2 && !v2.startsWith("-")) return v2;
|
|
20700
|
-
continue;
|
|
20701
|
-
}
|
|
20702
|
-
if (t2.startsWith("-p=") || t2.startsWith("--package=")) {
|
|
20703
|
-
return t2.slice(t2.indexOf("=") + 1) || null;
|
|
20704
|
-
}
|
|
20705
|
-
if (t2.startsWith("-")) continue;
|
|
20706
|
-
return t2;
|
|
20707
|
-
}
|
|
20708
|
-
return null;
|
|
20709
|
-
}
|
|
20710
|
-
function wouldNeedPackageJson(cmd) {
|
|
20711
|
-
const scriptMatch = cmd.match(NPM_SCRIPT_PATTERN);
|
|
20712
|
-
if (scriptMatch && scriptNameFromMatch(scriptMatch) !== null) return true;
|
|
20713
|
-
const shorthandMatch = cmd.match(PKG_SHORTHAND_PATTERN);
|
|
20714
|
-
if (shorthandMatch && !isManagerBuiltin(shorthandMatch[1], shorthandMatch[2])) return true;
|
|
20715
|
-
return /^npx\b/.test(cmd) || PKG_DEPENDENT_TOOL_PATTERN.test(cmd);
|
|
20716
|
-
}
|
|
20717
|
-
async function checkCommands(file2, projectRoot) {
|
|
20718
|
-
const issues = [];
|
|
20719
|
-
const pkgJson = loadPackageJson(projectRoot);
|
|
20720
|
-
const makefile = loadMakefile(projectRoot);
|
|
20721
|
-
if (!pkgJson) {
|
|
20722
|
-
const skipped = file2.references.commands.find((ref) => wouldNeedPackageJson(ref.value));
|
|
20723
|
-
if (skipped) {
|
|
20724
|
-
issues.push({
|
|
20725
|
-
severity: "info",
|
|
20726
|
-
check: "commands",
|
|
20727
|
-
ruleId: "commands/package-json-missing",
|
|
20728
|
-
line: skipped.line,
|
|
20729
|
-
message: "package.json missing or unparseable \u2014 command checks skipped",
|
|
20730
|
-
suggestion: "Add a parseable package.json at the project root so script, npx, and tool references can be validated."
|
|
20731
|
-
});
|
|
20732
|
-
}
|
|
20733
|
-
}
|
|
20734
|
-
for (const ref of file2.references.commands) {
|
|
20735
|
-
const cmd = ref.value;
|
|
20736
|
-
const scriptMatch = cmd.match(NPM_SCRIPT_PATTERN);
|
|
20737
|
-
if (scriptMatch && pkgJson) {
|
|
20738
|
-
const scriptName = scriptNameFromMatch(scriptMatch);
|
|
20739
|
-
if (scriptName && pkgJson.scripts && !(scriptName in pkgJson.scripts)) {
|
|
20740
|
-
const available = Object.keys(pkgJson.scripts).join(", ");
|
|
20741
|
-
issues.push({
|
|
20742
|
-
severity: "error",
|
|
20743
|
-
check: "commands",
|
|
20744
|
-
ruleId: "commands/script-not-found",
|
|
20745
|
-
line: ref.line,
|
|
20746
|
-
message: `"${cmd}" \u2014 script "${scriptName}" not found in package.json`,
|
|
20747
|
-
suggestion: available ? `Available scripts: ${available}` : void 0
|
|
20748
|
-
});
|
|
20749
|
-
}
|
|
20750
|
-
continue;
|
|
20751
|
-
}
|
|
20752
|
-
const shorthandMatch = cmd.match(PKG_SHORTHAND_PATTERN);
|
|
20753
|
-
if (shorthandMatch && pkgJson) {
|
|
20754
|
-
const manager = shorthandMatch[1];
|
|
20755
|
-
const scriptName = shorthandMatch[2];
|
|
20756
|
-
if (isManagerBuiltin(manager, scriptName)) continue;
|
|
20757
|
-
if (pkgJson.scripts && !(scriptName in pkgJson.scripts)) {
|
|
20758
|
-
issues.push({
|
|
20759
|
-
severity: "error",
|
|
20760
|
-
check: "commands",
|
|
20761
|
-
ruleId: "commands/script-not-found",
|
|
20762
|
-
line: ref.line,
|
|
20763
|
-
message: `"${cmd}" \u2014 script "${scriptName}" not found in package.json`
|
|
20764
|
-
});
|
|
20765
|
-
}
|
|
20766
|
-
continue;
|
|
20767
|
-
}
|
|
20768
|
-
if (/^npx\b/.test(cmd) && pkgJson) {
|
|
20769
|
-
const pkgName = extractNpxPackage(cmd);
|
|
20770
|
-
if (!pkgName) continue;
|
|
20771
|
-
const allDeps = {
|
|
20772
|
-
...pkgJson.dependencies,
|
|
20773
|
-
...pkgJson.devDependencies,
|
|
20774
|
-
...pkgJson.peerDependencies,
|
|
20775
|
-
...pkgJson.optionalDependencies
|
|
20776
|
-
};
|
|
20777
|
-
if (!(pkgName in allDeps)) {
|
|
20778
|
-
const binPath = path5.join(projectRoot, "node_modules", ".bin", pkgName);
|
|
20779
|
-
try {
|
|
20780
|
-
fs4.accessSync(binPath);
|
|
20781
|
-
} catch {
|
|
20782
|
-
issues.push({
|
|
20783
|
-
severity: "warning",
|
|
20784
|
-
check: "commands",
|
|
20785
|
-
ruleId: "commands/npx-not-in-deps",
|
|
20786
|
-
line: ref.line,
|
|
20787
|
-
message: `"${cmd}" \u2014 "${pkgName}" not found in dependencies`,
|
|
20788
|
-
suggestion: "If this is a global tool, consider adding it to devDependencies for reproducibility"
|
|
20789
|
-
});
|
|
20790
|
-
}
|
|
20791
|
-
}
|
|
20792
|
-
continue;
|
|
20793
|
-
}
|
|
20794
|
-
if (MAKE_PATTERN.test(cmd)) {
|
|
20795
|
-
if (!makefile) {
|
|
20796
|
-
issues.push({
|
|
20797
|
-
severity: "error",
|
|
20798
|
-
check: "commands",
|
|
20799
|
-
ruleId: "commands/no-makefile",
|
|
20800
|
-
line: ref.line,
|
|
20801
|
-
message: `"${cmd}" \u2014 no Makefile found in project`
|
|
20802
|
-
});
|
|
20803
|
-
} else {
|
|
20804
|
-
const target = extractMakeTarget(cmd);
|
|
20805
|
-
if (target && !hasMakeTarget(makefile, target)) {
|
|
20806
|
-
issues.push({
|
|
20807
|
-
severity: "error",
|
|
20808
|
-
check: "commands",
|
|
20809
|
-
ruleId: "commands/make-target-not-found",
|
|
20810
|
-
line: ref.line,
|
|
20811
|
-
message: `"${cmd}" \u2014 target "${target}" not found in Makefile`
|
|
20812
|
-
});
|
|
20813
|
-
}
|
|
20814
|
-
}
|
|
20815
|
-
continue;
|
|
20816
|
-
}
|
|
20817
|
-
const toolMatch = cmd.match(PKG_DEPENDENT_TOOL_PATTERN);
|
|
20818
|
-
if (toolMatch && pkgJson) {
|
|
20819
|
-
const tool = toolMatch[1];
|
|
20820
|
-
const pkgName = BIN_TO_PACKAGE[tool] ?? tool;
|
|
20821
|
-
const allDeps = {
|
|
20822
|
-
...pkgJson.dependencies,
|
|
20823
|
-
...pkgJson.devDependencies,
|
|
20824
|
-
...pkgJson.peerDependencies,
|
|
20825
|
-
...pkgJson.optionalDependencies
|
|
20826
|
-
};
|
|
20827
|
-
if (!(pkgName in allDeps)) {
|
|
20828
|
-
const binPath = path5.join(projectRoot, "node_modules", ".bin", tool);
|
|
20829
|
-
try {
|
|
20830
|
-
fs4.accessSync(binPath);
|
|
20831
|
-
} catch {
|
|
20832
|
-
issues.push({
|
|
20833
|
-
severity: "warning",
|
|
20834
|
-
check: "commands",
|
|
20835
|
-
ruleId: "commands/tool-not-found",
|
|
20836
|
-
line: ref.line,
|
|
20837
|
-
message: `"${cmd}" \u2014 "${tool}" not found in dependencies or node_modules/.bin`
|
|
20838
|
-
});
|
|
20839
|
-
}
|
|
20840
|
-
}
|
|
20841
|
-
}
|
|
20842
|
-
}
|
|
20843
|
-
return issues;
|
|
20844
|
-
}
|
|
20845
|
-
function loadMakefile(projectRoot) {
|
|
20846
|
-
try {
|
|
20847
|
-
return stripBom(fs4.readFileSync(path5.join(projectRoot, "Makefile"), "utf-8"));
|
|
20848
|
-
} catch {
|
|
20849
|
-
return null;
|
|
20850
|
-
}
|
|
20851
|
-
}
|
|
20852
|
-
function extractMakeTarget(cmd) {
|
|
20853
|
-
for (const token of cmd.split(/\s+/).slice(1)) {
|
|
20854
|
-
if (token.startsWith("-")) return null;
|
|
20855
|
-
if (token.includes("=")) continue;
|
|
20856
|
-
return token;
|
|
20857
|
-
}
|
|
20858
|
-
return null;
|
|
20859
|
-
}
|
|
20860
|
-
function hasMakeTarget(makefile, target) {
|
|
20861
|
-
const escaped = target.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
20862
|
-
const pattern = new RegExp(`^${escaped}\\s*:(?!:?=)`, "m");
|
|
20863
|
-
return pattern.test(makefile);
|
|
20864
|
-
}
|
|
20865
|
-
var NPM_SCRIPT_PATTERN, MAKE_PATTERN, PM_BUILTIN_SUBCOMMANDS, PM_MANAGER_BUILTINS, PKG_DEPENDENT_TOOL_PATTERN, BIN_TO_PACKAGE, PKG_SHORTHAND_PATTERN;
|
|
20866
|
-
var init_commands = __esm({
|
|
20867
|
-
"src/core/checks/commands.ts"() {
|
|
20868
|
-
"use strict";
|
|
20869
|
-
init_define_WEB_FIRST_SEGMENTS();
|
|
20870
|
-
init_fs();
|
|
20871
|
-
NPM_SCRIPT_PATTERN = /^(?:npm\s+run|(pnpm|yarn|bun)(?:\s+(run))?)\s+(\S+)/;
|
|
20872
|
-
MAKE_PATTERN = /^make\s+\S/;
|
|
20873
|
-
PM_BUILTIN_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
20874
|
-
"add",
|
|
20875
|
-
"approve-builds",
|
|
20876
|
-
"audit",
|
|
20877
|
-
"bin",
|
|
20878
|
-
"cache",
|
|
20879
|
-
"ci",
|
|
20880
|
-
"config",
|
|
20881
|
-
"create",
|
|
20882
|
-
"dedupe",
|
|
20883
|
-
"dlx",
|
|
20884
|
-
"doctor",
|
|
20885
|
-
"env",
|
|
20886
|
-
"exec",
|
|
20887
|
-
"fetch",
|
|
20888
|
-
"global",
|
|
20889
|
-
"help",
|
|
20890
|
-
"i",
|
|
20891
|
-
"import",
|
|
20892
|
-
"info",
|
|
20893
|
-
"init",
|
|
20894
|
-
"install",
|
|
20895
|
-
"licenses",
|
|
20896
|
-
"link",
|
|
20897
|
-
"list",
|
|
20898
|
-
"login",
|
|
20899
|
-
"logout",
|
|
20900
|
-
"ls",
|
|
20901
|
-
"node",
|
|
20902
|
-
"npm",
|
|
20903
|
-
"outdated",
|
|
20904
|
-
"pack",
|
|
20905
|
-
"patch",
|
|
20906
|
-
"patch-commit",
|
|
20907
|
-
"prune",
|
|
20908
|
-
"publish",
|
|
20909
|
-
"rebuild",
|
|
20910
|
-
"remove",
|
|
20911
|
-
"rm",
|
|
20912
|
-
"root",
|
|
20913
|
-
"run",
|
|
20914
|
-
"self-update",
|
|
20915
|
-
"set",
|
|
20916
|
-
"setup",
|
|
20917
|
-
"store",
|
|
20918
|
-
"team",
|
|
20919
|
-
"un",
|
|
20920
|
-
"uninstall",
|
|
20921
|
-
"unlink",
|
|
20922
|
-
"up",
|
|
20923
|
-
"update",
|
|
20924
|
-
"upgrade",
|
|
20925
|
-
"version",
|
|
20926
|
-
"whoami",
|
|
20927
|
-
"why",
|
|
20928
|
-
"workspace",
|
|
20929
|
-
"workspaces",
|
|
20930
|
-
"x"
|
|
20931
|
-
]);
|
|
20932
|
-
PM_MANAGER_BUILTINS = {
|
|
20933
|
-
bun: /* @__PURE__ */ new Set(["test"])
|
|
20934
|
-
};
|
|
20935
|
-
PKG_DEPENDENT_TOOL_PATTERN = /^(vitest|jest|pytest|mocha|eslint|prettier|tsc)\b/;
|
|
20936
|
-
BIN_TO_PACKAGE = {
|
|
20937
|
-
tsc: "typescript"
|
|
20938
|
-
};
|
|
20939
|
-
PKG_SHORTHAND_PATTERN = /^(npm|pnpm|yarn|bun)\s+(test|start|build|dev|lint|format|check|typecheck|clean|serve|preview|e2e)\b/;
|
|
20940
|
-
}
|
|
20941
|
-
});
|
|
20942
|
-
|
|
20943
|
-
// src/core/checks/staleness.ts
|
|
20944
|
-
import * as path6 from "node:path";
|
|
20945
|
-
async function checkStaleness(file2, projectRoot) {
|
|
20946
|
-
const issues = [];
|
|
20947
|
-
if (!await isGitRepo(projectRoot)) {
|
|
20948
|
-
return issues;
|
|
20949
|
-
}
|
|
20950
|
-
const relativePath = path6.relative(projectRoot, file2.filePath).replace(/\\/g, "/");
|
|
20951
|
-
const lastModified = await getFileLastModified(projectRoot, relativePath);
|
|
20952
|
-
if (!lastModified || isNaN(lastModified.getTime())) {
|
|
20953
|
-
return issues;
|
|
20954
|
-
}
|
|
20955
|
-
const daysSinceUpdate = Math.floor((Date.now() - lastModified.getTime()) / (1e3 * 60 * 60 * 24));
|
|
20956
|
-
if (daysSinceUpdate < INFO_DAYS) {
|
|
20957
|
-
return issues;
|
|
20958
|
-
}
|
|
20959
|
-
const referencedPaths = /* @__PURE__ */ new Set();
|
|
20960
|
-
const globByPrefix = /* @__PURE__ */ new Map();
|
|
20961
|
-
for (const ref of file2.references.paths) {
|
|
20962
|
-
const normalized = ref.value.replace(/\\/g, "/");
|
|
20963
|
-
if (normalized.includes("*")) {
|
|
20964
|
-
const staticSegments = [];
|
|
20965
|
-
for (const segment of normalized.split("/")) {
|
|
20966
|
-
if (segment === ".") continue;
|
|
20967
|
-
if (segment.includes("*")) break;
|
|
20968
|
-
staticSegments.push(segment);
|
|
20969
|
-
}
|
|
20970
|
-
const prefix = staticSegments.join("/");
|
|
20971
|
-
if (prefix) {
|
|
20972
|
-
const key = `${prefix}/`;
|
|
20973
|
-
referencedPaths.add(key);
|
|
20974
|
-
if (!globByPrefix.has(key)) globByPrefix.set(key, ref.value);
|
|
20975
|
-
}
|
|
20976
|
-
continue;
|
|
20977
|
-
}
|
|
20978
|
-
referencedPaths.add(ref.value);
|
|
20979
|
-
}
|
|
20980
|
-
if (referencedPaths.size === 0) {
|
|
20981
|
-
return issues;
|
|
20982
|
-
}
|
|
20983
|
-
const counts = await getCommitsSinceBatch(projectRoot, [...referencedPaths], lastModified);
|
|
20984
|
-
let totalCommits = 0;
|
|
20985
|
-
let mostActiveRef = "";
|
|
20986
|
-
let mostActiveCommits = 0;
|
|
20987
|
-
for (const [refPath, commits] of counts) {
|
|
20988
|
-
totalCommits += commits;
|
|
20989
|
-
if (commits > mostActiveCommits) {
|
|
20990
|
-
mostActiveCommits = commits;
|
|
20991
|
-
mostActiveRef = refPath;
|
|
20992
|
-
}
|
|
20993
|
-
}
|
|
20994
|
-
if (totalCommits === 0) {
|
|
20995
|
-
return issues;
|
|
20996
|
-
}
|
|
20997
|
-
const isStale = daysSinceUpdate >= WARNING_DAYS;
|
|
20998
|
-
const severity = isStale ? "warning" : "info";
|
|
20999
|
-
const mostActiveLabel = globByPrefix.get(mostActiveRef) ?? mostActiveRef;
|
|
21000
|
-
issues.push({
|
|
21001
|
-
severity,
|
|
21002
|
-
check: "staleness",
|
|
21003
|
-
ruleId: isStale ? "staleness/stale" : "staleness/aging",
|
|
21004
|
-
line: 1,
|
|
21005
|
-
message: `Last updated ${daysSinceUpdate} days ago. ${mostActiveLabel} has ${mostActiveCommits} commits since.`,
|
|
21006
|
-
suggestion: "Review and update this context file to reflect recent changes.",
|
|
21007
|
-
detail: `${totalCommits} total commits to referenced paths since last update.`
|
|
21008
|
-
});
|
|
21009
|
-
return issues;
|
|
21010
|
-
}
|
|
21011
|
-
var WARNING_DAYS, INFO_DAYS;
|
|
21012
|
-
var init_staleness = __esm({
|
|
21013
|
-
"src/core/checks/staleness.ts"() {
|
|
21014
|
-
"use strict";
|
|
21015
|
-
init_define_WEB_FIRST_SEGMENTS();
|
|
21016
|
-
init_git();
|
|
21017
|
-
WARNING_DAYS = 30;
|
|
21018
|
-
INFO_DAYS = 14;
|
|
21019
|
-
}
|
|
21020
|
-
});
|
|
21021
|
-
|
|
21022
|
-
// src/core/checks/tokens.ts
|
|
21023
|
-
function resolveTokenThresholds(overrides) {
|
|
21024
|
-
if (!overrides) return DEFAULT_TOKEN_THRESHOLDS;
|
|
21025
|
-
const merged = { ...DEFAULT_TOKEN_THRESHOLDS, ...overrides };
|
|
21026
|
-
if (merged.info >= merged.warning || merged.warning >= merged.error) {
|
|
21027
|
-
console.error(
|
|
21028
|
-
`Warning: token thresholds should satisfy info < warning < error (got ${merged.info}, ${merged.warning}, ${merged.error}) \u2014 using defaults`
|
|
21029
|
-
);
|
|
21030
|
-
return DEFAULT_TOKEN_THRESHOLDS;
|
|
21031
|
-
}
|
|
21032
|
-
return merged;
|
|
21033
|
-
}
|
|
21034
|
-
async function checkTokens(file2, _projectRoot, thresholds = DEFAULT_TOKEN_THRESHOLDS) {
|
|
21035
|
-
const issues = [];
|
|
21036
|
-
const tokens = file2.totalTokens;
|
|
21037
|
-
if (tokens >= thresholds.error) {
|
|
21038
|
-
issues.push({
|
|
21039
|
-
severity: "error",
|
|
21040
|
-
check: "tokens",
|
|
21041
|
-
ruleId: "tokens/excessive",
|
|
21042
|
-
line: 1,
|
|
21043
|
-
message: `${tokens.toLocaleString()} tokens \u2014 consumes significant context window space`,
|
|
21044
|
-
suggestion: "Consider splitting into focused sections or removing redundant content."
|
|
21045
|
-
});
|
|
21046
|
-
} else if (tokens >= thresholds.warning) {
|
|
21047
|
-
issues.push({
|
|
21048
|
-
severity: "warning",
|
|
21049
|
-
check: "tokens",
|
|
21050
|
-
ruleId: "tokens/large",
|
|
21051
|
-
line: 1,
|
|
21052
|
-
message: `${tokens.toLocaleString()} tokens \u2014 large context file`,
|
|
21053
|
-
suggestion: "Consider trimming \u2014 research shows diminishing returns past ~300 lines."
|
|
21054
|
-
});
|
|
21055
|
-
} else if (tokens >= thresholds.info) {
|
|
21056
|
-
issues.push({
|
|
21057
|
-
severity: "info",
|
|
21058
|
-
check: "tokens",
|
|
21059
|
-
ruleId: "tokens/info",
|
|
21060
|
-
line: 1,
|
|
21061
|
-
message: `Uses ~${tokens.toLocaleString()} tokens per session`
|
|
21062
|
-
});
|
|
21063
|
-
}
|
|
21064
|
-
return issues;
|
|
21065
|
-
}
|
|
21066
|
-
function checkAggregateTokens(files, thresholds = DEFAULT_TOKEN_THRESHOLDS) {
|
|
21067
|
-
const total = files.reduce((sum, f) => sum + f.tokens, 0);
|
|
21068
|
-
if (total >= thresholds.aggregate && files.length > 1) {
|
|
21069
|
-
return {
|
|
21070
|
-
severity: "warning",
|
|
21071
|
-
check: "tokens",
|
|
21072
|
-
ruleId: "tokens/aggregate",
|
|
21073
|
-
line: 0,
|
|
21074
|
-
message: `${files.length} context files consume ${total.toLocaleString()} tokens combined`,
|
|
21075
|
-
suggestion: "Consider consolidating or trimming to reduce per-session context cost."
|
|
21076
|
-
};
|
|
21077
|
-
}
|
|
21078
|
-
return null;
|
|
21079
|
-
}
|
|
21080
|
-
var DEFAULT_TOKEN_THRESHOLDS;
|
|
21081
|
-
var init_tokens2 = __esm({
|
|
21082
|
-
"src/core/checks/tokens.ts"() {
|
|
21083
|
-
"use strict";
|
|
21084
|
-
init_define_WEB_FIRST_SEGMENTS();
|
|
21085
|
-
DEFAULT_TOKEN_THRESHOLDS = {
|
|
21086
|
-
info: 1e3,
|
|
21087
|
-
warning: 3e3,
|
|
21088
|
-
error: 8e3,
|
|
21089
|
-
aggregate: 5e3,
|
|
21090
|
-
tierBreakdown: 1e3,
|
|
21091
|
-
tierAggregate: 4e3
|
|
21092
|
-
};
|
|
21093
|
-
}
|
|
21094
|
-
});
|
|
21095
|
-
|
|
21096
20686
|
// node_modules/.pnpm/jsonc-parser@3.3.1/node_modules/jsonc-parser/lib/esm/impl/scanner.js
|
|
21097
20687
|
function createScanner(text, ignoreTrivia = false) {
|
|
21098
20688
|
const len = text.length;
|
|
@@ -22170,6 +21760,494 @@ var init_main3 = __esm({
|
|
|
22170
21760
|
}
|
|
22171
21761
|
});
|
|
22172
21762
|
|
|
21763
|
+
// src/core/checks/commands.ts
|
|
21764
|
+
import * as fs4 from "node:fs";
|
|
21765
|
+
import * as path5 from "node:path";
|
|
21766
|
+
function isManagerBuiltin(manager, sub) {
|
|
21767
|
+
return PM_MANAGER_BUILTINS[manager]?.has(sub) ?? false;
|
|
21768
|
+
}
|
|
21769
|
+
function scriptNameFromMatch(match) {
|
|
21770
|
+
const [, manager, explicitRun, name] = match;
|
|
21771
|
+
if (name.startsWith("-")) return null;
|
|
21772
|
+
if (manager && !explicitRun && PM_BUILTIN_SUBCOMMANDS.has(name)) return null;
|
|
21773
|
+
if (manager && !explicitRun && isManagerBuiltin(manager, name)) return null;
|
|
21774
|
+
return name;
|
|
21775
|
+
}
|
|
21776
|
+
function extractNpxPackage(cmd) {
|
|
21777
|
+
if (!/^npx\b/.test(cmd)) return null;
|
|
21778
|
+
const tokens = cmd.split(/\s+/).slice(1);
|
|
21779
|
+
for (let i2 = 0; i2 < tokens.length; i2++) {
|
|
21780
|
+
const t2 = tokens[i2];
|
|
21781
|
+
if (t2 === "-p" || t2 === "--package") {
|
|
21782
|
+
const v2 = tokens[i2 + 1];
|
|
21783
|
+
if (v2 && !v2.startsWith("-")) return v2;
|
|
21784
|
+
continue;
|
|
21785
|
+
}
|
|
21786
|
+
if (t2.startsWith("-p=") || t2.startsWith("--package=")) {
|
|
21787
|
+
return t2.slice(t2.indexOf("=") + 1) || null;
|
|
21788
|
+
}
|
|
21789
|
+
if (t2.startsWith("-")) continue;
|
|
21790
|
+
return t2;
|
|
21791
|
+
}
|
|
21792
|
+
return null;
|
|
21793
|
+
}
|
|
21794
|
+
function wouldNeedPackageJson(cmd) {
|
|
21795
|
+
const scriptMatch = cmd.match(NPM_SCRIPT_PATTERN);
|
|
21796
|
+
if (scriptMatch && scriptNameFromMatch(scriptMatch) !== null) return true;
|
|
21797
|
+
const shorthandMatch = cmd.match(PKG_SHORTHAND_PATTERN);
|
|
21798
|
+
if (shorthandMatch && !isManagerBuiltin(shorthandMatch[1], shorthandMatch[2])) return true;
|
|
21799
|
+
return /^npx\b/.test(cmd) || PKG_DEPENDENT_TOOL_PATTERN.test(cmd);
|
|
21800
|
+
}
|
|
21801
|
+
function loadDeniedCommandPrefixes(projectRoot) {
|
|
21802
|
+
const prefixes = [];
|
|
21803
|
+
for (const rel of ["settings.json", "settings.local.json"]) {
|
|
21804
|
+
let content;
|
|
21805
|
+
try {
|
|
21806
|
+
content = stripBom(fs4.readFileSync(path5.join(projectRoot, ".claude", rel), "utf-8"));
|
|
21807
|
+
} catch {
|
|
21808
|
+
continue;
|
|
21809
|
+
}
|
|
21810
|
+
const data = parse2(content, [], { allowTrailingComma: true });
|
|
21811
|
+
const deny = data?.permissions?.deny;
|
|
21812
|
+
if (!Array.isArray(deny)) continue;
|
|
21813
|
+
for (const entry of deny) {
|
|
21814
|
+
if (typeof entry !== "string") continue;
|
|
21815
|
+
const inner = entry.replace(/^[A-Za-z]+\((.*)\)$/, "$1");
|
|
21816
|
+
const prefix = inner.replace(/:?\*+$/, "").trim();
|
|
21817
|
+
if (prefix) prefixes.push(prefix);
|
|
21818
|
+
}
|
|
21819
|
+
}
|
|
21820
|
+
return prefixes;
|
|
21821
|
+
}
|
|
21822
|
+
function isDeniedCommand(cmd, deniedPrefixes) {
|
|
21823
|
+
return deniedPrefixes.some((p2) => cmd === p2 || cmd.startsWith(`${p2} `));
|
|
21824
|
+
}
|
|
21825
|
+
async function checkCommands(file2, projectRoot) {
|
|
21826
|
+
const issues = [];
|
|
21827
|
+
const pkgJson = loadPackageJson(projectRoot);
|
|
21828
|
+
const makefile = loadMakefile(projectRoot);
|
|
21829
|
+
const deniedPrefixes = loadDeniedCommandPrefixes(projectRoot);
|
|
21830
|
+
if (!pkgJson) {
|
|
21831
|
+
const skipped = file2.references.commands.find((ref) => wouldNeedPackageJson(ref.value));
|
|
21832
|
+
if (skipped) {
|
|
21833
|
+
issues.push({
|
|
21834
|
+
severity: "info",
|
|
21835
|
+
check: "commands",
|
|
21836
|
+
ruleId: "commands/package-json-missing",
|
|
21837
|
+
line: skipped.line,
|
|
21838
|
+
message: "package.json missing or unparseable \u2014 command checks skipped",
|
|
21839
|
+
suggestion: "Add a parseable package.json at the project root so script, npx, and tool references can be validated."
|
|
21840
|
+
});
|
|
21841
|
+
}
|
|
21842
|
+
}
|
|
21843
|
+
for (const ref of file2.references.commands) {
|
|
21844
|
+
const cmd = ref.value;
|
|
21845
|
+
const scriptMatch = cmd.match(NPM_SCRIPT_PATTERN);
|
|
21846
|
+
if (scriptMatch && pkgJson) {
|
|
21847
|
+
const scriptName = scriptNameFromMatch(scriptMatch);
|
|
21848
|
+
if (scriptName && pkgJson.scripts && !(scriptName in pkgJson.scripts)) {
|
|
21849
|
+
const available = Object.keys(pkgJson.scripts).join(", ");
|
|
21850
|
+
issues.push({
|
|
21851
|
+
severity: "error",
|
|
21852
|
+
check: "commands",
|
|
21853
|
+
ruleId: "commands/script-not-found",
|
|
21854
|
+
line: ref.line,
|
|
21855
|
+
message: `"${cmd}" \u2014 script "${scriptName}" not found in package.json`,
|
|
21856
|
+
suggestion: available ? `Available scripts: ${available}` : void 0
|
|
21857
|
+
});
|
|
21858
|
+
}
|
|
21859
|
+
continue;
|
|
21860
|
+
}
|
|
21861
|
+
const shorthandMatch = cmd.match(PKG_SHORTHAND_PATTERN);
|
|
21862
|
+
if (shorthandMatch && pkgJson) {
|
|
21863
|
+
const manager = shorthandMatch[1];
|
|
21864
|
+
const scriptName = shorthandMatch[2];
|
|
21865
|
+
if (isManagerBuiltin(manager, scriptName)) continue;
|
|
21866
|
+
if (pkgJson.scripts && !(scriptName in pkgJson.scripts)) {
|
|
21867
|
+
issues.push({
|
|
21868
|
+
severity: "error",
|
|
21869
|
+
check: "commands",
|
|
21870
|
+
ruleId: "commands/script-not-found",
|
|
21871
|
+
line: ref.line,
|
|
21872
|
+
message: `"${cmd}" \u2014 script "${scriptName}" not found in package.json`
|
|
21873
|
+
});
|
|
21874
|
+
}
|
|
21875
|
+
continue;
|
|
21876
|
+
}
|
|
21877
|
+
if (/^npx\b/.test(cmd) && pkgJson) {
|
|
21878
|
+
const pkgName = extractNpxPackage(cmd);
|
|
21879
|
+
if (!pkgName) continue;
|
|
21880
|
+
const allDeps = {
|
|
21881
|
+
...pkgJson.dependencies,
|
|
21882
|
+
...pkgJson.devDependencies,
|
|
21883
|
+
...pkgJson.peerDependencies,
|
|
21884
|
+
...pkgJson.optionalDependencies
|
|
21885
|
+
};
|
|
21886
|
+
if (!(pkgName in allDeps)) {
|
|
21887
|
+
if (isDeniedCommand(cmd, deniedPrefixes)) continue;
|
|
21888
|
+
const binPath = path5.join(projectRoot, "node_modules", ".bin", pkgName);
|
|
21889
|
+
try {
|
|
21890
|
+
fs4.accessSync(binPath);
|
|
21891
|
+
} catch {
|
|
21892
|
+
issues.push({
|
|
21893
|
+
severity: "warning",
|
|
21894
|
+
check: "commands",
|
|
21895
|
+
ruleId: "commands/npx-not-in-deps",
|
|
21896
|
+
line: ref.line,
|
|
21897
|
+
message: `"${cmd}" \u2014 "${pkgName}" not found in dependencies`,
|
|
21898
|
+
suggestion: "If this is a global tool, consider adding it to devDependencies for reproducibility"
|
|
21899
|
+
});
|
|
21900
|
+
}
|
|
21901
|
+
}
|
|
21902
|
+
continue;
|
|
21903
|
+
}
|
|
21904
|
+
if (MAKE_PATTERN.test(cmd)) {
|
|
21905
|
+
if (!makefile) {
|
|
21906
|
+
issues.push({
|
|
21907
|
+
severity: "error",
|
|
21908
|
+
check: "commands",
|
|
21909
|
+
ruleId: "commands/no-makefile",
|
|
21910
|
+
line: ref.line,
|
|
21911
|
+
message: `"${cmd}" \u2014 no Makefile found in project`
|
|
21912
|
+
});
|
|
21913
|
+
} else {
|
|
21914
|
+
const target = extractMakeTarget(cmd);
|
|
21915
|
+
if (target && !hasMakeTarget(makefile, target)) {
|
|
21916
|
+
issues.push({
|
|
21917
|
+
severity: "error",
|
|
21918
|
+
check: "commands",
|
|
21919
|
+
ruleId: "commands/make-target-not-found",
|
|
21920
|
+
line: ref.line,
|
|
21921
|
+
message: `"${cmd}" \u2014 target "${target}" not found in Makefile`
|
|
21922
|
+
});
|
|
21923
|
+
}
|
|
21924
|
+
}
|
|
21925
|
+
continue;
|
|
21926
|
+
}
|
|
21927
|
+
const toolMatch = cmd.match(PKG_DEPENDENT_TOOL_PATTERN);
|
|
21928
|
+
if (toolMatch && pkgJson) {
|
|
21929
|
+
const tool = toolMatch[1];
|
|
21930
|
+
const pkgName = BIN_TO_PACKAGE[tool] ?? tool;
|
|
21931
|
+
const allDeps = {
|
|
21932
|
+
...pkgJson.dependencies,
|
|
21933
|
+
...pkgJson.devDependencies,
|
|
21934
|
+
...pkgJson.peerDependencies,
|
|
21935
|
+
...pkgJson.optionalDependencies
|
|
21936
|
+
};
|
|
21937
|
+
if (!(pkgName in allDeps)) {
|
|
21938
|
+
const binPath = path5.join(projectRoot, "node_modules", ".bin", tool);
|
|
21939
|
+
try {
|
|
21940
|
+
fs4.accessSync(binPath);
|
|
21941
|
+
} catch {
|
|
21942
|
+
issues.push({
|
|
21943
|
+
severity: "warning",
|
|
21944
|
+
check: "commands",
|
|
21945
|
+
ruleId: "commands/tool-not-found",
|
|
21946
|
+
line: ref.line,
|
|
21947
|
+
message: `"${cmd}" \u2014 "${tool}" not found in dependencies or node_modules/.bin`
|
|
21948
|
+
});
|
|
21949
|
+
}
|
|
21950
|
+
}
|
|
21951
|
+
}
|
|
21952
|
+
}
|
|
21953
|
+
return issues;
|
|
21954
|
+
}
|
|
21955
|
+
function loadMakefile(projectRoot) {
|
|
21956
|
+
try {
|
|
21957
|
+
return stripBom(fs4.readFileSync(path5.join(projectRoot, "Makefile"), "utf-8"));
|
|
21958
|
+
} catch {
|
|
21959
|
+
return null;
|
|
21960
|
+
}
|
|
21961
|
+
}
|
|
21962
|
+
function extractMakeTarget(cmd) {
|
|
21963
|
+
for (const token of cmd.split(/\s+/).slice(1)) {
|
|
21964
|
+
if (token.startsWith("-")) return null;
|
|
21965
|
+
if (token.includes("=")) continue;
|
|
21966
|
+
return token;
|
|
21967
|
+
}
|
|
21968
|
+
return null;
|
|
21969
|
+
}
|
|
21970
|
+
function hasMakeTarget(makefile, target) {
|
|
21971
|
+
const escaped = target.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
21972
|
+
const pattern = new RegExp(`^${escaped}\\s*:(?!:?=)`, "m");
|
|
21973
|
+
return pattern.test(makefile);
|
|
21974
|
+
}
|
|
21975
|
+
var NPM_SCRIPT_PATTERN, MAKE_PATTERN, PM_BUILTIN_SUBCOMMANDS, PM_MANAGER_BUILTINS, PKG_DEPENDENT_TOOL_PATTERN, BIN_TO_PACKAGE, PKG_SHORTHAND_PATTERN;
|
|
21976
|
+
var init_commands = __esm({
|
|
21977
|
+
"src/core/checks/commands.ts"() {
|
|
21978
|
+
"use strict";
|
|
21979
|
+
init_define_WEB_FIRST_SEGMENTS();
|
|
21980
|
+
init_main3();
|
|
21981
|
+
init_fs();
|
|
21982
|
+
NPM_SCRIPT_PATTERN = /^(?:npm\s+run|(pnpm|yarn|bun)(?:\s+(run))?)\s+(\S+)/;
|
|
21983
|
+
MAKE_PATTERN = /^make\s+\S/;
|
|
21984
|
+
PM_BUILTIN_SUBCOMMANDS = /* @__PURE__ */ new Set([
|
|
21985
|
+
"add",
|
|
21986
|
+
"approve-builds",
|
|
21987
|
+
"audit",
|
|
21988
|
+
"bin",
|
|
21989
|
+
"cache",
|
|
21990
|
+
"ci",
|
|
21991
|
+
"config",
|
|
21992
|
+
"create",
|
|
21993
|
+
"dedupe",
|
|
21994
|
+
"dlx",
|
|
21995
|
+
"doctor",
|
|
21996
|
+
"env",
|
|
21997
|
+
"exec",
|
|
21998
|
+
"fetch",
|
|
21999
|
+
"global",
|
|
22000
|
+
"help",
|
|
22001
|
+
"i",
|
|
22002
|
+
"import",
|
|
22003
|
+
"info",
|
|
22004
|
+
"init",
|
|
22005
|
+
"install",
|
|
22006
|
+
"licenses",
|
|
22007
|
+
"link",
|
|
22008
|
+
"list",
|
|
22009
|
+
"login",
|
|
22010
|
+
"logout",
|
|
22011
|
+
"ls",
|
|
22012
|
+
"node",
|
|
22013
|
+
"npm",
|
|
22014
|
+
"outdated",
|
|
22015
|
+
"pack",
|
|
22016
|
+
"patch",
|
|
22017
|
+
"patch-commit",
|
|
22018
|
+
"prune",
|
|
22019
|
+
"publish",
|
|
22020
|
+
"rebuild",
|
|
22021
|
+
"remove",
|
|
22022
|
+
"rm",
|
|
22023
|
+
"root",
|
|
22024
|
+
"run",
|
|
22025
|
+
"self-update",
|
|
22026
|
+
"set",
|
|
22027
|
+
"setup",
|
|
22028
|
+
"store",
|
|
22029
|
+
"team",
|
|
22030
|
+
"un",
|
|
22031
|
+
"uninstall",
|
|
22032
|
+
"unlink",
|
|
22033
|
+
"up",
|
|
22034
|
+
"update",
|
|
22035
|
+
"upgrade",
|
|
22036
|
+
"version",
|
|
22037
|
+
"whoami",
|
|
22038
|
+
"why",
|
|
22039
|
+
"workspace",
|
|
22040
|
+
"workspaces",
|
|
22041
|
+
"x"
|
|
22042
|
+
]);
|
|
22043
|
+
PM_MANAGER_BUILTINS = {
|
|
22044
|
+
bun: /* @__PURE__ */ new Set(["test"])
|
|
22045
|
+
};
|
|
22046
|
+
PKG_DEPENDENT_TOOL_PATTERN = /^(vitest|jest|pytest|mocha|eslint|prettier|tsc)\b/;
|
|
22047
|
+
BIN_TO_PACKAGE = {
|
|
22048
|
+
tsc: "typescript"
|
|
22049
|
+
};
|
|
22050
|
+
PKG_SHORTHAND_PATTERN = /^(npm|pnpm|yarn|bun)\s+(test|start|build|dev|lint|format|check|typecheck|clean|serve|preview|e2e)\b/;
|
|
22051
|
+
}
|
|
22052
|
+
});
|
|
22053
|
+
|
|
22054
|
+
// src/core/checks/staleness.ts
|
|
22055
|
+
import * as path6 from "node:path";
|
|
22056
|
+
async function checkStaleness(file2, projectRoot) {
|
|
22057
|
+
const issues = [];
|
|
22058
|
+
if (!await isGitRepo(projectRoot)) {
|
|
22059
|
+
return issues;
|
|
22060
|
+
}
|
|
22061
|
+
const relativePath = path6.relative(projectRoot, file2.filePath).replace(/\\/g, "/");
|
|
22062
|
+
const lastModified = await getFileLastModified(projectRoot, relativePath);
|
|
22063
|
+
if (!lastModified || isNaN(lastModified.getTime())) {
|
|
22064
|
+
return issues;
|
|
22065
|
+
}
|
|
22066
|
+
const daysSinceUpdate = Math.floor((Date.now() - lastModified.getTime()) / (1e3 * 60 * 60 * 24));
|
|
22067
|
+
if (daysSinceUpdate < INFO_DAYS) {
|
|
22068
|
+
return issues;
|
|
22069
|
+
}
|
|
22070
|
+
const referencedPaths = /* @__PURE__ */ new Set();
|
|
22071
|
+
const globByPrefix = /* @__PURE__ */ new Map();
|
|
22072
|
+
for (const ref of file2.references.paths) {
|
|
22073
|
+
const normalized = ref.value.replace(/\\/g, "/");
|
|
22074
|
+
if (normalized.includes("*")) {
|
|
22075
|
+
const staticSegments = [];
|
|
22076
|
+
for (const segment of normalized.split("/")) {
|
|
22077
|
+
if (segment === ".") continue;
|
|
22078
|
+
if (segment.includes("*")) break;
|
|
22079
|
+
staticSegments.push(segment);
|
|
22080
|
+
}
|
|
22081
|
+
const prefix = staticSegments.join("/");
|
|
22082
|
+
if (prefix) {
|
|
22083
|
+
const key = `${prefix}/`;
|
|
22084
|
+
referencedPaths.add(key);
|
|
22085
|
+
if (!globByPrefix.has(key)) globByPrefix.set(key, ref.value);
|
|
22086
|
+
}
|
|
22087
|
+
continue;
|
|
22088
|
+
}
|
|
22089
|
+
referencedPaths.add(ref.value);
|
|
22090
|
+
}
|
|
22091
|
+
if (referencedPaths.size === 0) {
|
|
22092
|
+
return issues;
|
|
22093
|
+
}
|
|
22094
|
+
const counts = await getCommitsSinceBatch(projectRoot, [...referencedPaths], lastModified);
|
|
22095
|
+
let totalCommits = 0;
|
|
22096
|
+
let mostActiveRef = "";
|
|
22097
|
+
let mostActiveCommits = 0;
|
|
22098
|
+
for (const [refPath, commits] of counts) {
|
|
22099
|
+
totalCommits += commits;
|
|
22100
|
+
if (commits > mostActiveCommits) {
|
|
22101
|
+
mostActiveCommits = commits;
|
|
22102
|
+
mostActiveRef = refPath;
|
|
22103
|
+
}
|
|
22104
|
+
}
|
|
22105
|
+
if (totalCommits === 0) {
|
|
22106
|
+
return issues;
|
|
22107
|
+
}
|
|
22108
|
+
const isStale = daysSinceUpdate >= WARNING_DAYS;
|
|
22109
|
+
const severity = isStale ? "warning" : "info";
|
|
22110
|
+
const mostActiveLabel = globByPrefix.get(mostActiveRef) ?? mostActiveRef;
|
|
22111
|
+
issues.push({
|
|
22112
|
+
severity,
|
|
22113
|
+
check: "staleness",
|
|
22114
|
+
ruleId: isStale ? "staleness/stale" : "staleness/aging",
|
|
22115
|
+
line: 1,
|
|
22116
|
+
message: `Last updated ${daysSinceUpdate} days ago. ${mostActiveLabel} has ${mostActiveCommits} commits since.`,
|
|
22117
|
+
suggestion: "Review and update this context file to reflect recent changes.",
|
|
22118
|
+
detail: `${totalCommits} total commits to referenced paths since last update.`
|
|
22119
|
+
});
|
|
22120
|
+
return issues;
|
|
22121
|
+
}
|
|
22122
|
+
var WARNING_DAYS, INFO_DAYS;
|
|
22123
|
+
var init_staleness = __esm({
|
|
22124
|
+
"src/core/checks/staleness.ts"() {
|
|
22125
|
+
"use strict";
|
|
22126
|
+
init_define_WEB_FIRST_SEGMENTS();
|
|
22127
|
+
init_git();
|
|
22128
|
+
WARNING_DAYS = 30;
|
|
22129
|
+
INFO_DAYS = 14;
|
|
22130
|
+
}
|
|
22131
|
+
});
|
|
22132
|
+
|
|
22133
|
+
// src/core/suppressions.ts
|
|
22134
|
+
function collectSuppressions(content) {
|
|
22135
|
+
const suppressions = /* @__PURE__ */ new Map();
|
|
22136
|
+
const lines = content.split("\n");
|
|
22137
|
+
const add = (lineNumber, checks) => {
|
|
22138
|
+
const existing = suppressions.get(lineNumber);
|
|
22139
|
+
if (existing === null) return;
|
|
22140
|
+
if (checks.length === 0) {
|
|
22141
|
+
suppressions.set(lineNumber, null);
|
|
22142
|
+
return;
|
|
22143
|
+
}
|
|
22144
|
+
const merged = existing ?? /* @__PURE__ */ new Set();
|
|
22145
|
+
for (const check2 of checks) merged.add(check2);
|
|
22146
|
+
suppressions.set(lineNumber, merged);
|
|
22147
|
+
};
|
|
22148
|
+
for (let i2 = 0; i2 < lines.length; i2++) {
|
|
22149
|
+
const line = lines[i2];
|
|
22150
|
+
if (!line.includes("ctxlint-ignore-")) continue;
|
|
22151
|
+
DIRECTIVE_RE.lastIndex = 0;
|
|
22152
|
+
let match;
|
|
22153
|
+
while ((match = DIRECTIVE_RE.exec(line)) !== null) {
|
|
22154
|
+
const kind = match[1];
|
|
22155
|
+
const checks = (match[2] ?? "").trim().split(/\s+/).filter(Boolean);
|
|
22156
|
+
add(kind === "next-line" ? i2 + 2 : i2 + 1, checks);
|
|
22157
|
+
}
|
|
22158
|
+
}
|
|
22159
|
+
return suppressions;
|
|
22160
|
+
}
|
|
22161
|
+
function isSuppressed(suppressions, issue2) {
|
|
22162
|
+
if (suppressions.size === 0) return false;
|
|
22163
|
+
if (!suppressions.has(issue2.line)) return false;
|
|
22164
|
+
const checks = suppressions.get(issue2.line);
|
|
22165
|
+
if (checks === null) return true;
|
|
22166
|
+
return checks.has(issue2.check);
|
|
22167
|
+
}
|
|
22168
|
+
var DIRECTIVE_RE;
|
|
22169
|
+
var init_suppressions = __esm({
|
|
22170
|
+
"src/core/suppressions.ts"() {
|
|
22171
|
+
"use strict";
|
|
22172
|
+
init_define_WEB_FIRST_SEGMENTS();
|
|
22173
|
+
DIRECTIVE_RE = /<!--\s*ctxlint-ignore-(next-line|line)((?:\s+[\w-]+)*)\s*-->/g;
|
|
22174
|
+
}
|
|
22175
|
+
});
|
|
22176
|
+
|
|
22177
|
+
// src/core/checks/tokens.ts
|
|
22178
|
+
function resolveTokenThresholds(overrides) {
|
|
22179
|
+
if (!overrides) return DEFAULT_TOKEN_THRESHOLDS;
|
|
22180
|
+
const merged = { ...DEFAULT_TOKEN_THRESHOLDS, ...overrides };
|
|
22181
|
+
if (merged.info >= merged.warning || merged.warning >= merged.error) {
|
|
22182
|
+
console.error(
|
|
22183
|
+
`Warning: token thresholds should satisfy info < warning < error (got ${merged.info}, ${merged.warning}, ${merged.error}) \u2014 using defaults`
|
|
22184
|
+
);
|
|
22185
|
+
return DEFAULT_TOKEN_THRESHOLDS;
|
|
22186
|
+
}
|
|
22187
|
+
return merged;
|
|
22188
|
+
}
|
|
22189
|
+
async function checkTokens(file2, _projectRoot, thresholds = DEFAULT_TOKEN_THRESHOLDS) {
|
|
22190
|
+
const issues = [];
|
|
22191
|
+
const tokens = file2.totalTokens;
|
|
22192
|
+
if (tokens >= thresholds.error) {
|
|
22193
|
+
issues.push({
|
|
22194
|
+
severity: "error",
|
|
22195
|
+
check: "tokens",
|
|
22196
|
+
ruleId: "tokens/excessive",
|
|
22197
|
+
line: 1,
|
|
22198
|
+
message: `${tokens.toLocaleString()} tokens \u2014 consumes significant context window space`,
|
|
22199
|
+
suggestion: "Consider splitting into focused sections or removing redundant content."
|
|
22200
|
+
});
|
|
22201
|
+
} else if (tokens >= thresholds.warning) {
|
|
22202
|
+
issues.push({
|
|
22203
|
+
severity: "warning",
|
|
22204
|
+
check: "tokens",
|
|
22205
|
+
ruleId: "tokens/large",
|
|
22206
|
+
line: 1,
|
|
22207
|
+
message: `${tokens.toLocaleString()} tokens \u2014 large context file`,
|
|
22208
|
+
suggestion: "Consider trimming \u2014 research shows diminishing returns past ~300 lines."
|
|
22209
|
+
});
|
|
22210
|
+
} else if (tokens >= thresholds.info) {
|
|
22211
|
+
issues.push({
|
|
22212
|
+
severity: "info",
|
|
22213
|
+
check: "tokens",
|
|
22214
|
+
ruleId: "tokens/info",
|
|
22215
|
+
line: 1,
|
|
22216
|
+
message: `Uses ~${tokens.toLocaleString()} tokens per session`
|
|
22217
|
+
});
|
|
22218
|
+
}
|
|
22219
|
+
return issues;
|
|
22220
|
+
}
|
|
22221
|
+
function checkAggregateTokens(files, thresholds = DEFAULT_TOKEN_THRESHOLDS) {
|
|
22222
|
+
const total = files.reduce((sum, f) => sum + f.tokens, 0);
|
|
22223
|
+
if (total >= thresholds.aggregate && files.length > 1) {
|
|
22224
|
+
return {
|
|
22225
|
+
severity: "warning",
|
|
22226
|
+
check: "tokens",
|
|
22227
|
+
ruleId: "tokens/aggregate",
|
|
22228
|
+
line: 0,
|
|
22229
|
+
message: `${files.length} context files consume ${total.toLocaleString()} tokens combined`,
|
|
22230
|
+
suggestion: "Consider consolidating or trimming to reduce per-session context cost."
|
|
22231
|
+
};
|
|
22232
|
+
}
|
|
22233
|
+
return null;
|
|
22234
|
+
}
|
|
22235
|
+
var DEFAULT_TOKEN_THRESHOLDS;
|
|
22236
|
+
var init_tokens2 = __esm({
|
|
22237
|
+
"src/core/checks/tokens.ts"() {
|
|
22238
|
+
"use strict";
|
|
22239
|
+
init_define_WEB_FIRST_SEGMENTS();
|
|
22240
|
+
DEFAULT_TOKEN_THRESHOLDS = {
|
|
22241
|
+
info: 1e3,
|
|
22242
|
+
warning: 3e3,
|
|
22243
|
+
error: 8e3,
|
|
22244
|
+
aggregate: 5e3,
|
|
22245
|
+
tierBreakdown: 1e3,
|
|
22246
|
+
tierAggregate: 4e3
|
|
22247
|
+
};
|
|
22248
|
+
}
|
|
22249
|
+
});
|
|
22250
|
+
|
|
22173
22251
|
// src/core/checks/tier-tokens.ts
|
|
22174
22252
|
import * as fs5 from "node:fs";
|
|
22175
22253
|
import * as os2 from "node:os";
|
|
@@ -24047,6 +24125,11 @@ function loadWebFirstSegments() {
|
|
|
24047
24125
|
function encodeProjectDir(fsPath) {
|
|
24048
24126
|
return fsPath.replace(/[:\\/\.]/g, "-");
|
|
24049
24127
|
}
|
|
24128
|
+
function projectDirCandidates(fsPath) {
|
|
24129
|
+
const encoded = encodeProjectDir(fsPath);
|
|
24130
|
+
const folded = encoded.replace(/_/g, "-");
|
|
24131
|
+
return folded === encoded ? [encoded] : [encoded, folded];
|
|
24132
|
+
}
|
|
24050
24133
|
function projectDirMatchesPath(encodedDir, fsPath) {
|
|
24051
24134
|
const normalized = fsPath.replace(/\\/g, "/");
|
|
24052
24135
|
return encodedDir === encodeProjectDir(normalized);
|
|
@@ -24933,13 +25016,19 @@ import { join as join8 } from "node:path";
|
|
|
24933
25016
|
async function checkMemoryIndexOverflow(ctx) {
|
|
24934
25017
|
const home2 = process.env.HOME || process.env.USERPROFILE || homedir5();
|
|
24935
25018
|
if (!home2) return [];
|
|
24936
|
-
|
|
24937
|
-
|
|
24938
|
-
|
|
24939
|
-
|
|
24940
|
-
|
|
24941
|
-
|
|
24942
|
-
|
|
25019
|
+
let content = "";
|
|
25020
|
+
let memoryFile = "";
|
|
25021
|
+
for (const encoded of projectDirCandidates(ctx.currentProject)) {
|
|
25022
|
+
const candidate = join8(home2, ".claude", "projects", encoded, "memory", "MEMORY.md");
|
|
25023
|
+
try {
|
|
25024
|
+
content = stripBom(await readFile3(candidate, "utf-8"));
|
|
25025
|
+
} catch {
|
|
25026
|
+
continue;
|
|
25027
|
+
}
|
|
25028
|
+
if (content) {
|
|
25029
|
+
memoryFile = candidate;
|
|
25030
|
+
break;
|
|
25031
|
+
}
|
|
24943
25032
|
}
|
|
24944
25033
|
if (!content) return [];
|
|
24945
25034
|
const lines = content.split("\n");
|
|
@@ -24984,16 +25073,429 @@ var init_memory_index_overflow = __esm({
|
|
|
24984
25073
|
}
|
|
24985
25074
|
});
|
|
24986
25075
|
|
|
24987
|
-
// src/core/
|
|
24988
|
-
import {
|
|
25076
|
+
// src/core/transcript.ts
|
|
25077
|
+
import { createReadStream as createReadStream2, existsSync as existsSync5, statSync as statSync4 } from "node:fs";
|
|
25078
|
+
import { readdir as readdir3 } from "node:fs/promises";
|
|
25079
|
+
import { homedir as homedir6 } from "node:os";
|
|
24989
25080
|
import { join as join9 } from "node:path";
|
|
24990
|
-
import {
|
|
25081
|
+
import { createInterface as createInterface2 } from "node:readline";
|
|
25082
|
+
function asString(v2) {
|
|
25083
|
+
return typeof v2 === "string" ? v2 : "";
|
|
25084
|
+
}
|
|
25085
|
+
function resultText(content) {
|
|
25086
|
+
if (typeof content === "string") return content;
|
|
25087
|
+
if (!Array.isArray(content)) return "";
|
|
25088
|
+
return content.map((b2) => b2 && typeof b2 === "object" ? asString(b2.text) : "").join("");
|
|
25089
|
+
}
|
|
25090
|
+
function resolveHome() {
|
|
25091
|
+
return process.env.HOME || process.env.USERPROFILE || homedir6();
|
|
25092
|
+
}
|
|
25093
|
+
function readProjectTranscript(project, home2 = resolveHome()) {
|
|
25094
|
+
const key = `${home2} ${project}`;
|
|
25095
|
+
let hit = cache.get(key);
|
|
25096
|
+
if (!hit) {
|
|
25097
|
+
hit = readUncached(project, home2);
|
|
25098
|
+
cache.set(key, hit);
|
|
25099
|
+
}
|
|
25100
|
+
return hit;
|
|
25101
|
+
}
|
|
25102
|
+
function candidateDirs(project, home2) {
|
|
25103
|
+
const root = join9(home2, ".claude", "projects");
|
|
25104
|
+
return projectDirCandidates(project).map((n7) => join9(root, n7)).filter((d) => existsSync5(d));
|
|
25105
|
+
}
|
|
25106
|
+
async function readUncached(project, home2) {
|
|
25107
|
+
if (!home2 || !project) return EMPTY;
|
|
25108
|
+
const dirs = candidateDirs(project, home2);
|
|
25109
|
+
if (dirs.length === 0) return EMPTY;
|
|
25110
|
+
const names = [];
|
|
25111
|
+
for (const dir of dirs) {
|
|
25112
|
+
for (const name of await readdir3(dir).catch(() => [])) {
|
|
25113
|
+
names.push({ dir, name });
|
|
25114
|
+
}
|
|
25115
|
+
}
|
|
25116
|
+
const files = names.filter(({ name }) => name.endsWith(".jsonl")).map(({ dir, name }) => {
|
|
25117
|
+
const p2 = join9(dir, name);
|
|
25118
|
+
try {
|
|
25119
|
+
return { p: p2, mtime: statSync4(p2).mtimeMs };
|
|
25120
|
+
} catch {
|
|
25121
|
+
return null;
|
|
25122
|
+
}
|
|
25123
|
+
}).filter((f) => f !== null).sort((a, b2) => b2.mtime - a.mtime);
|
|
25124
|
+
let truncated = files.length > MAX_TRANSCRIPTS;
|
|
25125
|
+
const selected = files.slice(0, MAX_TRANSCRIPTS);
|
|
25126
|
+
const events = [];
|
|
25127
|
+
const pending = /* @__PURE__ */ new Map();
|
|
25128
|
+
let lines = 0;
|
|
25129
|
+
for (const { p: p2 } of selected) {
|
|
25130
|
+
if (lines >= MAX_LINES2) {
|
|
25131
|
+
truncated = true;
|
|
25132
|
+
break;
|
|
25133
|
+
}
|
|
25134
|
+
const rl = createInterface2({
|
|
25135
|
+
input: createReadStream2(p2, { encoding: "utf-8" }),
|
|
25136
|
+
crlfDelay: Infinity
|
|
25137
|
+
});
|
|
25138
|
+
try {
|
|
25139
|
+
for await (const raw of rl) {
|
|
25140
|
+
if (++lines >= MAX_LINES2) {
|
|
25141
|
+
truncated = true;
|
|
25142
|
+
break;
|
|
25143
|
+
}
|
|
25144
|
+
const line = raw.trim();
|
|
25145
|
+
if (!line) continue;
|
|
25146
|
+
let rec;
|
|
25147
|
+
try {
|
|
25148
|
+
rec = JSON.parse(line);
|
|
25149
|
+
} catch {
|
|
25150
|
+
continue;
|
|
25151
|
+
}
|
|
25152
|
+
collect(rec, events, pending);
|
|
25153
|
+
}
|
|
25154
|
+
} catch {
|
|
25155
|
+
} finally {
|
|
25156
|
+
rl.close();
|
|
25157
|
+
}
|
|
25158
|
+
}
|
|
25159
|
+
return { events, filesRead: selected.length, truncated };
|
|
25160
|
+
}
|
|
25161
|
+
function collect(rec, events, pending) {
|
|
25162
|
+
const message = rec.message;
|
|
25163
|
+
const content = message?.content;
|
|
25164
|
+
if (!Array.isArray(content)) return;
|
|
25165
|
+
const timestamp = Date.parse(asString(rec.timestamp)) || 0;
|
|
25166
|
+
const sessionId = asString(rec.sessionId) || asString(rec.session_id);
|
|
25167
|
+
const gitBranch = asString(rec.gitBranch) || void 0;
|
|
25168
|
+
for (const raw of content) {
|
|
25169
|
+
if (!raw || typeof raw !== "object") continue;
|
|
25170
|
+
const block = raw;
|
|
25171
|
+
const type = asString(block.type);
|
|
25172
|
+
if (type === "text" && rec.type === "assistant") {
|
|
25173
|
+
const text = asString(block.text);
|
|
25174
|
+
if (text) {
|
|
25175
|
+
events.push({ kind: "assistant-text", text, tool: "", gitBranch, timestamp, sessionId });
|
|
25176
|
+
}
|
|
25177
|
+
continue;
|
|
25178
|
+
}
|
|
25179
|
+
if (type === "tool_use") {
|
|
25180
|
+
const tool = asString(block.name);
|
|
25181
|
+
const input = block.input ?? {};
|
|
25182
|
+
const cmdField = COMMAND_TOOLS[tool];
|
|
25183
|
+
const writeField = WRITE_TOOLS[tool];
|
|
25184
|
+
let ev = null;
|
|
25185
|
+
if (cmdField) {
|
|
25186
|
+
const text = asString(input[cmdField]);
|
|
25187
|
+
if (text) ev = { kind: "command", text, tool, gitBranch, timestamp, sessionId };
|
|
25188
|
+
} else if (writeField) {
|
|
25189
|
+
const text = asString(input[writeField]);
|
|
25190
|
+
if (text) ev = { kind: "file-write", text, tool, gitBranch, timestamp, sessionId };
|
|
25191
|
+
}
|
|
25192
|
+
if (ev) {
|
|
25193
|
+
events.push(ev);
|
|
25194
|
+
const id = asString(block.id);
|
|
25195
|
+
if (id) pending.set(id, ev);
|
|
25196
|
+
}
|
|
25197
|
+
continue;
|
|
25198
|
+
}
|
|
25199
|
+
if (type === "tool_result") {
|
|
25200
|
+
const id = asString(block.tool_use_id);
|
|
25201
|
+
const ev = id ? pending.get(id) : void 0;
|
|
25202
|
+
if (!ev) continue;
|
|
25203
|
+
pending.delete(id);
|
|
25204
|
+
ev.isError = block.is_error === true;
|
|
25205
|
+
ev.emptyOutput = resultText(block.content).trim().length === 0;
|
|
25206
|
+
}
|
|
25207
|
+
}
|
|
25208
|
+
}
|
|
25209
|
+
var WRITE_TOOLS, COMMAND_TOOLS, MAX_TRANSCRIPTS, MAX_LINES2, EMPTY, cache;
|
|
25210
|
+
var init_transcript = __esm({
|
|
25211
|
+
"src/core/transcript.ts"() {
|
|
25212
|
+
"use strict";
|
|
25213
|
+
init_define_WEB_FIRST_SEGMENTS();
|
|
25214
|
+
init_session_parser();
|
|
25215
|
+
WRITE_TOOLS = {
|
|
25216
|
+
Write: "file_path",
|
|
25217
|
+
Edit: "file_path",
|
|
25218
|
+
NotebookEdit: "notebook_path"
|
|
25219
|
+
};
|
|
25220
|
+
COMMAND_TOOLS = {
|
|
25221
|
+
Bash: "command",
|
|
25222
|
+
PowerShell: "command"
|
|
25223
|
+
};
|
|
25224
|
+
MAX_TRANSCRIPTS = 5;
|
|
25225
|
+
MAX_LINES2 = 2e5;
|
|
25226
|
+
EMPTY = { events: [], filesRead: 0, truncated: false };
|
|
25227
|
+
cache = /* @__PURE__ */ new Map();
|
|
25228
|
+
}
|
|
25229
|
+
});
|
|
25230
|
+
|
|
25231
|
+
// src/core/checks/session/shared-temp-path.ts
|
|
25232
|
+
function unquote(raw) {
|
|
25233
|
+
let s = raw;
|
|
25234
|
+
let prev;
|
|
25235
|
+
do {
|
|
25236
|
+
prev = s;
|
|
25237
|
+
s = s.replace(/^['"`]|['"`]$/g, "");
|
|
25238
|
+
} while (s !== prev);
|
|
25239
|
+
return s;
|
|
25240
|
+
}
|
|
25241
|
+
function normalize3(raw) {
|
|
25242
|
+
return unquote(raw).replace(/\\/g, "/").toLowerCase();
|
|
25243
|
+
}
|
|
25244
|
+
function isFixedTempPath(candidate) {
|
|
25245
|
+
const p2 = normalize3(candidate);
|
|
25246
|
+
const root = TEMP_ROOTS.map((r2) => normalize3(r2)).find((r2) => p2.startsWith(r2));
|
|
25247
|
+
if (!root) return false;
|
|
25248
|
+
if (p2.length <= root.length) return false;
|
|
25249
|
+
return !SCOPED_MARKERS.some((m) => p2.includes(m.toLowerCase()));
|
|
25250
|
+
}
|
|
25251
|
+
function extract(line, patterns) {
|
|
25252
|
+
const found = [];
|
|
25253
|
+
for (const re2 of patterns) {
|
|
25254
|
+
for (const m of line.matchAll(new RegExp(re2, "g"))) {
|
|
25255
|
+
if (m[1]) found.push(m[1]);
|
|
25256
|
+
}
|
|
25257
|
+
}
|
|
25258
|
+
return found;
|
|
25259
|
+
}
|
|
25260
|
+
async function checkSharedTempPath(ctx) {
|
|
25261
|
+
const issues = [];
|
|
25262
|
+
const written = /* @__PURE__ */ new Map();
|
|
25263
|
+
const reported = /* @__PURE__ */ new Set();
|
|
25264
|
+
const transcript = await readProjectTranscript(ctx.currentProject);
|
|
25265
|
+
const scanned = [
|
|
25266
|
+
...transcript.events.filter((e) => e.kind === "command").map((e) => ({ display: e.text, timestamp: e.timestamp })),
|
|
25267
|
+
...ctx.history.map((h2) => ({ display: h2.display, timestamp: h2.timestamp }))
|
|
25268
|
+
];
|
|
25269
|
+
const ordered = scanned.sort((a, b2) => a.timestamp - b2.timestamp);
|
|
25270
|
+
for (const entry of ordered) {
|
|
25271
|
+
const line = entry.display;
|
|
25272
|
+
const usesMktemp = /\bmktemp\b/.test(line);
|
|
25273
|
+
for (const candidate of extract(line, READ_PATTERNS)) {
|
|
25274
|
+
if (usesMktemp || !isFixedTempPath(candidate)) continue;
|
|
25275
|
+
const key = normalize3(candidate);
|
|
25276
|
+
const origin = written.get(key);
|
|
25277
|
+
if (!origin || reported.has(key)) continue;
|
|
25278
|
+
reported.add(key);
|
|
25279
|
+
issues.push({
|
|
25280
|
+
severity: "error",
|
|
25281
|
+
check: "session-shared-temp-path",
|
|
25282
|
+
ruleId: "session-shared-temp-path/shared-temp-path",
|
|
25283
|
+
line: 0,
|
|
25284
|
+
message: `Fixed temp path "${unquote(candidate)}" is written and later read back`,
|
|
25285
|
+
detail: `Written by: ${origin.display.trim().slice(0, 120)}
|
|
25286
|
+
Read by: ${line.trim().slice(0, 120)}`,
|
|
25287
|
+
suggestion: "Use a per-run path instead: `T=$(mktemp)` in shell, or a session-scoped scratch directory. A literal path under a shared temp root can be overwritten by any concurrent session between the write and the read, and a restore then puts the wrong bytes into your workspace."
|
|
25288
|
+
});
|
|
25289
|
+
}
|
|
25290
|
+
for (const candidate of extract(line, WRITE_PATTERNS)) {
|
|
25291
|
+
if (usesMktemp || !isFixedTempPath(candidate)) continue;
|
|
25292
|
+
written.set(normalize3(candidate), entry);
|
|
25293
|
+
}
|
|
25294
|
+
}
|
|
25295
|
+
return issues;
|
|
25296
|
+
}
|
|
25297
|
+
var TEMP_ROOTS, SCOPED_MARKERS, WRITE_PATTERNS, READ_PATTERNS;
|
|
25298
|
+
var init_shared_temp_path = __esm({
|
|
25299
|
+
"src/core/checks/session/shared-temp-path.ts"() {
|
|
25300
|
+
"use strict";
|
|
25301
|
+
init_define_WEB_FIRST_SEGMENTS();
|
|
25302
|
+
init_transcript();
|
|
25303
|
+
TEMP_ROOTS = [
|
|
25304
|
+
"/tmp/",
|
|
25305
|
+
"/var/tmp/",
|
|
25306
|
+
"$TMPDIR/",
|
|
25307
|
+
"${TMPDIR}/",
|
|
25308
|
+
"%TEMP%\\",
|
|
25309
|
+
"%TMP%\\"
|
|
25310
|
+
];
|
|
25311
|
+
SCOPED_MARKERS = [
|
|
25312
|
+
"$$",
|
|
25313
|
+
"$pid",
|
|
25314
|
+
"${pid}",
|
|
25315
|
+
"mktemp",
|
|
25316
|
+
"$random",
|
|
25317
|
+
"${random}",
|
|
25318
|
+
"$session",
|
|
25319
|
+
"${session}",
|
|
25320
|
+
"$uuid",
|
|
25321
|
+
"${uuid}"
|
|
25322
|
+
];
|
|
25323
|
+
WRITE_PATTERNS = [
|
|
25324
|
+
/>\s*(\S+)/,
|
|
25325
|
+
// shell redirect
|
|
25326
|
+
/\bcp\s+\S+\s+(\S+)/,
|
|
25327
|
+
// cp src dest
|
|
25328
|
+
/\bmv\s+\S+\s+(\S+)/,
|
|
25329
|
+
/\bcopy\s+\S+\s+(\S+)/,
|
|
25330
|
+
/\bmove\s+\S+\s+(\S+)/,
|
|
25331
|
+
/\btee\s+(\S+)/,
|
|
25332
|
+
/writeFileSync\(\s*['"`]([^'"`]+)/,
|
|
25333
|
+
/\bcurl\b[^|]*-o\s+(\S+)/
|
|
25334
|
+
];
|
|
25335
|
+
READ_PATTERNS = [
|
|
25336
|
+
/\bcp\s+(\S+)\s+\S+/,
|
|
25337
|
+
/\bcopy\s+(\S+)\s+\S+/,
|
|
25338
|
+
/\bcat\s+(\S+)/,
|
|
25339
|
+
/\btype\s+(\S+)/,
|
|
25340
|
+
/\bsource\s+(\S+)/,
|
|
25341
|
+
/\breadFileSync\(\s*['"`]([^'"`]+)/,
|
|
25342
|
+
// Input redirect. `(?!<)` excludes heredocs (`<<'EOF'`), and the leading
|
|
25343
|
+
// boundary plus the restricted character class keep it from matching a `<`
|
|
25344
|
+
// that is part of a comparison, an HTML fragment, or another operator --
|
|
25345
|
+
// a bare /<\s*(\S+)/ matched unrelated text in real transcripts.
|
|
25346
|
+
/(?:^|\s)<(?!<)\s*([^\s<>|&;]+)/
|
|
25347
|
+
];
|
|
25348
|
+
}
|
|
25349
|
+
});
|
|
25350
|
+
|
|
25351
|
+
// src/core/checks/session/unverified-gate-claimed-clean.ts
|
|
25352
|
+
function gateOf(cmd) {
|
|
25353
|
+
for (const { re: re2, gate } of GATE_PATTERNS) {
|
|
25354
|
+
if (re2.test(cmd)) return gate;
|
|
25355
|
+
}
|
|
25356
|
+
return null;
|
|
25357
|
+
}
|
|
25358
|
+
function isUnverified(ev) {
|
|
25359
|
+
return ev.isError === true || ev.emptyOutput === true;
|
|
25360
|
+
}
|
|
25361
|
+
async function checkUnverifiedGateClaimedClean(ctx) {
|
|
25362
|
+
const { events } = await readProjectTranscript(ctx.currentProject);
|
|
25363
|
+
if (events.length === 0) return [];
|
|
25364
|
+
const ordered = [...events].sort((a, b2) => a.timestamp - b2.timestamp);
|
|
25365
|
+
const issues = [];
|
|
25366
|
+
const reported = /* @__PURE__ */ new Set();
|
|
25367
|
+
for (let i2 = 0; i2 < ordered.length; i2++) {
|
|
25368
|
+
const ev = ordered[i2];
|
|
25369
|
+
if (ev.kind !== "command") continue;
|
|
25370
|
+
const gate = gateOf(ev.text);
|
|
25371
|
+
if (!gate || !isUnverified(ev)) continue;
|
|
25372
|
+
for (let j3 = i2 + 1; j3 < ordered.length && j3 <= i2 + ADJACENCY; j3++) {
|
|
25373
|
+
const next = ordered[j3];
|
|
25374
|
+
if (next.kind === "command" && gateOf(next.text) === gate) break;
|
|
25375
|
+
if (next.kind !== "assistant-text") continue;
|
|
25376
|
+
if (HONEST_MARKERS.some((re2) => re2.test(next.text))) break;
|
|
25377
|
+
if (!CLEAN_CLAIMS.some((re2) => re2.test(next.text))) continue;
|
|
25378
|
+
if (reported.has(gate)) break;
|
|
25379
|
+
reported.add(gate);
|
|
25380
|
+
const why = ev.isError ? "the invocation reported an error" : "the invocation produced no output";
|
|
25381
|
+
issues.push({
|
|
25382
|
+
severity: "warning",
|
|
25383
|
+
check: "session-unverified-gate-claimed-clean",
|
|
25384
|
+
ruleId: "session-unverified-gate-claimed-clean/unverified-gate-claimed-clean",
|
|
25385
|
+
line: 0,
|
|
25386
|
+
message: `'${gate}' asserted as passing, but ${why}`,
|
|
25387
|
+
detail: `Gate command: ${ev.text.trim().slice(0, 120)}
|
|
25388
|
+
Claim: ${next.text.trim().slice(0, 120)}`,
|
|
25389
|
+
suggestion: 'An empty or failed gate carries no information about cleanliness -- a runner that dies before emitting diagnostics looks exactly like one that found none. Either re-run the gate until it produces a real result, or report the state honestly ("lint UNVERIFIED -- runner crashed").'
|
|
25390
|
+
});
|
|
25391
|
+
break;
|
|
25392
|
+
}
|
|
25393
|
+
}
|
|
25394
|
+
return issues;
|
|
25395
|
+
}
|
|
25396
|
+
var GATE_PATTERNS, CLEAN_CLAIMS, HONEST_MARKERS, ADJACENCY;
|
|
25397
|
+
var init_unverified_gate_claimed_clean = __esm({
|
|
25398
|
+
"src/core/checks/session/unverified-gate-claimed-clean.ts"() {
|
|
25399
|
+
"use strict";
|
|
25400
|
+
init_define_WEB_FIRST_SEGMENTS();
|
|
25401
|
+
init_transcript();
|
|
25402
|
+
GATE_PATTERNS = [
|
|
25403
|
+
{ re: /\b(biome|eslint|ruff|clippy)\b/, gate: "lint" },
|
|
25404
|
+
{ re: /\blint(:fix)?\b/, gate: "lint" },
|
|
25405
|
+
{ re: /\btsc\b|\btypecheck\b|\btype-check\b/, gate: "typecheck" },
|
|
25406
|
+
{ re: /\b(vitest|jest|pytest|mocha)\b/, gate: "test" },
|
|
25407
|
+
{ re: /\btest\b/, gate: "test" },
|
|
25408
|
+
{ re: /\bbuild\b/, gate: "build" }
|
|
25409
|
+
];
|
|
25410
|
+
CLEAN_CLAIMS = [
|
|
25411
|
+
/\ball green\b/i,
|
|
25412
|
+
/\bno (lint )?(violations|errors|issues|diagnostics)\b/i,
|
|
25413
|
+
/\b0 errors\b/i,
|
|
25414
|
+
/\bzero (errors|violations|diagnostics)\b/i,
|
|
25415
|
+
/\b(lint|typecheck|type-check|tests?|build)\b[^.\n]{0,40}\b(clean|passe[sd]|passing|green|succeeded)\b/i,
|
|
25416
|
+
/\b(clean|passing|green)\b[^.\n]{0,25}\b(lint|typecheck|type-check|tests?|build)\b/i
|
|
25417
|
+
];
|
|
25418
|
+
HONEST_MARKERS = [
|
|
25419
|
+
/\bunverified\b/i,
|
|
25420
|
+
/\bcould not verify\b/i,
|
|
25421
|
+
/\bcannot verify\b/i,
|
|
25422
|
+
/\bunable to verify\b/i,
|
|
25423
|
+
/\bcrashed?\b/i,
|
|
25424
|
+
/\bsegfault/i,
|
|
25425
|
+
/\bblocked\b/i,
|
|
25426
|
+
/\bnot trustworthy\b/i,
|
|
25427
|
+
/\bno signal\b/i,
|
|
25428
|
+
/\bdid not run\b/i,
|
|
25429
|
+
/\binconclusive\b/i
|
|
25430
|
+
];
|
|
25431
|
+
ADJACENCY = 12;
|
|
25432
|
+
}
|
|
25433
|
+
});
|
|
25434
|
+
|
|
25435
|
+
// src/core/checks/session/default-branch-accumulation.ts
|
|
25436
|
+
function isCommit(cmd) {
|
|
25437
|
+
return /\bgit\s+commit\b/.test(cmd) && !/--dry-run\b/.test(cmd);
|
|
25438
|
+
}
|
|
25439
|
+
function isBranchAway(cmd) {
|
|
25440
|
+
return /\bgit\s+(checkout\s+-b|switch\s+-c|worktree\s+add)\b/.test(cmd);
|
|
25441
|
+
}
|
|
25442
|
+
async function checkDefaultBranchAccumulation(ctx) {
|
|
25443
|
+
const { events } = await readProjectTranscript(ctx.currentProject);
|
|
25444
|
+
if (events.length === 0) return [];
|
|
25445
|
+
const ordered = [...events].sort((a, b2) => a.timestamp - b2.timestamp);
|
|
25446
|
+
const pending = /* @__PURE__ */ new Set();
|
|
25447
|
+
let branch = "";
|
|
25448
|
+
let firstWrite = "";
|
|
25449
|
+
for (const ev of ordered) {
|
|
25450
|
+
if (ev.gitBranch) branch = ev.gitBranch;
|
|
25451
|
+
if (ev.kind === "command") {
|
|
25452
|
+
if (isCommit(ev.text) || isBranchAway(ev.text)) {
|
|
25453
|
+
pending.clear();
|
|
25454
|
+
firstWrite = "";
|
|
25455
|
+
}
|
|
25456
|
+
continue;
|
|
25457
|
+
}
|
|
25458
|
+
if (ev.kind !== "file-write") continue;
|
|
25459
|
+
if (!DEFAULT_BRANCHES.has(branch)) continue;
|
|
25460
|
+
if (pending.size === 0) firstWrite = ev.text;
|
|
25461
|
+
pending.add(ev.text);
|
|
25462
|
+
}
|
|
25463
|
+
if (pending.size < THRESHOLD) return [];
|
|
25464
|
+
const sample = [...pending].slice(0, 5);
|
|
25465
|
+
return [
|
|
25466
|
+
{
|
|
25467
|
+
severity: "warning",
|
|
25468
|
+
check: "session-default-branch-accumulation",
|
|
25469
|
+
ruleId: "session-default-branch-accumulation/default-branch-accumulation",
|
|
25470
|
+
line: 0,
|
|
25471
|
+
message: `${pending.size} files edited on '${branch}' with no intervening commit`,
|
|
25472
|
+
detail: `First uncommitted write: ${firstWrite}
|
|
25473
|
+
Sample: ${sample.join(", ")}${pending.size > sample.length ? ", ..." : ""}`,
|
|
25474
|
+
suggestion: "Commit as you go, or move the work to a topic branch (`git checkout -b`). A large uncommitted delta on a shared default branch is hard to review and, when other agent sessions share the checkout, one `git stash` or `git checkout --` away from being lost."
|
|
25475
|
+
}
|
|
25476
|
+
];
|
|
25477
|
+
}
|
|
25478
|
+
var DEFAULT_BRANCHES, THRESHOLD;
|
|
25479
|
+
var init_default_branch_accumulation = __esm({
|
|
25480
|
+
"src/core/checks/session/default-branch-accumulation.ts"() {
|
|
25481
|
+
"use strict";
|
|
25482
|
+
init_define_WEB_FIRST_SEGMENTS();
|
|
25483
|
+
init_transcript();
|
|
25484
|
+
DEFAULT_BRANCHES = /* @__PURE__ */ new Set(["main", "master"]);
|
|
25485
|
+
THRESHOLD = 10;
|
|
25486
|
+
}
|
|
25487
|
+
});
|
|
25488
|
+
|
|
25489
|
+
// src/core/checks/ci-coverage.ts
|
|
25490
|
+
import { readdir as readdir4, readFile as readFile4 } from "node:fs/promises";
|
|
25491
|
+
import { join as join10 } from "node:path";
|
|
25492
|
+
import { existsSync as existsSync6 } from "node:fs";
|
|
24991
25493
|
async function findReleaseWorkflows(projectRoot) {
|
|
24992
|
-
const workflowDir =
|
|
24993
|
-
if (!
|
|
25494
|
+
const workflowDir = join10(projectRoot, ".github", "workflows");
|
|
25495
|
+
if (!existsSync6(workflowDir)) return [];
|
|
24994
25496
|
let files;
|
|
24995
25497
|
try {
|
|
24996
|
-
files = await
|
|
25498
|
+
files = await readdir4(workflowDir);
|
|
24997
25499
|
} catch {
|
|
24998
25500
|
return [];
|
|
24999
25501
|
}
|
|
@@ -25005,7 +25507,7 @@ async function findReleaseWorkflows(projectRoot) {
|
|
|
25005
25507
|
continue;
|
|
25006
25508
|
}
|
|
25007
25509
|
try {
|
|
25008
|
-
const content = stripBom(await readFile4(
|
|
25510
|
+
const content = stripBom(await readFile4(join10(workflowDir, f), "utf-8"));
|
|
25009
25511
|
const nameMatch = content.match(/^\s*name:\s*(.+?)\s*$/m);
|
|
25010
25512
|
if (nameMatch) {
|
|
25011
25513
|
const name = nameMatch[1].replace(/^(['"])(.*)\1$/, "$2");
|
|
@@ -25064,15 +25566,15 @@ var init_ci_coverage = __esm({
|
|
|
25064
25566
|
});
|
|
25065
25567
|
|
|
25066
25568
|
// src/core/checks/ci-secrets.ts
|
|
25067
|
-
import { readdir as
|
|
25068
|
-
import { join as
|
|
25069
|
-
import { existsSync as
|
|
25569
|
+
import { readdir as readdir5, readFile as readFile5 } from "node:fs/promises";
|
|
25570
|
+
import { join as join11 } from "node:path";
|
|
25571
|
+
import { existsSync as existsSync7 } from "node:fs";
|
|
25070
25572
|
async function findSecretUsages(projectRoot) {
|
|
25071
|
-
const workflowDir =
|
|
25072
|
-
if (!
|
|
25573
|
+
const workflowDir = join11(projectRoot, ".github", "workflows");
|
|
25574
|
+
if (!existsSync7(workflowDir)) return [];
|
|
25073
25575
|
let files;
|
|
25074
25576
|
try {
|
|
25075
|
-
files = await
|
|
25577
|
+
files = await readdir5(workflowDir);
|
|
25076
25578
|
} catch {
|
|
25077
25579
|
return [];
|
|
25078
25580
|
}
|
|
@@ -25081,7 +25583,7 @@ async function findSecretUsages(projectRoot) {
|
|
|
25081
25583
|
if (!(f.endsWith(".yml") || f.endsWith(".yaml"))) continue;
|
|
25082
25584
|
let content;
|
|
25083
25585
|
try {
|
|
25084
|
-
content = stripBom(await readFile5(
|
|
25586
|
+
content = stripBom(await readFile5(join11(workflowDir, f), "utf-8"));
|
|
25085
25587
|
} catch {
|
|
25086
25588
|
continue;
|
|
25087
25589
|
}
|
|
@@ -25470,9 +25972,24 @@ function isMsysFlag(token) {
|
|
|
25470
25972
|
}
|
|
25471
25973
|
function translateMsysDrivePath(s, platform) {
|
|
25472
25974
|
if (platform !== "win32") return s;
|
|
25473
|
-
const m = /^\/([A-Za-z])\/(.*)$/.exec(s);
|
|
25975
|
+
const m = /^\/{1,2}([A-Za-z])\/(.*)$/.exec(s);
|
|
25474
25976
|
return m ? `${m[1]}:/${m[2]}` : s;
|
|
25475
25977
|
}
|
|
25978
|
+
function findExistingVariant(raw, projectRoot, homeDir, platform) {
|
|
25979
|
+
const token = stripMatcherWildcard(raw);
|
|
25980
|
+
const variants = /* @__PURE__ */ new Set();
|
|
25981
|
+
if (/^\/{2}[A-Za-z]\//.test(token)) variants.add(token.slice(1));
|
|
25982
|
+
if (/^\/[A-Za-z]\//.test(token)) variants.add(`/${token}`);
|
|
25983
|
+
if (token.startsWith("/") && !/^\/{1,2}[A-Za-z]\//.test(token)) {
|
|
25984
|
+
variants.add(token.replace(/^\/+/, ""));
|
|
25985
|
+
}
|
|
25986
|
+
if (token.includes("\\")) variants.add(token.replace(/\\/g, "/"));
|
|
25987
|
+
for (const v2 of variants) {
|
|
25988
|
+
const resolved = expandPath(v2, projectRoot, homeDir, platform);
|
|
25989
|
+
if (resolved !== null && fileExists(resolved)) return resolved;
|
|
25990
|
+
}
|
|
25991
|
+
return null;
|
|
25992
|
+
}
|
|
25476
25993
|
function extractCommandPaths(command, projectRoot, homeDir, platform) {
|
|
25477
25994
|
const tokens = command.match(/"[^"]*"|'[^']*'|\S+/g) ?? [];
|
|
25478
25995
|
const out = [];
|
|
@@ -25505,6 +26022,18 @@ function checkSource(source, projectRoot, homeDir, platform) {
|
|
|
25505
26022
|
if (cand.resolved === null) return;
|
|
25506
26023
|
if (fileExists(cand.resolved)) return;
|
|
25507
26024
|
const where = source.isUserGlobal ? ` (in ${source.displayPath})` : "";
|
|
26025
|
+
const variant = findExistingVariant(cand.raw, projectRoot, homeDir, platform);
|
|
26026
|
+
if (variant !== null) {
|
|
26027
|
+
issues.push({
|
|
26028
|
+
severity: "warning",
|
|
26029
|
+
check: "hook-coverage",
|
|
26030
|
+
ruleId: "hook-coverage/dead-hook",
|
|
26031
|
+
line,
|
|
26032
|
+
message: `${origin} references "${cand.raw}" which does not resolve${where}, but "${variant}" exists \u2014 the path form is wrong, so the gate silently no-ops`,
|
|
26033
|
+
suggestion: `Rewrite the entry to use "${variant}". Do NOT delete it \u2014 the target exists; only the path form is unresolvable.`
|
|
26034
|
+
});
|
|
26035
|
+
return;
|
|
26036
|
+
}
|
|
25508
26037
|
issues.push({
|
|
25509
26038
|
severity: "warning",
|
|
25510
26039
|
check: "hook-coverage",
|
|
@@ -27805,7 +28334,7 @@ import { readFileSync as readFileSync7 } from "node:fs";
|
|
|
27805
28334
|
import { resolve as resolve14, dirname as dirname6 } from "node:path";
|
|
27806
28335
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
27807
28336
|
function loadVersion() {
|
|
27808
|
-
if (true) return "0.18.
|
|
28337
|
+
if (true) return "0.18.7";
|
|
27809
28338
|
try {
|
|
27810
28339
|
const __dir = dirname6(fileURLToPath2(import.meta.url));
|
|
27811
28340
|
const pkgPath = resolve14(__dir, "../package.json");
|
|
@@ -27894,7 +28423,8 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
27894
28423
|
if (activeChecks.includes("content-secrets"))
|
|
27895
28424
|
checkPromises.push(checkContentSecrets(parseResult, projectRoot));
|
|
27896
28425
|
const results = await Promise.all(checkPromises);
|
|
27897
|
-
const
|
|
28426
|
+
const suppressions = collectSuppressions(parseResult.content);
|
|
28427
|
+
const singleFileIssues = results.flat().filter((issue2) => !isSuppressed(suppressions, issue2));
|
|
27898
28428
|
if (stat3 !== null) {
|
|
27899
28429
|
setCacheEntry(absPath, {
|
|
27900
28430
|
mtime: stat3.mtimeMs,
|
|
@@ -28043,6 +28573,12 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
28043
28573
|
sessionPromises.push(checkLoopDetection(sessionCtx));
|
|
28044
28574
|
if (sessionChecksToRun.includes("session-memory-index-overflow"))
|
|
28045
28575
|
sessionPromises.push(checkMemoryIndexOverflow(sessionCtx));
|
|
28576
|
+
if (sessionChecksToRun.includes("session-shared-temp-path"))
|
|
28577
|
+
sessionPromises.push(checkSharedTempPath(sessionCtx));
|
|
28578
|
+
if (sessionChecksToRun.includes("session-unverified-gate-claimed-clean"))
|
|
28579
|
+
sessionPromises.push(checkUnverifiedGateClaimedClean(sessionCtx));
|
|
28580
|
+
if (sessionChecksToRun.includes("session-default-branch-accumulation"))
|
|
28581
|
+
sessionPromises.push(checkDefaultBranchAccumulation(sessionCtx));
|
|
28046
28582
|
const sessionResults = await Promise.all(sessionPromises);
|
|
28047
28583
|
const sessionIssues = sessionResults.flat();
|
|
28048
28584
|
fileResults.push({
|
|
@@ -28221,6 +28757,7 @@ var init_audit = __esm({
|
|
|
28221
28757
|
init_paths();
|
|
28222
28758
|
init_commands();
|
|
28223
28759
|
init_staleness();
|
|
28760
|
+
init_suppressions();
|
|
28224
28761
|
init_tokens2();
|
|
28225
28762
|
init_tier_tokens();
|
|
28226
28763
|
init_redundancy();
|
|
@@ -28242,6 +28779,9 @@ var init_audit = __esm({
|
|
|
28242
28779
|
init_duplicate_memory();
|
|
28243
28780
|
init_loop_detection();
|
|
28244
28781
|
init_memory_index_overflow();
|
|
28782
|
+
init_shared_temp_path();
|
|
28783
|
+
init_unverified_gate_claimed_clean();
|
|
28784
|
+
init_default_branch_accumulation();
|
|
28245
28785
|
init_ci_coverage();
|
|
28246
28786
|
init_ci_secrets();
|
|
28247
28787
|
init_content_secrets();
|
|
@@ -28283,7 +28823,10 @@ var init_audit = __esm({
|
|
|
28283
28823
|
"session-stale-memory",
|
|
28284
28824
|
"session-duplicate-memory",
|
|
28285
28825
|
"session-loop-detection",
|
|
28286
|
-
"session-memory-index-overflow"
|
|
28826
|
+
"session-memory-index-overflow",
|
|
28827
|
+
"session-shared-temp-path",
|
|
28828
|
+
"session-unverified-gate-claimed-clean",
|
|
28829
|
+
"session-default-branch-accumulation"
|
|
28287
28830
|
];
|
|
28288
28831
|
ALL_SKILL_CHECKS = [
|
|
28289
28832
|
"skill-frontmatter",
|
|
@@ -54126,8 +54669,8 @@ var require_resolve = __commonJS({
|
|
|
54126
54669
|
}
|
|
54127
54670
|
return count;
|
|
54128
54671
|
}
|
|
54129
|
-
function getFullPath(resolver, id = "",
|
|
54130
|
-
if (
|
|
54672
|
+
function getFullPath(resolver, id = "", normalize4) {
|
|
54673
|
+
if (normalize4 !== false)
|
|
54131
54674
|
id = normalizeId(id);
|
|
54132
54675
|
const p2 = resolver.parse(id);
|
|
54133
54676
|
return _getFullPath(resolver, p2);
|
|
@@ -55530,7 +56073,7 @@ var require_fast_uri = __commonJS({
|
|
|
55530
56073
|
init_define_WEB_FIRST_SEGMENTS();
|
|
55531
56074
|
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils2();
|
|
55532
56075
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
55533
|
-
function
|
|
56076
|
+
function normalize4(uri, options) {
|
|
55534
56077
|
if (typeof uri === "string") {
|
|
55535
56078
|
uri = /** @type {T} */
|
|
55536
56079
|
normalizeString(uri, options);
|
|
@@ -55797,7 +56340,7 @@ var require_fast_uri = __commonJS({
|
|
|
55797
56340
|
}
|
|
55798
56341
|
var fastUri = {
|
|
55799
56342
|
SCHEMES,
|
|
55800
|
-
normalize:
|
|
56343
|
+
normalize: normalize4,
|
|
55801
56344
|
resolve: resolve18,
|
|
55802
56345
|
resolveComponent,
|
|
55803
56346
|
equal,
|
|
@@ -68350,6 +68893,8 @@ var init_ora = __esm({
|
|
|
68350
68893
|
});
|
|
68351
68894
|
|
|
68352
68895
|
// src/core/reporter.ts
|
|
68896
|
+
import { existsSync as existsSync8, readFileSync as readFileSync10 } from "node:fs";
|
|
68897
|
+
import { join as join16 } from "node:path";
|
|
68353
68898
|
function classifyFile(f) {
|
|
68354
68899
|
if (f.path === "(project)") return "context";
|
|
68355
68900
|
if (f.path === "(mcp)") return "mcp";
|
|
@@ -68371,10 +68916,32 @@ function classifyFile(f) {
|
|
|
68371
68916
|
function isSyntheticBucket(p2) {
|
|
68372
68917
|
return p2 === "(project)" || p2 === "(mcp)" || p2.includes(SESSION_AUDIT_PATH_MARKER) || p2.includes(SKILL_AUDIT_PATH_MARKER);
|
|
68373
68918
|
}
|
|
68919
|
+
function detectVersionSkew(projectRoot, runningVersion) {
|
|
68920
|
+
try {
|
|
68921
|
+
const pkgPath = join16(projectRoot, "package.json");
|
|
68922
|
+
if (!existsSync8(pkgPath)) return null;
|
|
68923
|
+
const raw = readFileSync10(pkgPath, "utf8").replace(/^\uFEFF/, "");
|
|
68924
|
+
const pkg = JSON.parse(raw);
|
|
68925
|
+
const name = pkg.name ?? "";
|
|
68926
|
+
if (name !== "ctxlint" && name !== "@yawlabs/ctxlint") return null;
|
|
68927
|
+
if (!pkg.version || pkg.version === runningVersion) return null;
|
|
68928
|
+
return pkg.version;
|
|
68929
|
+
} catch {
|
|
68930
|
+
return null;
|
|
68931
|
+
}
|
|
68932
|
+
}
|
|
68374
68933
|
function formatText(result, verbose = false) {
|
|
68375
68934
|
const lines = [];
|
|
68376
68935
|
lines.push("");
|
|
68377
68936
|
lines.push(source_default.bold(`ctxlint v${result.version}`));
|
|
68937
|
+
const skew = detectVersionSkew(result.projectRoot, result.version);
|
|
68938
|
+
if (skew !== null) {
|
|
68939
|
+
lines.push(
|
|
68940
|
+
source_default.yellow(
|
|
68941
|
+
` note: this checkout is v${skew} -- you are running v${result.version}. Rebuild or reinstall before trusting a result against it.`
|
|
68942
|
+
)
|
|
68943
|
+
);
|
|
68944
|
+
}
|
|
68378
68945
|
lines.push("");
|
|
68379
68946
|
lines.push(`Scanning ${result.projectRoot}...`);
|
|
68380
68947
|
lines.push("");
|
|
@@ -68748,6 +69315,27 @@ function buildRuleDescriptors() {
|
|
|
68748
69315
|
},
|
|
68749
69316
|
helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
|
|
68750
69317
|
},
|
|
69318
|
+
{
|
|
69319
|
+
id: "ctxlint/session-shared-temp-path",
|
|
69320
|
+
shortDescription: {
|
|
69321
|
+
text: "Fixed temp path written then read back -- clobberable by a concurrent session"
|
|
69322
|
+
},
|
|
69323
|
+
helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
|
|
69324
|
+
},
|
|
69325
|
+
{
|
|
69326
|
+
id: "ctxlint/session-unverified-gate-claimed-clean",
|
|
69327
|
+
shortDescription: {
|
|
69328
|
+
text: "A quality gate asserted as passing while its invocation failed or emitted nothing"
|
|
69329
|
+
},
|
|
69330
|
+
helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
|
|
69331
|
+
},
|
|
69332
|
+
{
|
|
69333
|
+
id: "ctxlint/session-default-branch-accumulation",
|
|
69334
|
+
shortDescription: {
|
|
69335
|
+
text: "Many files edited on the default branch with no intervening commit"
|
|
69336
|
+
},
|
|
69337
|
+
helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
|
|
69338
|
+
},
|
|
68751
69339
|
{
|
|
68752
69340
|
id: "ctxlint/skill-frontmatter",
|
|
68753
69341
|
shortDescription: { text: "Skill/agent definition missing required frontmatter" },
|
|
@@ -69144,8 +69732,8 @@ npx @yawlabs/ctxlint@${VERSION} --strict
|
|
|
69144
69732
|
program2.parse();
|
|
69145
69733
|
}
|
|
69146
69734
|
async function promptYesNo(question) {
|
|
69147
|
-
const { createInterface:
|
|
69148
|
-
const rl =
|
|
69735
|
+
const { createInterface: createInterface3 } = await import("node:readline/promises");
|
|
69736
|
+
const rl = createInterface3({ input: process.stdin, output: process.stderr });
|
|
69149
69737
|
try {
|
|
69150
69738
|
const answer = await rl.question(question);
|
|
69151
69739
|
return /^\s*y(es)?\s*$/i.test(answer);
|
|
@@ -69276,4 +69864,3 @@ if (args.includes("--lsp")) {
|
|
|
69276
69864
|
const { runCli: runCli2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
|
|
69277
69865
|
await runCli2();
|
|
69278
69866
|
}
|
|
69279
|
-
//# sourceMappingURL=index.js.map
|