simt-dev 0.6.3 → 0.7.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.
- package/CHANGELOG.md +162 -841
- package/README.md +21 -135
- package/agents/simt-agent.md +84 -0
- package/bin/simt.mjs +19 -55
- package/docs/decisions/ADR-009-skill-layout.md +1 -1
- package/docs/decisions/ADR-014-workspace-is-configuration.md +1 -1
- package/docs/decisions/ADR-086-the-stable-prefix-comes-first-and-the-lock-records-both-digests.md +1 -1
- package/docs/decisions/ADR-099-the-release-verifies-served-bytes-and-the-formula-mirrors-them.md +1 -1
- package/docs/decisions/ADR-106-the-harness-hook-runs-through-a-repo-shim.md +46 -0
- package/docs/decisions/ADR-107-an-install-can-name-one-harness-and-the-skipped-ones-are-named.md +51 -0
- package/docs/decisions/ADR-108-an-adoption-builds-the-index-and-a-command-refreshes-it-on-read.md +59 -0
- package/docs/decisions/ADR-109-simt-dev-is-its-own-consumer-and-the-guards-that-kept-it-from-being-one-are-retired.md +47 -0
- package/docs/decisions/ADR-110-the-lock-file-follows-the-manifest-name-and-the-old-name-is-read-until-the-next-write.md +43 -0
- package/docs/decisions/ADR-111-the-tool-ships-one-agent-rendered-per-harness-and-installs-it-with-init-and-a-command.md +75 -0
- package/docs/decisions/ADR-112-the-registry-is-what-simt-dev-ships-and-a-repo-owns-its-own-skills.md +81 -0
- package/docs/decisions/ADR-113-a-pre-push-that-carries-only-documentation-skips-the-test-gate-and-says-so.md +87 -0
- package/docs/decisions/ADR-114-the-bench-pairs-at-the-task-level-a-missing-cost-is-not-zero-and-an-unasked-cross-group-compare-is-refused.md +74 -0
- package/docs/decisions/ADR-115-the-index-stamps-its-schema-version-migrates-older-databases-in-place-and-serves-newer-ones-from-a-sidecar.md +69 -0
- package/docs/decisions/ADR-116-rust-support-reaches-the-example-path-the-indent-and-the-bench-runner.md +100 -0
- package/docs/decisions/ADR-117-the-darwin-release-builds-on-supported-macos-15-runners-and-a-re-run-is-idempotent.md +81 -0
- package/docs/decisions/ADR-118-the-bench-corpus-is-keyed-on-history-is-append-only-and-is-portable-as-a-sealed-document.md +91 -0
- package/docs/decisions/ADR-119-the-environment-variable-points-into-a-workspace-the-repo-declared.md +90 -0
- package/docs/decisions/ADR-120-a-pre-push-whose-tree-is-already-proven-green-skips-the-test-gate-on-that-evidence.md +96 -0
- package/docs/decisions/ADR-122-the-shims-move-into-the-state-directory-and-the-legacy-directory-removes-itself.md +94 -0
- package/docs/decisions/ADR-123-a-surplus-positional-is-not-refused-because-arity-is-not-in-the-source.md +57 -0
- package/docs/decisions/ADR-124-the-manifest-has-a-schema-in-the-tool-and-its-unknown-keys-stay-the-repositorys.md +64 -0
- package/docs/decisions/ADR-125-a-shared-context-belongs-to-a-read-and-a-writer-reads-directly.md +65 -0
- package/package.json +13 -3
- package/rules/core.md +9 -2
- package/rules/mdc/slice-commit.mdc +57 -4
- package/rules/mdc/test-gate.mdc +2 -0
- package/rules/stacks/android.md +3 -3
- package/rules/stacks/connectiq.md +2 -2
- package/rules/stacks/dart.md +1 -1
- package/rules/stacks/deno.md +1 -1
- package/rules/stacks/dotnet.md +1 -1
- package/rules/stacks/elixir.md +1 -1
- package/rules/stacks/go.md +1 -1
- package/rules/stacks/jvm.md +3 -2
- package/rules/stacks/node.md +2 -2
- package/rules/stacks/python.md +7 -6
- package/rules/stacks/ruby.md +1 -1
- package/rules/stacks/rust.md +1 -1
- package/rules/stacks/swift.md +1 -1
- package/rules/stacks/typescript.md +2 -2
- package/rules/stacks/uv.md +3 -2
- package/skills/simt-commands/SKILL.md +70 -0
- package/src/advice/advice.mjs +7 -3
- package/src/agenthooks/decide.mjs +141 -9
- package/src/agenthooks/opencode.mjs +72 -0
- package/src/agenthooks/prompt.mjs +10 -3
- package/src/agenthooks/render.mjs +186 -19
- package/src/agenthooks/shim.mjs +55 -0
- package/src/agents/render.mjs +277 -0
- package/src/api.mjs +29 -0
- package/src/argv.mjs +37 -3
- package/src/bench/ablate.mjs +31 -14
- package/src/bench/adapters.json +16 -0
- package/src/bench/corpus.mjs +150 -0
- package/src/bench/grade.mjs +23 -49
- package/src/bench/mine.mjs +247 -36
- package/src/bench/probes.mjs +104 -68
- package/src/bench/process.mjs +128 -0
- package/src/bench/report.mjs +260 -84
- package/src/bench/run.mjs +251 -150
- package/src/bench/variants.mjs +112 -40
- package/src/brief/assemble.mjs +44 -3
- package/src/ci/workflow.mjs +46 -14
- package/src/cli.mjs +45 -0
- package/src/commands/agent.mjs +205 -0
- package/src/commands/agenthook.mjs +113 -20
- package/src/commands/bench.mjs +192 -18
- package/src/commands/brief.mjs +5 -4
- package/src/commands/ci.mjs +2 -1
- package/src/commands/compare.mjs +50 -49
- package/src/commands/completions.mjs +2 -6
- package/src/commands/decide.mjs +10 -9
- package/src/commands/distill.mjs +4 -3
- package/src/commands/doctor.mjs +11 -7
- package/src/commands/evidence.mjs +19 -12
- package/src/commands/explain.mjs +0 -1
- package/src/commands/gate.mjs +98 -28
- package/src/commands/graph.mjs +20 -8
- package/src/commands/harness.mjs +3 -2
- package/src/commands/help.mjs +160 -31
- package/src/commands/hooks.mjs +163 -50
- package/src/commands/ignore.mjs +2 -1
- package/src/commands/impact.mjs +8 -9
- package/src/commands/index.mjs +278 -180
- package/src/commands/init.mjs +171 -38
- package/src/commands/lease.mjs +206 -39
- package/src/commands/lessons.mjs +72 -15
- package/src/commands/local.mjs +3 -3
- package/src/commands/map.mjs +47 -27
- package/src/commands/mcp.mjs +0 -1
- package/src/commands/pack.mjs +9 -6
- package/src/commands/plan.mjs +6 -5
- package/src/commands/plugin.mjs +3 -3
- package/src/commands/prbody.mjs +3 -3
- package/src/commands/repoindex.mjs +5 -3
- package/src/commands/review.mjs +2 -2
- package/src/commands/rules.mjs +124 -33
- package/src/commands/shared.mjs +2 -3
- package/src/commands/skills.mjs +82 -45
- package/src/commands/skillscompile.mjs +12 -12
- package/src/commands/skillsinstall.mjs +14 -2
- package/src/commands/slice.mjs +396 -26
- package/src/commands/spec.mjs +151 -0
- package/src/commands/status.mjs +4 -7
- package/src/commands/sync.mjs +50 -33
- package/src/commands/tests.mjs +11 -11
- package/src/commands/tokens.mjs +2 -2
- package/src/commands/undo.mjs +29 -31
- package/src/commands/upgrade.mjs +40 -4
- package/src/commands/why.mjs +19 -12
- package/src/compare/hooksstate.mjs +23 -1
- package/src/compare/learn.mjs +82 -57
- package/src/compare/parallel.mjs +50 -0
- package/src/compare/row.mjs +26 -3
- package/src/completions.mjs +5 -0
- package/src/context.mjs +64 -0
- package/src/decisions/dir.mjs +84 -0
- package/src/decisions/index-doc.mjs +46 -0
- package/src/decisions/index.mjs +109 -11
- package/src/diff.mjs +1 -1
- package/src/distill/distill.mjs +17 -2
- package/src/distill/engine.mjs +5 -1
- package/src/ecosystem/node-runner.mjs +144 -0
- package/src/explain.mjs +18 -6
- package/src/graph/load.mjs +42 -20
- package/src/hooks/capability.mjs +17 -4
- package/src/hooks/dispatcher.mjs +513 -36
- package/src/hooks/gates.mjs +0 -43
- package/src/hooks/resolve.mjs +11 -10
- package/src/hooks/shim.mjs +17 -9
- package/src/identity.mjs +96 -15
- package/src/index/extract.mjs +192 -11
- package/src/index/persist.mjs +307 -47
- package/src/index/store.mjs +263 -60
- package/src/index/tests.mjs +6 -35
- package/src/journal.mjs +372 -84
- package/src/kernel/card.mjs +37 -12
- package/src/kernel/checks.mjs +44 -9
- package/src/kernel/variants.mjs +27 -16
- package/src/lease.mjs +97 -15
- package/src/ledger/index.mjs +120 -25
- package/src/local/client.mjs +4 -6
- package/src/locate.mjs +33 -9
- package/src/manifest/schema.mjs +213 -0
- package/src/mcp/resources.mjs +12 -13
- package/src/mcp/server.mjs +151 -38
- package/src/mcp/tools.mjs +90 -16
- package/src/measure/session.mjs +3 -2
- package/src/memory/decide.mjs +98 -43
- package/src/memory/lessons.mjs +168 -33
- package/src/name.mjs +9 -0
- package/src/node-version.mjs +37 -0
- package/src/output/index.mjs +111 -3
- package/src/packs/manage.mjs +17 -3
- package/src/packs.mjs +20 -2
- package/src/plan/index.mjs +31 -8
- package/src/plan/template.mjs +27 -5
- package/src/plugins.mjs +6 -2
- package/src/prbody.mjs +7 -5
- package/src/projection/baselines.mjs +1 -1
- package/src/projection/disclosure.mjs +2 -2
- package/src/projection/loaders.mjs +41 -1
- package/src/projection/lock.mjs +31 -14
- package/src/projection/manifest.mjs +89 -53
- package/src/projection/markers.mjs +43 -3
- package/src/projection/mdc.mjs +34 -7
- package/src/projection/project.mjs +162 -18
- package/src/projection/rules.mjs +58 -6
- package/src/projection/surfaces.mjs +9 -1
- package/src/repo-root.mjs +88 -0
- package/src/resolve/body.mjs +4 -0
- package/src/resolve/fingerprint.mjs +24 -15
- package/src/resolve/local.mjs +58 -0
- package/src/review/critic.mjs +6 -3
- package/src/secrets.mjs +165 -22
- package/src/state.mjs +76 -0
- package/src/status.mjs +9 -4
- package/src/toolwords.mjs +8 -0
- package/src/tsconfig.mjs +39 -99
- package/src/workspace.mjs +55 -10
- package/templates/plan/tracker.json +1 -1
- package/docs/decisions/README.md +0 -591
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
// Sealed export / import of the mined benchmark corpus (review 0003 Q48).
|
|
2
|
+
//
|
|
3
|
+
// The vault is keyed on the repository's root commit, outside every checkout, so
|
|
4
|
+
// a clone of the repository cannot read the hidden test patches the miner wrote:
|
|
5
|
+
// the committed corpus was unrunnable from any machine that was not the one that
|
|
6
|
+
// mined it. A sealed document carries the task descriptors and both patches for
|
|
7
|
+
// every task in a single file, so `bench export` on one machine and `bench import`
|
|
8
|
+
// on another reproduce the corpus exactly.
|
|
9
|
+
//
|
|
10
|
+
// The document is deterministic: no timestamp, tasks sorted by id, canonical
|
|
11
|
+
// (key-sorted) JSON, and a sha256 seal over everything but the seal itself. Two
|
|
12
|
+
// exports of the same corpus are byte-identical, and a reader can verify the
|
|
13
|
+
// seal before trusting a single byte of it.
|
|
14
|
+
|
|
15
|
+
import { createHash } from 'node:crypto';
|
|
16
|
+
import { chmod, mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
|
|
17
|
+
import { dirname, join, resolve } from 'node:path';
|
|
18
|
+
|
|
19
|
+
import { STATE_DIR } from '../name.mjs';
|
|
20
|
+
import { packageVersion } from '../package-version.mjs';
|
|
21
|
+
import { resolveVaultDir } from './mine.mjs';
|
|
22
|
+
|
|
23
|
+
/** The document kind stamp, so a reader can refuse a file that is not a corpus. */
|
|
24
|
+
export const CORPUS_KIND = 'simt-bench-corpus';
|
|
25
|
+
|
|
26
|
+
/** The document schema version. Bump when the shape changes, not when content does. */
|
|
27
|
+
export const CORPUS_SCHEMA_VERSION = 1;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Canonical serialisation: object keys sorted, no whitespace. Arrays keep their
|
|
31
|
+
* order (the caller sorts them). This is what the seal hashes, so both the
|
|
32
|
+
* exporter and the importer must reduce the payload to exactly this form.
|
|
33
|
+
*
|
|
34
|
+
* @param {*} value a JSON value
|
|
35
|
+
* @returns {string}
|
|
36
|
+
*/
|
|
37
|
+
function canonical(value) {
|
|
38
|
+
if (value === null || typeof value !== 'object') return JSON.stringify(value);
|
|
39
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`;
|
|
40
|
+
const keys = Object.keys(value).sort();
|
|
41
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonical(value[k])}`).join(',')}}`;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param {{tasks: object[]}} payload the document minus its seal
|
|
46
|
+
* @returns {string} hex sha256 of the canonical payload
|
|
47
|
+
*/
|
|
48
|
+
function sealOf(payload) {
|
|
49
|
+
return createHash('sha256').update(canonical(payload)).digest('hex');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function payloadOf(doc) {
|
|
53
|
+
const { seal, ...payload } = doc;
|
|
54
|
+
void seal;
|
|
55
|
+
return payload;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Read every mined task and its hidden patches from `root` and assemble the
|
|
60
|
+
* sealed corpus document (no file written; the command decides where it goes).
|
|
61
|
+
*
|
|
62
|
+
* @param {string} root
|
|
63
|
+
* @param {{env?: NodeJS.ProcessEnv}} [options]
|
|
64
|
+
* @returns {Promise<object>} the sealed document
|
|
65
|
+
*/
|
|
66
|
+
export async function buildCorpusDocument(root, { env = process.env } = {}) {
|
|
67
|
+
const absRoot = resolve(root);
|
|
68
|
+
const tasksDir = join(absRoot, STATE_DIR, 'bench', 'tasks');
|
|
69
|
+
const vaultRoot = resolveVaultDir(absRoot, { env });
|
|
70
|
+
|
|
71
|
+
const names = (await readdir(tasksDir).catch(() => [])).filter((f) => f.endsWith('.json')).sort();
|
|
72
|
+
const tasks = [];
|
|
73
|
+
for (const name of names) {
|
|
74
|
+
const task = JSON.parse(await readFile(join(tasksDir, name), 'utf8'));
|
|
75
|
+
const testPatch = await readFile(join(vaultRoot, task.id, 'test.patch'))
|
|
76
|
+
.then((b) => b.toString('base64'))
|
|
77
|
+
.catch(() => '');
|
|
78
|
+
const goldPatch = await readFile(join(vaultRoot, task.id, 'gold.patch'))
|
|
79
|
+
.then((b) => b.toString('base64'))
|
|
80
|
+
.catch(() => '');
|
|
81
|
+
tasks.push({ id: task.id, task, testPatch, goldPatch });
|
|
82
|
+
}
|
|
83
|
+
tasks.sort((a, b) => a.id.localeCompare(b.id));
|
|
84
|
+
|
|
85
|
+
const corpus = await readFile(join(dirname(tasksDir), 'corpus.json'), 'utf8')
|
|
86
|
+
.then((t) => JSON.parse(t))
|
|
87
|
+
.catch(() => null);
|
|
88
|
+
|
|
89
|
+
const payload = {
|
|
90
|
+
schemaVersion: CORPUS_SCHEMA_VERSION,
|
|
91
|
+
kind: CORPUS_KIND,
|
|
92
|
+
minedFrom: corpus?.minedFrom ?? null,
|
|
93
|
+
simtVersion: corpus?.simtVersion ?? packageVersion(),
|
|
94
|
+
taskCount: tasks.length,
|
|
95
|
+
tasks,
|
|
96
|
+
};
|
|
97
|
+
return { ...payload, seal: sealOf(payload) };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Verify a corpus document's seal. Returns a reason string when the document is
|
|
102
|
+
* not a trusted corpus; `null` when it is.
|
|
103
|
+
*
|
|
104
|
+
* @param {object} doc
|
|
105
|
+
* @returns {string|null}
|
|
106
|
+
*/
|
|
107
|
+
export function verifyCorpusDocument(doc) {
|
|
108
|
+
if (doc === null || typeof doc !== 'object' || Array.isArray(doc)) return 'not a corpus document';
|
|
109
|
+
if (doc.kind !== CORPUS_KIND) return `unexpected kind \`${doc.kind}\` (want \`${CORPUS_KIND}\`)`;
|
|
110
|
+
if (doc.schemaVersion !== CORPUS_SCHEMA_VERSION) {
|
|
111
|
+
return `unsupported schemaVersion \`${doc.schemaVersion}\` (want ${CORPUS_SCHEMA_VERSION})`;
|
|
112
|
+
}
|
|
113
|
+
const payload = payloadOf(doc);
|
|
114
|
+
if (sealOf(payload) !== doc.seal) return 'seal mismatch: the document was modified after it was sealed';
|
|
115
|
+
if (!Array.isArray(payload.tasks)) return 'missing task list';
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Write a verified corpus document into `root`: task descriptors to
|
|
121
|
+
* `.simt/bench/tasks/` and the hidden patches to the vault. Idempotent — a task
|
|
122
|
+
* already present is overwritten with the document's copy, which is the point of
|
|
123
|
+
* importing a known corpus.
|
|
124
|
+
*
|
|
125
|
+
* @param {string} root
|
|
126
|
+
* @param {object} doc a document that passed `verifyCorpusDocument`
|
|
127
|
+
* @param {{env?: NodeJS.ProcessEnv, apply?: boolean}} [options]
|
|
128
|
+
* @returns {Promise<{applied: boolean, tasks: number, tasksDir: string, vaultRoot: string}>}
|
|
129
|
+
*/
|
|
130
|
+
export async function writeCorpusDocument(root, doc, { env = process.env, apply = true } = {}) {
|
|
131
|
+
const absRoot = resolve(root);
|
|
132
|
+
const tasksDir = join(absRoot, STATE_DIR, 'bench', 'tasks');
|
|
133
|
+
const vaultRoot = resolveVaultDir(absRoot, { env });
|
|
134
|
+
|
|
135
|
+
if (apply) await mkdir(tasksDir, { recursive: true });
|
|
136
|
+
for (const entry of doc.tasks) {
|
|
137
|
+
if (apply) {
|
|
138
|
+
await writeFile(join(tasksDir, `${entry.id}.json`), `${JSON.stringify(entry.task, null, 2)}\n`, 'utf8');
|
|
139
|
+
const taskVault = join(vaultRoot, entry.id);
|
|
140
|
+
// An imported corpus restores an answer key, so it gets the same 0700 the miner
|
|
141
|
+
// gives one (review 0003 Q49): the import must not be the cheaper way in.
|
|
142
|
+
await mkdir(taskVault, { recursive: true, mode: 0o700 });
|
|
143
|
+
await chmod(taskVault, 0o700).catch(() => {});
|
|
144
|
+
await writeFile(join(taskVault, 'test.patch'), Buffer.from(entry.testPatch, 'base64'));
|
|
145
|
+
await writeFile(join(taskVault, 'gold.patch'), Buffer.from(entry.goldPatch, 'base64'));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return { applied: apply, tasks: doc.tasks.length, tasksDir, vaultRoot };
|
|
150
|
+
}
|
package/src/bench/grade.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
2
|
import { readFile, rm } from 'node:fs/promises';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
|
|
5
5
|
import { runGates } from '../hooks/dispatcher.mjs';
|
|
6
6
|
import { packageVersion } from '../package-version.mjs';
|
|
7
7
|
import { readManifest } from '../projection/manifest.mjs';
|
|
8
|
-
import { detectTestRunner } from './mine.mjs';
|
|
8
|
+
import { detectTestRunner, readReports } from './mine.mjs';
|
|
9
|
+
import { spawnBounded } from './process.mjs';
|
|
9
10
|
|
|
10
11
|
const DEFAULT_GRADE_TIMEOUT_MS = 45_000;
|
|
11
12
|
|
|
@@ -17,48 +18,8 @@ function sanitizedEnv(extra = {}) {
|
|
|
17
18
|
return env;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
function runCmd(cmd, args, { cwd, input = null, timeoutMs = DEFAULT_GRADE_TIMEOUT_MS } = {}) {
|
|
21
|
-
return
|
|
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
|
-
});
|
|
21
|
+
function runCmd(cmd, args, { cwd, env, input = null, timeoutMs = DEFAULT_GRADE_TIMEOUT_MS } = {}) {
|
|
22
|
+
return spawnBounded(cmd, args, { cwd, env: env ?? sanitizedEnv(), input, timeoutMs });
|
|
62
23
|
}
|
|
63
24
|
|
|
64
25
|
/**
|
|
@@ -136,8 +97,10 @@ export async function gradeWorkspace(workDir, task, { vaultRoot, initialHead, ti
|
|
|
136
97
|
readFile(join(taskVaultDir, 'gold.patch')).catch(() => Buffer.alloc(0)),
|
|
137
98
|
]);
|
|
138
99
|
|
|
139
|
-
//
|
|
140
|
-
//
|
|
100
|
+
// Intent-to-add every untracked file so a file an agent created is part of the diff:
|
|
101
|
+
// `git diff` reports tracked changes only and would not count a new file at all.
|
|
102
|
+
// Nothing is removed — an arm's untracked file is an edit outside the blast radius,
|
|
103
|
+
// which is what it is.
|
|
141
104
|
await runCmd('git', ['add', '-N', '.'], { cwd: workDir });
|
|
142
105
|
const [diffNamesRes, diffPatchRes] = await Promise.all([
|
|
143
106
|
runCmd('git', ['diff', '--name-only', initialHead], { cwd: workDir }),
|
|
@@ -173,6 +136,7 @@ export async function gradeWorkspace(workDir, task, { vaultRoot, initialHead, ti
|
|
|
173
136
|
resolved: false,
|
|
174
137
|
gatesPassed: false,
|
|
175
138
|
diff,
|
|
139
|
+
f2pPassed: [],
|
|
176
140
|
goldFiles: [...goldFiles],
|
|
177
141
|
agentPatch: diffPatchRes.stdout,
|
|
178
142
|
};
|
|
@@ -184,16 +148,23 @@ export async function gradeWorkspace(workDir, task, { vaultRoot, initialHead, ti
|
|
|
184
148
|
resolved: false,
|
|
185
149
|
gatesPassed: false,
|
|
186
150
|
diff,
|
|
151
|
+
f2pPassed: [],
|
|
187
152
|
goldFiles: [...goldFiles],
|
|
188
153
|
agentPatch: diffPatchRes.stdout,
|
|
189
154
|
};
|
|
190
155
|
}
|
|
191
156
|
|
|
192
157
|
const execSpec = runner.buildExec(workDir);
|
|
158
|
+
// Same stale-report hazard as the miner: a report left by a prior run in
|
|
159
|
+
// this workspace would be graded as this agent's result (review 0003 Q48).
|
|
160
|
+
for (const p of execSpec.reportPaths ?? []) {
|
|
161
|
+
if (existsSync(p)) await rm(p, { recursive: true, force: true });
|
|
162
|
+
}
|
|
193
163
|
const runRes = await runCmd(execSpec.cmd, execSpec.args, { cwd: workDir, timeoutMs });
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
164
|
+
// readReports walks the report *directories* Gradle and Maven write (one
|
|
165
|
+
// XML per test class); the old single-file read saw none of them, so the
|
|
166
|
+
// grader resolved on an empty parse.
|
|
167
|
+
const reportText = await readReports(execSpec.reportPaths);
|
|
197
168
|
const parsed = runner.parse(runRes.stdout.toString('utf8'), reportText);
|
|
198
169
|
const testRun = {
|
|
199
170
|
code: runRes.code,
|
|
@@ -226,6 +197,9 @@ export async function gradeWorkspace(workDir, task, { vaultRoot, initialHead, ti
|
|
|
226
197
|
resolved,
|
|
227
198
|
gatesPassed,
|
|
228
199
|
diff,
|
|
200
|
+
// Which fail-to-pass tests actually passed. `resolved` is a boolean, so a regrade
|
|
201
|
+
// could only compare the verdict; the set is what a reader can check (review Q59).
|
|
202
|
+
f2pPassed: Array.isArray(task.f2p) ? task.f2p.filter((id) => passedSet.has(id)) : [],
|
|
229
203
|
goldFiles: [...goldFiles],
|
|
230
204
|
agentPatch: diffPatchRes.stdout,
|
|
231
205
|
};
|