amsd-pipeline-shared 0.5.9 → 0.5.10

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.
@@ -91,7 +91,7 @@ async function handle(a) {
91
91
  if (git(wt, 'status', '--porcelain', '--untracked-files=no').stdout.trim()) return stop('uncommitted', 'the agent left changes to tracked files uncommitted');
92
92
 
93
93
  const layer = r.answer.layer;
94
- const v = verifyRepair({ repo, base, branch, profile, layer, abortKind: c.kind });
94
+ const v = verifyRepair({ repo, base, branch, profile, layer, abortKind: c.kind, install });
95
95
  fs.writeFileSync(path.join(A, 'red.txt'), v.red || ''); fs.writeFileSync(path.join(A, 'green.txt'), v.green || '');
96
96
  fs.writeFileSync(path.join(A, 'patch.diff'), git(repo, 'diff', `${base}..${branch}`).stdout);
97
97
  if (!v.ok) { d.event('repair-rejected', { why: v.why }, attempt.n); return stop('rejected', v.why); }
@@ -58,6 +58,9 @@ function validate(input) {
58
58
  if (need('test', t, 'envUnset')) out.test.envUnset = strs('test.envUnset', t.envUnset, { nonEmpty: false });
59
59
  if (need('test', t, 'greenPatterns')) { if (Array.isArray(t.greenPatterns) && !t.greenPatterns.length) bad('test.greenPatterns', 'at least one pattern is required'); out.test.greenPatterns = rxs('test.greenPatterns', t.greenPatterns); }
60
60
  if (need('test', t, 'failedPattern')) out.test.failedPattern = rx('test.failedPattern', t.failedPattern);
61
+ // OPTIONAL: environment variables the test run needs (name -> value, {install} filled in): a test that reads the pipeline install it runs against
62
+ out.test.env = {};
63
+ if (has(t, 'env')) { if (!t.env || typeof t.env !== 'object' || Array.isArray(t.env)) bad('test.env', 'must be an object of name: value strings'); else for (const [k, v] of Object.entries(t.env)) { if (typeof v !== 'string') bad(`test.env.${k}`, 'must be a string'); else out.test.env[k] = v; } }
61
64
  // OPTIONAL: repository paths that git ignores and the tests read (a codeline copy, generated data) — linked into every throwaway worktree the verification runs its tests in
62
65
  out.test.fixtures = has(t, 'fixtures') ? (strs('test.fixtures', t.fixtures, { nonEmpty: false }) || []) : [];
63
66
  out.test.fixtures.forEach((d, i) => rel(`test.fixtures[${i}]`, d));
@@ -25,11 +25,16 @@ function expand(command, vars) {
25
25
  }
26
26
 
27
27
  /** The component's tests in a work tree; the output is the evidence. */
28
- function runTests(wt, files, profile) {
28
+ function runTests(wt, files, profile, install) {
29
29
  const c = [...hostWrap(), ...expand(profile.test.command, { node: process.execPath, files })];
30
30
  // a test run started inside another test run inherits that runner's context and then reports "passed" whatever happened: the run under
31
31
  // judgement gets the environment the profile names it needs clean, so its exit code is its own
32
32
  const env = { ...process.env }; for (const k of profile.test.envUnset) delete env[k];
33
+ // the variables the profile gives the test run ({install} filled in: a test may read the pipeline install it runs against)
34
+ for (const [k, v] of Object.entries(profile.test.env || {})) {
35
+ if (v.includes('{install}') && !install) throw new Error(`test.env.${k} uses {install}, and no install was given to the verification`);
36
+ env[k] = v.replace(/\{install\}/g, install);
37
+ }
33
38
  const r = spawnSync(c[0], c.slice(1), { cwd: wt, env, encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
34
39
  return { status: r.status, out: `${r.stdout || ''}${r.stderr || ''}` };
35
40
  }
@@ -55,7 +60,7 @@ function worktree(repo, rev, fixtures = []) {
55
60
  }
56
61
  const drop = (repo, wt) => { git(repo, 'worktree', 'remove', '--force', wt); git(repo, 'worktree', 'prune'); fs.rmSync(wt, { recursive: true, force: true }); };
57
62
 
58
- function verifyRepair({ repo, base, branch, profile, layer, abortKind }) {
63
+ function verifyRepair({ repo, base, branch, profile, layer, abortKind, install }) {
59
64
  // the layer the agent says it repaired and how the abort was classified: both required, neither defaulted
60
65
  if (!['code', 'agentic'].includes(layer)) throw new Error(`verifyRepair needs layer 'code' or 'agentic', got ${JSON.stringify(layer)}`);
61
66
  if (typeof abortKind !== 'string' || !abortKind) throw new Error('verifyRepair needs abortKind (how the abort was classified)');
@@ -86,11 +91,11 @@ function verifyRepair({ repo, base, branch, profile, layer, abortKind }) {
86
91
  wtBase = worktree(repo, base, profile.test.fixtures);
87
92
  const co = git(wtBase, 'checkout', branch, '--', ...tests);
88
93
  if (co.status !== 0) return bad(`cannot place the tests on the base: ${co.stderr.trim()}`, { files });
89
- const red = runTests(wtBase, tests, profile);
94
+ const red = runTests(wtBase, tests, profile, install);
90
95
  if (red.status === 0) return bad('the test passes without the fix: it is not RED, so it proves nothing', { files, red: red.out });
91
96
  // GREEN: the branch's tip
92
97
  wtTip = worktree(repo, branch, profile.test.fixtures);
93
- const green = runTests(wtTip, tests, profile);
98
+ const green = runTests(wtTip, tests, profile, install);
94
99
  if (green.status !== 0 || !profile.test.greenPatterns.every((r) => r.test(green.out))) return bad('the test fails with the fix: not GREEN', { files, red: red.out, green: green.out });
95
100
  return { ok: true, why: 'RED on the base, GREEN with the fix', files, tests, red: red.out, green: green.out };
96
101
  } finally { if (wtBase) drop(repo, wtBase); if (wtTip) drop(repo, wtTip); }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amsd-pipeline-shared",
3
- "version": "0.5.9",
3
+ "version": "0.5.10",
4
4
  "description": "The AMSD pipeline's shared components, one source for the bash pipeline and its Claude workflows: the model-call gateway (llm-handler.sh — retry, ladder, self-heal, cost) and the libraries it runs.",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {