@yawlabs/ctxlint 0.24.0 → 0.25.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/dist/index.js CHANGED
@@ -27551,6 +27551,20 @@ import { readdir as readdir3 } from "node:fs/promises";
27551
27551
  import { homedir as homedir6 } from "node:os";
27552
27552
  import { join as join9 } from "node:path";
27553
27553
  import { createInterface as createInterface2 } from "node:readline";
27554
+ function emptyRead() {
27555
+ return {
27556
+ events: [],
27557
+ filesRead: 0,
27558
+ truncated: false,
27559
+ sessionTurns: /* @__PURE__ */ new Map(),
27560
+ sessionCompactions: /* @__PURE__ */ new Map()
27561
+ };
27562
+ }
27563
+ function turnsCarried(read, ev) {
27564
+ const total = read.sessionTurns.get(ev.sessionId) ?? ev.turn;
27565
+ const boundary = read.sessionCompactions.get(ev.sessionId)?.find((b2) => b2 >= ev.turn);
27566
+ return Math.max(0, (boundary ?? total) - ev.turn);
27567
+ }
27554
27568
  function asString(v2) {
27555
27569
  return typeof v2 === "string" ? v2 : "";
27556
27570
  }
@@ -27571,14 +27585,17 @@ function readProjectTranscript(project, home2 = resolveHome()) {
27571
27585
  }
27572
27586
  return hit;
27573
27587
  }
27588
+ function clearTranscriptCache() {
27589
+ cache.clear();
27590
+ }
27574
27591
  function candidateDirs(project, home2) {
27575
27592
  const root = join9(home2, ".claude", "projects");
27576
27593
  return projectDirCandidates(project).map((n7) => join9(root, n7)).filter((d) => existsSync5(d));
27577
27594
  }
27578
27595
  async function readUncached(project, home2) {
27579
- if (!home2 || !project) return EMPTY;
27596
+ if (!home2 || !project) return emptyRead();
27580
27597
  const dirs = candidateDirs(project, home2);
27581
- if (dirs.length === 0) return EMPTY;
27598
+ if (dirs.length === 0) return emptyRead();
27582
27599
  const names = [];
27583
27600
  for (const dir of dirs) {
27584
27601
  for (const name of await readdir3(dir).catch(() => [])) {
@@ -27595,8 +27612,7 @@ async function readUncached(project, home2) {
27595
27612
  }).filter((f) => f !== null).sort((a, b2) => b2.mtime - a.mtime);
27596
27613
  let truncated = files.length > MAX_TRANSCRIPTS;
27597
27614
  const selected = files.slice(0, MAX_TRANSCRIPTS);
27598
- const events = [];
27599
- const pending = /* @__PURE__ */ new Map();
27615
+ const st2 = { events: [], pending: /* @__PURE__ */ new Map(), sessions: /* @__PURE__ */ new Map(), anonymous: 0 };
27600
27616
  let lines = 0;
27601
27617
  for (const { p: p2 } of selected) {
27602
27618
  if (lines >= MAX_LINES2) {
@@ -27621,21 +27637,75 @@ async function readUncached(project, home2) {
27621
27637
  } catch {
27622
27638
  continue;
27623
27639
  }
27624
- collect(rec, events, pending);
27640
+ collect(rec, st2);
27625
27641
  }
27626
27642
  } catch {
27627
27643
  } finally {
27628
27644
  rl.close();
27629
27645
  }
27630
27646
  }
27631
- return { events, filesRead: selected.length, truncated };
27647
+ const sessionTurns = /* @__PURE__ */ new Map();
27648
+ const sessionCompactions = /* @__PURE__ */ new Map();
27649
+ for (const [id, s] of st2.sessions) {
27650
+ sessionTurns.set(id, s.ordinals.size);
27651
+ if (s.compactions.length > 0) {
27652
+ sessionCompactions.set(
27653
+ id,
27654
+ [...s.compactions].sort((a, b2) => a - b2)
27655
+ );
27656
+ }
27657
+ }
27658
+ return {
27659
+ events: st2.events,
27660
+ filesRead: selected.length,
27661
+ truncated,
27662
+ sessionTurns,
27663
+ sessionCompactions
27664
+ };
27665
+ }
27666
+ function sessionState(st2, sessionId) {
27667
+ let s = st2.sessions.get(sessionId);
27668
+ if (!s) {
27669
+ s = { ordinals: /* @__PURE__ */ new Map(), compactions: [] };
27670
+ st2.sessions.set(sessionId, s);
27671
+ }
27672
+ return s;
27632
27673
  }
27633
- function collect(rec, events, pending) {
27674
+ function turnOf(rec, message, st2, sessionId) {
27675
+ const s = sessionState(st2, sessionId);
27676
+ if (message?.model === "<synthetic>" || rec.isSidechain === true) return s.ordinals.size;
27677
+ const key = asString(message?.id) || asString(rec.requestId) || `#anonymous:${++st2.anonymous}`;
27678
+ let ordinal = s.ordinals.get(key);
27679
+ if (ordinal === void 0) {
27680
+ ordinal = s.ordinals.size + 1;
27681
+ s.ordinals.set(key, ordinal);
27682
+ }
27683
+ return ordinal;
27684
+ }
27685
+ function isSet(v2) {
27686
+ return v2 !== void 0 && v2 !== null && v2 !== "";
27687
+ }
27688
+ function readResultLines(rec, path21) {
27689
+ const result = rec.toolUseResult;
27690
+ if (!result || typeof result !== "object") return void 0;
27691
+ const file2 = result.file;
27692
+ if (!file2 || typeof file2 !== "object") return void 0;
27693
+ const { filePath, numLines } = file2;
27694
+ if (filePath !== path21) return void 0;
27695
+ return typeof numLines === "number" ? numLines : void 0;
27696
+ }
27697
+ function collect(rec, st2) {
27698
+ const sessionId = asString(rec.sessionId) || asString(rec.session_id);
27699
+ if (rec.type === "system" && rec.subtype === "compact_boundary") {
27700
+ const s = sessionState(st2, sessionId);
27701
+ s.compactions.push(s.ordinals.size);
27702
+ return;
27703
+ }
27634
27704
  const message = rec.message;
27705
+ const turn = rec.type === "assistant" ? turnOf(rec, message, st2, sessionId) : st2.sessions.get(sessionId)?.ordinals.size ?? 0;
27635
27706
  const content = message?.content;
27636
27707
  if (!Array.isArray(content)) return;
27637
27708
  const timestamp = Date.parse(asString(rec.timestamp)) || 0;
27638
- const sessionId = asString(rec.sessionId) || asString(rec.session_id);
27639
27709
  const gitBranch = asString(rec.gitBranch) || void 0;
27640
27710
  for (const raw of content) {
27641
27711
  if (!raw || typeof raw !== "object") continue;
@@ -27644,7 +27714,15 @@ function collect(rec, events, pending) {
27644
27714
  if (type === "text" && rec.type === "assistant") {
27645
27715
  const text = asString(block.text);
27646
27716
  if (text) {
27647
- events.push({ kind: "assistant-text", text, tool: "", gitBranch, timestamp, sessionId });
27717
+ st2.events.push({
27718
+ kind: "assistant-text",
27719
+ text,
27720
+ tool: "",
27721
+ gitBranch,
27722
+ turn,
27723
+ timestamp,
27724
+ sessionId
27725
+ });
27648
27726
  }
27649
27727
  continue;
27650
27728
  }
@@ -27653,49 +27731,76 @@ function collect(rec, events, pending) {
27653
27731
  const input = block.input ?? {};
27654
27732
  const cmdField = COMMAND_TOOLS[tool];
27655
27733
  const writeField = WRITE_TOOLS[tool];
27734
+ const readField = READ_TOOLS[tool];
27656
27735
  let ev = null;
27657
27736
  if (cmdField) {
27658
27737
  const text = asString(input[cmdField]);
27659
- if (text) ev = { kind: "command", text, tool, gitBranch, timestamp, sessionId };
27738
+ if (text) ev = { kind: "command", text, tool, gitBranch, turn, timestamp, sessionId };
27660
27739
  } else if (writeField) {
27661
27740
  const text = asString(input[writeField]);
27662
- if (text) ev = { kind: "file-write", text, tool, gitBranch, timestamp, sessionId };
27741
+ if (text) ev = { kind: "file-write", text, tool, gitBranch, turn, timestamp, sessionId };
27742
+ } else if (readField) {
27743
+ const text = asString(input[readField]);
27744
+ if (text) {
27745
+ ev = {
27746
+ kind: "file-read",
27747
+ text,
27748
+ tool,
27749
+ partial: PARTIAL_READ_FIELDS.some((f) => isSet(input[f])),
27750
+ gitBranch,
27751
+ turn,
27752
+ timestamp,
27753
+ sessionId
27754
+ };
27755
+ }
27663
27756
  }
27664
27757
  if (ev) {
27665
- events.push(ev);
27666
27758
  const id = asString(block.id);
27667
- if (id) pending.set(id, ev);
27759
+ if (id) ev.toolUseId = id;
27760
+ st2.events.push(ev);
27761
+ if (id) st2.pending.set(id, ev);
27668
27762
  }
27669
27763
  continue;
27670
27764
  }
27671
27765
  if (type === "tool_result") {
27672
27766
  const id = asString(block.tool_use_id);
27673
- const ev = id ? pending.get(id) : void 0;
27767
+ const ev = id ? st2.pending.get(id) : void 0;
27674
27768
  if (!ev) continue;
27675
- pending.delete(id);
27769
+ st2.pending.delete(id);
27770
+ const out = resultText(block.content);
27676
27771
  ev.isError = block.is_error === true;
27677
- ev.emptyOutput = resultText(block.content).trim().length === 0;
27772
+ ev.emptyOutput = out.trim().length === 0;
27773
+ ev.outputChars = out.length;
27774
+ if (ev.kind === "file-read") {
27775
+ ev.outputTokens = countTokens(out);
27776
+ const lineCount = readResultLines(rec, ev.text);
27777
+ if (lineCount !== void 0) ev.outputLines = lineCount;
27778
+ }
27678
27779
  }
27679
27780
  }
27680
27781
  }
27681
- var WRITE_TOOLS, COMMAND_TOOLS, MAX_TRANSCRIPTS, MAX_LINES2, EMPTY, cache;
27782
+ var WRITE_TOOLS, READ_TOOLS, PARTIAL_READ_FIELDS, COMMAND_TOOLS, MAX_TRANSCRIPTS, MAX_LINES2, cache;
27682
27783
  var init_transcript = __esm({
27683
27784
  "src/core/transcript.ts"() {
27684
27785
  "use strict";
27685
27786
  init_define_WEB_FIRST_SEGMENTS();
27787
+ init_tokens();
27686
27788
  init_session_parser();
27687
27789
  WRITE_TOOLS = {
27688
27790
  Write: "file_path",
27689
27791
  Edit: "file_path",
27690
27792
  NotebookEdit: "notebook_path"
27691
27793
  };
27794
+ READ_TOOLS = {
27795
+ Read: "file_path"
27796
+ };
27797
+ PARTIAL_READ_FIELDS = ["offset", "limit", "pages"];
27692
27798
  COMMAND_TOOLS = {
27693
27799
  Bash: "command",
27694
27800
  PowerShell: "command"
27695
27801
  };
27696
27802
  MAX_TRANSCRIPTS = 5;
27697
27803
  MAX_LINES2 = 2e5;
27698
- EMPTY = { events: [], filesRead: 0, truncated: false };
27699
27804
  cache = /* @__PURE__ */ new Map();
27700
27805
  }
27701
27806
  });
@@ -27833,7 +27938,7 @@ function isUnverified(ev) {
27833
27938
  async function checkUnverifiedGateClaimedClean(ctx) {
27834
27939
  const { events } = await readProjectTranscript(ctx.currentProject);
27835
27940
  if (events.length === 0) return [];
27836
- const ordered = [...events].sort((a, b2) => a.timestamp - b2.timestamp);
27941
+ const ordered = events.filter((e) => e.kind !== "file-read").sort((a, b2) => a.timestamp - b2.timestamp);
27837
27942
  const issues = [];
27838
27943
  const reported = /* @__PURE__ */ new Set();
27839
27944
  for (let i2 = 0; i2 < ordered.length; i2++) {
@@ -27914,7 +28019,7 @@ function isBranchAway(cmd) {
27914
28019
  async function checkDefaultBranchAccumulation(ctx) {
27915
28020
  const { events } = await readProjectTranscript(ctx.currentProject);
27916
28021
  if (events.length === 0) return [];
27917
- const ordered = [...events].sort((a, b2) => a.timestamp - b2.timestamp);
28022
+ const ordered = events.filter((e) => e.kind !== "file-read").sort((a, b2) => a.timestamp - b2.timestamp);
27918
28023
  const pending = /* @__PURE__ */ new Set();
27919
28024
  let branch = "";
27920
28025
  let firstWrite = "";
@@ -28058,6 +28163,93 @@ var init_unresolvable_sha = __esm({
28058
28163
  }
28059
28164
  });
28060
28165
 
28166
+ // src/core/checks/session/large-read.ts
28167
+ import { isAbsolute as isAbsolute6, relative as relative6 } from "node:path";
28168
+ function qualifies(ev) {
28169
+ if (ev.kind !== "file-read" || ev.partial || ev.isError) return false;
28170
+ return (ev.outputTokens ?? 0) >= LARGE_READ_TOKENS;
28171
+ }
28172
+ function fmt(n7) {
28173
+ return n7.toLocaleString("en-US");
28174
+ }
28175
+ function displayPath(path21, project) {
28176
+ const rel = relative6(project, path21);
28177
+ if (rel && !rel.startsWith("..") && !isAbsolute6(rel)) return rel.replace(/\\/g, "/");
28178
+ return path21;
28179
+ }
28180
+ function fileKey(path21) {
28181
+ const slashed = path21.replace(/\\/g, "/");
28182
+ return process.platform === "win32" ? slashed.toLowerCase() : slashed;
28183
+ }
28184
+ async function checkLargeRead(ctx) {
28185
+ const read = await readProjectTranscript(ctx.currentProject);
28186
+ const byCall = /* @__PURE__ */ new Map();
28187
+ const unkeyed = [];
28188
+ for (const ev of read.events) {
28189
+ if (!qualifies(ev)) continue;
28190
+ const hit = { ev, tokens: ev.outputTokens ?? 0, carried: turnsCarried(read, ev) };
28191
+ if (!ev.toolUseId) {
28192
+ unkeyed.push(hit);
28193
+ continue;
28194
+ }
28195
+ const prev = byCall.get(ev.toolUseId);
28196
+ if (!prev || hit.carried < prev.carried) byCall.set(ev.toolUseId, hit);
28197
+ }
28198
+ const hits = [...byCall.values(), ...unkeyed];
28199
+ if (hits.length === 0) return [];
28200
+ let totalTokens = 0;
28201
+ let carry = 0;
28202
+ const files = /* @__PURE__ */ new Map();
28203
+ for (const { ev, tokens, carried } of hits) {
28204
+ totalTokens += tokens;
28205
+ carry += tokens * carried;
28206
+ const key = fileKey(ev.text);
28207
+ const file2 = files.get(key) ?? { path: ev.text, reads: 0, tokens: 0 };
28208
+ file2.reads += 1;
28209
+ file2.tokens += tokens;
28210
+ if (ev.outputLines !== void 0) file2.lines = Math.max(file2.lines ?? 0, ev.outputLines);
28211
+ files.set(key, file2);
28212
+ }
28213
+ const top = [...files.values()].sort((a, b2) => b2.tokens - a.tokens || a.path.localeCompare(b2.path)).slice(0, TOP_FILES);
28214
+ const topLines = top.map((f) => {
28215
+ const reads = `${f.reads} read${f.reads === 1 ? "" : "s"}`;
28216
+ const lines = f.lines !== void 0 ? `, ${fmt(f.lines)} line${f.lines === 1 ? "" : "s"}` : "";
28217
+ return ` ${displayPath(f.path, ctx.currentProject)} -- ${reads}, ${fmt(f.tokens)} tokens${lines}`;
28218
+ });
28219
+ const count = hits.length;
28220
+ const detail = [
28221
+ `Largest files by tokens read whole:`,
28222
+ ...topLines,
28223
+ `Carry = each Read's result tokens x the later turns of its session that re-sent it (to the session's end or its next /compact). An estimate: tokens are counted with a proxy tokenizer, and a result's first re-send is a cache write rather than a read.`
28224
+ ];
28225
+ if (read.truncated) {
28226
+ detail.push(
28227
+ `Transcript read was capped (${read.filesRead} most recent transcripts, bounded line count): these figures cover only what was read, so the real totals are higher.`
28228
+ );
28229
+ }
28230
+ return [
28231
+ {
28232
+ severity: "info",
28233
+ check: "session-large-read",
28234
+ ruleId: "session-large-read/large-read",
28235
+ line: 0,
28236
+ message: `${count} whole-file Read${count === 1 ? "" : "s"} of ${fmt(LARGE_READ_TOKENS)}+ tokens (${fmt(totalTokens)} tokens); est. ${fmt(carry)} tokens of cache-read carry on later turns`,
28237
+ detail: detail.join("\n"),
28238
+ suggestion: "Before reading a large file whole, find the part you need with `grep -n` (or the Grep tool) and Read just that range with `offset`/`limit`. For a question that needs the whole file, delegate it to a subagent: its reads stay in its own context and only the answer comes back."
28239
+ }
28240
+ ];
28241
+ }
28242
+ var LARGE_READ_TOKENS, TOP_FILES;
28243
+ var init_large_read = __esm({
28244
+ "src/core/checks/session/large-read.ts"() {
28245
+ "use strict";
28246
+ init_define_WEB_FIRST_SEGMENTS();
28247
+ init_transcript();
28248
+ LARGE_READ_TOKENS = 4e3;
28249
+ TOP_FILES = 3;
28250
+ }
28251
+ });
28252
+
28061
28253
  // src/core/checks/ci-coverage.ts
28062
28254
  import { readdir as readdir4, readFile as readFile4 } from "node:fs/promises";
28063
28255
  import { join as join10 } from "node:path";
@@ -29108,7 +29300,7 @@ import { readFileSync as readFileSync8 } from "node:fs";
29108
29300
  import { resolve as resolve15, dirname as dirname7 } from "node:path";
29109
29301
  import { fileURLToPath as fileURLToPath2 } from "node:url";
29110
29302
  function loadVersion() {
29111
- if (true) return "0.24.0";
29303
+ if (true) return "0.25.0";
29112
29304
  try {
29113
29305
  const __dir = dirname7(fileURLToPath2(import.meta.url));
29114
29306
  const pkgPath = resolve15(__dir, "../package.json");
@@ -29357,6 +29549,8 @@ async function runAudit(projectRoot, activeChecks, options = {}) {
29357
29549
  sessionPromises.push(checkDefaultBranchAccumulation(sessionCtx));
29358
29550
  if (sessionChecksToRun.includes("session-unresolvable-sha"))
29359
29551
  sessionPromises.push(checkUnresolvableSha(sessionCtx));
29552
+ if (sessionChecksToRun.includes("session-large-read"))
29553
+ sessionPromises.push(checkLargeRead(sessionCtx));
29360
29554
  const sessionResults = await Promise.all(sessionPromises);
29361
29555
  const sessionIssues = sessionResults.flat();
29362
29556
  fileResults.push({
@@ -29562,6 +29756,7 @@ var init_audit = __esm({
29562
29756
  init_unverified_gate_claimed_clean();
29563
29757
  init_default_branch_accumulation();
29564
29758
  init_unresolvable_sha();
29759
+ init_large_read();
29565
29760
  init_ci_coverage();
29566
29761
  init_ci_secrets();
29567
29762
  init_content_secrets();
@@ -29607,7 +29802,8 @@ var init_audit = __esm({
29607
29802
  "session-shared-temp-path",
29608
29803
  "session-unverified-gate-claimed-clean",
29609
29804
  "session-default-branch-accumulation",
29610
- "session-unresolvable-sha"
29805
+ "session-unresolvable-sha",
29806
+ "session-large-read"
29611
29807
  ];
29612
29808
  ALL_SKILL_CHECKS = [
29613
29809
  "skill-frontmatter",
@@ -56870,49 +57066,49 @@ var require_fast_uri = __commonJS({
56870
57066
  schemelessOptions.skipEscape = true;
56871
57067
  return serialize(resolved, schemelessOptions);
56872
57068
  }
56873
- function resolveComponent(base, relative8, options, skipNormalization) {
57069
+ function resolveComponent(base, relative9, options, skipNormalization) {
56874
57070
  const target = {};
56875
57071
  if (!skipNormalization) {
56876
57072
  base = parse5(serialize(base, options), options);
56877
- relative8 = parse5(serialize(relative8, options), options);
57073
+ relative9 = parse5(serialize(relative9, options), options);
56878
57074
  }
56879
57075
  options = options || {};
56880
- if (!options.tolerant && relative8.scheme) {
56881
- target.scheme = relative8.scheme;
56882
- target.userinfo = relative8.userinfo;
56883
- target.host = relative8.host;
56884
- target.port = relative8.port;
56885
- target.path = removeDotSegments(relative8.path || "");
56886
- target.query = relative8.query;
57076
+ if (!options.tolerant && relative9.scheme) {
57077
+ target.scheme = relative9.scheme;
57078
+ target.userinfo = relative9.userinfo;
57079
+ target.host = relative9.host;
57080
+ target.port = relative9.port;
57081
+ target.path = removeDotSegments(relative9.path || "");
57082
+ target.query = relative9.query;
56887
57083
  } else {
56888
- if (relative8.userinfo !== void 0 || relative8.host !== void 0 || relative8.port !== void 0) {
56889
- target.userinfo = relative8.userinfo;
56890
- target.host = relative8.host;
56891
- target.port = relative8.port;
56892
- target.path = removeDotSegments(relative8.path || "");
56893
- target.query = relative8.query;
57084
+ if (relative9.userinfo !== void 0 || relative9.host !== void 0 || relative9.port !== void 0) {
57085
+ target.userinfo = relative9.userinfo;
57086
+ target.host = relative9.host;
57087
+ target.port = relative9.port;
57088
+ target.path = removeDotSegments(relative9.path || "");
57089
+ target.query = relative9.query;
56894
57090
  } else {
56895
- if (!relative8.path) {
57091
+ if (!relative9.path) {
56896
57092
  target.path = base.path;
56897
- if (relative8.query !== void 0) {
56898
- target.query = relative8.query;
57093
+ if (relative9.query !== void 0) {
57094
+ target.query = relative9.query;
56899
57095
  } else {
56900
57096
  target.query = base.query;
56901
57097
  }
56902
57098
  } else {
56903
- if (relative8.path[0] === "/") {
56904
- target.path = removeDotSegments(relative8.path);
57099
+ if (relative9.path[0] === "/") {
57100
+ target.path = removeDotSegments(relative9.path);
56905
57101
  } else {
56906
57102
  if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
56907
- target.path = "/" + relative8.path;
57103
+ target.path = "/" + relative9.path;
56908
57104
  } else if (!base.path) {
56909
- target.path = relative8.path;
57105
+ target.path = relative9.path;
56910
57106
  } else {
56911
- target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative8.path;
57107
+ target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative9.path;
56912
57108
  }
56913
57109
  target.path = removeDotSegments(target.path);
56914
57110
  }
56915
- target.query = relative8.query;
57111
+ target.query = relative9.query;
56916
57112
  }
56917
57113
  target.userinfo = base.userinfo;
56918
57114
  target.host = base.host;
@@ -56920,7 +57116,7 @@ var require_fast_uri = __commonJS({
56920
57116
  }
56921
57117
  target.scheme = base.scheme;
56922
57118
  }
56923
- target.fragment = relative8.fragment;
57119
+ target.fragment = relative9.fragment;
56924
57120
  return target;
56925
57121
  }
56926
57122
  function equal(uriA, uriB, options) {
@@ -59407,11 +59603,11 @@ var require_format = __commonJS({
59407
59603
  }
59408
59604
  function getFormat(fmtDef) {
59409
59605
  const code = fmtDef instanceof RegExp ? (0, codegen_1.regexpCode)(fmtDef) : opts.code.formats ? (0, codegen_1._)`${opts.code.formats}${(0, codegen_1.getProperty)(schema)}` : void 0;
59410
- const fmt = gen.scopeValue("formats", { key: schema, ref: fmtDef, code });
59606
+ const fmt2 = gen.scopeValue("formats", { key: schema, ref: fmtDef, code });
59411
59607
  if (typeof fmtDef == "object" && !(fmtDef instanceof RegExp)) {
59412
- return [fmtDef.type || "string", fmtDef.validate, (0, codegen_1._)`${fmt}.validate`];
59608
+ return [fmtDef.type || "string", fmtDef.validate, (0, codegen_1._)`${fmt2}.validate`];
59413
59609
  }
59414
- return ["string", fmtDef, fmt];
59610
+ return ["string", fmtDef, fmt2];
59415
59611
  }
59416
59612
  function validCondition() {
59417
59613
  if (typeof formatDef == "object" && !(formatDef instanceof RegExp) && formatDef.async) {
@@ -60081,8 +60277,8 @@ var require_limit = __commonJS({
60081
60277
  ref: self2.formats,
60082
60278
  code: opts.code.formats
60083
60279
  });
60084
- const fmt = gen.const("fmt", (0, codegen_1._)`${fmts}[${fCxt.schemaCode}]`);
60085
- cxt.fail$data((0, codegen_1.or)((0, codegen_1._)`typeof ${fmt} != "object"`, (0, codegen_1._)`${fmt} instanceof RegExp`, (0, codegen_1._)`typeof ${fmt}.compare != "function"`, compareCode(fmt)));
60280
+ const fmt2 = gen.const("fmt", (0, codegen_1._)`${fmts}[${fCxt.schemaCode}]`);
60281
+ cxt.fail$data((0, codegen_1.or)((0, codegen_1._)`typeof ${fmt2} != "object"`, (0, codegen_1._)`${fmt2} instanceof RegExp`, (0, codegen_1._)`typeof ${fmt2}.compare != "function"`, compareCode(fmt2)));
60086
60282
  }
60087
60283
  function validateFormat() {
60088
60284
  const format3 = fCxt.schema;
@@ -60092,15 +60288,15 @@ var require_limit = __commonJS({
60092
60288
  if (typeof fmtDef != "object" || fmtDef instanceof RegExp || typeof fmtDef.compare != "function") {
60093
60289
  throw new Error(`"${keyword}": format "${format3}" does not define "compare" function`);
60094
60290
  }
60095
- const fmt = gen.scopeValue("formats", {
60291
+ const fmt2 = gen.scopeValue("formats", {
60096
60292
  key: format3,
60097
60293
  ref: fmtDef,
60098
60294
  code: opts.code.formats ? (0, codegen_1._)`${opts.code.formats}${(0, codegen_1.getProperty)(format3)}` : void 0
60099
60295
  });
60100
- cxt.fail$data(compareCode(fmt));
60296
+ cxt.fail$data(compareCode(fmt2));
60101
60297
  }
60102
- function compareCode(fmt) {
60103
- return (0, codegen_1._)`${fmt}.compare(${data}, ${schemaCode}) ${KWDs[keyword].fail} 0`;
60298
+ function compareCode(fmt2) {
60299
+ return (0, codegen_1._)`${fmt2}.compare(${data}, ${schemaCode}) ${KWDs[keyword].fail} 0`;
60104
60300
  }
60105
60301
  },
60106
60302
  dependencies: ["format"]
@@ -62825,6 +63021,7 @@ var init_server4 = __esm({
62825
63021
  init_tokens();
62826
63022
  init_git();
62827
63023
  init_paths();
63024
+ init_transcript();
62828
63025
  init_version2();
62829
63026
  contextCheckEnum = external_exports.enum(ALL_CHECKS);
62830
63027
  mcpCheckEnum = external_exports.enum(ALL_MCP_CHECKS);
@@ -63077,7 +63274,7 @@ var init_server4 = __esm({
63077
63274
  );
63078
63275
  server.tool(
63079
63276
  "ctxlint_session_audit",
63080
- "Audit AI agent session data for cross-project consistency. Checks for missing GitHub secrets, diverged config files, missing workflows, stale memory entries, and duplicate memories across sibling repositories.",
63277
+ "Audit AI agent session data for cross-project consistency and session hazards. Checks for missing GitHub secrets, diverged config files and missing workflows across sibling repositories; stale, duplicate or overflowing memory entries; command loops; and, from Claude Code transcripts, shared temp paths, gates claimed clean after failing, edits piling up on the default branch, unresolvable commit SHAs in memory, and large whole-file Reads re-sent as context on later turns.",
63081
63278
  {
63082
63279
  projectPath: external_exports.string().optional().describe("Path to the project root. Defaults to current working directory."),
63083
63280
  checks: external_exports.array(sessionCheckEnum).optional().describe("Specific session checks to run (default: all session-* checks).")
@@ -63110,6 +63307,7 @@ var init_server4 = __esm({
63110
63307
  resetGit();
63111
63308
  resetPathsCache();
63112
63309
  resetPackageJsonCache();
63310
+ clearTranscriptCache();
63113
63311
  }
63114
63312
  }
63115
63313
  );
@@ -70128,6 +70326,13 @@ function buildRuleDescriptors() {
70128
70326
  },
70129
70327
  helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
70130
70328
  },
70329
+ {
70330
+ id: "ctxlint/session-large-read",
70331
+ shortDescription: {
70332
+ text: "Whole-file Reads of large files re-sent as context on every later turn"
70333
+ },
70334
+ helpUri: "https://github.com/yawlabs/ctxlint#what-it-checks"
70335
+ },
70131
70336
  {
70132
70337
  id: "ctxlint/skill-frontmatter",
70133
70338
  shortDescription: { text: "Skill/agent definition missing required frontmatter" },
@@ -70362,6 +70567,7 @@ Fixed ${applied.totalFixes} issue${applied.totalFixes !== 1 ? "s" : ""} in ${app
70362
70567
  resetGit();
70363
70568
  resetPathsCache();
70364
70569
  resetPackageJsonCache();
70570
+ clearTranscriptCache();
70365
70571
  }
70366
70572
  if (opts.watch) {
70367
70573
  const chalk2 = (await Promise.resolve().then(() => (init_source(), source_exports))).default;
@@ -70458,6 +70664,7 @@ Fixed ${applied.totalFixes} issue${applied.totalFixes !== 1 ? "s" : ""} in ${app
70458
70664
  resetPathsCache();
70459
70665
  resetPackageJsonCache();
70460
70666
  clearFileCache();
70667
+ clearTranscriptCache();
70461
70668
  }
70462
70669
  console.log(chalk2.dim("\nWatching for changes... (Ctrl+C to stop)\n"));
70463
70670
  }, 300);
@@ -70651,6 +70858,7 @@ var init_cli = __esm({
70651
70858
  init_ora();
70652
70859
  init_paths();
70653
70860
  init_cache();
70861
+ init_transcript();
70654
70862
  init_reporter();
70655
70863
  init_fixer();
70656
70864
  init_tokens();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yawlabs/ctxlint",
3
- "version": "0.24.0",
3
+ "version": "0.25.0",
4
4
  "mcpName": "io.github.YawLabs/ctxlint",
5
5
  "description": "Lint your AI agent context files, MCP server configs, and session data against your actual codebase",
6
6
  "bin": {