amsd-pipeline-shared 0.5.21 → 0.5.23
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/docs/gateway-baseline-2.0.80.md5 +8 -0
- package/docs/gateway-files.txt +8 -0
- package/orchestrations/scripts/lib/codeline-lanes.sh +20 -0
- package/orchestrations/scripts/lib/mint-and-spec.sh +859 -0
- package/orchestrations/scripts/lib/prd-change-review.sh +380 -0
- package/orchestrations/scripts/lib/prelaunch.js +21 -1
- package/orchestrations/scripts/lib/project-prompt-builder.js +1413 -0
- package/orchestrations/scripts/lib/prompt-library.js +298 -0
- package/orchestrations/scripts/lib/prompt-variant.sh +123 -0
- package/orchestrations/scripts/lib/story-guards.sh +970 -0
- package/orchestrations/scripts/mint-agents-step.js +1417 -0
- package/package.json +1 -1
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* THE PROMPT LIBRARY. Resolves and renders an agent's PROJECT-AUTHORITY prompt.
|
|
4
|
+
*
|
|
5
|
+
* Operator mandate, 2026-08-11, verbatim:
|
|
6
|
+
* "WE WILL NEVER EVER run the template version of the prompts - NEVER. NEVER - no
|
|
7
|
+
* fallbacks. Only project authority prompts - the self-health mechanism is the only
|
|
8
|
+
* process permitted to mutate project level prompts. You will keep each prompt in a
|
|
9
|
+
* separate file (json) and never hide prompts in code ever again."
|
|
10
|
+
*
|
|
11
|
+
* So, precisely:
|
|
12
|
+
* - The TEMPLATE (orchestrations/prompts/templates/<id>.json) is the immutable generic
|
|
13
|
+
* source a project prompt is MINTED FROM. It is NEVER executed.
|
|
14
|
+
* - The PROJECT prompt (<project-config-dir>/prompts/<id>.json) is the ONLY thing
|
|
15
|
+
* rendered and sent to a model.
|
|
16
|
+
* - A missing project prompt is a HARD FAILURE. It never degrades to the template,
|
|
17
|
+
* because a silent degrade is how an engine-embedded default runs for a whole
|
|
18
|
+
* campaign without anyone noticing.
|
|
19
|
+
*
|
|
20
|
+
* WHY THIS EXISTS AT ALL: the failure-analyst prompt lived as a 44-line shell heredoc in
|
|
21
|
+
* claude.sh, generic, with no project-level variant — so the self-heal mechanism had
|
|
22
|
+
* nothing to mutate, and the prompt could not be corrected without editing the engine.
|
|
23
|
+
* Four more prompts are still embedded the same way (PROMPT_HEADER, PLAN_PROMPT_EOF,
|
|
24
|
+
* MODRES_EOF, COORD_PROMPT); this module is the pattern they migrate to.
|
|
25
|
+
*
|
|
26
|
+
* RENDERING IS TOTAL AND STRICT. Every declared placeholder must be supplied, and every
|
|
27
|
+
* placeholder appearing in the body must be declared. A prompt that reaches a model with
|
|
28
|
+
* a literal __STORY_ACS__ in it has silently lost its evidence, which is exactly the
|
|
29
|
+
* class of defect this pipeline keeps paying for.
|
|
30
|
+
*/
|
|
31
|
+
'use strict';
|
|
32
|
+
|
|
33
|
+
const fs = require('fs');
|
|
34
|
+
const path = require('path');
|
|
35
|
+
|
|
36
|
+
const TEMPLATE_DIR_REL = 'orchestrations/prompts/templates';
|
|
37
|
+
const PROJECT_PROMPT_SUBDIR = 'prompts';
|
|
38
|
+
// THE PLACEHOLDER RULE LIVES IN ONE PLACE.
|
|
39
|
+
//
|
|
40
|
+
// This regex existed in THREE files — here, engine-prompt.js and project-prompt-contract.js —
|
|
41
|
+
// and only one of them was fixed when adjacent placeholders turned out to merge. ${a}${b}
|
|
42
|
+
// becomes __A____B__ in a template, and a greedy match reads that as a single token. The two
|
|
43
|
+
// unfixed copies then disagreed with the fixed one about what a template declares, so a valid
|
|
44
|
+
// generated prompt was refused as "dropping placeholders" and a valid template was refused as
|
|
45
|
+
// "using undeclared placeholders". Three copies of a rule is three rules.
|
|
46
|
+
const { placeholdersIn: _placeholdersIn, substituteOnce } = require('./engine-prompt.js');
|
|
47
|
+
|
|
48
|
+
/** Repo root, derived from this file's location — never an env guess. */
|
|
49
|
+
function repoRoot() {
|
|
50
|
+
return path.resolve(__dirname, '..', '..', '..');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function templatePath(id) {
|
|
54
|
+
return path.join(repoRoot(), TEMPLATE_DIR_REL, `${id}.json`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function projectPromptPath(id, projectConfigDir) {
|
|
58
|
+
return path.join(projectConfigDir, PROJECT_PROMPT_SUBDIR, `${id}.json`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function readJson(file) {
|
|
62
|
+
let raw;
|
|
63
|
+
try {
|
|
64
|
+
raw = fs.readFileSync(file, 'utf8');
|
|
65
|
+
} catch (e) {
|
|
66
|
+
throw new Error(`prompt file unreadable (${file}): ${e && e.message}`);
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
return JSON.parse(raw);
|
|
70
|
+
} catch (e) {
|
|
71
|
+
// Never "fall back to the template" here. An unparseable project prompt means the
|
|
72
|
+
// authority for this agent is broken, and running SOMETHING ELSE hides that.
|
|
73
|
+
throw new Error(`prompt file is not valid JSON (${file}): ${e && e.message}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Load the project-authority prompt. NO FALLBACK — see the header.
|
|
79
|
+
* @param {string} id prompt id, e.g. 'failure-analyst'
|
|
80
|
+
* @param {string} projectConfigDir EPAM_PROJECT_CONFIG_DIR
|
|
81
|
+
*/
|
|
82
|
+
function loadProjectPrompt(id, projectConfigDir, opts) {
|
|
83
|
+
if (!id || typeof id !== 'string') throw new Error('prompt id is required');
|
|
84
|
+
if (!projectConfigDir) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`no project config dir given for prompt '${id}' — EPAM_PROJECT_CONFIG_DIR must be set. ` +
|
|
87
|
+
'The template is never executed, so there is nothing to fall back to.',
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
const file = projectPromptPath(id, projectConfigDir);
|
|
91
|
+
if (!fs.existsSync(file)) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`project-authority prompt missing: ${file}. Mint it from ` +
|
|
94
|
+
`${TEMPLATE_DIR_REL}/${id}.json — the template is NEVER executed directly.`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
const doc = readJson(file);
|
|
98
|
+
|
|
99
|
+
// A MULTI-PART PROMPT: ONE POLICY, SEVERAL AUDIENCES.
|
|
100
|
+
//
|
|
101
|
+
// Some rules bind more than one agent, and the two halves must never drift. "Tests are NOT
|
|
102
|
+
// your job" lived only in the writer's prompt; the reviewer had never heard of it and raised
|
|
103
|
+
// a blocker for missing tests, so the writer was ordered to create what it was forbidden to
|
|
104
|
+
// create. Splitting that across two prompt files would recreate the same defect with extra
|
|
105
|
+
// steps, so a prompt may declare `bodies: { <part>: text }` and each consumer asks for its
|
|
106
|
+
// own part of the SAME document.
|
|
107
|
+
//
|
|
108
|
+
// A part is REQUIRED when bodies exist: silently picking one would make the caller's audience
|
|
109
|
+
// an accident of ordering.
|
|
110
|
+
if (doc && doc.bodies && typeof doc.bodies === 'object') {
|
|
111
|
+
const part = opts && opts.part;
|
|
112
|
+
if (!part) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`prompt '${id}' declares parts (${Object.keys(doc.bodies).join(', ')}) — ask for one by name`);
|
|
115
|
+
}
|
|
116
|
+
if (typeof doc.bodies[part] !== 'string' || !doc.bodies[part].trim()) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`prompt '${id}' has no part '${part}' (declares: ${Object.keys(doc.bodies).join(', ')})`);
|
|
119
|
+
}
|
|
120
|
+
// THE PART'S CONTRACT IS THE PART'S PLACEHOLDERS. `placeholders` on a multi-part document
|
|
121
|
+
// is the UNION across parts — that is what the whole document declares. render() then
|
|
122
|
+
// compares it against ONE part's body and refuses it for "declaring placeholders it never
|
|
123
|
+
// uses", so every multi-part project prompt was unrenderable through this path.
|
|
124
|
+
//
|
|
125
|
+
// Surfaced when seam prompts started routing here instead of to the template renderer, which
|
|
126
|
+
// computes its declared set per body already. Narrowed rather than relaxing render(): a
|
|
127
|
+
// prompt that declares a placeholder its body never uses is still a real defect, and the
|
|
128
|
+
// check that catches it is worth keeping.
|
|
129
|
+
const used = new Set(placeholdersIn(doc.bodies[part]));
|
|
130
|
+
return {
|
|
131
|
+
...doc,
|
|
132
|
+
body: doc.bodies[part],
|
|
133
|
+
placeholders: (doc.placeholders || []).filter((x) => used.has(x)),
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!doc || typeof doc.body !== 'string' || !doc.body.trim()) {
|
|
138
|
+
throw new Error(`project-authority prompt has no body: ${file}`);
|
|
139
|
+
}
|
|
140
|
+
if (doc.authority && doc.authority !== 'project') {
|
|
141
|
+
throw new Error(`refusing to run a non-project prompt (authority='${doc.authority}'): ${file}`);
|
|
142
|
+
}
|
|
143
|
+
return doc;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Placeholders actually present in a body, deduped and sorted. */
|
|
147
|
+
function placeholdersIn(body) {
|
|
148
|
+
return [..._placeholdersIn(body)].sort();
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Render a prompt body with values. Strict in both directions.
|
|
153
|
+
*
|
|
154
|
+
* Values are substituted with a replacer FUNCTION, not a string, so that `$&`, `$1` and
|
|
155
|
+
* friends inside a value (a diff, a regex, a test log — all routine here) are inserted
|
|
156
|
+
* literally instead of being interpreted as replacement patterns. That bug would corrupt
|
|
157
|
+
* evidence silently and only for values that happen to contain a dollar sign.
|
|
158
|
+
*/
|
|
159
|
+
function render(doc, values) {
|
|
160
|
+
const body = doc.body;
|
|
161
|
+
const declared = Array.isArray(doc.placeholders) ? [...doc.placeholders].sort() : [];
|
|
162
|
+
const present = placeholdersIn(body);
|
|
163
|
+
|
|
164
|
+
const undeclared = present.filter((p) => !declared.includes(p));
|
|
165
|
+
if (undeclared.length) {
|
|
166
|
+
throw new Error(`prompt '${doc.id}' uses undeclared placeholders: ${undeclared.join(', ')}`);
|
|
167
|
+
}
|
|
168
|
+
const orphanDeclared = declared.filter((p) => !present.includes(p));
|
|
169
|
+
if (orphanDeclared.length) {
|
|
170
|
+
throw new Error(`prompt '${doc.id}' declares placeholders it never uses: ${orphanDeclared.join(', ')}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const supplied = values && typeof values === 'object' ? values : {};
|
|
174
|
+
|
|
175
|
+
// A VALUE NOTHING CONSUMES IS EVIDENCE THROWN AWAY.
|
|
176
|
+
//
|
|
177
|
+
// This checked three things and not this one, so a value supplied to a prompt with no
|
|
178
|
+
// matching placeholder was silently dropped. engine-prompt.js has always rejected exactly
|
|
179
|
+
// that — "was given values it does not use" — so the two renderers disagreed on the same
|
|
180
|
+
// class, and the lenient one lost evidence without a trace.
|
|
181
|
+
//
|
|
182
|
+
// Live 20260821T212250Z: prior-reviews.py produced the reviewer's history correctly and it
|
|
183
|
+
// was supplied as __PRIOR_REVIEW__. The project's prompt declares no such placeholder, so
|
|
184
|
+
// it went nowhere. All three review cycles ran with no memory of each other and the
|
|
185
|
+
// approval missed a regression the earlier cycle had caught. The run log mentions prior
|
|
186
|
+
// review zero times: a whole feedback loop absent, with nothing to show it.
|
|
187
|
+
//
|
|
188
|
+
// Loud, because the caller believed it sent something. A stale project prompt is now a
|
|
189
|
+
// failure at render rather than a quiet degrade — which is the same rule the rest of this
|
|
190
|
+
// file already applies in the other direction.
|
|
191
|
+
const unconsumed = Object.keys(supplied).filter((k) => /^__[A-Z0-9_]+__$/.test(k) && !present.includes(k));
|
|
192
|
+
if (unconsumed.length) {
|
|
193
|
+
// LOUD, NOT FATAL — and that asymmetry is deliberate.
|
|
194
|
+
//
|
|
195
|
+
// Throwing here kills the seam outright: the reviewer supplies __PRIOR_REVIEW__ to project
|
|
196
|
+
// prompts minted before that placeholder existed, so a hard failure would take the reviewer
|
|
197
|
+
// out of every such run. Losing one block of context is bad; losing the whole review is
|
|
198
|
+
// worse, and the operator has not asked for a re-mint.
|
|
199
|
+
//
|
|
200
|
+
// engine-prompt.js DOES throw on this, correctly: it renders templates, which are the
|
|
201
|
+
// in-repo source and can always be corrected. A project prompt is generated, may lag the
|
|
202
|
+
// template, and cannot be corrected from here.
|
|
203
|
+
//
|
|
204
|
+
// What was unacceptable was silence. This is the trace that was missing when the reviewer's
|
|
205
|
+
// entire prior-review history was dropped on 20260821T212250Z and the run log mentioned it
|
|
206
|
+
// zero times.
|
|
207
|
+
process.stderr.write(
|
|
208
|
+
`[prompt-library] '${doc.id}' was given values it does not use: ${unconsumed.join(', ')}. `
|
|
209
|
+
+ 'That evidence is being DROPPED — the prompt has no placeholder for it. '
|
|
210
|
+
+ 'Add the placeholder to this project prompt, or stop supplying the value.\n');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// A LOOKUP THAT FOUND NOTHING IS ABSENCE, NOT A VALUE. `p in supplied` is true for a key whose
|
|
214
|
+
// value is undefined, so the rule stated below — absent is not empty — was defeated by the one
|
|
215
|
+
// shape absence actually takes in JavaScript, and the prompt rendered with the literal word
|
|
216
|
+
// "undefined" where the evidence belonged. null is the same thing arriving from JSON.
|
|
217
|
+
const missing = present.filter((p) => !(p in supplied) || supplied[p] === undefined || supplied[p] === null);
|
|
218
|
+
if (missing.length) {
|
|
219
|
+
// An empty string is a legitimate value and must be passed explicitly. Absent is not
|
|
220
|
+
// empty: a prompt rendered without its evidence is worse than one that fails loudly.
|
|
221
|
+
throw new Error(`prompt '${doc.id}' is missing values for: ${missing.join(', ')}`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ONE PASS OVER THE BODY. Replacing each key in turn over the accumulating output meant every
|
|
225
|
+
// value was re-read by every later key: a diff that mentioned __B__ had __B__'s content
|
|
226
|
+
// spliced into it, and a diff carrying any double-underscore token — a Python dunder, a C
|
|
227
|
+
// macro — tripped the leftover check below and killed the render of a complete prompt. A
|
|
228
|
+
// single alternation pass inserts each value exactly where the BODY asked for it and never
|
|
229
|
+
// looks at what was inserted.
|
|
230
|
+
//
|
|
231
|
+
// Replacer FUNCTION, not a string, so a `$&` or `$1` inside a diff, a log or a JSON example
|
|
232
|
+
// is inserted literally instead of being read as a replacement pattern.
|
|
233
|
+
// AN EMPTY VALUE IS SILENCE, AND SILENCE REACHES THE MODEL AS A BLANK SECTION.
|
|
234
|
+
//
|
|
235
|
+
// The renderer refused a MISSING key and accepted a present-but-empty one, so `|| ''` — which
|
|
236
|
+
// fifteen call sites use — turned a failed lookup into a prompt that looked complete and said
|
|
237
|
+
// nothing. Live 2026-08-23: the roster reviewer was handed a header with a blank line under it
|
|
238
|
+
// for every agent, reported the briefs "entirely empty", and blocked the roster twice. The
|
|
239
|
+
// briefs existed; the lookup read the wrong map.
|
|
240
|
+
//
|
|
241
|
+
// A placeholder that may legitimately be empty says so IN THE TEMPLATE, as `mayBeEmpty`. A retry
|
|
242
|
+
// note absent on a first attempt is a real state; a brief is not. That distinction belongs with
|
|
243
|
+
// the prompt, which is reviewable, rather than in the producer, which is where it got lost.
|
|
244
|
+
const mayBeEmpty = new Set(Array.isArray(doc.mayBeEmpty) ? doc.mayBeEmpty : []);
|
|
245
|
+
const blank = present.filter((p) => !mayBeEmpty.has(p)
|
|
246
|
+
&& typeof supplied[p] === 'string' && !supplied[p].trim());
|
|
247
|
+
if (blank.length) {
|
|
248
|
+
throw new Error(
|
|
249
|
+
`prompt '${doc.id}' was given EMPTY values for: ${blank.join(', ')}. An empty payload renders `
|
|
250
|
+
+ 'as a blank section and the agent answers about silence — it cannot tell a failed lookup '
|
|
251
|
+
+ 'from a genuinely absent one. Supply the value, or declare the placeholder in the '
|
|
252
|
+
+ "template's `mayBeEmpty` if absent is a real state for it.");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const out = substituteOnce(body, present, supplied);
|
|
256
|
+
|
|
257
|
+
// The check that means something is on the BODY: a placeholder this pass did not cover.
|
|
258
|
+
// Scanning the OUTPUT could only ever find tokens that arrived as evidence.
|
|
259
|
+
const uncovered = placeholdersIn(body).filter((p) => !present.includes(p));
|
|
260
|
+
if (uncovered.length) {
|
|
261
|
+
throw new Error(`prompt '${doc.id}' has placeholders no value covered: ${uncovered.join(', ')}`);
|
|
262
|
+
}
|
|
263
|
+
return out;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Convenience: load + render in one call. */
|
|
267
|
+
function buildPrompt(id, projectConfigDir, values, opts) {
|
|
268
|
+
return render(loadProjectPrompt(id, projectConfigDir, opts), values);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
module.exports = {
|
|
272
|
+
loadProjectPrompt,
|
|
273
|
+
render,
|
|
274
|
+
buildPrompt,
|
|
275
|
+
placeholdersIn,
|
|
276
|
+
templatePath,
|
|
277
|
+
projectPromptPath,
|
|
278
|
+
TEMPLATE_DIR_REL,
|
|
279
|
+
PROJECT_PROMPT_SUBDIR,
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* CLI, so shell can use the library without reimplementing resolution:
|
|
284
|
+
* node prompt-library.js render <id> <projectConfigDir> <values.json> [part]
|
|
285
|
+
* Values arrive as a JSON file rather than argv because they routinely contain newlines,
|
|
286
|
+
* quotes and megabytes of test output.
|
|
287
|
+
*/
|
|
288
|
+
if (require.main === module) {
|
|
289
|
+
const [, , cmd, id, dir, valuesFile, part] = process.argv;
|
|
290
|
+
try {
|
|
291
|
+
if (cmd !== 'render') throw new Error(`unknown command '${cmd}' (expected: render)`);
|
|
292
|
+
const values = valuesFile ? readJson(valuesFile) : {};
|
|
293
|
+
process.stdout.write(buildPrompt(id, dir, values, part ? { part } : undefined));
|
|
294
|
+
} catch (e) {
|
|
295
|
+
process.stderr.write(`[prompt-library] ${e && e.message}\n`);
|
|
296
|
+
process.exit(1);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# THE COMPLETION MARKER'S NAME — ONE DERIVATION, FOR EVERY SHELL CALL SITE.
|
|
3
|
+
#
|
|
4
|
+
# `.prompt-cache/.complete-<codeline>` is the claim that a codeline's prompts are fully
|
|
5
|
+
# provisioned, and its presence makes the mint skip ENTIRELY. It is tested in three shell places
|
|
6
|
+
# (run-agent-orchestration.sh twice, pre-run-reset.sh) and written by project-prompt-builder.js.
|
|
7
|
+
#
|
|
8
|
+
# A brownfield run generates DIFFERENT prompts from the same seams, so it must not inherit a
|
|
9
|
+
# greenfield codeline's claim — it would skip provisioning and serve the greenfield prompts on
|
|
10
|
+
# disk, which on a cached codeline means the brownfield variant never executes at all.
|
|
11
|
+
#
|
|
12
|
+
# Greenfield's name is unchanged, byte for byte: no marker already on disk is invalidated.
|
|
13
|
+
# The JS side of this rule is _markerPath in lib/project-prompt-builder.js; the two are asserted
|
|
14
|
+
# to agree by test/unit/orchestration/a-brownfield-run-generates-its-own-prompts.test.ts.
|
|
15
|
+
prompt_marker_key() {
|
|
16
|
+
if [ "${EPAM_BROWNFIELD:-0}" = "1" ]; then
|
|
17
|
+
printf '.complete-%s.brownfield' "$1"
|
|
18
|
+
else
|
|
19
|
+
printf '.complete-%s' "$1"
|
|
20
|
+
fi
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
# codeline_prompts_complete <codeline> — were this codeline's prompts built from the CURRENT
|
|
24
|
+
# inputs? Answered by project-prompt-builder.js (the digest the marker records vs the digest
|
|
25
|
+
# of the template layer, registry and generator now). A marker's mere existence is not an
|
|
26
|
+
# answer: an empty one let a launch reuse prompts built from the previous day's templates
|
|
27
|
+
# (regintel 20260920T232518Z, $5.73). Says why on stderr when the answer is no.
|
|
28
|
+
codeline_prompts_complete() {
|
|
29
|
+
local _cl="${1:-}"
|
|
30
|
+
[ -n "$_cl" ] && [ -n "${EPAM_PROJECT_CONFIG_DIR:-}" ] || return 1
|
|
31
|
+
local _here; _here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
32
|
+
local _builder="${_here}/project-prompt-builder.js"
|
|
33
|
+
local _out
|
|
34
|
+
_out=$("${NODE_BIN:-node}" -e '
|
|
35
|
+
const b = require(process.argv[1]);
|
|
36
|
+
const r = b.codelinePromptsComplete({ projectConfigDir: process.argv[2], codeline: process.argv[3],
|
|
37
|
+
templatesDir: process.env.EPAM_PROMPT_TEMPLATES_DIR || undefined, registryFile: process.env.EPAM_SEAM_REGISTRY_FILE || undefined });
|
|
38
|
+
process.stdout.write((r.complete ? "yes" : "no") + "\t" + (r.reason || ""));
|
|
39
|
+
' "$_builder" "$EPAM_PROJECT_CONFIG_DIR" "$_cl" 2>/dev/null) || return 1
|
|
40
|
+
case "$_out" in
|
|
41
|
+
yes*) return 0 ;;
|
|
42
|
+
*) echo "[prompts] codeline ${_cl}: ${_out#* }" >&2; return 1 ;;
|
|
43
|
+
esac
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# THIS CODELINE'S MINTED AGENTS ON DISK — restored from what its completion recorded
|
|
47
|
+
# (.prompt-cache/<codeline>/minted, project-prompt-builder.js). Returns 1, touching nothing, when the
|
|
48
|
+
# codeline recorded none: a marker cannot vouch for agents it never saw (AMSD-2974 run 3, 2026-09-30).
|
|
49
|
+
codeline_minted_restore() {
|
|
50
|
+
local _cl="${1:-}"
|
|
51
|
+
[ -n "$_cl" ] && [ -n "${EPAM_PROJECT_CONFIG_DIR:-}" ] || return 1
|
|
52
|
+
local _here; _here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
53
|
+
"${NODE_BIN:-node}" -e '
|
|
54
|
+
const b = require(process.argv[1]);
|
|
55
|
+
process.exit(b.restoreMinted({ projectConfigDir: process.argv[2], codeline: process.argv[3] }) ? 0 : 1);
|
|
56
|
+
' "${_here}/project-prompt-builder.js" "$EPAM_PROJECT_CONFIG_DIR" "$_cl" 2>/dev/null
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# The mint's clean slate: every minted file (the one list, in project-prompt-builder.js), removed.
|
|
60
|
+
codeline_minted_clear() {
|
|
61
|
+
[ -n "${EPAM_PROJECT_CONFIG_DIR:-}" ] || return 0
|
|
62
|
+
local _here; _here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
63
|
+
"${NODE_BIN:-node}" -e 'require(process.argv[1]).clearMinted({ projectConfigDir: process.argv[2] });' \
|
|
64
|
+
"${_here}/project-prompt-builder.js" "$EPAM_PROJECT_CONFIG_DIR" 2>/dev/null || true
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# lane_codeline_set_restore <lane PRD> — A LANE WORKS FROM ITS OWN CODELINE'S SET (operator, 2026-10-22: "prompts/profiles BY
|
|
68
|
+
# codeline"). The lane's codeline is its PRD's project.outputDir folder; that codeline's recorded agents and prompts become the
|
|
69
|
+
# active set before the lane's agents run (project-prompt-builder.js restoreCodelineSet). A codeline with no recorded set returns 1,
|
|
70
|
+
# says why and changes nothing: the lane never runs another codeline's set or the project-wide one.
|
|
71
|
+
lane_codeline_set_restore() {
|
|
72
|
+
local _prd="${1:-}"
|
|
73
|
+
[ -n "$_prd" ] && [ -f "$_prd" ] && [ -n "${EPAM_PROJECT_CONFIG_DIR:-}" ] || { echo "[prompts] lane: no PRD or project to resolve its codeline's set from" >&2; return 1; }
|
|
74
|
+
local _here; _here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
75
|
+
local _out
|
|
76
|
+
_out=$("${NODE_BIN:-node}" -e '
|
|
77
|
+
const b = require(process.argv[1]);
|
|
78
|
+
const codeline = b.codelineOfPrd(process.argv[3]);
|
|
79
|
+
const r = codeline ? b.restoreCodelineSet({ projectConfigDir: process.argv[2], codeline }) : { ok: false, reason: "its PRD names no codeline (project.outputDir)" };
|
|
80
|
+
process.stdout.write((r.ok ? "yes" : "no") + "\t" + (codeline || "?") + "\t" + (r.reason || ""));
|
|
81
|
+
' "${_here}/project-prompt-builder.js" "$EPAM_PROJECT_CONFIG_DIR" "$_prd" 2>&1) || { echo "[prompts] lane: its codeline's set could not be resolved: ${_out}" >&2; return 1; }
|
|
82
|
+
local _ok="${_out%% *}" _rest="${_out#* }"; local _cl="${_rest%% *}" _why="${_rest#* }"
|
|
83
|
+
if [ "$_ok" = "yes" ]; then
|
|
84
|
+
echo "[prompts] lane: working from codeline ${_cl}'s own recorded agents and prompts" >&2
|
|
85
|
+
return 0
|
|
86
|
+
fi
|
|
87
|
+
echo "[prompts] lane: codeline ${_cl} has no recorded set (${_why}) — the lane does not run another codeline's or the project-wide set" >&2
|
|
88
|
+
return 1
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# scope_codeline_of_prd <PRD> — the codeline the mint's reuse is decided for (project-prompt-builder.js scopeCodelineFromPrd): the
|
|
92
|
+
# story's single codeline; for several, its own codeline when EVERY codeline of the story has its complete recorded set; else empty.
|
|
93
|
+
scope_codeline_of_prd() {
|
|
94
|
+
local _prd="${1:-}"
|
|
95
|
+
[ -n "$_prd" ] && [ -f "$_prd" ] || return 0
|
|
96
|
+
local _here; _here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
97
|
+
"${NODE_BIN:-node}" -e '
|
|
98
|
+
const b = require(process.argv[1]);
|
|
99
|
+
process.stdout.write(b.scopeCodelineFromPrd({ prdFile: process.argv[3], projectConfigDir: process.argv[2],
|
|
100
|
+
templatesDir: process.env.EPAM_PROMPT_TEMPLATES_DIR || undefined, registryFile: process.env.EPAM_SEAM_REGISTRY_FILE || undefined }) || "");
|
|
101
|
+
' "${_here}/project-prompt-builder.js" "${EPAM_PROJECT_CONFIG_DIR:-}" "$_prd" 2>/dev/null || true
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
# parallel_lanes_codeline_set_restore <lane PRD>... — LANES THAT RUN TOGETHER SHARE ONE ACTIVE SET. Each can work from its own
|
|
105
|
+
# codeline's set only when those sets are the same, byte for byte (project-prompt-builder.js codelineSetsIdentical): then the set is
|
|
106
|
+
# restored once, before they start. Otherwise returns 1 and says which differ — no lane runs another codeline's set; run the lanes
|
|
107
|
+
# one at a time (EPAM_PARALLEL_CODELINES=0) and each restores its own.
|
|
108
|
+
parallel_lanes_codeline_set_restore() {
|
|
109
|
+
[ "$#" -gt 0 ] || return 0
|
|
110
|
+
local _here; _here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
111
|
+
local _why
|
|
112
|
+
_why=$("${NODE_BIN:-node}" -e '
|
|
113
|
+
const b = require(process.argv[1]);
|
|
114
|
+
const codelines = process.argv.slice(3).map((f) => b.codelineOfPrd(f));
|
|
115
|
+
if (codelines.some((c) => !c)) { process.stdout.write("a lane PRD names no codeline (project.outputDir)"); process.exit(1); }
|
|
116
|
+
const r = b.codelineSetsIdentical({ projectConfigDir: process.argv[2], codelines });
|
|
117
|
+
if (!r.same) { process.stdout.write(r.reason); process.exit(1); }
|
|
118
|
+
' "${_here}/project-prompt-builder.js" "${EPAM_PROJECT_CONFIG_DIR:-}" "$@" 2>&1) || {
|
|
119
|
+
echo "[prompts] parallel lanes: their codelines' recorded sets are not the same (${_why}) — run them one at a time (EPAM_PARALLEL_CODELINES=0) so each works from its own" >&2
|
|
120
|
+
return 1
|
|
121
|
+
}
|
|
122
|
+
lane_codeline_set_restore "$1"
|
|
123
|
+
}
|