@yawlabs/ctxlint 0.18.7 → 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 +592 -39
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -13770,8 +13770,8 @@ 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
13777
|
if (/^[a-z][\w-]*(?:\/[a-z][\w-]*){2,}$/.test(cleanValue)) continue;
|
|
@@ -19966,8 +19966,8 @@ async function findRenames(projectRoot, filePath) {
|
|
|
19966
19966
|
async function findRenamesBatch(projectRoot, relPaths) {
|
|
19967
19967
|
const result = /* @__PURE__ */ new Map();
|
|
19968
19968
|
if (relPaths.length === 0) return result;
|
|
19969
|
-
let
|
|
19970
|
-
if (!
|
|
19969
|
+
let cache2 = renameCache.get(projectRoot);
|
|
19970
|
+
if (!cache2) {
|
|
19971
19971
|
try {
|
|
19972
19972
|
const git = getGit(projectRoot);
|
|
19973
19973
|
const rawOutput = await git.raw([
|
|
@@ -19985,14 +19985,14 @@ async function findRenamesBatch(projectRoot, relPaths) {
|
|
|
19985
19985
|
} catch {
|
|
19986
19986
|
}
|
|
19987
19987
|
const { allRenames: allRenames2, basenameBuckets: basenameBuckets2 } = parseRenameLogAll(rawOutput);
|
|
19988
|
-
|
|
19989
|
-
renameCache.set(projectRoot,
|
|
19988
|
+
cache2 = { allRenames: allRenames2, basenameBuckets: basenameBuckets2, prefix: prefix2 };
|
|
19989
|
+
renameCache.set(projectRoot, cache2);
|
|
19990
19990
|
} catch {
|
|
19991
19991
|
for (const p2 of relPaths) result.set(p2, null);
|
|
19992
19992
|
return result;
|
|
19993
19993
|
}
|
|
19994
19994
|
}
|
|
19995
|
-
const { allRenames, basenameBuckets, prefix } =
|
|
19995
|
+
const { allRenames, basenameBuckets, prefix } = cache2;
|
|
19996
19996
|
for (const relPath of relPaths) {
|
|
19997
19997
|
const rel = relPath.replace(/\\/g, "/");
|
|
19998
19998
|
const target = prefix + rel;
|
|
@@ -22130,6 +22130,50 @@ var init_staleness = __esm({
|
|
|
22130
22130
|
}
|
|
22131
22131
|
});
|
|
22132
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
|
+
|
|
22133
22177
|
// src/core/checks/tokens.ts
|
|
22134
22178
|
function resolveTokenThresholds(overrides) {
|
|
22135
22179
|
if (!overrides) return DEFAULT_TOKEN_THRESHOLDS;
|
|
@@ -24081,6 +24125,11 @@ function loadWebFirstSegments() {
|
|
|
24081
24125
|
function encodeProjectDir(fsPath) {
|
|
24082
24126
|
return fsPath.replace(/[:\\/\.]/g, "-");
|
|
24083
24127
|
}
|
|
24128
|
+
function projectDirCandidates(fsPath) {
|
|
24129
|
+
const encoded = encodeProjectDir(fsPath);
|
|
24130
|
+
const folded = encoded.replace(/_/g, "-");
|
|
24131
|
+
return folded === encoded ? [encoded] : [encoded, folded];
|
|
24132
|
+
}
|
|
24084
24133
|
function projectDirMatchesPath(encodedDir, fsPath) {
|
|
24085
24134
|
const normalized = fsPath.replace(/\\/g, "/");
|
|
24086
24135
|
return encodedDir === encodeProjectDir(normalized);
|
|
@@ -24967,13 +25016,19 @@ import { join as join8 } from "node:path";
|
|
|
24967
25016
|
async function checkMemoryIndexOverflow(ctx) {
|
|
24968
25017
|
const home2 = process.env.HOME || process.env.USERPROFILE || homedir5();
|
|
24969
25018
|
if (!home2) return [];
|
|
24970
|
-
|
|
24971
|
-
|
|
24972
|
-
|
|
24973
|
-
|
|
24974
|
-
|
|
24975
|
-
|
|
24976
|
-
|
|
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
|
+
}
|
|
24977
25032
|
}
|
|
24978
25033
|
if (!content) return [];
|
|
24979
25034
|
const lines = content.split("\n");
|
|
@@ -25018,16 +25073,429 @@ var init_memory_index_overflow = __esm({
|
|
|
25018
25073
|
}
|
|
25019
25074
|
});
|
|
25020
25075
|
|
|
25021
|
-
// src/core/
|
|
25022
|
-
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";
|
|
25023
25080
|
import { join as join9 } from "node:path";
|
|
25024
|
-
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";
|
|
25025
25493
|
async function findReleaseWorkflows(projectRoot) {
|
|
25026
|
-
const workflowDir =
|
|
25027
|
-
if (!
|
|
25494
|
+
const workflowDir = join10(projectRoot, ".github", "workflows");
|
|
25495
|
+
if (!existsSync6(workflowDir)) return [];
|
|
25028
25496
|
let files;
|
|
25029
25497
|
try {
|
|
25030
|
-
files = await
|
|
25498
|
+
files = await readdir4(workflowDir);
|
|
25031
25499
|
} catch {
|
|
25032
25500
|
return [];
|
|
25033
25501
|
}
|
|
@@ -25039,7 +25507,7 @@ async function findReleaseWorkflows(projectRoot) {
|
|
|
25039
25507
|
continue;
|
|
25040
25508
|
}
|
|
25041
25509
|
try {
|
|
25042
|
-
const content = stripBom(await readFile4(
|
|
25510
|
+
const content = stripBom(await readFile4(join10(workflowDir, f), "utf-8"));
|
|
25043
25511
|
const nameMatch = content.match(/^\s*name:\s*(.+?)\s*$/m);
|
|
25044
25512
|
if (nameMatch) {
|
|
25045
25513
|
const name = nameMatch[1].replace(/^(['"])(.*)\1$/, "$2");
|
|
@@ -25098,15 +25566,15 @@ var init_ci_coverage = __esm({
|
|
|
25098
25566
|
});
|
|
25099
25567
|
|
|
25100
25568
|
// src/core/checks/ci-secrets.ts
|
|
25101
|
-
import { readdir as
|
|
25102
|
-
import { join as
|
|
25103
|
-
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";
|
|
25104
25572
|
async function findSecretUsages(projectRoot) {
|
|
25105
|
-
const workflowDir =
|
|
25106
|
-
if (!
|
|
25573
|
+
const workflowDir = join11(projectRoot, ".github", "workflows");
|
|
25574
|
+
if (!existsSync7(workflowDir)) return [];
|
|
25107
25575
|
let files;
|
|
25108
25576
|
try {
|
|
25109
|
-
files = await
|
|
25577
|
+
files = await readdir5(workflowDir);
|
|
25110
25578
|
} catch {
|
|
25111
25579
|
return [];
|
|
25112
25580
|
}
|
|
@@ -25115,7 +25583,7 @@ async function findSecretUsages(projectRoot) {
|
|
|
25115
25583
|
if (!(f.endsWith(".yml") || f.endsWith(".yaml"))) continue;
|
|
25116
25584
|
let content;
|
|
25117
25585
|
try {
|
|
25118
|
-
content = stripBom(await readFile5(
|
|
25586
|
+
content = stripBom(await readFile5(join11(workflowDir, f), "utf-8"));
|
|
25119
25587
|
} catch {
|
|
25120
25588
|
continue;
|
|
25121
25589
|
}
|
|
@@ -25504,9 +25972,24 @@ function isMsysFlag(token) {
|
|
|
25504
25972
|
}
|
|
25505
25973
|
function translateMsysDrivePath(s, platform) {
|
|
25506
25974
|
if (platform !== "win32") return s;
|
|
25507
|
-
const m = /^\/([A-Za-z])\/(.*)$/.exec(s);
|
|
25975
|
+
const m = /^\/{1,2}([A-Za-z])\/(.*)$/.exec(s);
|
|
25508
25976
|
return m ? `${m[1]}:/${m[2]}` : s;
|
|
25509
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
|
+
}
|
|
25510
25993
|
function extractCommandPaths(command, projectRoot, homeDir, platform) {
|
|
25511
25994
|
const tokens = command.match(/"[^"]*"|'[^']*'|\S+/g) ?? [];
|
|
25512
25995
|
const out = [];
|
|
@@ -25539,6 +26022,18 @@ function checkSource(source, projectRoot, homeDir, platform) {
|
|
|
25539
26022
|
if (cand.resolved === null) return;
|
|
25540
26023
|
if (fileExists(cand.resolved)) return;
|
|
25541
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
|
+
}
|
|
25542
26037
|
issues.push({
|
|
25543
26038
|
severity: "warning",
|
|
25544
26039
|
check: "hook-coverage",
|
|
@@ -27839,7 +28334,7 @@ import { readFileSync as readFileSync7 } from "node:fs";
|
|
|
27839
28334
|
import { resolve as resolve14, dirname as dirname6 } from "node:path";
|
|
27840
28335
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
27841
28336
|
function loadVersion() {
|
|
27842
|
-
if (true) return "0.18.
|
|
28337
|
+
if (true) return "0.18.7";
|
|
27843
28338
|
try {
|
|
27844
28339
|
const __dir = dirname6(fileURLToPath2(import.meta.url));
|
|
27845
28340
|
const pkgPath = resolve14(__dir, "../package.json");
|
|
@@ -27928,7 +28423,8 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
27928
28423
|
if (activeChecks.includes("content-secrets"))
|
|
27929
28424
|
checkPromises.push(checkContentSecrets(parseResult, projectRoot));
|
|
27930
28425
|
const results = await Promise.all(checkPromises);
|
|
27931
|
-
const
|
|
28426
|
+
const suppressions = collectSuppressions(parseResult.content);
|
|
28427
|
+
const singleFileIssues = results.flat().filter((issue2) => !isSuppressed(suppressions, issue2));
|
|
27932
28428
|
if (stat3 !== null) {
|
|
27933
28429
|
setCacheEntry(absPath, {
|
|
27934
28430
|
mtime: stat3.mtimeMs,
|
|
@@ -28077,6 +28573,12 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
|
|
|
28077
28573
|
sessionPromises.push(checkLoopDetection(sessionCtx));
|
|
28078
28574
|
if (sessionChecksToRun.includes("session-memory-index-overflow"))
|
|
28079
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));
|
|
28080
28582
|
const sessionResults = await Promise.all(sessionPromises);
|
|
28081
28583
|
const sessionIssues = sessionResults.flat();
|
|
28082
28584
|
fileResults.push({
|
|
@@ -28255,6 +28757,7 @@ var init_audit = __esm({
|
|
|
28255
28757
|
init_paths();
|
|
28256
28758
|
init_commands();
|
|
28257
28759
|
init_staleness();
|
|
28760
|
+
init_suppressions();
|
|
28258
28761
|
init_tokens2();
|
|
28259
28762
|
init_tier_tokens();
|
|
28260
28763
|
init_redundancy();
|
|
@@ -28276,6 +28779,9 @@ var init_audit = __esm({
|
|
|
28276
28779
|
init_duplicate_memory();
|
|
28277
28780
|
init_loop_detection();
|
|
28278
28781
|
init_memory_index_overflow();
|
|
28782
|
+
init_shared_temp_path();
|
|
28783
|
+
init_unverified_gate_claimed_clean();
|
|
28784
|
+
init_default_branch_accumulation();
|
|
28279
28785
|
init_ci_coverage();
|
|
28280
28786
|
init_ci_secrets();
|
|
28281
28787
|
init_content_secrets();
|
|
@@ -28317,7 +28823,10 @@ var init_audit = __esm({
|
|
|
28317
28823
|
"session-stale-memory",
|
|
28318
28824
|
"session-duplicate-memory",
|
|
28319
28825
|
"session-loop-detection",
|
|
28320
|
-
"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"
|
|
28321
28830
|
];
|
|
28322
28831
|
ALL_SKILL_CHECKS = [
|
|
28323
28832
|
"skill-frontmatter",
|
|
@@ -54160,8 +54669,8 @@ var require_resolve = __commonJS({
|
|
|
54160
54669
|
}
|
|
54161
54670
|
return count;
|
|
54162
54671
|
}
|
|
54163
|
-
function getFullPath(resolver, id = "",
|
|
54164
|
-
if (
|
|
54672
|
+
function getFullPath(resolver, id = "", normalize4) {
|
|
54673
|
+
if (normalize4 !== false)
|
|
54165
54674
|
id = normalizeId(id);
|
|
54166
54675
|
const p2 = resolver.parse(id);
|
|
54167
54676
|
return _getFullPath(resolver, p2);
|
|
@@ -55564,7 +56073,7 @@ var require_fast_uri = __commonJS({
|
|
|
55564
56073
|
init_define_WEB_FIRST_SEGMENTS();
|
|
55565
56074
|
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils2();
|
|
55566
56075
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
55567
|
-
function
|
|
56076
|
+
function normalize4(uri, options) {
|
|
55568
56077
|
if (typeof uri === "string") {
|
|
55569
56078
|
uri = /** @type {T} */
|
|
55570
56079
|
normalizeString(uri, options);
|
|
@@ -55831,7 +56340,7 @@ var require_fast_uri = __commonJS({
|
|
|
55831
56340
|
}
|
|
55832
56341
|
var fastUri = {
|
|
55833
56342
|
SCHEMES,
|
|
55834
|
-
normalize:
|
|
56343
|
+
normalize: normalize4,
|
|
55835
56344
|
resolve: resolve18,
|
|
55836
56345
|
resolveComponent,
|
|
55837
56346
|
equal,
|
|
@@ -68384,6 +68893,8 @@ var init_ora = __esm({
|
|
|
68384
68893
|
});
|
|
68385
68894
|
|
|
68386
68895
|
// src/core/reporter.ts
|
|
68896
|
+
import { existsSync as existsSync8, readFileSync as readFileSync10 } from "node:fs";
|
|
68897
|
+
import { join as join16 } from "node:path";
|
|
68387
68898
|
function classifyFile(f) {
|
|
68388
68899
|
if (f.path === "(project)") return "context";
|
|
68389
68900
|
if (f.path === "(mcp)") return "mcp";
|
|
@@ -68405,10 +68916,32 @@ function classifyFile(f) {
|
|
|
68405
68916
|
function isSyntheticBucket(p2) {
|
|
68406
68917
|
return p2 === "(project)" || p2 === "(mcp)" || p2.includes(SESSION_AUDIT_PATH_MARKER) || p2.includes(SKILL_AUDIT_PATH_MARKER);
|
|
68407
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
|
+
}
|
|
68408
68933
|
function formatText(result, verbose = false) {
|
|
68409
68934
|
const lines = [];
|
|
68410
68935
|
lines.push("");
|
|
68411
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
|
+
}
|
|
68412
68945
|
lines.push("");
|
|
68413
68946
|
lines.push(`Scanning ${result.projectRoot}...`);
|
|
68414
68947
|
lines.push("");
|
|
@@ -68782,6 +69315,27 @@ function buildRuleDescriptors() {
|
|
|
68782
69315
|
},
|
|
68783
69316
|
helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
|
|
68784
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
|
+
},
|
|
68785
69339
|
{
|
|
68786
69340
|
id: "ctxlint/skill-frontmatter",
|
|
68787
69341
|
shortDescription: { text: "Skill/agent definition missing required frontmatter" },
|
|
@@ -69178,8 +69732,8 @@ npx @yawlabs/ctxlint@${VERSION} --strict
|
|
|
69178
69732
|
program2.parse();
|
|
69179
69733
|
}
|
|
69180
69734
|
async function promptYesNo(question) {
|
|
69181
|
-
const { createInterface:
|
|
69182
|
-
const rl =
|
|
69735
|
+
const { createInterface: createInterface3 } = await import("node:readline/promises");
|
|
69736
|
+
const rl = createInterface3({ input: process.stdin, output: process.stderr });
|
|
69183
69737
|
try {
|
|
69184
69738
|
const answer = await rl.question(question);
|
|
69185
69739
|
return /^\s*y(es)?\s*$/i.test(answer);
|
|
@@ -69310,4 +69864,3 @@ if (args.includes("--lsp")) {
|
|
|
69310
69864
|
const { runCli: runCli2 } = await Promise.resolve().then(() => (init_cli(), cli_exports));
|
|
69311
69865
|
await runCli2();
|
|
69312
69866
|
}
|
|
69313
|
-
//# sourceMappingURL=index.js.map
|