simt-dev 0.2.1 → 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.
- package/CHANGELOG.md +239 -0
- package/bin/simt-dev.mjs +21 -2
- package/docs/decisions/ADR-064-the-context-meter-counts-what-the-tool-wrote.md +107 -0
- package/docs/decisions/ADR-065-a-pack-is-one-shared-rule-set-pinned-by-digest.md +97 -0
- package/docs/decisions/ADR-066-a-monorepo-projects-once-per-package.md +115 -0
- package/docs/decisions/ADR-067-which-harnesses-read-agents-md-is-a-column.md +110 -0
- package/docs/decisions/ADR-068-the-mcp-revision-list-cites-what-was-read.md +111 -0
- package/docs/decisions/ADR-069-the-conformance-lint-checks-the-published-specification.md +90 -0
- package/docs/decisions/ADR-070-the-json-payload-carries-its-own-version.md +91 -0
- package/docs/decisions/ADR-071-a-rule-that-can-be-narrowed-is-written-narrow.md +106 -0
- package/docs/decisions/ADR-072-the-budget-is-a-declaration-beside-the-gates.md +104 -0
- package/docs/decisions/ADR-073-a-kernel-is-a-smaller-projection-the-repository-asks-for.md +92 -0
- package/docs/decisions/ADR-074-a-rule-and-a-decision-are-readable-by-uri.md +99 -0
- package/docs/decisions/ADR-075-nothing-leaves-the-machine.md +56 -0
- package/docs/decisions/ADR-076-a-change-to-what-agents-read-is-measured.md +70 -0
- package/docs/decisions/ADR-077-the-command-card-leads-the-region-and-reads-committed-evidence.md +91 -0
- package/docs/decisions/ADR-078-a-line-the-repository-already-proves-does-not-ship.md +117 -0
- package/docs/decisions/ADR-079-the-rules-are-enforced-at-the-tool-call-from-one-policy-table.md +162 -0
- package/docs/decisions/ADR-080-the-tools-own-names-are-not-repositories-and-neither-reader-decides-by-shape.md +99 -0
- package/docs/decisions/ADR-081-a-tracker-is-read-through-the-repositorys-template.md +36 -0
- package/docs/decisions/ADR-082-the-tracker-override-is-a-manifest-key.md +35 -0
- package/package.json +2 -1
- package/rules/core.md +1 -0
- package/rules/stacks/android.md +2 -0
- package/rules/stacks/cloudflare-workers.md +2 -0
- package/rules/stacks/connectiq.md +1 -0
- package/rules/stacks/container.md +2 -0
- package/rules/stacks/dart.md +2 -0
- package/rules/stacks/deno.md +2 -0
- package/rules/stacks/dotnet.md +2 -0
- package/rules/stacks/elixir.md +2 -0
- package/rules/stacks/go.md +2 -0
- package/rules/stacks/jvm.md +2 -0
- package/rules/stacks/node.md +2 -0
- package/rules/stacks/python.md +2 -0
- package/rules/stacks/react-native.md +2 -0
- package/rules/stacks/react-web.md +2 -0
- package/rules/stacks/ruby.md +2 -0
- package/rules/stacks/rust.md +2 -0
- package/rules/stacks/swift.md +2 -0
- package/rules/stacks/typescript.md +2 -0
- package/rules/stacks/uv.md +2 -0
- package/src/agenthooks/decide.mjs +123 -0
- package/src/agenthooks/policy.mjs +78 -0
- package/src/agenthooks/render.mjs +240 -0
- package/src/argv.mjs +55 -1
- package/src/bench/ablate.mjs +630 -0
- package/src/bench/adapters.json +149 -0
- package/src/bench/grade.mjs +229 -0
- package/src/bench/mine.mjs +811 -0
- package/src/bench/probes.mjs +1350 -0
- package/src/bench/report.mjs +899 -0
- package/src/bench/run.mjs +1002 -0
- package/src/commands/agenthook.mjs +282 -0
- package/src/commands/bench.mjs +359 -0
- package/src/commands/compare.mjs +15 -0
- package/src/commands/graph.mjs +45 -21
- package/src/commands/harness.mjs +301 -0
- package/src/commands/help.mjs +73 -2
- package/src/commands/index.mjs +67 -25
- package/src/commands/init.mjs +12 -8
- package/src/commands/mcp.mjs +20 -5
- package/src/commands/plan.mjs +99 -0
- package/src/commands/rules.mjs +42 -57
- package/src/commands/skills.mjs +67 -92
- package/src/commands/skillsinstall.mjs +17 -2
- package/src/commands/sync.mjs +62 -6
- package/src/commands/tokens.mjs +169 -0
- package/src/compare/row.mjs +73 -2
- package/src/decisions/index.mjs +23 -5
- package/src/graph/spec.mjs +227 -0
- package/src/graph/speccheck.mjs +68 -0
- package/src/hooks/dispatcher.mjs +35 -3
- package/src/kernel/card.mjs +254 -0
- package/src/kernel/inferability.mjs +188 -0
- package/src/mcp/resources.mjs +157 -0
- package/src/mcp/schema.mjs +139 -0
- package/src/mcp/server.mjs +228 -20
- package/src/mcp/tools.mjs +372 -1
- package/src/measure/context.mjs +542 -0
- package/src/measure/loaderView.mjs +435 -0
- package/src/measure/session.mjs +267 -0
- package/src/monorepo.mjs +339 -0
- package/src/output/index.mjs +78 -2
- package/src/packs.mjs +182 -0
- package/src/plan/index.mjs +650 -0
- package/src/plan/template.mjs +331 -0
- package/src/projection/loaders.mjs +689 -0
- package/src/projection/manifest.mjs +114 -3
- package/src/projection/project.mjs +140 -9
- package/src/projection/rules.mjs +365 -22
- package/src/projection/surfaces.mjs +155 -23
- package/src/projection/targets.mjs +37 -0
- package/src/toolwords.mjs +69 -0
- package/templates/plan/tracker.json +36 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"adapters": {
|
|
4
|
+
"claude": {
|
|
5
|
+
"id": "claude",
|
|
6
|
+
"label": "Claude Code",
|
|
7
|
+
"kind": "cli",
|
|
8
|
+
"bin": "claude",
|
|
9
|
+
"template": "claude -p \"{prompt}\" --output-format json --max-turns {turns}",
|
|
10
|
+
"args": ["-p", "{prompt}", "--output-format", "json", "--max-turns", "{turns}"],
|
|
11
|
+
"modelArgs": ["--model", "{model}"],
|
|
12
|
+
"format": "json",
|
|
13
|
+
"usageSource": "usage.*, total_cost_usd, num_turns",
|
|
14
|
+
"fields": {
|
|
15
|
+
"input": ["usage.input_tokens", "usage.input"],
|
|
16
|
+
"cacheWrite": ["usage.cache_creation_input_tokens", "usage.cacheWrite"],
|
|
17
|
+
"cacheRead": ["usage.cache_read_input_tokens", "usage.cacheRead"],
|
|
18
|
+
"output": ["usage.output_tokens", "usage.output"],
|
|
19
|
+
"turns": ["num_turns", "usage.turns"],
|
|
20
|
+
"toolCalls": ["num_tool_calls", "tool_calls", "usage.toolCalls"],
|
|
21
|
+
"costUsd": ["total_cost_usd", "cost_usd", "costUsd"]
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"codex": {
|
|
25
|
+
"id": "codex",
|
|
26
|
+
"label": "Codex",
|
|
27
|
+
"kind": "cli",
|
|
28
|
+
"bin": "codex",
|
|
29
|
+
"template": "codex exec --json \"{prompt}\"",
|
|
30
|
+
"args": ["exec", "--json", "{prompt}"],
|
|
31
|
+
"modelArgs": ["--model", "{model}"],
|
|
32
|
+
"format": "jsonl",
|
|
33
|
+
"usageSource": "token events in JSONL",
|
|
34
|
+
"fields": {
|
|
35
|
+
"input": ["usage.input_tokens", "input_tokens", "usage.input"],
|
|
36
|
+
"cacheWrite": ["usage.cache_creation_input_tokens", "usage.cache_write_input_tokens", "cache_write_input_tokens"],
|
|
37
|
+
"cacheRead": ["usage.cached_input_tokens", "usage.cache_read_input_tokens", "cached_input_tokens"],
|
|
38
|
+
"output": ["usage.output_tokens", "output_tokens", "usage.output"],
|
|
39
|
+
"turns": ["turns", "num_turns"],
|
|
40
|
+
"toolCalls": ["tool_calls", "num_tool_calls"],
|
|
41
|
+
"costUsd": ["total_cost_usd", "cost_usd", "cost"]
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"gemini": {
|
|
45
|
+
"id": "gemini",
|
|
46
|
+
"label": "Gemini CLI",
|
|
47
|
+
"kind": "cli",
|
|
48
|
+
"bin": "gemini",
|
|
49
|
+
"template": "gemini -p \"{prompt}\" --output-format json",
|
|
50
|
+
"args": ["-p", "{prompt}", "--output-format", "json"],
|
|
51
|
+
"modelArgs": ["--model", "{model}"],
|
|
52
|
+
"format": "json",
|
|
53
|
+
"usageSource": "stats block",
|
|
54
|
+
"fields": {
|
|
55
|
+
"input": ["stats.inputTokens", "stats.tokens.prompt", "stats.prompt_tokens", "usage.input_tokens"],
|
|
56
|
+
"cacheWrite": ["stats.cacheWriteTokens", "stats.tokens.cacheWrite", "usage.cache_creation_input_tokens"],
|
|
57
|
+
"cacheRead": ["stats.cachedTokens", "stats.tokens.cached", "usage.cache_read_input_tokens"],
|
|
58
|
+
"output": ["stats.outputTokens", "stats.tokens.candidates", "stats.completion_tokens", "usage.output_tokens"],
|
|
59
|
+
"turns": ["stats.turns", "stats.totalTurns", "num_turns"],
|
|
60
|
+
"toolCalls": ["stats.toolCalls", "stats.tools.totalCalls", "num_tool_calls"],
|
|
61
|
+
"costUsd": ["stats.totalCostUsd", "stats.costUsd", "total_cost_usd"]
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"opencode": {
|
|
65
|
+
"id": "opencode",
|
|
66
|
+
"label": "opencode",
|
|
67
|
+
"kind": "cli",
|
|
68
|
+
"bin": "opencode",
|
|
69
|
+
"template": "opencode run \"{prompt}\"",
|
|
70
|
+
"args": ["run", "--format", "json", "{prompt}"],
|
|
71
|
+
"modelArgs": ["--model", "{model}"],
|
|
72
|
+
"format": "jsonl",
|
|
73
|
+
"usageSource": "session export",
|
|
74
|
+
"fields": {
|
|
75
|
+
"input": ["tokens.input", "usage.input_tokens", "part.tokens.input"],
|
|
76
|
+
"cacheWrite": ["tokens.cache.write", "usage.cache_creation_input_tokens", "part.tokens.cache.write"],
|
|
77
|
+
"cacheRead": ["tokens.cache.read", "usage.cache_read_input_tokens", "part.tokens.cache.read"],
|
|
78
|
+
"output": ["tokens.output", "usage.output_tokens", "part.tokens.output"],
|
|
79
|
+
"turns": ["turns", "num_turns"],
|
|
80
|
+
"toolCalls": ["toolCalls", "num_tool_calls"],
|
|
81
|
+
"costUsd": ["cost", "part.cost", "total_cost_usd"]
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"cursor": {
|
|
85
|
+
"id": "cursor",
|
|
86
|
+
"label": "Cursor CLI",
|
|
87
|
+
"kind": "cli",
|
|
88
|
+
"bin": "cursor-agent",
|
|
89
|
+
"template": "cursor-agent -p \"{prompt}\" --output-format json",
|
|
90
|
+
"args": ["-p", "{prompt}", "--output-format", "json"],
|
|
91
|
+
"modelArgs": ["--model", "{model}"],
|
|
92
|
+
"format": "json",
|
|
93
|
+
"usageSource": "result JSON",
|
|
94
|
+
"fields": {
|
|
95
|
+
"input": ["usage.input_tokens", "usage.input", "stats.inputTokens"],
|
|
96
|
+
"cacheWrite": ["usage.cache_creation_input_tokens", "usage.cacheWrite", "stats.cacheWriteTokens"],
|
|
97
|
+
"cacheRead": ["usage.cache_read_input_tokens", "usage.cacheRead", "stats.cachedTokens"],
|
|
98
|
+
"output": ["usage.output_tokens", "usage.output", "stats.outputTokens"],
|
|
99
|
+
"turns": ["num_turns", "turns", "usage.turns"],
|
|
100
|
+
"toolCalls": ["num_tool_calls", "tool_calls", "usage.toolCalls", "toolCalls"],
|
|
101
|
+
"costUsd": ["total_cost_usd", "costUsd", "cost_usd"]
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
"aider": {
|
|
105
|
+
"id": "aider",
|
|
106
|
+
"label": "Aider",
|
|
107
|
+
"kind": "cli",
|
|
108
|
+
"bin": "aider",
|
|
109
|
+
"template": "aider --message \"{prompt}\" --yes-always",
|
|
110
|
+
"args": ["--message", "{prompt}", "--yes-always", "--analytics-log", "{analyticsLog}"],
|
|
111
|
+
"modelArgs": ["--model", "{model}"],
|
|
112
|
+
"format": "analytics-log",
|
|
113
|
+
"usageSource": "--analytics-log file",
|
|
114
|
+
"fields": {
|
|
115
|
+
"input": ["prompt_tokens", "properties.prompt_tokens", "usage.input_tokens"],
|
|
116
|
+
"cacheWrite": ["cache_creation_input_tokens", "properties.cache_write_tokens"],
|
|
117
|
+
"cacheRead": ["cache_read_input_tokens", "properties.cache_read_tokens"],
|
|
118
|
+
"output": ["completion_tokens", "properties.completion_tokens", "usage.output_tokens"],
|
|
119
|
+
"turns": ["turns", "properties.turns"],
|
|
120
|
+
"toolCalls": ["tool_calls", "properties.tool_calls"],
|
|
121
|
+
"costUsd": ["cost", "total_cost", "properties.cost", "properties.total_cost"]
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
"gold": {
|
|
125
|
+
"id": "gold",
|
|
126
|
+
"label": "Gold Oracle",
|
|
127
|
+
"kind": "builtin",
|
|
128
|
+
"bin": null,
|
|
129
|
+
"template": null,
|
|
130
|
+
"args": [],
|
|
131
|
+
"modelArgs": [],
|
|
132
|
+
"format": "builtin",
|
|
133
|
+
"usageSource": "vault gold.patch",
|
|
134
|
+
"fields": {}
|
|
135
|
+
},
|
|
136
|
+
"noop": {
|
|
137
|
+
"id": "noop",
|
|
138
|
+
"label": "No-op Baseline",
|
|
139
|
+
"kind": "builtin",
|
|
140
|
+
"bin": null,
|
|
141
|
+
"template": null,
|
|
142
|
+
"args": [],
|
|
143
|
+
"modelArgs": [],
|
|
144
|
+
"format": "builtin",
|
|
145
|
+
"usageSource": "none",
|
|
146
|
+
"fields": {}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { readFile, rm } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { runGates } from '../hooks/dispatcher.mjs';
|
|
6
|
+
import { packageVersion } from '../package-version.mjs';
|
|
7
|
+
import { readManifest } from '../projection/manifest.mjs';
|
|
8
|
+
import { detectTestRunner } from './mine.mjs';
|
|
9
|
+
|
|
10
|
+
const DEFAULT_GRADE_TIMEOUT_MS = 45_000;
|
|
11
|
+
|
|
12
|
+
function sanitizedEnv(extra = {}) {
|
|
13
|
+
const env = { ...process.env, SIMT_DEV_WORKSPACE: 'off', ...extra };
|
|
14
|
+
for (const key of Object.keys(env)) {
|
|
15
|
+
if (key.startsWith('NODE_TEST')) delete env[key];
|
|
16
|
+
}
|
|
17
|
+
return env;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function runCmd(cmd, args, { cwd, input = null, timeoutMs = DEFAULT_GRADE_TIMEOUT_MS } = {}) {
|
|
21
|
+
return new Promise((resolveOutcome) => {
|
|
22
|
+
const child = spawn(cmd, args, {
|
|
23
|
+
cwd,
|
|
24
|
+
env: sanitizedEnv(),
|
|
25
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
26
|
+
});
|
|
27
|
+
const out = [];
|
|
28
|
+
const err = [];
|
|
29
|
+
let timedOut = false;
|
|
30
|
+
const timer =
|
|
31
|
+
timeoutMs > 0
|
|
32
|
+
? setTimeout(() => {
|
|
33
|
+
timedOut = true;
|
|
34
|
+
child.kill('SIGKILL');
|
|
35
|
+
}, timeoutMs)
|
|
36
|
+
: null;
|
|
37
|
+
|
|
38
|
+
child.stdout.on('data', (chunk) => out.push(chunk));
|
|
39
|
+
child.stderr.on('data', (chunk) => err.push(chunk));
|
|
40
|
+
child.on('error', (error) => {
|
|
41
|
+
if (timer) clearTimeout(timer);
|
|
42
|
+
resolveOutcome({
|
|
43
|
+
code: 1,
|
|
44
|
+
timedOut,
|
|
45
|
+
stdout: Buffer.concat(out),
|
|
46
|
+
stderr: Buffer.from(String(error.message ?? error)),
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
child.on('close', (code) => {
|
|
50
|
+
if (timer) clearTimeout(timer);
|
|
51
|
+
resolveOutcome({
|
|
52
|
+
code: timedOut ? 124 : (code ?? 1),
|
|
53
|
+
timedOut,
|
|
54
|
+
stdout: Buffer.concat(out),
|
|
55
|
+
stderr: Buffer.concat(err),
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
if (input !== null) child.stdin.end(input);
|
|
60
|
+
else child.stdin.end();
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Extract repository-relative file paths touched by a unified git diff.
|
|
66
|
+
*
|
|
67
|
+
* @param {Buffer|string} patch
|
|
68
|
+
* @returns {string[]}
|
|
69
|
+
*/
|
|
70
|
+
export function extractPatchFiles(patch) {
|
|
71
|
+
const text = Buffer.isBuffer(patch) ? patch.toString('utf8') : String(patch ?? '');
|
|
72
|
+
const files = new Set();
|
|
73
|
+
for (const line of text.split('\n')) {
|
|
74
|
+
const match = /^diff --git a\/(.+?) b\/(.+)$/.exec(line);
|
|
75
|
+
if (match) files.add(match[2]);
|
|
76
|
+
}
|
|
77
|
+
return [...files].sort();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function resetFilesToHead(workDir, initialHead, files) {
|
|
81
|
+
for (const file of files) {
|
|
82
|
+
const existsAtHead = await runCmd('git', ['cat-file', '-e', `${initialHead}:${file}`], { cwd: workDir });
|
|
83
|
+
if (existsAtHead.code === 0) {
|
|
84
|
+
await runCmd('git', ['checkout', '-f', initialHead, '--', file], { cwd: workDir });
|
|
85
|
+
} else {
|
|
86
|
+
await runCmd('git', ['rm', '-f', '--ignore-unmatch', '--', file], { cwd: workDir });
|
|
87
|
+
await rm(join(workDir, file), { force: true }).catch(() => {});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function evaluateDeclaredGates(workDir, testPassed) {
|
|
93
|
+
const { exists, data: manifest, error } = await readManifest(workDir);
|
|
94
|
+
if (!exists) return testPassed;
|
|
95
|
+
if (error || !manifest) return false;
|
|
96
|
+
|
|
97
|
+
const hasCustomTestCmd = Boolean(manifest?.gates?.commands?.test);
|
|
98
|
+
const filterGates = (list) =>
|
|
99
|
+
Array.isArray(list) ? list.filter((g) => g !== 'test' || hasCustomTestCmd) : [];
|
|
100
|
+
|
|
101
|
+
const alignedManifest = {
|
|
102
|
+
...manifest,
|
|
103
|
+
projection: {
|
|
104
|
+
...(manifest.projection ?? {}),
|
|
105
|
+
version: packageVersion(),
|
|
106
|
+
},
|
|
107
|
+
gates: {
|
|
108
|
+
...(manifest.gates ?? {}),
|
|
109
|
+
preCommit: filterGates(manifest?.gates?.preCommit),
|
|
110
|
+
prePush: filterGates(manifest?.gates?.prePush),
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
await runCmd('git', ['add', '-A'], { cwd: workDir });
|
|
115
|
+
const preCommit = await runGates('pre-commit', { root: workDir, manifest: alignedManifest });
|
|
116
|
+
const prePush = await runGates('pre-push', { root: workDir, manifest: alignedManifest });
|
|
117
|
+
return testPassed && !preCommit.failed && !prePush.failed;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Grade an isolated agent workspace after the agent finishes.
|
|
122
|
+
*
|
|
123
|
+
* Measures the agent's diff before applying the hidden verifier test patch, then resets
|
|
124
|
+
* any test files touched by `test.patch` to `initialHead` so an agent cannot pass by
|
|
125
|
+
* weakening or deleting the test suite.
|
|
126
|
+
*
|
|
127
|
+
* @param {string} workDir
|
|
128
|
+
* @param {object} task
|
|
129
|
+
* @param {{vaultRoot: string, initialHead: string, timeoutMs?: number}} options
|
|
130
|
+
* @returns {Promise<{resolved: boolean, gatesPassed: boolean, diff: {files: number, outsideBlastRadius: number}, agentPatch: Buffer}>}
|
|
131
|
+
*/
|
|
132
|
+
export async function gradeWorkspace(workDir, task, { vaultRoot, initialHead, timeoutMs = DEFAULT_GRADE_TIMEOUT_MS }) {
|
|
133
|
+
const taskVaultDir = join(vaultRoot, task.id);
|
|
134
|
+
const [testPatch, goldPatch] = await Promise.all([
|
|
135
|
+
readFile(join(taskVaultDir, 'test.patch')),
|
|
136
|
+
readFile(join(taskVaultDir, 'gold.patch')).catch(() => Buffer.alloc(0)),
|
|
137
|
+
]);
|
|
138
|
+
|
|
139
|
+
// Remove transient brief file if the `brief` arm or agent left one untracked, so it
|
|
140
|
+
// is not counted as an implementation edit outside the task's blast radius.
|
|
141
|
+
await runCmd('git', ['add', '-N', '.'], { cwd: workDir });
|
|
142
|
+
const [diffNamesRes, diffPatchRes] = await Promise.all([
|
|
143
|
+
runCmd('git', ['diff', '--name-only', initialHead], { cwd: workDir }),
|
|
144
|
+
runCmd('git', ['diff', '--binary', initialHead], { cwd: workDir }),
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
const changedFiles = diffNamesRes.stdout
|
|
148
|
+
.toString('utf8')
|
|
149
|
+
.split('\n')
|
|
150
|
+
.map((s) => s.trim())
|
|
151
|
+
.filter((f) => Boolean(f) && !f.startsWith('.simt-dev/'));
|
|
152
|
+
|
|
153
|
+
const goldFiles = new Set(extractPatchFiles(goldPatch));
|
|
154
|
+
const outsideBlastRadius =
|
|
155
|
+
goldFiles.size > 0
|
|
156
|
+
? changedFiles.filter((file) => !goldFiles.has(file)).length
|
|
157
|
+
: 0;
|
|
158
|
+
const diff = {
|
|
159
|
+
files: changedFiles.length,
|
|
160
|
+
outsideBlastRadius,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const testPatchFiles = extractPatchFiles(testPatch);
|
|
164
|
+
await resetFilesToHead(workDir, initialHead, testPatchFiles);
|
|
165
|
+
|
|
166
|
+
const applyTest = await runCmd('git', ['apply', '-'], {
|
|
167
|
+
cwd: workDir,
|
|
168
|
+
input: testPatch,
|
|
169
|
+
timeoutMs,
|
|
170
|
+
});
|
|
171
|
+
if (applyTest.code !== 0) {
|
|
172
|
+
return {
|
|
173
|
+
resolved: false,
|
|
174
|
+
gatesPassed: false,
|
|
175
|
+
diff,
|
|
176
|
+
agentPatch: diffPatchRes.stdout,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const runner = await detectTestRunner(workDir, testPatchFiles);
|
|
181
|
+
if (!runner) {
|
|
182
|
+
return {
|
|
183
|
+
resolved: false,
|
|
184
|
+
gatesPassed: false,
|
|
185
|
+
diff,
|
|
186
|
+
agentPatch: diffPatchRes.stdout,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const execSpec = runner.buildExec(workDir);
|
|
191
|
+
const runRes = await runCmd(execSpec.cmd, execSpec.args, { cwd: workDir, timeoutMs });
|
|
192
|
+
const reportText = execSpec.reportPath
|
|
193
|
+
? await readFile(execSpec.reportPath, 'utf8').catch(() => '')
|
|
194
|
+
: '';
|
|
195
|
+
const parsed = runner.parse(runRes.stdout.toString('utf8'), reportText);
|
|
196
|
+
const testRun = {
|
|
197
|
+
code: runRes.code,
|
|
198
|
+
timedOut: runRes.timedOut,
|
|
199
|
+
passed: parsed.passed,
|
|
200
|
+
failed: parsed.failed,
|
|
201
|
+
};
|
|
202
|
+
const passedSet = new Set(testRun.passed);
|
|
203
|
+
const f2pResolved =
|
|
204
|
+
Array.isArray(task.f2p) &&
|
|
205
|
+
task.f2p.length > 0 &&
|
|
206
|
+
task.f2p.every((id) => passedSet.has(id));
|
|
207
|
+
const p2pIntact =
|
|
208
|
+
Array.isArray(task.p2p) &&
|
|
209
|
+
task.p2p.every((id) => passedSet.has(id));
|
|
210
|
+
|
|
211
|
+
const resolved =
|
|
212
|
+
!testRun.timedOut &&
|
|
213
|
+
testRun.code === 0 &&
|
|
214
|
+
testRun.failed.length === 0 &&
|
|
215
|
+
f2pResolved &&
|
|
216
|
+
p2pIntact;
|
|
217
|
+
|
|
218
|
+
const gatesPassed = await evaluateDeclaredGates(
|
|
219
|
+
workDir,
|
|
220
|
+
!testRun.timedOut && testRun.code === 0 && testRun.failed.length === 0,
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
return {
|
|
224
|
+
resolved,
|
|
225
|
+
gatesPassed,
|
|
226
|
+
diff,
|
|
227
|
+
agentPatch: diffPatchRes.stdout,
|
|
228
|
+
};
|
|
229
|
+
}
|