amsd-pipeline-workflow 0.1.23 → 0.1.24

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.
@@ -0,0 +1,32 @@
1
+ 'use strict';
2
+ // THE SIBLING PROVIDER IS BUILT BEFORE THE WRITER, OUTSIDE ITS MEMORY SCOPE (run 11, 2026-10-21): a consumer's Step 19 type-checks it against the provider's own build, and the pipeline builds that provider as a hook INSIDE the
3
+ // Writer session — cx-shared's build is 3.2 GB (anon) and 67 s, and the session's scope is 4096 MB: the kernel killed it twice. The pipeline's own provisioning (provision_lane_worktree → link_sibling_builds) builds once per
4
+ // provider checkout and stamps it in LOG_DIR; this runs that same provisioning from the driver — in the leg's scope — on a throwaway worktree of the consumer, with LOG_DIR = the lane's persistent log, so the stamp survives
5
+ // and the generator seeds it into the Writer's state (generator/sibling-stamps.js). Nothing is re-implemented: the handler that finds the providers and the function that builds them are the pipeline's.
6
+ // prebuildSiblings(env, { root, laneLog, prd, log? }) → { providers: n } a build that fails is reported by the pipeline's function and left unstamped (tried again next time); nothing here throws
7
+ const fs = require('fs');
8
+ const os = require('os');
9
+ const path = require('path');
10
+ const { spawnSync } = require('child_process');
11
+ const { bash, handler } = require('./pipeline.js');
12
+
13
+ const git = (repo, ...a) => spawnSync('git', ['-C', repo, ...a], { encoding: 'utf8' });
14
+
15
+ function prebuildSiblings(env, { root, laneLog, prd, log }) {
16
+ const out = handler(env, 'sibling-providers', [prd, root]);
17
+ const providers = out.stdout.split('\n').filter(Boolean);
18
+ if (!providers.length) return { providers: 0 };
19
+ fs.mkdirSync(laneLog, { recursive: true });
20
+ const tmp = fs.mkdtempSync(path.join(env.TMPDIR || os.tmpdir(), 'receiving-')); const wt = path.join(tmp, path.basename(root));
21
+ try {
22
+ const add = git(root, 'worktree', 'add', '-q', '--detach', wt, 'HEAD');
23
+ if (add.status !== 0) { if (log) log(`[sibling] prebuild: could not make a worktree of ${root}: ${(add.stderr || '').trim()}`); return { providers: providers.length, error: 'worktree' }; }
24
+ if (log) log(`[sibling] prebuild: ${providers.map((l) => l.split('\t')[0]).join(', ')} for ${path.basename(root)} — built here, before the Writer, so the Writer's session never builds it`);
25
+ bash({ ...env, LOG_DIR: laneLog, PRD_FILE: prd, MAIN_PRD_FILE: prd }, 'provision_lane_worktree', [root, wt], { log });
26
+ } finally {
27
+ git(root, 'worktree', 'remove', '--force', wt); fs.rmSync(tmp, { recursive: true, force: true }); git(root, 'worktree', 'prune');
28
+ }
29
+ return { providers: providers.length };
30
+ }
31
+
32
+ module.exports = { prebuildSiblings };
package/driver/stages.js CHANGED
@@ -7,6 +7,7 @@ const path = require('path');
7
7
  const { spawnSync } = require('child_process');
8
8
  const runtime = require('./runtime.js');
9
9
  const { bash } = require('./pipeline.js');
10
+ const { prebuildSiblings } = require('./sibling-prebuild.js');
10
11
 
11
12
  // THE NAME EACH WORKFLOW IS RUN BY (and so the file it is placed in, <codeline>/.claude/workflows/<name>.js). No name may match a test runner's default pattern: the Spec workflow was "spec", the client's jest ran
12
13
  // spec.js as a test, and every whole-suite run in a codeline reported the pipeline's own file as a failing suite (run 11, 2026-10-20; test/integration/workflow-placement.test.js).
@@ -213,6 +214,9 @@ function overlay(ctx, lane, lineSlug) {
213
214
 
214
215
  async function runWriter(ctx, lane, env, { story, out, lineSlug, reviewCommitted }) {
215
216
  fs.rmSync(out, { recursive: true, force: true });
217
+ // A CODELINE THAT RECEIVES ANOTHER'S CHANGE has the provider built BEFORE the Writer, here, outside the session's memory scope (driver/sibling-prebuild.js; run 11's two OOM kills). Never blocks the Writer: said, and the
218
+ // session's own Step 19 would then try the build itself, as before.
219
+ try { prebuildSiblings(env, { root: lane.wt, laneLog: env.LOG_DIR, prd: env.PRD_FILE || env.MAIN_PRD_FILE, log: ctx.log }); } catch (e) { ctx.log(`[sibling] prebuild failed — ${e.message}`); }
216
220
  const inst = overlay(ctx, lane, lineSlug);
217
221
  // the run's recorded launch settings (driver/launch-env.js) go to the generator as its --launch-env file (NAME=value
218
222
  // lines), so plan.launchEnv records them and every Writer hook replays them (decision: record and replay)
@@ -243,6 +247,8 @@ async function runWriter(ctx, lane, env, { story, out, lineSlug, reviewCommitted
243
247
  * keeps it (story-attempt.sh:2666–2688) and the next generation reads it (generator/ladder.sh). */
244
248
  function persistFailures(out, laneLog) {
245
249
  const f = path.join(out, 'checks', 'runs', 'pipeline-env', 'logs', 'story-failures.jsonl');
250
+ // a provider build the session itself made is stamped in the lane's log too (generator/sibling-stamps.js)
251
+ if (laneLog) require('../generator/sibling-stamps.js').persist(path.join(out, 'checks', 'runs', 'pipeline-env', 'logs'), laneLog);
246
252
  if (!laneLog || !fs.existsSync(f)) return;
247
253
  fs.mkdirSync(laneLog, { recursive: true });
248
254
  fs.copyFileSync(f, path.join(laneLog, 'story-failures.jsonl'));
@@ -149,6 +149,9 @@ function main(a) {
149
149
  fs.mkdirSync(path.join(checksState, 'pipeline-env', 'logs'), { recursive: true });
150
150
  fs.copyFileSync(path.join(process.env.LOG_DIR, 'story-failures.jsonl'), path.join(checksState, 'pipeline-env', 'logs', 'story-failures.jsonl'));
151
151
  }
152
+ // THE "PROVIDER BUILT" STAMPS (generator/sibling-stamps.js): the driver builds a sibling provider before the Writer (driver/sibling-prebuild.js) and stamps it in the lane's log; Step 19's hook inside the session finds the
153
+ // stamp here and does not build it again (run 11: that build, 3.2 GB, is what the session's 4096 MB scope could not hold)
154
+ if (process.env.LOG_DIR) require('./sibling-stamps.js').seed(process.env.LOG_DIR, path.join(checksState, 'pipeline-env', 'logs'));
152
155
  // the plan the run works from, published as the pipeline publishes it after the spec pass (hooks/publish-agent-outputs.sh),
153
156
  // in the hooks' state before any hook renders from it
154
157
  const pub = spawnSync('bash', [path.join(__dirname, '..', 'hooks', 'publish-agent-outputs.sh'), pipelineJson, checksState], { encoding: 'utf8' });
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+ // THE "BUILT ONCE" STAMPS OF SIBLING PROVIDERS (lib/codeline-lanes.sh link_sibling_builds: `sibling-build-<hash of the provider's path>-<its HEAD>` in LOG_DIR, written when the provider's build succeeded). The Bash pipeline's LOG_DIR
3
+ // is the lane's persistent log; the Writer's hooks run with a per-invocation state folder, so a stamp made in the lane's log (by driver/sibling-prebuild.js, outside the session) is COPIED into the state before the session
4
+ // (seed), and one a session made is copied back (persist). Only stamp files move — never the build logs or anything else. No AI.
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ const STAMP = /^sibling-build-[0-9a-f]{12}-[0-9a-z]+$/;
9
+
10
+ function copyStamps(from, to) {
11
+ let names = [];
12
+ try { names = fs.readdirSync(from).filter((n) => STAMP.test(n)); } catch { return []; }
13
+ const moved = [];
14
+ for (const n of names) {
15
+ if (fs.existsSync(path.join(to, n))) continue;
16
+ fs.mkdirSync(to, { recursive: true }); fs.copyFileSync(path.join(from, n), path.join(to, n)); moved.push(n);
17
+ }
18
+ return moved;
19
+ }
20
+
21
+ /** The lane's stamps into the Writer's state logs; returns the names copied. */
22
+ const seed = (laneLog, stateLogs) => copyStamps(laneLog, stateLogs);
23
+ /** The stamps a session made, back into the lane's log; returns the names copied. */
24
+ const persist = (stateLogs, laneLog) => copyStamps(stateLogs, laneLog);
25
+
26
+ module.exports = { seed, persist, STAMP };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amsd-pipeline-workflow",
3
- "version": "0.1.23",
3
+ "version": "0.1.24",
4
4
  "description": "The AMSD pipeline as Claude workflows. Installed by the amsd-pipeline installer: install.sh --workflows",
5
5
  "license": "UNLICENSED",
6
6
  "bin": {