amsd-pipeline-workflow 0.1.19 → 0.1.20

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.
@@ -61,12 +61,22 @@
61
61
  "never works a real codeline",
62
62
  "cannot resume run",
63
63
  "operator's yes",
64
- "needs the operator"
64
+ "needs the operator",
65
+ "Missing required env vars",
66
+ "ingest reads Jira",
67
+ "UNHEALTHY",
68
+ "greenfield scaffolding is off",
69
+ "Brownfield worktree is not a git repository",
70
+ "the manifest could not be read",
71
+ ", not a copy"
65
72
  ],
66
73
  "faultMarks": [
67
74
  "\\(exit undefined\\)",
68
- "\\(exit NaN\\)"
69
- ]
75
+ "\\(exit NaN\\)",
76
+ "the same block \\d+ times in a row",
77
+ "the self-healing retry changed nothing"
78
+ ],
79
+ "policy": "all-but-refusals"
70
80
  },
71
81
  "settings": {
72
82
  "on": "EPAM_PIPELINE_DOCTOR",
@@ -89,7 +99,8 @@
89
99
  "evidence": [
90
100
  "driver.log"
91
101
  ],
92
- "evidenceTailLines": 200
102
+ "evidenceTailLines": 200,
103
+ "legStart": "^\\[launch\\] replayed"
93
104
  },
94
105
  "branch": {
95
106
  "prefix": "doctor/"
@@ -3,7 +3,8 @@
3
3
  // retries"). A recorded difference from the pipeline (backlog B39): its lane loop retries an exit-2 phase while HEAD moves,
4
4
  // and every Writer run moves it, so a story a gate keeps blocking retries forever. Here a phase stops — failed, naming
5
5
  // the gate — when its last BLOCK_CAP runs ended in the same block of the same story.
6
- const BLOCK_CAP = 3;
6
+ // RELAXED 3 → 5 (operator, 2026-10-20: "relax limits like self heal"): the pipeline's own retry gets more runs before the phase gives up, and giving up is a fault the doctor now takes (doctor/profile.json faultMarks).
7
+ const BLOCK_CAP = 5;
7
8
 
8
9
  /** What blocked a Writer run: the story and the gate (the reason up to its detail), or null when nothing blocked. */
9
10
  function signature(story, res) {
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
  const gateway = require('./gateway.js');
3
+ const { packPlan } = require('./pack-plan.js');
3
4
  // THE CODELINE, AGENTS WORKFLOW: plan (data) + emitted script (decisions 10, 15, 16, 25).
4
5
  //
5
6
  // The script holds the stage's ORDER — the pipeline's own: the launcher's reset (orchestrate.sh), the resume
@@ -16,6 +17,7 @@ const REPLY_SCHEMA = { type: 'object', required: ['reply'], properties: { reply:
16
17
  const RUN_SCHEMA = { type: 'object', properties: { done: { type: 'boolean' } } };
17
18
 
18
19
  function emit(plan) {
20
+ const packed = packPlan(plan);
19
21
  return `export const meta = {
20
22
  name: 'codeline-agents',
21
23
  description: 'Codeline, Agents stage: reset, resume, ingest (discovery, AC gate, PRD), agents (survey, mint, roster, roles, prompts), pause 1',
@@ -35,7 +37,7 @@ async function agentOrNull(prompt, opts) {
35
37
  }
36
38
 
37
39
  // GENERATED by amsd-pipeline-workflow/generator — do not edit. Source: ${plan.source}
38
- const PLAN = ${JSON.stringify(plan)}
40
+ ${packed.decls}const PLAN = ${packed.expr}
39
41
  const REPLY = ${JSON.stringify(REPLY_SCHEMA)}
40
42
  const RUN = ${JSON.stringify(RUN_SCHEMA)}
41
43
 
@@ -0,0 +1,74 @@
1
+ 'use strict';
2
+ // THE PLAN, AS SOURCE FOR THE EMITTED WORKFLOW (decision 12 continued): a workflow script embeds its `plan` as a literal
3
+ // — the real Workflow tool's sandbox gives a script no `fs`, nothing else can reach generation-time data (test/layer1/
4
+ // harness.js's vm context: no `fs`, no `require`) — but `plan.prompts` can hold several near-duplicate renders (the
5
+ // Writer's first/retry/review variants, each the pipeline's FULL prompt with one small insertion). LIVE-AMSD-2974-11
6
+ // died on "Workflow \"writer\" not found. Available: deep-research, spec": three ~105KB near-copies pushed the script
7
+ // past the real Workflow tool's own script-size cap (524288 bytes — this repo's own Workflow tool documents the same
8
+ // figure). Every `prompts` value that shares a long prefix and suffix with an earlier one is written as a slice of it,
9
+ // not copied again; the runtime value is unchanged (packPlan's output round-trips to a deep-equal `plan`).
10
+ const MIN_SHARE = 200; // below this, the slice expression costs more source than copying the text saves
11
+
12
+ function sharedPrefixLen(a, b) {
13
+ const n = Math.min(a.length, b.length);
14
+ let i = 0;
15
+ while (i < n && a[i] === b[i]) i += 1;
16
+ return i;
17
+ }
18
+ function sharedSuffixLen(a, b, max) {
19
+ let i = 0;
20
+ while (i < max && a[a.length - 1 - i] === b[b.length - 1 - i]) i += 1;
21
+ return i;
22
+ }
23
+
24
+ /** `prompts` (string values), as a JS object-literal expression: a value sharing >= MIN_SHARE chars of prefix+suffix
25
+ * with an earlier (unfactored) one is sliced from it; every other value is its own literal. */
26
+ function packPrompts(prompts) {
27
+ const keys = Object.keys(prompts);
28
+ const bases = [];
29
+ let decls = '';
30
+ const entries = keys.map((k) => {
31
+ const text = prompts[k];
32
+ let best = null;
33
+ if (typeof text === 'string') {
34
+ for (const b of bases) {
35
+ const p = sharedPrefixLen(text, b.text);
36
+ const s = sharedSuffixLen(text, b.text, Math.min(text.length, b.text.length) - p);
37
+ const saved = p + s;
38
+ if (saved >= MIN_SHARE && (!best || saved > best.saved)) best = { b, p, s, saved };
39
+ }
40
+ }
41
+ let expr;
42
+ if (best) {
43
+ const { b, p, s } = best;
44
+ const mid = text.slice(p, text.length - s);
45
+ const parts = [];
46
+ if (p > 0) parts.push(`${b.name}.slice(0,${p})`);
47
+ if (mid) parts.push(JSON.stringify(mid));
48
+ if (s > 0) parts.push(`${b.name}.slice(${b.text.length - s})`);
49
+ expr = parts.join('+') || '""';
50
+ } else {
51
+ expr = JSON.stringify(text);
52
+ if (typeof text === 'string' && text.length >= MIN_SHARE) {
53
+ const name = `B${bases.length}`;
54
+ decls += `const ${name} = ${expr}\n`;
55
+ bases.push({ name, text });
56
+ expr = name;
57
+ }
58
+ }
59
+ return `${JSON.stringify(k)}: ${expr}`;
60
+ });
61
+ return { decls, expr: `{ ${entries.join(', ')} }` };
62
+ }
63
+
64
+ /** `plan`, as source for `const PLAN = <expr>`: every field by JSON.stringify, except `prompts` (packPrompts above). */
65
+ function packPlan(plan) {
66
+ if (!plan || typeof plan !== 'object' || !plan.prompts || typeof plan.prompts !== 'object') {
67
+ return { decls: '', expr: JSON.stringify(plan) };
68
+ }
69
+ const { prompts, ...rest } = plan;
70
+ const packed = packPrompts(prompts);
71
+ return { decls: packed.decls, expr: `Object.assign(${JSON.stringify(rest)}, { prompts: ${packed.expr} })` };
72
+ }
73
+
74
+ module.exports = { packPlan, packPrompts, sharedPrefixLen, sharedSuffixLen };
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
  const gateway = require('./gateway.js');
3
+ const { packPlan } = require('./pack-plan.js');
3
4
  // THE SPEC WORKFLOW (Spec → pause 2): the order of spec-mode-runner.js run() and its callees, re-expressed as a script
4
5
  // (decisions 10, 15, 16). Every model call is an agent with its seam's definition; every judgement on an answer is the
5
6
  // pipeline's own code, run by the hook on that answer (hooks/spec-step.js) — the script only follows the result: which
@@ -9,6 +10,7 @@ const gateway = require('./gateway.js');
9
10
 
10
11
  /** The script. Plain JS, no clock, no randomness; the plan is embedded as a literal. */
11
12
  function emit(plan) {
13
+ const packed = packPlan(plan);
12
14
  return `export const meta = {
13
15
  name: 'spec',
14
16
  description: 'Spec stage for one lane: spec coordinator, ticket links, detective, openspec with its verification-criteria guard, change review, speckit, coordinator and model review, pause 2',
@@ -28,7 +30,7 @@ async function agentOrNull(prompt, opts) {
28
30
  }
29
31
  ${gateway.textFor(plan)}
30
32
  // GENERATED by amsd-pipeline-workflow/generator/generate-spec.js — do not edit. Source: ${plan.source}
31
- const PLAN = ${JSON.stringify(plan)}
33
+ ${packed.decls}const PLAN = ${packed.expr}
32
34
  const A = PLAN.agents, P = PLAN.prompts, M = PLAN.models, E = PLAN.efforts, L = PLAN.limits, SCH = PLAN.schemas
33
35
  const STEP = { type: 'object', properties: { done: { type: 'boolean' } } }
34
36
 
@@ -11,6 +11,7 @@ const fs = require('fs');
11
11
  const path = require('path');
12
12
  const { spawnSync } = require('child_process');
13
13
  const tcs = require('./tc-slots.js');
14
+ const { packPlan } = require('./pack-plan.js');
14
15
 
15
16
  const SLOT = /@@SLOT:([a-z_]+)@@/g;
16
17
 
@@ -66,6 +67,7 @@ const REVIEW_SCHEMA = { type: 'object', required: ['verdict'], properties: {
66
67
 
67
68
  /** The script. Plain JS, no clock, no randomness; the plan is embedded as a literal. */
68
69
  function emit(plan) {
70
+ const packed = packPlan(plan);
69
71
  return `export const meta = {
70
72
  name: 'writer',
71
73
  description: 'Writer stage for one story: writer, checks (hook-owned verdict), failure analyst, model ladder, review cycle',
@@ -86,7 +88,7 @@ async function agentOrNull(prompt, opts) {
86
88
 
87
89
  ${gateway.textFor(plan)}
88
90
  // GENERATED by amsd-pipeline-workflow/generator — do not edit. Source: ${plan.source}
89
- const PLAN = ${JSON.stringify(plan)}
91
+ ${packed.decls}const PLAN = ${packed.expr}
90
92
 
91
93
  // lib/handlers/ac-apply.py reads the remediator's answer for an object with "acs" (a list of criterion texts).
92
94
  const REMEDIATION_SCHEMA = { type: 'object', required: ['acs'], properties: { acs: { type: 'array', items: { type: 'string' } } } };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "amsd-pipeline-workflow",
3
- "version": "0.1.19",
3
+ "version": "0.1.20",
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": {