@drkmttr/adhere 0.17.5 → 0.18.1

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.
@@ -1,4 +1,4 @@
1
- import { decodeConfig, resolveConfig, type Rule, type Rules } from "#config.ts";
1
+ import { decodeConfig, resolveConfig, type JevState, type Rule, type Rules } from "#config.ts";
2
2
  import {
3
3
  type AskOptions,
4
4
  ContextOverflow,
@@ -12,8 +12,9 @@ import {
12
12
  import { AdhereConfig } from "#services/AdhereConfig.ts";
13
13
  import { Credentials } from "#services/Credentials.ts";
14
14
  import { JevLive } from "#services/Jev.http.ts";
15
- import { Jev } from "#services/Jev.ts";
16
- import { Effect, Layer, Redacted } from "effect";
15
+ import { describeState, Jev, judgeBody, locateBody, requestsOf } from "#services/Jev.ts";
16
+ import { questionsOf, requestOf, type SystemOneRequest } from "#providers/systemOne.ts";
17
+ import { Effect, Layer, Logger, Redacted, References } from "effect";
17
18
  import { FetchHttpClient } from "effect/http";
18
19
  import { describe, expect, it } from "vite-plus/test";
19
20
 
@@ -25,6 +26,12 @@ const named: Rule = {
25
26
  const lines = ["const port = 3000;"];
26
27
  const file = { path: "/repo/src/server.ts", contents: lines.join("\n") };
27
28
 
29
+ // Restore JSON escapes only inside string tokens, leaving layout and source backslashes intact.
30
+ const jsonFromDiagnostic = (text: string): string =>
31
+ text.replace(/"(?:\\[\s\S]|[^"\\])*"/g, (token) =>
32
+ token.replace(/\n/g, "\\n").replace(/\t/g, "\\t"),
33
+ );
34
+
28
35
  /**
29
36
  * Jev under a config whose provider answers: any request Jev's own provider
30
37
  * sends, or any read of its key, dies. The saved OpenAI key is "sk-saved".
@@ -38,10 +45,13 @@ const judged = (
38
45
  ask,
39
46
  ...(savedKey === undefined ? {} : { savedKey }),
40
47
  },
48
+ kind: "judge" | "locate" = "judge",
41
49
  ) =>
42
50
  Effect.gen(function* () {
43
51
  const jev = yield* Jev;
44
- return yield* jev.judge(lines, rules, [], file);
52
+ return kind === "judge"
53
+ ? yield* jev.judge(lines, rules, [], file)
54
+ : yield* jev.locate(lines, rules, file);
45
55
  }).pipe(
46
56
  Effect.provide(
47
57
  JevLive.pipe(
@@ -117,6 +127,272 @@ describe("a config's provider", () => {
117
127
  expect(blocked).toMatchObject({ _tag: "JevBlocked", ray: "ref-7" });
118
128
  });
119
129
 
130
+ it.each([
131
+ ["All", "judge"],
132
+ ["Trace", "judge"],
133
+ ["Debug", "judge"],
134
+ ["All", "locate"],
135
+ ["Trace", "locate"],
136
+ ["Debug", "locate"],
137
+ ] as const)(
138
+ "logs split requests after appendState with line criteria only at All (minimum: %s, kind: %s)",
139
+ async (minimum, kind) => {
140
+ const context = `export function caller() {\n\tconst escaped = "\\n\\t";\n\treturn "${"hook-context".repeat(40_000)}";\n}`;
141
+ const added = { references: { "port:caller.ts": context } };
142
+ const hooked: Rule = {
143
+ ...plain,
144
+ appliesTo: ["a server"],
145
+ appendState: async () => added,
146
+ };
147
+ const body = (
148
+ kind === "judge"
149
+ ? judgeBody("clef-flash", lines, { plain: hooked })
150
+ : locateBody("clef-flash", lines, { plain: hooked })
151
+ ) as SystemOneRequest & { readonly state: JevState };
152
+ const requests = requestsOf({ ...body, state: describeState({ ...body.state, ...added }) });
153
+ expect(requests.length).toBeGreaterThan(1);
154
+ const request = requests[0]!;
155
+ const asked: Array<{
156
+ state: State;
157
+ questions: ReadonlyArray<Question>;
158
+ apiKey: string | undefined;
159
+ }> = [];
160
+ let originalBody = "";
161
+ const originalAdded = JSON.stringify(added);
162
+ const logs: Array<Logger.Options<unknown>> = [];
163
+ const overflowed = await Effect.runPromise(
164
+ Effect.flip(
165
+ judged(
166
+ async (state, questions, { apiKey }) => {
167
+ asked.push({ state, questions, apiKey });
168
+ originalBody = JSON.stringify(requestOf("clef-flash", state, questions));
169
+ throw new ContextOverflow("too long");
170
+ },
171
+ "openai",
172
+ { plain: hooked },
173
+ undefined,
174
+ kind,
175
+ ),
176
+ ).pipe(
177
+ Effect.provide(Logger.layer([Logger.make((entry) => logs.push(entry))])),
178
+ Effect.provideService(References.MinimumLogLevel, minimum),
179
+ ),
180
+ );
181
+ expect(overflowed._tag).toBe("JevOverflow");
182
+ expect(asked).toEqual([
183
+ { state: request.state, questions: questionsOf(request.questions), apiKey: "sk-saved" },
184
+ ]);
185
+ const messages = logs.flatMap(({ message }) =>
186
+ Array.isArray(message) ? message : [message],
187
+ );
188
+ const oversized = messages.filter((message) =>
189
+ String(message).includes("too long request payload"),
190
+ );
191
+ if (minimum !== "Debug") {
192
+ const label = `${kind}: too long request payload (provider overflow)\n`;
193
+ expect(oversized).toHaveLength(1);
194
+ expect(String(oversized[0]).startsWith(label)).toBe(true);
195
+ const json = jsonFromDiagnostic(String(oversized[0]).slice(label.length));
196
+ const logged = JSON.parse(json) as typeof request;
197
+ const id = kind === "judge" ? "plain" : "start:plain:1";
198
+ expect(Object.keys(request.questions)).toEqual([id]);
199
+ const question = request.questions[id]!;
200
+ expect(question.instructions).toMatchObject({ rule: plain.description, must: plain.must });
201
+ if (minimum === "Trace" && question.type === "choice") {
202
+ const { criteria, ...withoutCriteria } = question;
203
+ expect(criteria).toEqual({ "1": lines[0] });
204
+ expect(logged.questions[id]).not.toHaveProperty("criteria");
205
+ expect(logged).toEqual({ ...request, questions: { [id]: withoutCriteria } });
206
+ expect(json).toBe(JSON.stringify(logged, null, 2));
207
+ } else {
208
+ expect(json).toBe(JSON.stringify(request, null, 2));
209
+ expect(logged).toEqual(request);
210
+ expect(logged.questions[id]?.criteria).toBeDefined();
211
+ }
212
+ expect(logged.state).toMatchObject(added);
213
+ if (minimum === "Trace") expect(Object.keys(logged.state)[0]).toBe("code");
214
+ expect(logged.model).toBe("clef-flash");
215
+ expect(
216
+ logs.find(({ message }) => Array.isArray(message) && message.includes(oversized[0]))
217
+ ?.logLevel,
218
+ ).toBe("Trace");
219
+ } else {
220
+ expect(oversized).toEqual([]);
221
+ const label = `${kind}: first ${kind === "locate" ? "match-to-lines " : ""}request payload\n`;
222
+ const previews = messages.filter((message) => String(message).startsWith(label));
223
+ expect(previews).toHaveLength(1);
224
+ const logged = JSON.parse(
225
+ jsonFromDiagnostic(String(previews[0]).slice(label.length)),
226
+ ) as SystemOneRequest;
227
+ expect(logged.state).toEqual(request.state);
228
+ expect(Object.keys(logged.state)[0]).toBe("code");
229
+ expect(logged.model).toBe(request.model);
230
+ const id = kind === "judge" ? "plain" : "start:plain:1";
231
+ if (kind === "locate") {
232
+ const { criteria, ...question } = request.questions[id]!;
233
+ expect(criteria).toBeDefined();
234
+ expect(logged.questions).toEqual({ [id]: question });
235
+ } else {
236
+ expect(logged.questions).toEqual(request.questions);
237
+ }
238
+ }
239
+ for (const message of messages.filter((message) =>
240
+ String(message).includes("request payload"),
241
+ )) {
242
+ expect(String(message)).toContain("\n\tconst escaped = ");
243
+ expect(String(message)).toContain(String.raw`\\n\\t`);
244
+ expect(String(message)).not.toContain(String.raw`\n\tconst escaped`);
245
+ }
246
+ expect(originalBody).toContain(String.raw`\n\tconst escaped`);
247
+ expect(JSON.stringify(added)).toBe(originalAdded);
248
+ const sent = asked[0]!;
249
+ expect(JSON.stringify(requestOf("clef-flash", sent.state, sent.questions))).toBe(
250
+ originalBody,
251
+ );
252
+ expect(JSON.parse(originalBody)).toEqual(request);
253
+ expect(JSON.stringify(messages)).not.toContain("sk-saved");
254
+ expect(request).not.toHaveProperty("apiKey");
255
+ expect(request).not.toHaveProperty("signal");
256
+ expect(messages).toContain("attempt failed: too long");
257
+ },
258
+ );
259
+
260
+ it.each(["All", "Trace", "Debug", "Info"] as const)(
261
+ "previews only the first judge and match-to-lines request per shared JevLive (minimum: %s)",
262
+ async (minimum) => {
263
+ const context = `export function caller() {\n\tconst escaped = "\\n\\t";\n\treturn "${"preview-context".repeat(40_000)}";\n}`;
264
+ const added = { references: { "port:caller.ts": context } };
265
+ const readState = { workspacePackages: { server: "src" } };
266
+ const hookedRules = {
267
+ plain: { ...plain, appliesTo: ["a server"], appendState: async () => added },
268
+ named: { ...named, appliesTo: ["a server"], appendState: async () => added },
269
+ };
270
+ const files = [file, { path: "/repo/src/other.ts", contents: " const port = 8080;" }, file];
271
+ const expected = files.flatMap((current) => {
272
+ const code = current.contents.split("\n");
273
+ return ["judge", "locate"].flatMap((kind) =>
274
+ Object.entries(hookedRules).flatMap(([id, rule]) => {
275
+ const body = (
276
+ kind === "judge"
277
+ ? judgeBody("clef-flash", code, { [id]: rule }, [], readState)
278
+ : locateBody("clef-flash", code, { [id]: rule }, readState)
279
+ ) as SystemOneRequest & { readonly state: JevState };
280
+ const split = requestsOf({
281
+ ...body,
282
+ state: describeState({ ...body.state, ...added }),
283
+ });
284
+ expect(split.length).toBeGreaterThan(1);
285
+ return split;
286
+ }),
287
+ );
288
+ });
289
+ const firstJudge = expected[0]!;
290
+ const firstLocate = expected.find((body) =>
291
+ Object.keys(body.questions)[0]?.startsWith("start:"),
292
+ )!;
293
+ const asked: Array<SystemOneRequest> = [];
294
+ const snapshots: string[] = [];
295
+ const keys: Array<string | undefined> = [];
296
+ const logs: Array<Logger.Options<unknown>> = [];
297
+ const provider: Provider = {
298
+ model: "clef-flash",
299
+ savedKey: "openai",
300
+ ask: async (state, questions, { apiKey }) => {
301
+ const body = requestOf("clef-flash", state, questions);
302
+ asked.push(body);
303
+ snapshots.push(JSON.stringify(body));
304
+ keys.push(apiKey);
305
+ if (asked.length === 1) throw new RequestFailed("retry first request", 503);
306
+ return { answers: {} };
307
+ },
308
+ };
309
+ await Effect.runPromise(
310
+ Effect.gen(function* () {
311
+ const jev = yield* Jev;
312
+ for (const current of files) {
313
+ const code = current.contents.split("\n");
314
+ const judged = yield* jev.judge(code, hookedRules, [], current, readState);
315
+ expect(judged.probabilities).toEqual({});
316
+ expect(yield* jev.locate(code, hookedRules, current, readState)).toEqual({});
317
+ }
318
+ }).pipe(
319
+ Effect.provide(
320
+ JevLive.pipe(
321
+ Layer.provide([
322
+ Layer.succeed(FetchHttpClient.Fetch, (() => {
323
+ throw new Error("preview tests send no HTTP requests");
324
+ }) as unknown as typeof globalThis.fetch),
325
+ Layer.succeed(AdhereConfig, resolveConfig({ presets: [], rules: {}, provider })),
326
+ Layer.succeed(Credentials, {
327
+ key: () => Effect.succeed(Redacted.make("sk-saved")),
328
+ file: Effect.die("unused"),
329
+ save: () => Effect.die("unused"),
330
+ remove: () => Effect.die("unused"),
331
+ }),
332
+ ]),
333
+ ),
334
+ ),
335
+ Effect.provide(Logger.layer([Logger.make((entry) => logs.push(entry))])),
336
+ Effect.provideService(References.MinimumLogLevel, minimum),
337
+ ),
338
+ );
339
+ expect(asked).toEqual([firstJudge, ...expected]);
340
+ expect(asked.map((body) => JSON.stringify(body))).toEqual(snapshots);
341
+ expect(snapshots[0]).toContain(String.raw`\n\tconst escaped`);
342
+ expect(snapshots.map((json) => JSON.parse(json))).toEqual(asked);
343
+ expect(keys).toEqual(asked.map(() => "sk-saved"));
344
+ const messages = logs.flatMap(({ message }) =>
345
+ Array.isArray(message) ? message : [message],
346
+ );
347
+ const previews = messages.filter((message) => String(message).includes("request payload"));
348
+ if (minimum === "Info") {
349
+ expect(previews).toEqual([]);
350
+ expect(JSON.stringify(messages)).not.toContain("preview-context");
351
+ } else {
352
+ const labels = [
353
+ "judge: first request payload\n",
354
+ "locate: first match-to-lines request payload\n",
355
+ ];
356
+ expect(previews).toHaveLength(2);
357
+ for (const [index, body] of [firstJudge, firstLocate].entries()) {
358
+ const label = labels[index]!;
359
+ expect(String(previews[index]).startsWith(label)).toBe(true);
360
+ const text = String(previews[index]).slice(label.length);
361
+ expect(text).toContain("\n\tconst escaped = ");
362
+ expect(text).toContain(String.raw`\\n\\t`);
363
+ expect(text).not.toContain(String.raw`\n\tconst escaped`);
364
+ const json = jsonFromDiagnostic(text);
365
+ const logged = JSON.parse(json) as SystemOneRequest;
366
+ expect(logged.model).toBe(body.model);
367
+ expect(logged.state).toEqual(body.state);
368
+ expect(logged.state).toMatchObject({ ...readState, ...added });
369
+ if (minimum === "All") {
370
+ expect(json).toBe(JSON.stringify(body, null, 2));
371
+ expect(logged).toEqual(body);
372
+ } else {
373
+ expect(Object.keys(logged.state)[0]).toBe("code");
374
+ if (index === 0) {
375
+ expect(logged.questions).toEqual(body.questions);
376
+ expect(logged.questions.plain?.criteria).toBeDefined();
377
+ } else {
378
+ const { criteria, ...question } = body.questions["start:plain:1"]!;
379
+ expect(criteria).toEqual({ "1": lines[0] });
380
+ expect(logged.questions).toEqual({ "start:plain:1": question });
381
+ }
382
+ }
383
+ expect(
384
+ logs.find(({ message }) => Array.isArray(message) && message.includes(previews[index]))
385
+ ?.logLevel,
386
+ ).toBe("Debug");
387
+ }
388
+ }
389
+ expect(JSON.stringify(messages)).not.toContain("sk-saved");
390
+ expect(messages.some((message) => String(message).includes("too long request payload"))).toBe(
391
+ false,
392
+ );
393
+ },
394
+ );
395
+
120
396
  it("is asked as a method, so a class can implement it", async () => {
121
397
  class Fixed implements Provider {
122
398
  readonly model = "fixed";
@@ -1,5 +1,7 @@
1
1
  import {
2
2
  type AppendState,
3
+ DEFAULT_MAX_SYMBOL_DOCUMENTATION_LENGTH,
4
+ DEFAULT_MAX_SYMBOL_LENGTH,
3
5
  type JevState,
4
6
  type ReadState,
5
7
  type ResolvedConfig,
@@ -10,6 +12,7 @@ import { AdhereConfig } from "#services/AdhereConfig.ts";
10
12
  import { Credentials } from "#services/Credentials.ts";
11
13
  import { JevLive } from "#services/Jev.http.ts";
12
14
  import {
15
+ describeState,
13
16
  fits,
14
17
  Jev,
15
18
  judgeBody,
@@ -28,11 +31,11 @@ import { FetchHttpClient } from "effect/http";
28
31
  import { describe, expect, it } from "vite-plus/test";
29
32
 
30
33
  const plain: Rule = { description: "Ports must be named.", must: "const port = PORT;" };
31
- const lines = ["const port = 3000;"];
34
+ const lines = ["const port = 3000;"] as const;
32
35
  const codeState = { code: { "1": lines[0] } };
33
36
  const file = { path: "/repo/src/server.ts", contents: lines.join("\n") };
34
37
  const symbols = { port: "```typescript\nconst port: number\n```" };
35
- const references = { PORT: { "src/config.ts": "export const PORT = 3000;\r\n" } };
38
+ const references = { "PORT:src/config.ts": "export const PORT = 3000;\r\n" };
36
39
  const codeSufficiency = {
37
40
  type: "noul",
38
41
  instructions: {
@@ -73,9 +76,12 @@ const recordedJev = (
73
76
  Layer.provide([
74
77
  Layer.succeed(FetchHttpClient.Fetch, fetch),
75
78
  Layer.succeed(AdhereConfig, {
79
+ typeAware: false,
76
80
  model: "jev-latest",
77
81
  threshold: 0.8,
78
82
  sufficiencyThreshold: 0.6,
83
+ maxSymbolDocumentationLength: DEFAULT_MAX_SYMBOL_DOCUMENTATION_LENGTH,
84
+ maxSymbolLength: DEFAULT_MAX_SYMBOL_LENGTH,
79
85
  reads,
80
86
  rules: {},
81
87
  workspacePackages,
@@ -153,7 +159,19 @@ describe("prepared shared Jev reads", () => {
153
159
  project: { language: "typescript" },
154
160
  code: { "1": "replaced" },
155
161
  };
156
- const enriched = { ...readState, ...codeState };
162
+ const enriched = describeState({ ...readState, ...codeState });
163
+ expect(enriched.__comment).toMatch(/only.*code|code.*only/i);
164
+ expect(enriched.__comment).toMatch(/context/i);
165
+ expect(enriched.symbols).toEqual({ ...symbols, __comment: expect.stringMatching(/symbol/i) });
166
+ expect(enriched.references).toEqual({
167
+ ...references,
168
+ __comment: expect.stringMatching(/symbol.*path|symbol.*file/i),
169
+ });
170
+ expect(enriched.workspacePackages).toEqual({
171
+ orders: "packages/orders",
172
+ __comment: expect.stringMatching(/package/i),
173
+ });
174
+ expect(Object.keys(enriched.code)).toEqual(["1"]);
157
175
  expect(judgeBody("", lines, rules, [], readState).state).toEqual(enriched);
158
176
  expect(locateBody("", lines, rules, readState).state).toEqual(enriched);
159
177
  const groups = requestGroups(rules);
@@ -166,8 +184,8 @@ describe("prepared shared Jev reads", () => {
166
184
  expect(judgeBody("", lines, group, [], readState).state).toEqual(enriched);
167
185
  const body = locateBody("", lines, group, readState);
168
186
  expect(body.state).toEqual(enriched);
169
- expect(body.state.symbols).toBe(symbols);
170
- expect(body.state.references).toBe(references);
187
+ expect(body.state.symbols).toEqual({ ...symbols, __comment: expect.any(String) });
188
+ expect(body.state.references).toEqual({ ...references, __comment: expect.any(String) });
171
189
  for (const id of Object.keys(group)) {
172
190
  expect(body.questions[`sufficient:${id}`]).toEqual(sufficiencyQuestion(plain, readState));
173
191
  }
@@ -176,9 +194,56 @@ describe("prepared shared Jev reads", () => {
176
194
  expect(locateRequests(lines, rules, readState)).toBe(3);
177
195
  });
178
196
 
197
+ it("preserves caller comments and raw context through judge, locate, and appended maps", async () => {
198
+ const readState = {
199
+ __comment: "User context: only target code is audited.",
200
+ symbols: { ...symbols, __comment: "User symbol description." },
201
+ references: { ...references, __comment: "User reference description." },
202
+ workspacePackages: { orders: "packages/orders", __comment: "User package description." },
203
+ };
204
+ const original = JSON.stringify(readState);
205
+ const hooked = {
206
+ ...plain,
207
+ appendState: () => ({ references: { "port:src/caller.ts": "\tuse(port);\r\n" } }),
208
+ };
209
+ const { sent, layer } = recordedJev();
210
+ await Effect.runPromise(
211
+ Effect.gen(function* () {
212
+ const jev = yield* Jev;
213
+ yield* jev.judge(lines, { plain, hooked }, [], file, readState);
214
+ yield* jev.locate(lines, { plain, hooked }, file, readState);
215
+ }).pipe(Effect.provide(layer)),
216
+ );
217
+ expect(sent).toHaveLength(4);
218
+ for (const body of sent) {
219
+ expect(body.state.code).toEqual(codeState.code);
220
+ expect(body.state.__comment).toBe(readState.__comment);
221
+ expect(body.state.symbols).toEqual(readState.symbols);
222
+ expect(body.state.workspacePackages).toEqual(readState.workspacePackages);
223
+ }
224
+ expect(sent[0]?.state.references).toEqual(readState.references);
225
+ expect(sent[2]?.state.references).toEqual(readState.references);
226
+ for (const index of [1, 3]) {
227
+ expect(sent[index]?.state.references).toEqual({
228
+ "port:src/caller.ts": "\tuse(port);\r\n",
229
+ __comment: expect.stringMatching(/symbol.*path|symbol.*file/i),
230
+ });
231
+ }
232
+ expect(JSON.stringify(readState)).toBe(original);
233
+ expect(
234
+ sent
235
+ .slice(2)
236
+ .flatMap(({ questions }) => Object.values(questions))
237
+ .filter(({ type }) => type === "noul")
238
+ .every(({ instructions }) => !instructions.question.includes("__comment")),
239
+ ).toBe(true);
240
+ });
241
+
179
242
  it("names actual shared keys in sufficiency, including packages but excluding duplicate code", () => {
180
243
  expect(sufficiencyQuestion(plain)).toEqual(codeSufficiency);
181
- expect(sufficiencyQuestion(plain, { code: {} })).toEqual(codeSufficiency);
244
+ expect(sufficiencyQuestion(plain, { code: {}, __comment: "context description" })).toEqual(
245
+ codeSufficiency,
246
+ );
182
247
  expect(sufficiencyQuestion({ ...plain, appendState: () => ({ private: true }) })).toEqual(
183
248
  codeSufficiency,
184
249
  );
@@ -186,22 +251,32 @@ describe("prepared shared Jev reads", () => {
186
251
  { workspacePackages: {} },
187
252
  { symbols },
188
253
  { references },
189
- { workspacePackages: {}, symbols, references, project: true, code: {} },
254
+ {
255
+ workspacePackages: {},
256
+ symbols,
257
+ references,
258
+ project: true,
259
+ code: {},
260
+ __comment: "context description",
261
+ },
190
262
  ]) {
191
263
  const question = sufficiencyQuestion(plain, readState);
192
264
  expect(question.instructions.question).not.toContain("`code` alone");
193
- for (const key of Object.keys(readState).filter((key) => key !== "code")) {
265
+ for (const key of Object.keys(readState).filter(
266
+ (key) => key !== "code" && key !== "__comment",
267
+ )) {
194
268
  expect(question.instructions.question).toContain(`\`${key}\``);
195
269
  expect(question.criteria.true).toContain(`\`${key}\``);
196
270
  expect(question.criteria.false).toContain(`\`${key}\``);
197
271
  }
198
272
  expect(question.instructions.question.match(/`code`/g)).toHaveLength(2);
273
+ expect(JSON.stringify(question)).not.toContain("__comment");
199
274
  }
200
275
  });
201
276
 
202
277
  it("budgets all shared context beside every rule's questions", () => {
203
278
  const source = "export const value = 1;\n".repeat(2200);
204
- const readState = { references: { value: { "src/related.ts": source } } };
279
+ const readState = { references: { "value:src/related.ts": source } };
205
280
  const large = { ...plain, must: "x".repeat(60_000) };
206
281
  const hooked = { ...plain, appendState: () => ({ private: "x".repeat(120_000) }) };
207
282
  expect(fits(lines, { large, hooked })).toBe(true);
@@ -295,7 +370,7 @@ describe("prepared shared Jev reads", () => {
295
370
  };
296
371
  const rules = { plain, native };
297
372
  const readState = {
298
- references: { files: { "src/related.ts": "export const value = 1;\n".repeat(1500) } },
373
+ references: { "value:src/related.ts": "export const value = 1;\n".repeat(1500) },
299
374
  };
300
375
  const sampled = ["native"];
301
376
  expect(fits(long, rules, readState)).toBe(true);
@@ -338,7 +413,7 @@ describe("prepared shared Jev reads", () => {
338
413
 
339
414
  it("sends shared context once for non-hook rules and inherits it in private hook groups", async () => {
340
415
  const seen: Array<JevState> = [];
341
- const privateSymbols = [{ name: "private", section: 1, usages: 0 }];
416
+ const privateSymbols = { private: "const private: number" };
342
417
  const appendState: AppendState = (state, receivedFile) => {
343
418
  expect(receivedFile).toBe(file);
344
419
  seen.push(state);
@@ -373,16 +448,19 @@ describe("prepared shared Jev reads", () => {
373
448
  });
374
449
  expect(result.judged.linter).toEqual({ plain: 0.9, hooked: 0.9 });
375
450
  expect(Object.keys(result.located)).toEqual(Object.keys(rules));
376
- const enriched = { ...readState, ...codeState };
451
+ const enriched = describeState({ ...readState, ...codeState });
377
452
  expect(seen).toEqual([enriched, enriched, enriched, enriched]);
378
453
  for (const state of seen) {
379
- expect(state.symbols).toBe(symbols);
380
- expect(state.references).toBe(references);
381
- expect(state.workspacePackages).toBe(workspacePackages);
454
+ expect(state.symbols).toEqual({ ...symbols, __comment: expect.any(String) });
455
+ expect(state.references).toEqual({ ...references, __comment: expect.any(String) });
456
+ expect(state.workspacePackages).toEqual({
457
+ ...workspacePackages,
458
+ __comment: expect.any(String),
459
+ });
382
460
  }
383
461
  const expectedStates = [
384
462
  enriched,
385
- { ...enriched, private: "first", symbols: privateSymbols },
463
+ describeState({ ...enriched, private: "first", symbols: privateSymbols }),
386
464
  { ...enriched, private: "second" },
387
465
  ];
388
466
  expect(sent.map((body) => body.state)).toEqual([...expectedStates, ...expectedStates]);
@@ -416,9 +494,10 @@ describe("prepared shared Jev reads", () => {
416
494
  expect(readState.code).toEqual({});
417
495
  });
418
496
 
419
- it("does not prepare context in HTTP even with resolved default reads and omitted or partial shared state", async () => {
497
+ it("uses only caller-prepared context in HTTP with workspace-only defaults", async () => {
420
498
  const config = resolveConfig({ presets: [], rules: { plain } });
421
- expect(config.reads).toEqual(["workspacePackages", "symbols", "references"]);
499
+ expect(config.reads).toEqual(["workspacePackages"]);
500
+ expect(config.typeAware).toBe(false);
422
501
  const { sent, layer } = recordedJev({ unused: "packages/unused" }, config.reads);
423
502
  await Effect.runPromise(
424
503
  Effect.gen(function* () {
@@ -432,8 +511,8 @@ describe("prepared shared Jev reads", () => {
432
511
  expect(sent.map((body) => body.state)).toEqual([
433
512
  codeState,
434
513
  codeState,
435
- { symbols, ...codeState },
436
- { symbols, ...codeState },
514
+ describeState({ symbols, ...codeState }),
515
+ describeState({ symbols, ...codeState }),
437
516
  ]);
438
517
  });
439
518
  });
@@ -9,8 +9,8 @@ import {
9
9
  type Rules,
10
10
  } from "#config.ts";
11
11
  import { type Range, sectionsOf } from "#excerpt.ts";
12
- import type { SystemOneRequest } from "#providers/systemOne.ts";
13
- import { Context, type Effect, Record, Schema } from "effect";
12
+ import type { SystemOneQuestion, SystemOneRequest } from "#providers/systemOne.ts";
13
+ import { Context, Effect, LogLevel, Record, References, Schema } from "effect";
14
14
 
15
15
  export type { Rules };
16
16
  export type Lines = ReadonlyArray<string>;
@@ -139,6 +139,32 @@ const bytesOf = (value: unknown): number => encoder.encode(JSON.stringify(value)
139
139
  */
140
140
  export const tokensOf = (value: unknown): number => Math.ceil(bytesOf(value) / 3);
141
141
 
142
+ const READ_COMMENTS: Readonly<Record<string, string>> = {
143
+ symbols:
144
+ "Compiler-rendered types and documentation for symbols used in `code`; keys are symbol names. A declared type, assertion, or type predicate is only a compile-time claim, not proof of runtime validation. When `references` show an implementation, inspect its actual checks. A predicate that returns true, tests only truthiness, or checks only whether a value is an object does not validate the fields it claims.",
145
+ references:
146
+ "Declarations of symbols used in `code`. Each key is `symbol:pathname`: the symbol name as used in `code` followed by its repository-relative declaration path. Each value is a complete declaration excerpt.",
147
+ workspacePackages:
148
+ "Workspace packages imported by `code`; keys are package names and values are repository-relative package directories. Only packages imported by this file are listed.",
149
+ };
150
+
151
+ /** Explain the known context maps while preserving caller-provided descriptions and target code. */
152
+ export const describeState = (state: JevState): JevState => {
153
+ const described = { ...state };
154
+ for (const [name, comment] of Object.entries(READ_COMMENTS)) {
155
+ const value = state[name];
156
+ if (value !== null && typeof value === "object" && !Array.isArray(value))
157
+ described[name] = { __comment: comment, ...value };
158
+ }
159
+ if (
160
+ described.__comment === undefined &&
161
+ Object.keys(state).some((name) => name !== "code" && name !== "__comment")
162
+ )
163
+ described.__comment =
164
+ "Only `code` is the audit target; it is split into numbered sections. All other state entries provide context, not additional code to audit.";
165
+ return described;
166
+ };
167
+
142
168
  /**
143
169
  * The file as Jev reads it: its sections, by `sectionsOf`, under their
144
170
  * numbers from 1, so the locate question can name one. Prepared context is
@@ -149,15 +175,16 @@ const stateOf = (
149
175
  lines: Lines,
150
176
  readState: ReadState = {},
151
177
  sections: ReadonlyArray<Range> = sectionsOf(lines),
152
- ): JevState => ({
153
- ...readState,
154
- code: Object.fromEntries(
155
- sections.map((section, index) => [
156
- String(index + 1),
157
- lines.slice(section.first - 1, section.last).join("\n"),
158
- ]),
159
- ),
160
- });
178
+ ): JevState =>
179
+ describeState({
180
+ ...readState,
181
+ code: Object.fromEntries(
182
+ sections.map((section, index) => [
183
+ String(index + 1),
184
+ lines.slice(section.first - 1, section.last).join("\n"),
185
+ ]),
186
+ ),
187
+ });
161
188
 
162
189
  /** The field an example goes under in a question: its word, with `should not` as `should_not`. */
163
190
  const fieldOf = (example: Example): string => example.word.replace(" ", "_");
@@ -361,7 +388,7 @@ export const judgeBody = (
361
388
  * it scored below 0.6, 62% were false, where 22% of all were.
362
389
  */
363
390
  const sufficiencyQuestionWith = (fields: ReturnType<typeof ruleFields>, readState: ReadState) => {
364
- const keys = Object.keys(readState).filter((key) => key !== "code");
391
+ const keys = Object.keys(readState).filter((key) => key !== "code" && key !== "__comment");
365
392
  const context =
366
393
  keys.length === 0 ? undefined : ["`code`", ...keys.map((key) => `\`${key}\``)].join(" and ");
367
394
  return {
@@ -547,6 +574,43 @@ export const fits = (lines: Lines, rules: Rules, readState: ReadState = {}): boo
547
574
  /** A request to Jev, its questions typed as `Q`: `Question`, as adhere asks them, or anything an eval arm asks. */
548
575
  export type Body<Q = unknown> = SystemOneRequest<Q>;
549
576
 
577
+ type RequestPayload =
578
+ | Body<SystemOneQuestion>
579
+ | { readonly judge: Body<SystemOneQuestion>; readonly locate: Body<SystemOneQuestion> };
580
+
581
+ const traceBody = (body: Body<SystemOneQuestion>) => ({
582
+ ...body,
583
+ state: { code: body.state.code, ...body.state },
584
+ questions: Record.map(body.questions, (question, id) =>
585
+ question.type === "choice" && /^(start|end):/.test(id)
586
+ ? { ...question, criteria: undefined }
587
+ : question,
588
+ ),
589
+ });
590
+
591
+ /** Debug/Trace put target code first and omit line-option copies; All retains the complete request. */
592
+ export const logRequestPayload = (
593
+ message: string,
594
+ payload: () => RequestPayload,
595
+ level: "Debug" | "Trace" = "Trace",
596
+ ): Effect.Effect<void> =>
597
+ Effect.gen(function* () {
598
+ const minimum = yield* References.MinimumLogLevel;
599
+ if (LogLevel.isGreaterThan(minimum, level)) return;
600
+ const full = payload();
601
+ const visible =
602
+ minimum === "All"
603
+ ? full
604
+ : "questions" in full
605
+ ? traceBody(full)
606
+ : { judge: traceBody(full.judge), locate: traceBody(full.locate) };
607
+ // Consume escaped backslashes first so source literals such as "\\n" remain literals.
608
+ const text = JSON.stringify(visible, null, 2).replace(/\\\\|\\[nt]/g, (escaped) =>
609
+ escaped === "\\n" ? "\n" : escaped === "\\t" ? "\t" : escaped,
610
+ );
611
+ yield* (level === "Debug" ? Effect.logDebug : Effect.logTrace)(`${message}\n${text}`);
612
+ });
613
+
550
614
  /**
551
615
  * A body as requests Jev can take: its questions in order, with a new
552
616
  * request whenever the next question would not fit beside the state and