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,179 @@
1
+ import type { TSchema } from "@sinclair/typebox";
2
+ import { Value } from "@sinclair/typebox/value";
3
+ import { spawnExec } from "../adapters/exec.ts";
4
+ import { ConfigController } from "../configuration.ts";
5
+ import { MCP_VALIDATION_MAX_ERRORS } from "../constants.ts";
6
+ import type { GitExec } from "../core/git.ts";
7
+ import { type ResultReportV1, resultIsError } from "../core/result-report.ts";
8
+ import { Guide } from "../guide.ts";
9
+ import { mcpHost } from "../host.ts";
10
+ import type { JevClient } from "../jev/types.ts";
11
+ import { mcpStructuredResultSchema } from "../report-schema.ts";
12
+ import type { ToolDependencies } from "../runtime.ts";
13
+ import { readSessionLimits, Session } from "../session.ts";
14
+ import { renderAgentInstructions } from "../texts/instructions.ts";
15
+ import { createAskTool } from "../tools/ask.ts";
16
+ import { createAskFilesTool } from "../tools/ask-files.ts";
17
+ import { createCheckDiffTool } from "../tools/check-diff.ts";
18
+ import { createFindFilesTool } from "../tools/find.ts";
19
+ import { createLocateTool } from "../tools/locate.ts";
20
+ import { createSelectTestsTool } from "../tools/select-tests.ts";
21
+ import { McpInvalidParams, type McpTool } from "./protocol.ts";
22
+
23
+ /** The shape every tool factory already returns for pi/omp. */
24
+ interface HarnessTool {
25
+ name: string;
26
+ label: string;
27
+ description: string;
28
+ parameters: TSchema;
29
+ promptGuidelines?: string[];
30
+ execute(
31
+ id: string,
32
+ args: never,
33
+ signal: AbortSignal | undefined,
34
+ update: unknown,
35
+ ctx: { cwd: string },
36
+ ): Promise<{
37
+ content: { type: "text"; text: string }[];
38
+ details: { result: ResultReportV1 };
39
+ }>;
40
+ }
41
+
42
+ export interface McpToolOptions {
43
+ /** Repository directory the tools operate in. */
44
+ root: string;
45
+ env?: NodeJS.ProcessEnv;
46
+ /** Test seams; production uses the configured HTTP client and spawn. */
47
+ client?: JevClient;
48
+ exec?: GitExec;
49
+ /** Saved-configuration directory; defaults to the pi/omp setup location. */
50
+ configDirectory?: string;
51
+ }
52
+
53
+ function validationError(schema: TSchema, value: unknown): string | undefined {
54
+ if (Value.Check(schema, value)) return undefined;
55
+ const problems: string[] = [];
56
+ for (const error of Value.Errors(schema, value)) {
57
+ problems.push(`${error.path || "/"}: ${error.message}`);
58
+ if (problems.length === MCP_VALIDATION_MAX_ERRORS) break;
59
+ }
60
+ return `Invalid arguments. ${problems.join("; ")}`;
61
+ }
62
+
63
+ /**
64
+ * Effective Jev client for MCP, with the same precedence as pi/omp minus the
65
+ * interactive layers: environment variables, then the configuration saved by
66
+ * `/jev-setup` in pi or omp. Storage problems never stop the server; the tools
67
+ * then explain the missing configuration and `warning` says why.
68
+ */
69
+ export async function loadMcpClient(
70
+ env: NodeJS.ProcessEnv,
71
+ configDirectory?: string,
72
+ ): Promise<{ client?: JevClient; apiKey?: string; warning?: string }> {
73
+ try {
74
+ const controller = new ConfigController({
75
+ env,
76
+ ...(configDirectory ? { directory: configDirectory } : {}),
77
+ });
78
+ const configured = () =>
79
+ controller.client
80
+ ? { client: controller.client, apiKey: controller.values().apiKey }
81
+ : {};
82
+ try {
83
+ await controller.initialize({});
84
+ } catch (error) {
85
+ // Saved storage unusable: environment configuration (if any) still applies.
86
+ return {
87
+ ...configured(),
88
+ warning: error instanceof Error ? error.message : String(error),
89
+ };
90
+ }
91
+ return configured();
92
+ } catch (error) {
93
+ return { warning: error instanceof Error ? error.message : String(error) };
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Reuse the six harness tool factories unchanged. One MCP server process is
99
+ * one session: limits, cache and counters live as long as the connection.
100
+ */
101
+ export async function createMcpTools(options: McpToolOptions): Promise<{
102
+ tools: McpTool[];
103
+ instructions: string;
104
+ configured: boolean;
105
+ warning?: string;
106
+ }> {
107
+ const env = options.env ?? process.env;
108
+ const host = mcpHost();
109
+ const loaded = options.client
110
+ ? { client: options.client, apiKey: env.JEV_TOOLS_API_KEY }
111
+ : await loadMcpClient(env, options.configDirectory);
112
+ const client = loaded.client;
113
+ const dependencies: ToolDependencies = {
114
+ client,
115
+ apiKey: loaded.apiKey,
116
+ host,
117
+ evidenceOrigin: "server",
118
+ runtime: {
119
+ session: new Session(readSessionLimits(env)),
120
+ guide: new Guide(host),
121
+ },
122
+ exec: options.exec ?? spawnExec,
123
+ };
124
+ const harness = [
125
+ createAskTool(dependencies),
126
+ createAskFilesTool(dependencies),
127
+ createFindFilesTool(dependencies),
128
+ createLocateTool(dependencies),
129
+ createCheckDiffTool(dependencies),
130
+ createSelectTestsTool(dependencies),
131
+ ] as unknown as HarnessTool[];
132
+ let id = 0;
133
+ const tools = harness.map((tool): McpTool => {
134
+ const runsCommands =
135
+ tool.name === "jev_ask" &&
136
+ JSON.stringify(tool.parameters).includes('"command"');
137
+ return {
138
+ name: tool.name,
139
+ title: tool.label,
140
+ description: tool.description,
141
+ inputSchema: JSON.parse(
142
+ JSON.stringify(tool.parameters),
143
+ ) as McpTool["inputSchema"],
144
+ annotations: {
145
+ title: tool.label,
146
+ // Evidence is read-only unless jev_ask may run a shell command.
147
+ readOnlyHint: !runsCommands,
148
+ destructiveHint: runsCommands,
149
+ idempotentHint: false,
150
+ // Evidence is sent to the configured judgment endpoint.
151
+ openWorldHint: true,
152
+ },
153
+ outputSchema: mcpStructuredResultSchema as McpTool["inputSchema"],
154
+ async call(args, signal) {
155
+ const invalid = validationError(tool.parameters, args);
156
+ if (invalid) throw new McpInvalidParams(invalid);
157
+ const result = await tool.execute(
158
+ `mcp-${++id}`,
159
+ args as never,
160
+ signal,
161
+ undefined,
162
+ { cwd: options.root },
163
+ );
164
+ return {
165
+ content: result.content,
166
+ structuredContent: { result: result.details.result },
167
+ ...(resultIsError(result.details.result) ? { isError: true } : {}),
168
+ };
169
+ },
170
+ };
171
+ });
172
+ const instructions = renderAgentInstructions("mcp", host.names);
173
+ return {
174
+ tools,
175
+ instructions,
176
+ configured: client !== undefined,
177
+ ...(loaded.warning ? { warning: loaded.warning } : {}),
178
+ };
179
+ }
package/src/render.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { Envelope, OutputLine } from "./core/output.ts";
2
+ import type { Knowledge, ResultReportV1, Scope } from "./core/result-report.ts";
2
3
 
3
4
  function renderLine(line: OutputLine): string {
4
5
  switch (line.type) {
@@ -43,3 +44,111 @@ function renderLine(line: OutputLine): string {
43
44
  export function renderEnvelope(envelope: Envelope): string {
44
45
  return envelope.lines.map(renderLine).join("\n");
45
46
  }
47
+
48
+ function knowledge<T>(
49
+ value: Knowledge<T>,
50
+ format: (value: T) => string = String,
51
+ ): string {
52
+ return value.status === "known"
53
+ ? format(value.value)
54
+ : `${value.status} (${value.reason})`;
55
+ }
56
+ function scopeLabel(scope: Scope): string {
57
+ return scope.kind === "call"
58
+ ? "call"
59
+ : scope.kind === "item"
60
+ ? `items ${scope.itemIds.join(", ")}`
61
+ : scope.kind === "group"
62
+ ? `groups ${scope.groupIds.join(", ")}`
63
+ : `inventory ${scope.inventoryIds.join(", ")}`;
64
+ }
65
+
66
+ /** Human projection uses the same typed facts as structured transport, never prose inference. */
67
+ export function renderResultReport(
68
+ report: ResultReportV1,
69
+ options: { details?: Envelope } = {},
70
+ ): string {
71
+ const { context: c, accounting: a } = report;
72
+ const counts = a.requestedResults;
73
+ const mode =
74
+ counts.fresh + counts.cache
75
+ ? `${counts.fresh} fresh / ${counts.cache} cache Jev results`
76
+ : counts.static
77
+ ? "static selection; no Jev judgment"
78
+ : report.items.some((item) => item.selection?.selected)
79
+ ? "conservative fallback selection; no Jev judgment"
80
+ : "no admissible judgment";
81
+ const lines = [
82
+ `${report.execution} — ${mode}${counts.notJudged ? `; ${counts.notJudged} not judged` : ""}`,
83
+ `context: authority ${knowledge(c.authority, (v) => `${v.path} (${v.origin})`)} · requested root ${knowledge(c.requestedRoot)} · effective root ${knowledge(c.effectiveRoot, (v) => `${v.path} (${v.origin}); common-dir ${knowledge(v.commonDir)}`)} · base ${knowledge(c.requestedBase)} → ${knowledge(c.resolvedBase)}`,
84
+ ];
85
+ for (const inv of c.inventories) {
86
+ lines.push(
87
+ `inventory ${inv.id} (${inv.kind}): discovered ${knowledge(inv.discovered)} · considered ${knowledge(inv.considered)} · scopeRestricted ${inv.scopeRestricted} · rules ${inv.rules.join("; ")} · restrictions ${inv.restrictions.join("; ") || "none"}`,
88
+ );
89
+ for (const criterion of inv.criteria)
90
+ lines.push(
91
+ `criterion ${JSON.stringify(criterion.criterion)}: ${criterion.outcome}; matches ${knowledge(criterion.matches)}`,
92
+ );
93
+ }
94
+ if (c.command.execution !== "not_requested")
95
+ lines.push(
96
+ `command: ${c.command.execution} · cwd ${knowledge(c.command.cwd)} · exit ${knowledge(c.command.exitCode)} · timedOut ${knowledge(c.command.timedOut)}`,
97
+ );
98
+ for (const item of report.items) {
99
+ if (item.treatment === "judged") {
100
+ const j = item.judgment;
101
+ lines.push(
102
+ `${j.band === "verdict" ? "" : `${j.band} `}${j.uncalibrated ? "uncalibrated " : ""}${item.label} = ${String(j.result)} (${j.measure.kind} ${knowledge(j.measure.value)}) [${item.source}]${j.reason ? ` — ${j.reason}` : ""}`,
103
+ );
104
+ for (const raw of j.rawValues)
105
+ lines.push(
106
+ ` raw ${raw.label} = ${String(raw.value)}; probability ${knowledge(raw.probability)}`,
107
+ );
108
+ for (const control of j.controls) {
109
+ lines.push(
110
+ ` control ${control.id} (${control.kind}, ${control.source}): ${control.outcome}`,
111
+ );
112
+ for (const raw of control.rawValues)
113
+ lines.push(
114
+ ` raw ${raw.label} = ${String(raw.value)}; probability ${knowledge(raw.probability)}`,
115
+ );
116
+ }
117
+ } else
118
+ lines.push(
119
+ `${item.treatment === "static" ? "static" : "unjudged"} ${item.label}${item.treatment === "static" ? ` — ${item.staticReason}` : ""}`,
120
+ );
121
+ for (const evidence of item.evidence)
122
+ lines.push(
123
+ ` evidence ${evidence.target} (${evidence.side}): ${knowledge(evidence.canonicalPath)} · aliases ${evidence.aliases.join(", ") || "none"} · revision ${knowledge(evidence.revision)}`,
124
+ );
125
+ if (item.selection)
126
+ lines.push(
127
+ ` selection: ${item.selection.selected ? "selected" : "not selected"} — ${item.selection.reason}`,
128
+ );
129
+ if (item.diagnosticIds.length)
130
+ lines.push(` diagnostics ${item.diagnosticIds.join(", ")}`);
131
+ if (item.actionIds.length)
132
+ lines.push(` actions ${item.actionIds.join(", ")}`);
133
+ }
134
+ // Complete diagnostic lists keep legacy text-only clients self-contained.
135
+ for (const d of report.diagnostics)
136
+ lines.push(
137
+ `${d.material ? "material " : ""}${d.effect} ${d.id} — ${d.cause}: ${d.fact} · target ${knowledge(d.target)} · origin ${d.origin} · scope ${scopeLabel(d.scope)} · members ${knowledge(d.memberCount)}${d.omittedMembers.length ? ` · omitted ${d.omittedMembers.join(", ")}` : ""} · actions ${d.actionIds.join(", ") || "none"}`,
138
+ );
139
+ for (const action of report.actions)
140
+ lines.push(
141
+ `next ${action.id} (${action.code}; ${scopeLabel(action.scope)}; target ${knowledge(action.target)}): ${action.condition} — ${action.instruction}`,
142
+ );
143
+ if (options.details) {
144
+ const detailLines = options.details.lines.filter(
145
+ (line) => line.type === "command" || line.type === "list",
146
+ );
147
+ if (detailLines.length)
148
+ lines.push("details:", renderEnvelope({ lines: detailLines }));
149
+ }
150
+ lines.push(
151
+ `HTTP attempts ${a.httpAttempts} · questions sent ${a.questionsSent} · requested results total ${knowledge(counts.total)} / fresh ${counts.fresh} / cache ${counts.cache} / not judged ${counts.notJudged} / static ${counts.static} · cache probes ${a.cacheHits}/${a.cacheRequests} · auxiliary controls fresh ${a.auxiliary.controls.fresh} / cache ${a.auxiliary.controls.cache} / not judged ${a.auxiliary.controls.notJudged} / static ${a.auxiliary.controls.static} · passages fresh ${a.auxiliary.passages.fresh} / cache ${a.auxiliary.passages.cache} / not judged ${a.auxiliary.passages.notJudged} · current cost USD ${knowledge(a.costUsd)} · elapsed ${a.elapsedMs} ms`,
152
+ );
153
+ return lines.join("\n");
154
+ }