jev-agent-tools 0.1.4 → 0.3.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.
Files changed (180) hide show
  1. package/CHANGELOG.md +106 -1
  2. package/CONTRIBUTING.md +43 -0
  3. package/README.md +58 -17
  4. package/SECURITY.md +43 -0
  5. package/dist/adapters/analysis-context.js +75 -0
  6. package/dist/adapters/ask-files.js +198 -0
  7. package/dist/adapters/ask-proof.js +200 -0
  8. package/dist/adapters/ask-syntax.js +385 -0
  9. package/dist/adapters/canonical-path.js +17 -0
  10. package/dist/adapters/command.js +234 -0
  11. package/dist/adapters/docs.js +192 -0
  12. package/dist/adapters/evidence-context.js +119 -0
  13. package/dist/adapters/exec.js +207 -0
  14. package/dist/adapters/files.js +418 -0
  15. package/dist/adapters/find.js +150 -0
  16. package/dist/adapters/git-base.js +32 -0
  17. package/dist/adapters/git-inventory.js +71 -0
  18. package/dist/adapters/git.js +483 -0
  19. package/dist/adapters/locate-file.js +197 -0
  20. package/dist/adapters/output-lines.js +46 -0
  21. package/dist/adapters/private-storage.js +106 -0
  22. package/dist/adapters/risk-callers.js +429 -0
  23. package/dist/adapters/runner-version.js +78 -0
  24. package/dist/adapters/shell.js +92 -0
  25. package/dist/adapters/syntax.js +187 -0
  26. package/dist/adapters/test-inventory.js +139 -0
  27. package/dist/adapters/usage.js +20 -0
  28. package/dist/adapters/utf8.js +47 -0
  29. package/dist/configuration.js +267 -0
  30. package/dist/constants.js +140 -0
  31. package/dist/core/ask-closure.js +282 -0
  32. package/dist/core/ask-proof.js +1 -0
  33. package/dist/core/ask-references.js +278 -0
  34. package/dist/core/asks.js +507 -0
  35. package/dist/core/batches.js +65 -0
  36. package/dist/core/command-output.js +224 -0
  37. package/dist/core/diff.js +178 -0
  38. package/dist/core/docs.js +302 -0
  39. package/dist/core/find.js +108 -0
  40. package/dist/core/git.js +1 -0
  41. package/dist/core/imports.js +550 -0
  42. package/dist/core/integrity.js +45 -0
  43. package/dist/core/lexical.js +132 -0
  44. package/dist/core/locate.js +169 -0
  45. package/dist/core/output.js +137 -0
  46. package/dist/core/pointer.js +29 -0
  47. package/dist/core/result-report.js +302 -0
  48. package/dist/core/risk-callers.js +851 -0
  49. package/dist/core/runner-version.js +45 -0
  50. package/dist/core/secret-path.js +34 -0
  51. package/dist/core/sections.js +230 -0
  52. package/dist/core/state.js +51 -0
  53. package/dist/core/syntax.js +1 -0
  54. package/dist/core/test-commands.js +334 -0
  55. package/dist/core/test-coverage.js +74 -0
  56. package/dist/core/test-discovery.js +1382 -0
  57. package/dist/core/test-evidence.js +527 -0
  58. package/dist/core/test-state.js +81 -0
  59. package/dist/core/truncate.js +12 -0
  60. package/dist/core/units.js +349 -0
  61. package/dist/describe.js +23 -0
  62. package/dist/guide.js +33 -0
  63. package/dist/host.js +24 -0
  64. package/dist/jev/client.js +456 -0
  65. package/dist/jev/pool.js +54 -0
  66. package/dist/jev/types.js +1 -0
  67. package/dist/mcp/main.js +124 -0
  68. package/dist/mcp/protocol.js +210 -0
  69. package/dist/mcp/tools.js +129 -0
  70. package/dist/presets/docs.js +62 -0
  71. package/dist/presets/risk.js +179 -0
  72. package/dist/presets/spec.js +81 -0
  73. package/dist/presets/witnesses.js +249 -0
  74. package/dist/render.js +114 -0
  75. package/dist/report-schema.js +1356 -0
  76. package/dist/result-types.js +1 -0
  77. package/dist/result.js +3 -0
  78. package/dist/runtime.js +1 -0
  79. package/dist/session.js +147 -0
  80. package/dist/texts/ask-files.js +3 -0
  81. package/dist/texts/ask.js +4 -0
  82. package/dist/texts/check-diff.js +20 -0
  83. package/dist/texts/configuration.js +1 -0
  84. package/dist/texts/find.js +19 -0
  85. package/dist/texts/guide.js +3 -0
  86. package/dist/texts/instructions.js +72 -0
  87. package/dist/texts/locate.js +15 -0
  88. package/dist/texts/select-tests.js +4 -0
  89. package/dist/tools/ask-files.js +450 -0
  90. package/dist/tools/ask-schema.js +70 -0
  91. package/dist/tools/ask.js +1147 -0
  92. package/dist/tools/check-diff.js +594 -0
  93. package/dist/tools/docs-check.js +408 -0
  94. package/dist/tools/find.js +682 -0
  95. package/dist/tools/locate.js +602 -0
  96. package/dist/tools/review-report.js +230 -0
  97. package/dist/tools/select-tests.js +821 -0
  98. package/dist/tools/spec-check.js +263 -0
  99. package/docs/adr/0001-strict-typescript-pure-core-offline-tests.md +31 -0
  100. package/docs/adr/0002-one-http-protocol-across-hosts.md +17 -0
  101. package/docs/adr/0003-explicit-scope-conservative-automation.md +19 -0
  102. package/docs/adr/0004-compiled-typed-intents.md +19 -0
  103. package/docs/adr/0005-evidence-construction-before-judgment.md +19 -0
  104. package/docs/adr/0006-visible-uncertainty-constrained-controls.md +21 -0
  105. package/docs/adr/0007-bounded-evidence-visible-limits.md +21 -0
  106. package/docs/adr/0008-static-test-discovery-conservative-plans.md +19 -0
  107. package/docs/adr/0009-session-cache-requested-model-identity.md +17 -0
  108. package/docs/adr/0010-mcp-server-thin-host.md +23 -0
  109. package/docs/agent-instructions.md +120 -0
  110. package/docs/design.md +16 -4
  111. package/docs/mcp.md +233 -0
  112. package/docs/tools/jev_ask.md +8 -5
  113. package/docs/tools/jev_ask_files.md +2 -1
  114. package/docs/tools/jev_check_diff.md +4 -1
  115. package/docs/tools/jev_find_files.md +2 -1
  116. package/docs/tools/jev_locate_in_file.md +5 -0
  117. package/docs/tools/jev_select_tests.md +4 -1
  118. package/package.json +19 -4
  119. package/rules/jev-ask.md +22 -1
  120. package/server.json +57 -0
  121. package/src/adapters/ask-files.ts +11 -3
  122. package/src/adapters/ask-proof.ts +69 -11
  123. package/src/adapters/canonical-path.ts +18 -0
  124. package/src/adapters/command.ts +102 -36
  125. package/src/adapters/docs.ts +33 -14
  126. package/src/adapters/evidence-context.ts +169 -0
  127. package/src/adapters/exec.ts +226 -0
  128. package/src/adapters/files.ts +146 -16
  129. package/src/adapters/find.ts +37 -7
  130. package/src/adapters/git-base.ts +7 -1
  131. package/src/adapters/git.ts +61 -8
  132. package/src/adapters/locate-file.ts +51 -9
  133. package/src/adapters/private-storage.ts +155 -0
  134. package/src/adapters/risk-callers.ts +7 -2
  135. package/src/adapters/shell.ts +113 -0
  136. package/src/adapters/test-inventory.ts +12 -4
  137. package/src/configuration.ts +55 -14
  138. package/src/constants.ts +37 -5
  139. package/src/core/ask-references.ts +262 -146
  140. package/src/core/asks.ts +79 -7
  141. package/src/core/command-output.ts +17 -1
  142. package/src/core/import-boundaries.ts +8 -3
  143. package/src/core/locate.ts +8 -5
  144. package/src/core/output.ts +34 -0
  145. package/src/core/result-report.ts +410 -0
  146. package/src/core/secret-path.ts +37 -0
  147. package/src/core/state.ts +8 -1
  148. package/src/core/units.ts +3 -2
  149. package/src/host.ts +11 -0
  150. package/src/index.ts +3 -0
  151. package/src/jev/client.ts +66 -16
  152. package/src/jev/types.ts +24 -3
  153. package/src/mcp/main.ts +135 -0
  154. package/src/mcp/protocol.ts +332 -0
  155. package/src/mcp/tools.ts +179 -0
  156. package/src/render.ts +109 -0
  157. package/src/report-schema.ts +1380 -0
  158. package/src/result-types.ts +234 -0
  159. package/src/result.ts +4 -1
  160. package/src/runtime.ts +6 -0
  161. package/src/session.ts +59 -0
  162. package/src/setup.ts +13 -5
  163. package/src/texts/ask-files.ts +4 -1
  164. package/src/texts/ask.ts +8 -1
  165. package/src/texts/check-diff.ts +7 -4
  166. package/src/texts/find.ts +8 -2
  167. package/src/texts/guide.ts +8 -16
  168. package/src/texts/instructions.ts +98 -0
  169. package/src/texts/locate.ts +8 -2
  170. package/src/texts/run-end.ts +2 -2
  171. package/src/texts/select-tests.ts +4 -1
  172. package/src/tools/ask-files.ts +311 -18
  173. package/src/tools/ask.ts +722 -95
  174. package/src/tools/check-diff.ts +337 -31
  175. package/src/tools/docs-check.ts +241 -38
  176. package/src/tools/find.ts +389 -29
  177. package/src/tools/locate.ts +387 -25
  178. package/src/tools/review-report.ts +308 -0
  179. package/src/tools/select-tests.ts +484 -23
  180. package/src/tools/spec-check.ts +194 -19
@@ -0,0 +1,278 @@
1
+ import { ASK_MAX_FILES } from "../constants.js";
2
+ const fileSelector = /\b(files|files_before)\s*\[\s*("(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*')\s*\]/g;
3
+ const selector = /\b[A-Za-z_$][\w$]*(?:(?:\s*\.\s*[A-Za-z_$][\w$]*)|(?:\s*\[\s*(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\d+)\s*\]))*/g;
4
+ function unquote(value) {
5
+ if (value.startsWith('"')) {
6
+ try {
7
+ const parsed = JSON.parse(value);
8
+ if (typeof parsed === "string")
9
+ return parsed;
10
+ }
11
+ catch {
12
+ /* retain the literal selector */
13
+ }
14
+ }
15
+ return value.slice(1, -1).replace(/\\(['\\])/g, "$1");
16
+ }
17
+ function normalized(path) {
18
+ return path.replace(/\\/g, "/").replace(/^(?:\.\/)+/, "");
19
+ }
20
+ function forbidden(path) {
21
+ return (/^(?:\/|[A-Za-z]:|[a-z][a-z\d+.-]*:\/\/)/i.test(path) ||
22
+ path.split(/[\\/]/).some((part) => part === ".." || part === ".git"));
23
+ }
24
+ function explicitPaths(text) {
25
+ const references = new Map();
26
+ for (const match of text.matchAll(fileSelector)) {
27
+ const reference = unquote(match[2] ?? "");
28
+ const side = match[1];
29
+ references.set(JSON.stringify([side, reference]), { reference, side });
30
+ }
31
+ return [...references.values()];
32
+ }
33
+ /** Lexical discovery hints, never requirements. Ordinary member expressions are excluded. */
34
+ export function citedPaths(text) {
35
+ const found = new Set(explicitPaths(text).map(({ reference }) => reference));
36
+ const cleaned = text
37
+ .replace(/\b(?:[a-z][a-z\d+.-]*:\/\/|www\.)[^\s<>"'`]+/gi, " ")
38
+ .replace(fileSelector, " ")
39
+ .replace(selector, (value) => value.includes("[") ||
40
+ /^(?:state|output|docs?|files|files_before)\./.test(value)
41
+ ? " "
42
+ : value);
43
+ for (const match of cleaned.matchAll(/`([^`\n]+)`|"([^"\n]+)"|'([^'\n]+)'/g)) {
44
+ const value = match[1] ?? match[2] ?? match[3] ?? "";
45
+ if (/^(?:[\w@+-]+\/)+[\w.@+-]+$/.test(value))
46
+ found.add(value);
47
+ }
48
+ for (const match of cleaned.matchAll(/\b(?:README|LICENSE|LICENCE|Makefile|Dockerfile|CHANGELOG|AGENTS|CLAUDE)(?:\.[\w-]+)?\b/g))
49
+ found.add(match[0]);
50
+ for (const match of cleaned.matchAll(/(?:\.?\.?\/)?[\w@+-]+(?:\/[\w.@+-]+)*\.(?:[cm]?[jt]sx?|py|jsonc?|ya?ml|toml|ini|cfg|conf|mdx?|txt|rst|html?|css|scss|sql|sh|bash|rs|go|java|rb|php|cpp|hpp|vue|svelte|xml|csv|lock)\b/g)) {
51
+ const start = match.index ?? 0;
52
+ if ((start && /[\w.]/.test(cleaned[start - 1] ?? "")) ||
53
+ /^(?:Node|Next|Vue)\.js$/i.test(match[0]))
54
+ continue;
55
+ found.add(match[0]);
56
+ }
57
+ return [...found];
58
+ }
59
+ function questionText(question) {
60
+ return [
61
+ question.instructions,
62
+ ...Object.values(question.criteria ?? {}),
63
+ ].join("\n");
64
+ }
65
+ function readingTexts(plan) {
66
+ return plan.readings.map((reading) => ({
67
+ id: reading.id,
68
+ about: plan.asks[reading.askIndex]?.about,
69
+ text: [reading.id, reading.twin, ...Object.values(reading.controls ?? {})]
70
+ .flatMap((id) => id && plan.questions[id] ? [questionText(plan.questions[id])] : [])
71
+ .join("\n"),
72
+ }));
73
+ }
74
+ export function resolveAskReferences(plan, note, evidence, inventory) {
75
+ const index = evidence.files !== null && typeof evidence.files === "object"
76
+ ? evidence
77
+ : { files: evidence };
78
+ const supplied = [
79
+ ...new Set([
80
+ ...Object.keys(index.files),
81
+ ...Object.keys(index.files_before ?? {}),
82
+ ]),
83
+ ].sort();
84
+ const currentKnown = [...new Set(inventory)].sort();
85
+ const beforeKnown = [
86
+ ...new Set([...inventory, ...(index.beforeInventory ?? [])]),
87
+ ].sort();
88
+ const selected = new Set(supplied.map(normalized));
89
+ const versions = new Set([
90
+ ...Object.keys(index.files).map((path) => `files:${normalized(path)}`),
91
+ ...Object.keys(index.files_before ?? {}).map((path) => `files_before:${normalized(path)}`),
92
+ ]);
93
+ const additions = [];
94
+ const unresolved = [];
95
+ const invalid = [];
96
+ const aliases = Object.create(null);
97
+ const sources = [
98
+ ...readingTexts(plan).map(({ id, text, about }) => ({
99
+ text: `${about ?? ""}\n${text}`,
100
+ scope: id,
101
+ origin: "question",
102
+ output: about === "output",
103
+ })),
104
+ {
105
+ text: note ?? "",
106
+ scope: "global",
107
+ origin: "note",
108
+ output: false,
109
+ },
110
+ ];
111
+ for (const source of sources) {
112
+ const explicit = explicitPaths(source.text);
113
+ const explicitNames = new Set(explicit.map(({ reference }) => reference));
114
+ const references = [
115
+ ...explicit.map((ref) => ({ ...ref, required: true })),
116
+ ...(source.output
117
+ ? []
118
+ : citedPaths(source.text)
119
+ .filter((reference) => !explicitNames.has(reference))
120
+ .map((reference) => ({
121
+ reference,
122
+ side: "files",
123
+ required: false,
124
+ }))),
125
+ ];
126
+ for (const ref of references) {
127
+ const { reference, required, side } = ref;
128
+ const known = side === "files_before" ? beforeKnown : currentKnown;
129
+ const facts = {
130
+ reference,
131
+ required,
132
+ side,
133
+ origin: source.origin,
134
+ scope: source.scope,
135
+ };
136
+ if (forbidden(reference)) {
137
+ if (required)
138
+ invalid.push({
139
+ ...facts,
140
+ reason: `Path not permitted: ${reference}`,
141
+ });
142
+ continue;
143
+ }
144
+ const key = normalized(reference);
145
+ // Directory-bearing explicit paths are exact, never suffix fallbacks.
146
+ const exact = supplied.find((path) => normalized(path) === key);
147
+ const abbreviated = !key.includes("/");
148
+ let matches = exact ? [exact] : [];
149
+ if (!matches.length && abbreviated) {
150
+ const alias = index.aliases && Object.hasOwn(index.aliases, reference)
151
+ ? index.aliases[reference]
152
+ : undefined;
153
+ if (alias !== undefined)
154
+ matches = (typeof alias === "string" ? [alias] : [...alias]).filter((path) => supplied.includes(path));
155
+ if (!matches.length)
156
+ matches = supplied.filter((path) => normalized(path).endsWith(`/${key}`));
157
+ }
158
+ if (!matches.length)
159
+ matches = known.filter((path) => normalized(path) === key);
160
+ if (!matches.length && abbreviated)
161
+ matches = known.filter((path) => normalized(path).endsWith(`/${key}`));
162
+ if (matches.length !== 1) {
163
+ if (required)
164
+ unresolved.push({
165
+ ...facts,
166
+ reason: `${reference} is required but missing from state (${matches.length ? "ambiguous path" : "not found"})`,
167
+ });
168
+ continue;
169
+ }
170
+ const path = matches[0];
171
+ if (path === undefined)
172
+ continue;
173
+ aliases[reference] = path;
174
+ const identity = normalized(path);
175
+ const version = `${side}:${identity}`;
176
+ if (versions.has(version))
177
+ continue;
178
+ if (!selected.has(identity) && selected.size >= ASK_MAX_FILES) {
179
+ unresolved.push({
180
+ ...facts,
181
+ reason: `${reference} omitted (ASK_MAX_FILES limit: ${ASK_MAX_FILES})`,
182
+ });
183
+ continue;
184
+ }
185
+ selected.add(identity);
186
+ versions.add(version);
187
+ additions.push({ ...facts, path });
188
+ }
189
+ }
190
+ return { additions, unresolved, aliases, invalid };
191
+ }
192
+ function object(value) {
193
+ return value !== null && typeof value === "object";
194
+ }
195
+ function unpack(value) {
196
+ if (typeof value !== "string" || !/^[\s]*[[{]/.test(value))
197
+ return value;
198
+ try {
199
+ return JSON.parse(value);
200
+ }
201
+ catch {
202
+ return value;
203
+ }
204
+ }
205
+ function empty(value) {
206
+ return (value === undefined ||
207
+ value === null ||
208
+ (typeof value === "string" && value.trim() === "") ||
209
+ (object(value) && Object.keys(value).length === 0));
210
+ }
211
+ function emptyReference(text, state) {
212
+ const metadata = object(state.evidence) ? state.evidence : {};
213
+ const aliases = object(metadata.aliases) ? metadata.aliases : {};
214
+ for (const match of text.matchAll(selector)) {
215
+ const parts = [
216
+ ...match[0].matchAll(/[A-Za-z_$][\w$]*|"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\d+/g),
217
+ ].map(([part]) => (/^["']/.test(part) ? unquote(part) : part));
218
+ const root = parts[0];
219
+ if (!root)
220
+ continue;
221
+ if (!["state", "output", "files", "files_before"].includes(root) &&
222
+ !Object.hasOwn(state, root))
223
+ continue;
224
+ if (!/[.[]/.test(match[0]) && text.trim() !== match[0].trim())
225
+ continue;
226
+ let value = root === "state"
227
+ ? Object.hasOwn(state, "state")
228
+ ? state.state
229
+ : state
230
+ : state[root];
231
+ const tail = parts.slice(1);
232
+ if ((root === "files" || root === "files_before") &&
233
+ tail[0] &&
234
+ object(value) &&
235
+ !Object.hasOwn(value, tail[0])) {
236
+ const alias = Object.hasOwn(aliases, tail[0])
237
+ ? aliases[tail[0]]
238
+ : undefined;
239
+ if (typeof alias === "string")
240
+ tail[0] = alias;
241
+ }
242
+ for (const part of tail) {
243
+ value = unpack(value);
244
+ value =
245
+ object(value) && Object.hasOwn(value, part) ? value[part] : undefined;
246
+ }
247
+ // null before is admitted only by the base reader for a tracked-tree absence.
248
+ if (root === "files_before" &&
249
+ value === null &&
250
+ typeof state.base_sha === "string" &&
251
+ state.base_sha.length > 0)
252
+ continue;
253
+ if (empty(root === "files" || root === "files_before" ? value : unpack(value)))
254
+ return match[0].trim();
255
+ }
256
+ return undefined;
257
+ }
258
+ export function blockedAskReadings(plan, state, unresolved, note) {
259
+ const blocked = new Map();
260
+ const noteEmpty = emptyReference(note ?? "", state);
261
+ for (const { id, text, about } of readingTexts(plan)) {
262
+ const explicit = new Set(explicitPaths(`${about ?? ""}\n${text}`).map(({ reference }) => reference));
263
+ const missing = unresolved.find((ref) => ref.required !== false &&
264
+ (ref.scope === "global" ||
265
+ ref.scope === id ||
266
+ (ref.scope === undefined && explicit.has(ref.reference))));
267
+ if (missing) {
268
+ blocked.set(id, missing.reason);
269
+ continue;
270
+ }
271
+ const reference = noteEmpty ??
272
+ (about ? emptyReference(about, state) : undefined) ??
273
+ emptyReference(text, state);
274
+ if (reference)
275
+ blocked.set(id, `${reference} is missing or empty`);
276
+ }
277
+ return blocked;
278
+ }