jev-agent-tools 0.1.4 → 0.2.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 (131) hide show
  1. package/CHANGELOG.md +70 -1
  2. package/CONTRIBUTING.md +40 -0
  3. package/README.md +42 -9
  4. package/SECURITY.md +27 -0
  5. package/dist/adapters/analysis-context.js +75 -0
  6. package/dist/adapters/ask-files.js +189 -0
  7. package/dist/adapters/ask-proof.js +144 -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 +181 -0
  11. package/dist/adapters/docs.js +172 -0
  12. package/dist/adapters/exec.js +207 -0
  13. package/dist/adapters/files.js +293 -0
  14. package/dist/adapters/find.js +122 -0
  15. package/dist/adapters/git-base.js +26 -0
  16. package/dist/adapters/git-inventory.js +71 -0
  17. package/dist/adapters/git.js +439 -0
  18. package/dist/adapters/locate-file.js +159 -0
  19. package/dist/adapters/output-lines.js +46 -0
  20. package/dist/adapters/private-storage.js +98 -0
  21. package/dist/adapters/risk-callers.js +426 -0
  22. package/dist/adapters/runner-version.js +78 -0
  23. package/dist/adapters/shell.js +76 -0
  24. package/dist/adapters/syntax.js +187 -0
  25. package/dist/adapters/test-inventory.js +131 -0
  26. package/dist/adapters/usage.js +20 -0
  27. package/dist/adapters/utf8.js +47 -0
  28. package/dist/configuration.js +257 -0
  29. package/dist/constants.js +119 -0
  30. package/dist/core/ask-closure.js +282 -0
  31. package/dist/core/ask-proof.js +1 -0
  32. package/dist/core/ask-references.js +194 -0
  33. package/dist/core/asks.js +436 -0
  34. package/dist/core/batches.js +65 -0
  35. package/dist/core/command-output.js +224 -0
  36. package/dist/core/diff.js +178 -0
  37. package/dist/core/docs.js +302 -0
  38. package/dist/core/find.js +108 -0
  39. package/dist/core/git.js +1 -0
  40. package/dist/core/imports.js +550 -0
  41. package/dist/core/integrity.js +45 -0
  42. package/dist/core/lexical.js +132 -0
  43. package/dist/core/locate.js +169 -0
  44. package/dist/core/output.js +120 -0
  45. package/dist/core/pointer.js +29 -0
  46. package/dist/core/risk-callers.js +851 -0
  47. package/dist/core/runner-version.js +45 -0
  48. package/dist/core/sections.js +230 -0
  49. package/dist/core/state.js +44 -0
  50. package/dist/core/syntax.js +1 -0
  51. package/dist/core/test-commands.js +334 -0
  52. package/dist/core/test-coverage.js +74 -0
  53. package/dist/core/test-discovery.js +1382 -0
  54. package/dist/core/test-evidence.js +527 -0
  55. package/dist/core/test-state.js +81 -0
  56. package/dist/core/truncate.js +12 -0
  57. package/dist/core/units.js +349 -0
  58. package/dist/describe.js +23 -0
  59. package/dist/guide.js +33 -0
  60. package/dist/host.js +24 -0
  61. package/dist/jev/client.js +434 -0
  62. package/dist/jev/pool.js +54 -0
  63. package/dist/jev/types.js +1 -0
  64. package/dist/mcp/main.js +124 -0
  65. package/dist/mcp/protocol.js +187 -0
  66. package/dist/mcp/tools.js +116 -0
  67. package/dist/presets/docs.js +62 -0
  68. package/dist/presets/risk.js +179 -0
  69. package/dist/presets/spec.js +81 -0
  70. package/dist/presets/witnesses.js +249 -0
  71. package/dist/render.js +42 -0
  72. package/dist/result.js +3 -0
  73. package/dist/runtime.js +1 -0
  74. package/dist/session.js +147 -0
  75. package/dist/texts/ask-files.js +1 -0
  76. package/dist/texts/ask.js +2 -0
  77. package/dist/texts/check-diff.js +17 -0
  78. package/dist/texts/configuration.js +1 -0
  79. package/dist/texts/find.js +14 -0
  80. package/dist/texts/guide.js +16 -0
  81. package/dist/texts/locate.js +10 -0
  82. package/dist/texts/select-tests.js +2 -0
  83. package/dist/tools/ask-files.js +217 -0
  84. package/dist/tools/ask-schema.js +70 -0
  85. package/dist/tools/ask.js +686 -0
  86. package/dist/tools/check-diff.js +402 -0
  87. package/dist/tools/docs-check.js +299 -0
  88. package/dist/tools/find.js +389 -0
  89. package/dist/tools/locate.js +303 -0
  90. package/dist/tools/select-tests.js +567 -0
  91. package/dist/tools/spec-check.js +166 -0
  92. package/docs/adr/0001-strict-typescript-pure-core-offline-tests.md +31 -0
  93. package/docs/adr/0002-one-http-protocol-across-hosts.md +17 -0
  94. package/docs/adr/0003-explicit-scope-conservative-automation.md +19 -0
  95. package/docs/adr/0004-compiled-typed-intents.md +19 -0
  96. package/docs/adr/0005-evidence-construction-before-judgment.md +19 -0
  97. package/docs/adr/0006-visible-uncertainty-constrained-controls.md +21 -0
  98. package/docs/adr/0007-bounded-evidence-visible-limits.md +21 -0
  99. package/docs/adr/0008-static-test-discovery-conservative-plans.md +19 -0
  100. package/docs/adr/0009-session-cache-requested-model-identity.md +17 -0
  101. package/docs/adr/0010-mcp-server-thin-host.md +23 -0
  102. package/docs/agent-instructions.md +91 -0
  103. package/docs/design.md +3 -3
  104. package/docs/mcp.md +231 -0
  105. package/package.json +19 -4
  106. package/server.json +57 -0
  107. package/src/adapters/canonical-path.ts +18 -0
  108. package/src/adapters/command.ts +7 -4
  109. package/src/adapters/exec.ts +226 -0
  110. package/src/adapters/private-storage.ts +143 -0
  111. package/src/adapters/risk-callers.ts +4 -2
  112. package/src/adapters/shell.ts +97 -0
  113. package/src/configuration.ts +39 -12
  114. package/src/constants.ts +11 -0
  115. package/src/core/command-output.ts +17 -1
  116. package/src/host.ts +11 -0
  117. package/src/jev/client.ts +12 -0
  118. package/src/jev/types.ts +6 -0
  119. package/src/mcp/main.ts +135 -0
  120. package/src/mcp/protocol.ts +282 -0
  121. package/src/mcp/tools.ts +166 -0
  122. package/src/session.ts +59 -0
  123. package/src/setup.ts +13 -5
  124. package/src/tools/ask-files.ts +5 -7
  125. package/src/tools/ask.ts +26 -22
  126. package/src/tools/check-diff.ts +8 -5
  127. package/src/tools/docs-check.ts +1 -0
  128. package/src/tools/find.ts +5 -2
  129. package/src/tools/locate.ts +5 -8
  130. package/src/tools/select-tests.ts +7 -4
  131. package/src/tools/spec-check.ts +1 -0
@@ -0,0 +1,217 @@
1
+ import { createHash } from "node:crypto";
2
+ import { resolve } from "node:path";
3
+ import { Type } from "@sinclair/typebox";
4
+ import { collectAskFiles } from "../adapters/ask-files.js";
5
+ import { canonicalPath } from "../adapters/canonical-path.js";
6
+ import { shareGitInventory } from "../adapters/git-inventory.js";
7
+ import { hostUsage } from "../adapters/usage.js";
8
+ import { compileAsks, readAsks, reverseQuestions } from "../core/asks.js";
9
+ import { checkIntegrity } from "../core/integrity.js";
10
+ import { buildEnvelope } from "../core/output.js";
11
+ import { interpolate } from "../describe.js";
12
+ import { renderEnvelope } from "../render.js";
13
+ import { ASK_FILES_DESCRIPTION } from "../texts/ask-files.js";
14
+ import { NOT_CONFIGURED } from "../texts/configuration.js";
15
+ import { asksParameter } from "./ask-schema.js";
16
+ export const askFilesParameters = Type.Object({
17
+ paths: Type.Array(Type.String({ minLength: 1 }), { minItems: 1 }),
18
+ asks: asksParameter("files"),
19
+ max_calls: Type.Optional(Type.Integer({ minimum: 0 })),
20
+ }, { additionalProperties: false });
21
+ export function createAskFilesTool(dependencies) {
22
+ const { host, runtime, exec: execute } = dependencies;
23
+ return {
24
+ name: "jev_ask_files",
25
+ label: "Jev ask files",
26
+ description: interpolate(ASK_FILES_DESCRIPTION, host.names),
27
+ parameters: askFilesParameters,
28
+ ...(host.isOmp
29
+ ? { approval: "read", loadMode: "essential" }
30
+ : {
31
+ promptSnippet: "Typed answers about many files from intents you declare, without reading them",
32
+ promptGuidelines: [
33
+ "jev_ask_files: put every ask in one call; declare intents (verify, classify, rate, decide), never raw questions",
34
+ "jev_ask_files: use it to decide what to read, then read only the files that matter",
35
+ ],
36
+ }),
37
+ async execute(_id, args, signal, _update, ctx) {
38
+ const client = dependencies.client;
39
+ const cwd = await canonicalPath(ctx.cwd);
40
+ const started = performance.now();
41
+ const exec = shareGitInventory(execute);
42
+ const totals = {
43
+ calls: 0,
44
+ questions: 0,
45
+ cacheHits: 0,
46
+ cacheRequests: 0,
47
+ inputTokens: 0,
48
+ costUsd: 0,
49
+ };
50
+ const records = [];
51
+ let skipped = [];
52
+ const finish = (input) => {
53
+ const envelope = buildEnvelope({
54
+ ...input,
55
+ yield: { ...totals, elapsedMs: performance.now() - started },
56
+ });
57
+ runtime.session.record(envelope);
58
+ runtime.guide.deliver(ctx);
59
+ const usage = hostUsage(host.isOmp, {
60
+ inputTokens: totals.inputTokens,
61
+ costUsd: totals.costUsd,
62
+ });
63
+ return {
64
+ content: [{ type: "text", text: renderEnvelope(envelope) }],
65
+ details: { files: records, skipped, envelope },
66
+ ...usage,
67
+ };
68
+ };
69
+ if (!client)
70
+ return finish({ refusal: NOT_CONFIGURED });
71
+ const plan = compileAsks(args.asks, { surface: "files" });
72
+ if (!plan.ok)
73
+ return finish({ refusal: plan.error });
74
+ const collected = await collectAskFiles(cwd, args.paths, signal, exec);
75
+ if (!collected.ok)
76
+ return finish({ refusal: collected.error });
77
+ skipped = collected.skipped;
78
+ if (!collected.files.length && skipped.length)
79
+ return finish({
80
+ refusal: `No readable evidence: ${skipped.join("; ")}`,
81
+ });
82
+ let sent = 0;
83
+ let budget;
84
+ const options = {
85
+ signal,
86
+ ...runtime.session.requestGate(),
87
+ beforeRequest: (count) => {
88
+ if (args.max_calls !== undefined && sent >= args.max_calls) {
89
+ budget = {
90
+ kind: "max_calls",
91
+ message: `max_calls=${args.max_calls} reached`,
92
+ };
93
+ return { ok: false, error: budget.message };
94
+ }
95
+ const admission = runtime.session.admit(count);
96
+ if (!admission.ok) {
97
+ budget = { kind: "session", message: admission.error };
98
+ return admission;
99
+ }
100
+ sent++;
101
+ return admission;
102
+ },
103
+ onUsage: (usage) => runtime.session.recordUsage(usage),
104
+ };
105
+ const accumulate = (result) => {
106
+ totals.calls += result.calls ?? 0;
107
+ totals.questions += result.questions ?? 0;
108
+ totals.cacheHits += result.cacheHits ?? 0;
109
+ totals.cacheRequests += result.cacheRequests ?? 0;
110
+ totals.inputTokens += result.usage?.inputTokens ?? 0;
111
+ totals.costUsd += result.usage?.costUsd ?? 0;
112
+ };
113
+ const rows = await Promise.all(collected.files.map(async (file) => {
114
+ const state = { path: file.path, content: file.content };
115
+ const integrity = checkIntegrity(state, plan.asks, [
116
+ {
117
+ ...file.identity,
118
+ insertedPath: resolve(cwd, state.path),
119
+ content: state.content,
120
+ insertedSha256: createHash("sha256")
121
+ .update(state.content)
122
+ .digest("hex"),
123
+ },
124
+ ]);
125
+ const initial = await client.judge(state, plan.questions, {
126
+ ...options,
127
+ groups: plan.groups,
128
+ });
129
+ accumulate(initial);
130
+ let reversed;
131
+ let reverseAnswers;
132
+ if (initial.ok) {
133
+ const questions = reverseQuestions(plan, initial.answers);
134
+ if (Object.keys(questions).length) {
135
+ reversed = await client.judge(state, questions, options);
136
+ accumulate(reversed);
137
+ reverseAnswers = reversed.ok
138
+ ? reversed.answers
139
+ : Object.fromEntries(Object.keys(questions).map((id) => [
140
+ id,
141
+ {
142
+ type: "unjudged",
143
+ reason: reversed && !reversed.ok
144
+ ? reversed.error
145
+ : "reverse control failed",
146
+ },
147
+ ]));
148
+ }
149
+ }
150
+ const answers = initial.ok
151
+ ? readAsks(plan, initial.answers, reverseAnswers)
152
+ : [];
153
+ return { file, initial, reversed, integrity, answers };
154
+ }));
155
+ const answers = [];
156
+ const unchecked = [];
157
+ const unjudged = [];
158
+ for (const row of rows) {
159
+ records.push({
160
+ path: row.file.path,
161
+ initial: row.initial,
162
+ ...(row.reversed ? { reversed: row.reversed } : {}),
163
+ });
164
+ if (!row.initial.ok) {
165
+ unchecked.push(row.file.path);
166
+ unjudged.push({
167
+ label: row.file.path,
168
+ reason: row.initial.error,
169
+ next: "read the file or retry",
170
+ });
171
+ continue;
172
+ }
173
+ if (Object.values(row.initial.answers).some((answer) => answer.type === "unjudged") ||
174
+ (row.reversed &&
175
+ (!row.reversed.ok ||
176
+ Object.values(row.reversed.answers).some((answer) => answer.type === "unjudged"))))
177
+ unchecked.push(row.file.path);
178
+ for (const answer of row.answers)
179
+ answers.push({
180
+ ...answer,
181
+ label: `${row.file.path} ${answer.label}`,
182
+ ...(row.integrity.controls.length
183
+ ? {
184
+ band: "unsure",
185
+ reason: row.integrity.controls
186
+ .map((control) => control.fact)
187
+ .join("; "),
188
+ }
189
+ : {}),
190
+ });
191
+ }
192
+ return finish({
193
+ state: {
194
+ files: collected.files.length,
195
+ integrity: rows.some((row) => row.integrity.controls.length)
196
+ ? "failed"
197
+ : "ok",
198
+ warnings: plan.warnings.map((warning) => warning.fact),
199
+ },
200
+ answers,
201
+ unjudged,
202
+ unchecked,
203
+ budget,
204
+ lines: skipped.length
205
+ ? [{ type: "list", title: "skipped", items: skipped }]
206
+ : [],
207
+ limitations: [
208
+ ...plan.warnings,
209
+ ...rows.flatMap((row) => [
210
+ ...row.integrity.controls,
211
+ ...row.integrity.notes,
212
+ ]),
213
+ ],
214
+ });
215
+ },
216
+ };
217
+ }
@@ -0,0 +1,70 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import { CHOICE_MAX_OPTIONS, SCORE_MAX_LEVELS, SCORE_MIN_LEVELS, } from "../constants.js";
3
+ const criteria = Type.Record(Type.String(), Type.String(), {
4
+ minProperties: 1,
5
+ maxProperties: CHOICE_MAX_OPTIONS,
6
+ });
7
+ const question = Type.Union([
8
+ Type.Object({
9
+ type: Type.Literal("bool"),
10
+ instructions: Type.String({ minLength: 1 }),
11
+ criteria: Type.Optional(Type.Object({
12
+ true: Type.Optional(Type.String()),
13
+ false: Type.Optional(Type.String()),
14
+ }, { additionalProperties: false })),
15
+ }, { additionalProperties: false }),
16
+ Type.Object({
17
+ type: Type.Literal("choice"),
18
+ instructions: Type.String({ minLength: 1 }),
19
+ criteria,
20
+ }, { additionalProperties: false }),
21
+ Type.Object({
22
+ type: Type.Literal("score"),
23
+ instructions: Type.String({ minLength: 1 }),
24
+ criteria: Type.Array(Type.String(), {
25
+ minItems: SCORE_MIN_LEVELS,
26
+ maxItems: SCORE_MAX_LEVELS,
27
+ }),
28
+ }, { additionalProperties: false }),
29
+ ]);
30
+ const fullAskSchema = Type.Union(createAskVariants({ about: Type.Optional(Type.String({ minLength: 1 })) }));
31
+ const filesAskSchema = Type.Union(createAskVariants({}).slice(0, -1));
32
+ export function askSchema(surface) {
33
+ return surface === "ask" ? fullAskSchema : filesAskSchema;
34
+ }
35
+ function createAskVariants(about) {
36
+ const variants = [
37
+ Type.Object({ intent: Type.Literal("free"), ...about, question }, { additionalProperties: false }),
38
+ Type.Object({ intent: Type.Literal("verify"), ...about, claims: criteria }, { additionalProperties: false }),
39
+ Type.Object({
40
+ intent: Type.Literal("classify"),
41
+ ...about,
42
+ categories: criteria,
43
+ pick: Type.Optional(Type.Union([Type.Literal("one"), Type.Literal("many")])),
44
+ by: Type.Optional(Type.String()),
45
+ }, { additionalProperties: false }),
46
+ Type.Object({ intent: Type.Literal("decide"), ...about, hypotheses: criteria }, { additionalProperties: false }),
47
+ Type.Object({
48
+ intent: Type.Literal("rate"),
49
+ ...about,
50
+ dimension: Type.String(),
51
+ levels: Type.Array(Type.String(), {
52
+ minItems: SCORE_MIN_LEVELS,
53
+ maxItems: SCORE_MAX_LEVELS,
54
+ }),
55
+ }, { additionalProperties: false }),
56
+ Type.Object({
57
+ intent: Type.Literal("locate"),
58
+ ...about,
59
+ target: Type.String(),
60
+ among: Type.Array(Type.String(), { minItems: 1 }),
61
+ count: Type.Optional(Type.Union([Type.Literal("one"), Type.Literal("many")])),
62
+ attribution: Type.Optional(Type.Boolean()),
63
+ }, { additionalProperties: false }),
64
+ ];
65
+ return variants;
66
+ }
67
+ export function asksParameter(surface) {
68
+ const ask = askSchema(surface);
69
+ return Type.Union([ask, Type.Array(ask, { minItems: 1 }), Type.String()]);
70
+ }