amsd-pipeline-shared 0.5.25 → 0.5.27

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.
@@ -23,12 +23,20 @@ function signaturesFor(runtime, injected) {
23
23
  internalFrame: rxOf(s.internalFrame), dependencyDir: s.dependencyDir };
24
24
  }
25
25
 
26
- function classify({ profile, portRoot, res, crash, aborted, since, signatures, evidence }) {
26
+ function classify({ profile, portRoot, res, crash, aborted, since, signatures, evidence, runDir }) {
27
27
  const sig = signaturesFor(profile.classify.runtime, signatures);
28
28
  const takesMost = profile.classify.policy === 'all-but-refusals';
29
29
  // WHAT IS EXTERNAL: the machine, the network, the model service, a kill. Under "all-but-refusals" a kill that happened inside the pipeline's OWN memory budget (the bounded wrapper's start banner is in the text:
30
30
  // the cap the pipeline chose, so what ran inside it is the pipeline's to bound — run 11, jest workers inside a Writer session's cap) is not external
31
- const isExternal = (text) => sig.external.test(text) && !(takesMost && sig.ownBudget.test(text));
31
+ // An access error on the leg's OWN run folder is the code's too (live run 13, 2026-10-22: the driver copied a locked set into the run's
32
+ // project copy and could not clear it — "EACCES … rmdir '<run>/project/…'"): every path the message quotes is inside runDir
33
+ const ownRunPaths = (text) => {
34
+ if (!takesMost || !runDir || !/\bE(ACCES|PERM)\b/.test(text)) return false;
35
+ const paths = [...String(text).matchAll(/'(\/[^']+)'/g)].map((m) => path.resolve(m[1]));
36
+ const root = path.resolve(runDir);
37
+ return paths.length > 0 && paths.every((p) => p === root || p.startsWith(`${root}${path.sep}`));
38
+ };
39
+ const isExternal = (text) => sig.external.test(text) && !(takesMost && sig.ownBudget.test(text)) && !ownRunPaths(text);
32
40
  if (!res || typeof res !== 'object') return no('no-result', 'the leg gave no result');
33
41
  if (aborted) return no('aborted', 'the operator or a watcher ended the leg');
34
42
  // AN AGENT-CAUSED ABORT (operator, 2026-10-20: look for the agentic solution first): a failed leg whose reason is one of the pipeline's own agent-caused
@@ -33,7 +33,7 @@ async function handle(a) {
33
33
  // the logs are cumulative across legs: the decision reads only what follows the last leg-start line in each file (the whole tail when the profile names none or the tail holds none)
34
34
  const thisLeg = (text) => { if (!profile.logs.legStart) return text; const ls = text.split('\n'); let at = -1; ls.forEach((l, i) => { if (profile.logs.legStart.test(l)) at = i; }); return at < 0 ? text : ls.slice(at).join('\n'); };
35
35
  const evidence = profile.logs.evidence.map((f) => thisLeg(tail(path.join(runDir, f), profile.logs.evidenceTailLines))).join('\n');
36
- const c = classify({ profile, portRoot, res, crash, aborted, since, evidence });
36
+ const c = classify({ profile, portRoot, res, crash, aborted, since, evidence, runDir });
37
37
  if (!c.call) { d.event('not-called', { kind: c.kind, why: c.why }); return { handled: false, kind: c.kind, why: c.why }; }
38
38
  d.event('trigger', { kind: c.kind, layer: c.layer, why: c.why, step: (crash && crash.step) || '' });
39
39
 
@@ -129,6 +129,13 @@ function cache({ install, project, codeline, variant = process.env.EPAM_BROWNFIE
129
129
  const C = path.join(O, 'projects', project, '.prompt-cache', codeline);
130
130
  const b = require(path.join(scriptsOf(install), 'lib', 'project-prompt-builder.js'));
131
131
  const tpl = (t) => path.join(O, 'prompts', 'templates', `${t}.json`);
132
+ // A COMPLETE SET IS REUSED WHOLE (2026-10-22): the mint step keeps a codeline whose set is complete ("completed its agents and
133
+ // prompts — kept") and no prompt is built, so per-prompt entries are not needed — the builder's own test, as the mint step's
134
+ const prev = process.env.EPAM_BROWNFIELD; process.env.EPAM_BROWNFIELD = variant === 'brownfield' ? '1' : '';
135
+ let whole;
136
+ try { whole = b.codelineSetComplete({ projectConfigDir: path.join(O, 'projects', project), codeline, templatesDir: path.join(O, 'prompts', 'templates'), registryFile: path.join(O, 'agents', 'invocation-profiles.json') }); }
137
+ finally { if (prev === undefined) delete process.env.EPAM_BROWNFIELD; else process.env.EPAM_BROWNFIELD = prev; }
138
+ if (whole && whole.complete) return res('cache', true, `${codeline}: its complete set is reused whole by the mint step — nothing rebuilt`, { required: 0, missing: [], willRebuild: [] });
132
139
  const problems = [];
133
140
  const prompts = fs.existsSync(path.join(C, 'prompts')) ? fs.readdirSync(path.join(C, 'prompts')).filter((n) => n.endsWith('.json')).map((n) => n.replace(/\.json$/, '')) : [];
134
141
  if (!prompts.length) problems.push(`no cached prompt set (${path.join(C, 'prompts')})`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amsd-pipeline-shared",
3
- "version": "0.5.25",
3
+ "version": "0.5.27",
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": {