amsd-pipeline-shared 0.5.11 → 0.5.13
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/README.md +13 -0
- package/docs/gateway-baseline-2.0.80.md5 +130 -0
- package/docs/gateway-files.txt +131 -0
- package/orchestrations/prompts/templates/ac-classification.json +32 -0
- package/orchestrations/prompts/templates/ac-elaboration.json +22 -0
- package/orchestrations/prompts/templates/ac-gate-codeline-assignment.json +27 -0
- package/orchestrations/prompts/templates/agent-name-refusal.json +15 -0
- package/orchestrations/prompts/templates/agent-plan-mode.json +22 -0
- package/orchestrations/prompts/templates/agent-proposal.json +40 -0
- package/orchestrations/prompts/templates/agent-retry-prefix.json +47 -0
- package/orchestrations/prompts/templates/assign-agent-roles.json +22 -0
- package/orchestrations/prompts/templates/bug-fix-story.json +33 -0
- package/orchestrations/prompts/templates/change-reviewer.json +30 -0
- package/orchestrations/prompts/templates/code-graph-detective.json +41 -0
- package/orchestrations/prompts/templates/codeline-bridge.json +20 -0
- package/orchestrations/prompts/templates/content-retry-correction.json +28 -0
- package/orchestrations/prompts/templates/coordinator-amendment.json +37 -0
- package/orchestrations/prompts/templates/corrective-note.json +24 -0
- package/orchestrations/prompts/templates/cpa-inference.json +29 -0
- package/orchestrations/prompts/templates/cpa-system.json +20 -0
- package/orchestrations/prompts/templates/declared-packages.json +14 -0
- package/orchestrations/prompts/templates/detective-extract.json +16 -0
- package/orchestrations/prompts/templates/detective-overall-plan-given.json +19 -0
- package/orchestrations/prompts/templates/detective-overall-plan.json +26 -0
- package/orchestrations/prompts/templates/detective-prescription.json +22 -0
- package/orchestrations/prompts/templates/detective-retry-note.json +20 -0
- package/orchestrations/prompts/templates/detective-shared-package.json +28 -0
- package/orchestrations/prompts/templates/discovery-vocabulary.json +23 -0
- package/orchestrations/prompts/templates/e2e-route-check.json +22 -0
- package/orchestrations/prompts/templates/estate-survey.json +32 -0
- package/orchestrations/prompts/templates/estimation-assessment.json +26 -0
- package/orchestrations/prompts/templates/fix-plan-shared.json +19 -0
- package/orchestrations/prompts/templates/guard-vocabulary-codegraph.json +25 -0
- package/orchestrations/prompts/templates/guard-vocabulary-documentation.json +21 -0
- package/orchestrations/prompts/templates/guard-vocabulary.json +34 -0
- package/orchestrations/prompts/templates/hybrid-prephase-coordinator.json +16 -0
- package/orchestrations/prompts/templates/inference-ladder-coordinator.json +27 -0
- package/orchestrations/prompts/templates/kb-enforcement-synthesis.json +28 -0
- package/orchestrations/prompts/templates/lint-ac-remediator.json +20 -0
- package/orchestrations/prompts/templates/lint-fixer.json +19 -0
- package/orchestrations/prompts/templates/manifest-analysis.json +13 -0
- package/orchestrations/prompts/templates/mint-existing-roster.json +25 -0
- package/orchestrations/prompts/templates/new-dependency-directive.json +28 -0
- package/orchestrations/prompts/templates/new-dependency-lockfile-note.json +20 -0
- package/orchestrations/prompts/templates/phase-assessment-header.json +19 -0
- package/orchestrations/prompts/templates/pipeline-tools-command.json +13 -0
- package/orchestrations/prompts/templates/pipeline-tools-hosted.json +12 -0
- package/orchestrations/prompts/templates/plan-corrective.json +21 -0
- package/orchestrations/prompts/templates/plan-producer.json +24 -0
- package/orchestrations/prompts/templates/plan-reviewer.json +20 -0
- package/orchestrations/prompts/templates/prd-change-reviewer-mode-rules.json +28 -0
- package/orchestrations/prompts/templates/prd-change-reviewer-spec.json +33 -0
- package/orchestrations/prompts/templates/prd-change-reviewer.json +27 -0
- package/orchestrations/prompts/templates/prd-change-summarizer-text.json +19 -0
- package/orchestrations/prompts/templates/prd-change-summarizer-tool.json +21 -0
- package/orchestrations/prompts/templates/prd-configuration-block.json +24 -0
- package/orchestrations/prompts/templates/prd-generation.json +16 -0
- package/orchestrations/prompts/templates/prd-model-coordinator.json +24 -0
- package/orchestrations/prompts/templates/previous-refusal.json +15 -0
- package/orchestrations/prompts/templates/profile-augmentor.json +24 -0
- package/orchestrations/prompts/templates/project-prompt-generation.json +54 -0
- package/orchestrations/prompts/templates/project-roster-review.json +11 -0
- package/orchestrations/prompts/templates/prompt-claim-adjudication.json +21 -0
- package/orchestrations/prompts/templates/prompt-review.json +48 -0
- package/orchestrations/prompts/templates/qa-brownfield-scope.json +36 -0
- package/orchestrations/prompts/templates/qa-evidence-labels.json +28 -0
- package/orchestrations/prompts/templates/qa-fuzz-weaver.json +35 -0
- package/orchestrations/prompts/templates/qa-mutant-hunter.json +28 -0
- package/orchestrations/prompts/templates/qa-oracle-evidence.json +35 -0
- package/orchestrations/prompts/templates/qa-perf-sentinel.json +30 -0
- package/orchestrations/prompts/templates/qa-review-ranger.json +28 -0
- package/orchestrations/prompts/templates/qa-sast-sentinel.json +35 -0
- package/orchestrations/prompts/templates/qa-spec-validator.json +30 -0
- package/orchestrations/prompts/templates/release-line-port.json +22 -0
- package/orchestrations/prompts/templates/repro-feedback.json +31 -0
- package/orchestrations/prompts/templates/repro-role.json +32 -0
- package/orchestrations/prompts/templates/repro-test-writer.json +55 -0
- package/orchestrations/prompts/templates/retry-extension-coordinator.json +22 -0
- package/orchestrations/prompts/templates/review-ac-remediator.json +24 -0
- package/orchestrations/prompts/templates/review-finding-routing.json +17 -0
- package/orchestrations/prompts/templates/review-scope-block.json +18 -0
- package/orchestrations/prompts/templates/roster-review.json +46 -0
- package/orchestrations/prompts/templates/roster-specialisation-one.json +14 -0
- package/orchestrations/prompts/templates/roster-specialisation-plan.json +12 -0
- package/orchestrations/prompts/templates/roster-specialisation.json +24 -0
- package/orchestrations/prompts/templates/roster-tool-grant.json +20 -0
- package/orchestrations/prompts/templates/runtime-boundary-review.json +30 -0
- package/orchestrations/prompts/templates/shared-file-ownership.json +24 -0
- package/orchestrations/prompts/templates/skill-assessment-postphase.json +22 -0
- package/orchestrations/prompts/templates/skill-assessment-prephase.json +35 -0
- package/orchestrations/prompts/templates/skills-coordinator.json +19 -0
- package/orchestrations/prompts/templates/spec-agent-openspec.brownfield.json +67 -0
- package/orchestrations/prompts/templates/spec-agent-openspec.json +63 -0
- package/orchestrations/prompts/templates/spec-agent-speckit-refine.json +30 -0
- package/orchestrations/prompts/templates/spec-agent-speckit.json +28 -0
- package/orchestrations/prompts/templates/spec-authoring-rules.json +25 -0
- package/orchestrations/prompts/templates/spec-brownfield-mode.json +33 -0
- package/orchestrations/prompts/templates/spec-context-fragments.json +39 -0
- package/orchestrations/prompts/templates/spec-coordinator-review.json +29 -0
- package/orchestrations/prompts/templates/spec-coordinator.json +26 -0
- package/orchestrations/prompts/templates/spec-model-review.json +23 -0
- package/orchestrations/prompts/templates/spec-review-criteria.json +23 -0
- package/orchestrations/prompts/templates/spec-shared-consumer.json +20 -0
- package/orchestrations/prompts/templates/spec-story-block.json +42 -0
- package/orchestrations/prompts/templates/speckit-split-rules.json +20 -0
- package/orchestrations/prompts/templates/squad-coder.json +19 -0
- package/orchestrations/prompts/templates/squad-leader.json +19 -0
- package/orchestrations/prompts/templates/squad-security-auditor.json +19 -0
- package/orchestrations/prompts/templates/squad-tester.json +19 -0
- package/orchestrations/prompts/templates/story-ac-remediator.json +29 -0
- package/orchestrations/prompts/templates/story-diff-not-inlined.json +16 -0
- package/orchestrations/prompts/templates/story-file-generation.json +28 -0
- package/orchestrations/prompts/templates/story-implementation.json +25 -0
- package/orchestrations/prompts/templates/story-kind-hint-defect.json +18 -0
- package/orchestrations/prompts/templates/story-kind-hint-other.json +20 -0
- package/orchestrations/prompts/templates/story-plan-agent.json +31 -0
- package/orchestrations/prompts/templates/story-writer-main.json +96 -0
- package/orchestrations/prompts/templates/story-writer.json +28 -0
- package/orchestrations/prompts/templates/survey-review.json +38 -0
- package/orchestrations/prompts/templates/tc-writer.json +36 -0
- package/orchestrations/prompts/templates/test-gate-finding.json +27 -0
- package/orchestrations/prompts/templates/test-ownership.json +38 -0
- package/orchestrations/prompts/templates/tools-coordinator.json +18 -0
- package/orchestrations/prompts/templates/topology-router.json +30 -0
- package/orchestrations/prompts/templates/update-invalidated-tests.json +33 -0
- package/orchestrations/prompts/templates/vc-coverage.json +20 -0
- package/orchestrations/prompts/templates/vc-regenerate.json +33 -0
- package/orchestrations/prompts/templates/vc-review.json +27 -0
- package/orchestrations/prompts/templates/worktree-autocommit-message.json +27 -0
- package/orchestrations/prompts/templates/writer-codegraph-block.json +25 -0
- package/orchestrations/prompts/templates/writer-plan-section.json +28 -0
- package/orchestrations/prompts/templates/writer-string-invariants.json +17 -0
- package/orchestrations/scripts/lib/project-prompt-contract.js +414 -0
- package/orchestrations/scripts/refresh-project-prompts.js +96 -0
- package/package.json +1 -1
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$why": [
|
|
3
|
+
"Moved out of worktree-health-check.sh on 2026-08-16.",
|
|
4
|
+
"",
|
|
5
|
+
"Not a prompt — no model receives it. It is the commit message the health check writes when an",
|
|
6
|
+
"agent produced files and did not commit them, and it is PROSE: a human reads it in git log,",
|
|
7
|
+
"months later, trying to work out why a commit exists that no agent claims.",
|
|
8
|
+
"",
|
|
9
|
+
"Text a person reads belongs in the template layer for the same reason a prompt does. As a",
|
|
10
|
+
"heredoc it could not be reviewed, diffed against the other things the engine says, or changed",
|
|
11
|
+
"without editing a shell script — and the attribution line at the bottom had gone stale where",
|
|
12
|
+
"nobody could see it."
|
|
13
|
+
],
|
|
14
|
+
"description": "The commit message written when an agent leaves its output uncommitted.",
|
|
15
|
+
"seams": [],
|
|
16
|
+
"consumer": "engine",
|
|
17
|
+
"id": "worktree-autocommit-message",
|
|
18
|
+
"version": 1,
|
|
19
|
+
"body": "chore(wt-__LANE__): auto-commit agent output for phase __PHASE__\n\nAutomated commit of __CHANGED_COUNT__ file(s) produced by the __LANE__ agent.\nPhase: __PHASE__\nLane: __LANE__\nTimestamp: __TIMESTAMP__\n\nWARNING: this was auto-committed by worktree-health-check.sh because the agent\ndid not commit its own changes. Review these files.\n\nCo-Authored-By: __AUTHOR__\n",
|
|
20
|
+
"placeholders": [
|
|
21
|
+
"__LANE__",
|
|
22
|
+
"__PHASE__",
|
|
23
|
+
"__CHANGED_COUNT__",
|
|
24
|
+
"__TIMESTAMP__",
|
|
25
|
+
"__AUTHOR__"
|
|
26
|
+
]
|
|
27
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$why": [
|
|
3
|
+
"Moved out of claude.sh on 2026-08-16.",
|
|
4
|
+
"",
|
|
5
|
+
"Three mutually exclusive states, each a different instruction to the writer: the helper to",
|
|
6
|
+
"reuse is already identified, the prescribed fix is known to be incomplete, or nothing is",
|
|
7
|
+
"known and here is the tool to find out. One body each — collapsing them would tell a writer",
|
|
8
|
+
"a helper was found when none was."
|
|
9
|
+
],
|
|
10
|
+
"description": "Tells the writer what CodeGraph already established about the fix, or how to query it.",
|
|
11
|
+
"seams": [
|
|
12
|
+
"story-writer"
|
|
13
|
+
],
|
|
14
|
+
"id": "writer-codegraph-block",
|
|
15
|
+
"version": 1,
|
|
16
|
+
"bodies": {
|
|
17
|
+
"helper_identified": "## The helper to reuse is ALREADY identified — do NOT search\nThe Root Cause Analysis above names the exact existing helper(s) to reuse: `__PRESCRIBED_HELPER__`. EVERY ONE of them must be imported and called — a write that uses some but not all is rejected at write time for the ones it omitted, and the rejection names only what was missed. Do NOT run CodeGraph or explore the codebase to re-find them — that wastes your turn budget. Import them, apply the prescribed minimal fix, write your file(s), and stop. Only search if you hit something the prescribed fix genuinely does not cover.",
|
|
18
|
+
"fix_incomplete": "## The prescribed fix is KNOWN INCOMPLETE — explore for the rest\nThe Root Cause Analysis above names existing helper(s) to reuse: `__PRESCRIBED_HELPER__` — apply those parts directly and use EVERY one of them, do NOT re-search for them. But the prescribed fix does NOT address every verification criterion. These are UNCOVERED and need your own investigation beyond the prescribed sites:\n__UNCOVERED_LIST__\nUse the codegraph_query tool (mode=\"helpers\", args=\"<domain nouns>\") to find existing code for THESE before writing new logic — do not assume they are out of scope.",
|
|
19
|
+
"tool_available": "## CodeGraph Tool — find EXISTING functions to reuse (do this BEFORE writing any new helper)\nA codegraph_query tool is available — call it directly (NOT via Bash) to discover reusable functions instead of inventing new ones:\n codegraph_query(mode=\"helpers\", args=\"<domain nouns>\") # existing util/parser/formatter to REUSE (symbol + import path)\n codegraph_query(mode=\"query\", args=\"<SymbolName>\") # exact definition site of a symbol\n codegraph_query(mode=\"callees\", args=\"<SymbolName>\") # what a function already calls\nRULE: Before you add ANY new function, run `helpers` for what it would do. If a suitable function already exists, import and call it — do NOT duplicate it. Fewer lines of code is always better."
|
|
20
|
+
},
|
|
21
|
+
"placeholders": [
|
|
22
|
+
"__PRESCRIBED_HELPER__",
|
|
23
|
+
"__UNCOVERED_LIST__"
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$why": [
|
|
3
|
+
"Moved out of claude.sh on 2026-08-16.",
|
|
4
|
+
"",
|
|
5
|
+
"Two sections appended to the writer's prompt: the execution plan it must follow, and the coordinator guidance identified from a previous failure. Both appear at more than one call site and were written out separately at each.",
|
|
6
|
+
"",
|
|
7
|
+
"The guidance section has two variants: one carries the whole amendment, the other the most recent three with the rest left in the log. That second one DECLARES its own truncation in the heading, which is the honest form -- whole entries dropped and said so, rather than a string cut mid-sentence."
|
|
8
|
+
],
|
|
9
|
+
"description": "Sections appended to a writer prompt: the plan to follow, and guidance from a previous failure.",
|
|
10
|
+
"seams": [
|
|
11
|
+
"story-writer"
|
|
12
|
+
],
|
|
13
|
+
"id": "writer-plan-section",
|
|
14
|
+
"version": 1,
|
|
15
|
+
"bodies": {
|
|
16
|
+
"coordinator_guidance_full": "__PROMPT__\n\n## Coordinator Guidance (retry __RETRY_COUNT__)\nThe following targeted instruction was identified from the previous failure:\n__COORDINATOR_PROMPT_AMENDMENT__",
|
|
17
|
+
"coordinator_guidance_trimmed": "__PROMPT__\n\n## Coordinator Guidance (retry __RETRY_COUNT__, showing most recent up to 3 — full retry history: __SCRATCHPAD_FILE__)\n__TRIMMED_AMENDMENT__",
|
|
18
|
+
"execution_plan": "__PROMPT__\n\n## Execution Plan\nFollow this plan step by step:\n__STORY_PLAN__"
|
|
19
|
+
},
|
|
20
|
+
"placeholders": [
|
|
21
|
+
"__COORDINATOR_PROMPT_AMENDMENT__",
|
|
22
|
+
"__RETRY_COUNT__",
|
|
23
|
+
"__PROMPT__",
|
|
24
|
+
"__TRIMMED_AMENDMENT__",
|
|
25
|
+
"__SCRATCHPAD_FILE__",
|
|
26
|
+
"__STORY_PLAN__"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$why": [
|
|
3
|
+
"Moved out of claude.sh on 2026-08-16. Names the string literals a change must not disturb.",
|
|
4
|
+
"",
|
|
5
|
+
"The list was formatted inside the prompt by a sed pipeline. The caller formats it and passes it: a prompt that runs a shell pipeline to lay out its own content cannot be read as prose, which is what this layer exists for."
|
|
6
|
+
],
|
|
7
|
+
"description": "Tells the writer which string literals in the target file must not change.",
|
|
8
|
+
"seams": [
|
|
9
|
+
"story-writer"
|
|
10
|
+
],
|
|
11
|
+
"id": "writer-string-invariants",
|
|
12
|
+
"version": 1,
|
|
13
|
+
"body": "\n## CRITICAL COMPLIANCE: STRING INVARIANTS\nThe Acceptance Criteria above contain exact string matches required by the test suite.\nYou are FORBIDDEN from paraphrasing, summarizing, or optimizing these strings — reproduce them character-for-character, including case and spacing, wherever the AC requires them.\n\nLITERAL STRINGS TO USE VERBATIM:\n__STRING_LIST__\n",
|
|
14
|
+
"placeholders": [
|
|
15
|
+
"__STRING_LIST__"
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* THE CONTRACT A GENERATED PROJECT PROMPT MUST HONOUR.
|
|
4
|
+
*
|
|
5
|
+
* Templates are generic and immutable. The project-authority copy is generated from one and
|
|
6
|
+
* is the ONLY thing prompt-library.js will render — there is no fallback to the template, by
|
|
7
|
+
* operator mandate, because a silent degrade runs an engine-embedded default for a whole
|
|
8
|
+
* campaign without anyone noticing.
|
|
9
|
+
*
|
|
10
|
+
* WHY THIS IS CODE AND NOT AN INSTRUCTION IN THE PROMPT. Rendering is strict in both
|
|
11
|
+
* directions: every declared placeholder must appear in the body, every placeholder in the
|
|
12
|
+
* body must be declared, and every one must be supplied at render time. A generator that
|
|
13
|
+
* adds, drops or renames a single placeholder therefore produces a prompt that does not
|
|
14
|
+
* degrade — it THROWS, mid-run, after the roster is minted and the run is already spending.
|
|
15
|
+
* Asking a model nicely to preserve placeholders is not a guarantee; checking its output is.
|
|
16
|
+
*
|
|
17
|
+
* The check runs at GENERATION time so the failure names the generator, rather than surfacing
|
|
18
|
+
* three stages later at whichever seam happened to need that prompt.
|
|
19
|
+
*/
|
|
20
|
+
'use strict';
|
|
21
|
+
|
|
22
|
+
const crypto = require('crypto');
|
|
23
|
+
|
|
24
|
+
// ONE DEFINITION, IMPORTED — not a second copy of the rule.
|
|
25
|
+
//
|
|
26
|
+
// This file carried its own placeholder regex, and it was the GREEDY version. engine-prompt.js
|
|
27
|
+
// was fixed to match lazily so that adjacent placeholders (__A____B__, which is what ${a}${b}
|
|
28
|
+
// becomes) read as two, and this copy was not — so the contract saw seven adjacent blocks as
|
|
29
|
+
// one token and refused every generated spec-agent-openspec as "dropping placeholders" the
|
|
30
|
+
// template required. Two copies of a rule is two rules.
|
|
31
|
+
const { placeholdersIn: _placeholdersIn } = require('./engine-prompt.js');
|
|
32
|
+
|
|
33
|
+
/** Placeholders present in a body, deduped and sorted. */
|
|
34
|
+
function placeholdersIn(body) {
|
|
35
|
+
return [..._placeholdersIn(body)].sort();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const sorted = (a) => [...(Array.isArray(a) ? a : [])].sort();
|
|
39
|
+
const missingFrom = (want, have) => want.filter((p) => !have.includes(p));
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Is this generated prompt safe to install as the project authority?
|
|
43
|
+
*
|
|
44
|
+
* @param {{id:string, body:string, placeholders:string[]}} template the generic source
|
|
45
|
+
* @param {{id:string, body:string, placeholders:string[]}} generated the model's output
|
|
46
|
+
* @returns {{ok:boolean, reason:string}} reason NAMES the offending placeholders — a bare
|
|
47
|
+
* "invalid" leaves the operator diffing two prompts by eye.
|
|
48
|
+
*/
|
|
49
|
+
function checkGeneratedPrompt(template, generated) {
|
|
50
|
+
if (!generated || typeof generated.body !== 'string' || !generated.body.trim()) {
|
|
51
|
+
return { ok: false, reason: 'the generated prompt has an empty body — nothing would be sent' };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const tplWanted = sorted(template && template.placeholders);
|
|
55
|
+
const genUsed = placeholdersIn(generated.body);
|
|
56
|
+
const genDeclared = sorted(generated.placeholders);
|
|
57
|
+
|
|
58
|
+
// 1. Every value the pipeline supplies must still have somewhere to land.
|
|
59
|
+
const dropped = missingFrom(tplWanted, genUsed);
|
|
60
|
+
if (dropped.length) {
|
|
61
|
+
return {
|
|
62
|
+
ok: false,
|
|
63
|
+
reason: `the generated prompt dropped placeholder(s) the template requires: ${dropped.join(', ')}. `
|
|
64
|
+
+ 'The evidence they carry would silently never reach the agent.',
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 2. Nothing may be invented: render supplies values by NAME, so an unknown placeholder is
|
|
69
|
+
// never substituted and prompt-library throws "still contains placeholders after rendering".
|
|
70
|
+
const invented = missingFrom(genUsed, tplWanted);
|
|
71
|
+
if (invented.length) {
|
|
72
|
+
return {
|
|
73
|
+
ok: false,
|
|
74
|
+
reason: `the generated prompt invented placeholder(s) nothing supplies: ${invented.join(', ')}. `
|
|
75
|
+
+ 'Rendering would throw at the seam that needs this prompt.',
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// 3. The declaration must match the body, in both directions — prompt-library enforces this
|
|
80
|
+
// at render time, and catching it here names the generator instead of the seam.
|
|
81
|
+
const undeclared = missingFrom(genUsed, genDeclared);
|
|
82
|
+
if (undeclared.length) {
|
|
83
|
+
return { ok: false, reason: `body uses undeclared placeholder(s): ${undeclared.join(', ')}` };
|
|
84
|
+
}
|
|
85
|
+
const orphan = missingFrom(genDeclared, genUsed);
|
|
86
|
+
if (orphan.length) {
|
|
87
|
+
return { ok: false, reason: `declares placeholder(s) it never uses: ${orphan.join(', ')}` };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 4. THE OUTPUT CONTRACT MUST SURVIVE GENERATION.
|
|
91
|
+
//
|
|
92
|
+
// A generated prompt may specialise anything about HOW an agent works — this project's
|
|
93
|
+
// codelines, its stack, its tools. It may not change what an ANSWER looks like, because the
|
|
94
|
+
// consumer that reads the answer was not regenerated with it.
|
|
95
|
+
//
|
|
96
|
+
// Checked because two real generations shipped without it. prompt-review lost
|
|
97
|
+
// <PROMPT_REVIEW>{"falseClaims": []}</PROMPT_REVIEW> while lib/prompt-review.js parses exactly
|
|
98
|
+
// that tag, so the reviewer's answer could never be read. skill-assessment-prephase lost every
|
|
99
|
+
// output key and came back half the length. Both are the run-8 shape: the agent obeys its
|
|
100
|
+
// prompt and the engine reads something else, and nothing can say which side is wrong.
|
|
101
|
+
//
|
|
102
|
+
// ADDITIONS ARE FINE. Only a LOSS is refused: the consumer still reads what the template
|
|
103
|
+
// promised, so dropping it is what breaks.
|
|
104
|
+
const tplBody = String((template && template.body) || Object.values((template && template.bodies) || {}).join('\n') || '');
|
|
105
|
+
const genBody = generated.body;
|
|
106
|
+
|
|
107
|
+
// Response tags — <PROMPT_REVIEW>, <SPEC_AGENT> — are how a consumer finds the answer at all.
|
|
108
|
+
// A RESPONSE TAG IS PAIRED. A VALUE PLACEHOLDER IS NOT.
|
|
109
|
+
//
|
|
110
|
+
// This took every <UPPERCASE> in the body. skill-assessment-prephase's instructions contain an
|
|
111
|
+
// example JSONL record — {"timestamp":"<ISO8601>", ...} — where <ISO8601> is a TYPE placeholder
|
|
112
|
+
// inside an illustration, not a marker any consumer looks for. The guard demanded the generated
|
|
113
|
+
// prompt reproduce it verbatim, the generator legitimately rephrased the example, and mock3
|
|
114
|
+
// run 9 exhausted all three attempts and failed the step after 29 prompts had succeeded.
|
|
115
|
+
//
|
|
116
|
+
// A tag a consumer parses always wraps something: <PROMPT_REVIEW>…</PROMPT_REVIEW>. Across all
|
|
117
|
+
// 117 templates that separates them exactly — 4 paired (DISCOVERY_VOCABULARY, MODEL_REVIEW,
|
|
118
|
+
// PROMPT_REVIEW, SPEC_REVIEW), 1 unpaired (ISO8601). So pairing is the discriminator, and it is
|
|
119
|
+
// derived from the template rather than an exception list that would go stale.
|
|
120
|
+
const tplTags = [...new Set(tplBody.match(/<[A-Z][A-Z0-9_]+>/g) || [])]
|
|
121
|
+
.filter((t) => tplBody.includes(`</${t.slice(1, -1)}>`))
|
|
122
|
+
.flatMap((t) => [t, `</${t.slice(1, -1)}>`]);
|
|
123
|
+
const lostTags = tplTags.filter((t) => !genBody.includes(t));
|
|
124
|
+
if (lostTags.length) {
|
|
125
|
+
return {
|
|
126
|
+
ok: false,
|
|
127
|
+
reason: `the generated prompt dropped the output tag(s) the template states: ${lostTags.join(', ')}. `
|
|
128
|
+
+ 'The consumer looks for exactly that marker, so the answer would be unreadable however '
|
|
129
|
+
+ 'good it is.',
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Field names the template states as part of its response shape.
|
|
134
|
+
const tplKeys = outputFieldsIn(tplBody);
|
|
135
|
+
const lostKeys = tplKeys.filter((k) => !genBody.includes(k));
|
|
136
|
+
if (lostKeys.length) {
|
|
137
|
+
return {
|
|
138
|
+
ok: false,
|
|
139
|
+
reason: `the generated prompt dropped output field(s) the template states: ${lostKeys.join(', ')}. `
|
|
140
|
+
+ 'The consumer still reads them, so the agent would answer a shape nothing accepts.',
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return { ok: true, reason: '' };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* THE RESPONSE-SHAPE FIELD NAMES A TEMPLATE STATES.
|
|
149
|
+
*
|
|
150
|
+
* Defined once and exported, because two places need the same answer: this contract check, which
|
|
151
|
+
* REFUSES a generated prompt that lost a field, and the generator prompt, which must be TOLD the
|
|
152
|
+
* fields so it can keep them. Judging an agent against a list it was never shown is how
|
|
153
|
+
* skill-assessment-prephase came back missing timestamp, phase_id, agent_role, event,
|
|
154
|
+
* skill_category, context and added_by — the generator prompt gave placeholders their own section
|
|
155
|
+
* and an explicit list, and gave output fields one clause in the middle of a sentence.
|
|
156
|
+
*/
|
|
157
|
+
function outputFieldsIn(body) {
|
|
158
|
+
return [...new Set((String(body == null ? '' : body).match(/"([a-zA-Z][a-zA-Z0-9_]*)"\s*:/g) || [])
|
|
159
|
+
.map((m) => m.replace(/[":\s]/g, '')))];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Assemble the document to write as the project authority.
|
|
164
|
+
*
|
|
165
|
+
* derivedFromSha256 is taken over the TEMPLATE, so a later run can tell whether the template
|
|
166
|
+
* has moved on since this copy was generated. Without it, a project prompt generated from a
|
|
167
|
+
* since-corrected template looks indistinguishable from a current one.
|
|
168
|
+
*/
|
|
169
|
+
/** The template digest a GENERATED copy records — one definition, also read when a resume keeps a copy. */
|
|
170
|
+
// THE TEXT, WHEREVER THE TEMPLATE KEEPS IT. Reading `body` alone hashed a template whose text is in `bodies` the same whatever
|
|
171
|
+
// it said, so its project copy was never rebuilt (blocker-discipline v3, 2026-10-21). A `body` template hashes as before.
|
|
172
|
+
function generatedTemplateDigest(template) {
|
|
173
|
+
const text = template.body !== undefined ? { body: template.body } : { body: template.body, bodies: template.bodies };
|
|
174
|
+
return crypto
|
|
175
|
+
.createHash('sha256')
|
|
176
|
+
.update(JSON.stringify({ id: template.id, ...text, placeholders: sorted(template.placeholders) }))
|
|
177
|
+
.digest('hex');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function buildGeneratedDoc(template, generatedBody) {
|
|
181
|
+
const body = String(generatedBody);
|
|
182
|
+
return {
|
|
183
|
+
$why: [
|
|
184
|
+
`Generated from the ${template.id} template by the agent that minted this project's roster.`,
|
|
185
|
+
'The template is never executed; this copy is. Placeholders were checked against the',
|
|
186
|
+
'template before installation — see lib/project-prompt-contract.js.',
|
|
187
|
+
],
|
|
188
|
+
authority: 'project',
|
|
189
|
+
body,
|
|
190
|
+
derivedFromSha256: generatedTemplateDigest(template),
|
|
191
|
+
derivedFromVersion: template.version == null ? null : template.version,
|
|
192
|
+
id: template.id,
|
|
193
|
+
// NO `seams` FIELD. It was a hand-maintained inverse index of something the registry already
|
|
194
|
+
// declares — a seam names the template it runs — and the two copies drifted: run
|
|
195
|
+
// 20260817T211517Z installed a copy claiming seams ["failure-analyst"] where its template says
|
|
196
|
+
// ["impl-failure-analyst"], and the link failed after 37 prompts had provisioned. Worse, that
|
|
197
|
+
// one template serves TWO seams and a single array cannot name both, so the relationship was
|
|
198
|
+
// unrepresentable rather than merely mis-copied.
|
|
199
|
+
//
|
|
200
|
+
// prompt-agent-link now reads the registry directly, so nothing consumes this field. Writing
|
|
201
|
+
// it anyway would only invite the drift back.
|
|
202
|
+
placeholders: placeholdersIn(body),
|
|
203
|
+
// THE DECLARATION TRAVELS WITH THE COPY THAT IS EXECUTED.
|
|
204
|
+
//
|
|
205
|
+
// mayBeEmpty says a placeholder may legitimately render blank — no prior gaps on a first pass,
|
|
206
|
+
// no fix sites before discovery, no forced retry on attempt one. The TEMPLATE declared it and
|
|
207
|
+
// this doc did not carry it, so every generated prompt lost the protection and the renderer
|
|
208
|
+
// refused at runtime on a block that was supposed to be empty.
|
|
209
|
+
//
|
|
210
|
+
// Live 2026-08-27, run 20260827T125654Z: the pipeline reached the specification pass for the
|
|
211
|
+
// first time and died there deterministically —
|
|
212
|
+
// prompt 'spec-agent-openspec' was given EMPTY values for: __DECLARED_FILE_BLOCK__,
|
|
213
|
+
// __FIX_SITE_BLOCK__, __FORCED_RETRY_BLOCK__, __PRIOR_GAPS_BLOCK__, …
|
|
214
|
+
// Three retries changed nothing because no model was ever asked anything, and both lanes
|
|
215
|
+
// halted. guard-vocabulary's template declared two and its generated copy carried none.
|
|
216
|
+
//
|
|
217
|
+
// FILTERED TO WHAT THIS BODY ACTUALLY HAS. A declaration naming a placeholder the generated
|
|
218
|
+
// text does not contain protects nothing and would only mislead the next reader — the same
|
|
219
|
+
// drift that removed the `seams` field above.
|
|
220
|
+
mayBeEmpty: (Array.isArray(template.mayBeEmpty) ? template.mayBeEmpty : [])
|
|
221
|
+
.filter((ph) => body.includes(ph)),
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* splitByPlaceholders(body) — prose segments and the slots between them.
|
|
227
|
+
*
|
|
228
|
+
* THE SLOTS ARE DELIMITERS, AND TREATING THEM AS PAYLOAD IS THE DEFECT. prompt-builder asked the
|
|
229
|
+
* model to rewrite a whole template while transcribing every __SLOT__ character-for-character:
|
|
230
|
+
* a writing task and a copying task at once. Models lose literals while rewriting the prose around
|
|
231
|
+
* them — live 2026-09-08, 25 refusals, every one "dropped placeholder(s) the template requires",
|
|
232
|
+
* and runtime-boundary-review lost all six of its slots five attempts running and aborted the run.
|
|
233
|
+
*
|
|
234
|
+
* Split here, and the model is only ever shown, and only ever returns, the prose BETWEEN slots.
|
|
235
|
+
* It cannot drop a placeholder it was never given.
|
|
236
|
+
*
|
|
237
|
+
* INVARIANT: body === segments[0] + slots[0] + segments[1] + ... + segments[n], so segments is
|
|
238
|
+
* always exactly one longer than slots, and every occurrence of a repeated slot is kept in order.
|
|
239
|
+
*/
|
|
240
|
+
function splitByPlaceholders(body) {
|
|
241
|
+
const text = String(body == null ? '' : body);
|
|
242
|
+
const slots = [];
|
|
243
|
+
const segments = [];
|
|
244
|
+
const re = /__[A-Z][A-Z0-9_]*?__/g;
|
|
245
|
+
let last = 0;
|
|
246
|
+
let m = re.exec(text);
|
|
247
|
+
while (m) {
|
|
248
|
+
segments.push(text.slice(last, m.index));
|
|
249
|
+
slots.push(m[0]);
|
|
250
|
+
last = m.index + m[0].length;
|
|
251
|
+
m = re.exec(text);
|
|
252
|
+
}
|
|
253
|
+
segments.push(text.slice(last));
|
|
254
|
+
return { segments, slots };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* assembleFromSegments(segments, slots) — the body the pipeline builds, never the model.
|
|
259
|
+
*
|
|
260
|
+
* The slots go back exactly where they were, in their original order, however the model reworded
|
|
261
|
+
* the prose. Every contract invariant — nothing dropped, nothing invented, declared == used —
|
|
262
|
+
* therefore holds BY CONSTRUCTION rather than by inspection and retry.
|
|
263
|
+
*
|
|
264
|
+
* REFUSES A WRONG COUNT rather than padding. A model that returns the wrong number of segments has
|
|
265
|
+
* misunderstood the task, and quietly filling the gap would install a prompt whose prose no longer
|
|
266
|
+
* lines up with the evidence its slots carry — the same silent-wrongness this whole change exists
|
|
267
|
+
* to remove.
|
|
268
|
+
*/
|
|
269
|
+
function assembleFromSegments(segments, slots) {
|
|
270
|
+
const segs = Array.isArray(segments) ? segments : [];
|
|
271
|
+
const sl = Array.isArray(slots) ? slots : [];
|
|
272
|
+
if (segs.length !== sl.length + 1) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
`[project-prompt-contract] expected ${sl.length + 1} segment(s) for ${sl.length} placeholder(s), got ${segs.length}`);
|
|
275
|
+
}
|
|
276
|
+
let out = '';
|
|
277
|
+
for (let i = 0; i < sl.length; i += 1) out += String(segs[i]) + sl[i];
|
|
278
|
+
return out + String(segs[segs.length - 1]);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* extractSegmentsReply(text, expectedCount) — the segments a reply meant to give.
|
|
283
|
+
*
|
|
284
|
+
* MODELS THINK OUT LOUD, and that is not an error worth a paid retry. Live 2026-09-08 with real
|
|
285
|
+
* tokens, replies routinely looked like:
|
|
286
|
+
*
|
|
287
|
+
* {"segments":["","","","","","","",""][0:0]}
|
|
288
|
+
* Let me answer properly.
|
|
289
|
+
* {"segments":["","\n\n---\nRUNTIME BOUNDARY REVIEW: "," — ", ...
|
|
290
|
+
*
|
|
291
|
+
* The first extractor used /\{[\s\S]*\}/ — greedy — so it spanned the abandoned attempt, the
|
|
292
|
+
* prose, and the real answer as one string and failed to parse every time. runtime-boundary-review
|
|
293
|
+
* burned all three attempts on replies whose FINAL object was perfectly good.
|
|
294
|
+
*
|
|
295
|
+
* Scanned with a brace counter that respects strings and escapes, because segment prose legitimately
|
|
296
|
+
* contains braces and quotes. The LAST candidate that parses and carries a segments array wins; when
|
|
297
|
+
* a count is known, a candidate matching it wins over one that does not, so a false start of the
|
|
298
|
+
* wrong length cannot beat the real answer.
|
|
299
|
+
*/
|
|
300
|
+
function extractSegmentsReply(text, expectedCount) {
|
|
301
|
+
const s = String(text == null ? '' : text);
|
|
302
|
+
const found = [];
|
|
303
|
+
for (let i = 0; i < s.length; i += 1) {
|
|
304
|
+
if (s[i] !== '{') continue;
|
|
305
|
+
let depth = 0; let inStr = false; let esc = false;
|
|
306
|
+
for (let j = i; j < s.length; j += 1) {
|
|
307
|
+
const ch = s[j];
|
|
308
|
+
if (esc) { esc = false; continue; }
|
|
309
|
+
if (inStr) {
|
|
310
|
+
if (ch === '\\') esc = true;
|
|
311
|
+
else if (ch === '"') inStr = false;
|
|
312
|
+
continue;
|
|
313
|
+
}
|
|
314
|
+
if (ch === '"') { inStr = true; continue; }
|
|
315
|
+
if (ch === '{') depth += 1;
|
|
316
|
+
else if (ch === '}') {
|
|
317
|
+
depth -= 1;
|
|
318
|
+
if (depth === 0) {
|
|
319
|
+
try {
|
|
320
|
+
const o = JSON.parse(s.slice(i, j + 1));
|
|
321
|
+
if (o && Array.isArray(o.segments)) found.push(o.segments);
|
|
322
|
+
} catch { /* not this candidate */ }
|
|
323
|
+
i = j; // continue scanning AFTER this object
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
if (!found.length) {
|
|
330
|
+
throw new Error('[project-prompt-contract] no {"segments":[...]} object in the reply');
|
|
331
|
+
}
|
|
332
|
+
if (typeof expectedCount === 'number') {
|
|
333
|
+
for (let k = found.length - 1; k >= 0; k -= 1) {
|
|
334
|
+
if (found[k].length === expectedCount) return found[k];
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return found[found.length - 1];
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* parseSegmentsReply(text, expectedCount) — the segments a reply meant to give, delimited.
|
|
342
|
+
*
|
|
343
|
+
* WHY NOT JSON. The segments contract asked for the prose between placeholders as a JSON array.
|
|
344
|
+
* Live 2026-09-08, pipeline-tests-45: team-lead-review (17 placeholders, 5,545-char body) burned
|
|
345
|
+
* all five attempts and aborted the run at 7 of 39 prompts. The replies were not truncated -- they
|
|
346
|
+
* ended correctly with "]} -- they simply did not parse:
|
|
347
|
+
*
|
|
348
|
+
* Expected ',' or ']' after array element at position 609
|
|
349
|
+
* ..."worse than no fix.","\n---\nREVIEW TASK: Story ","," — ","\n\nDESCRIPTION:\n"...
|
|
350
|
+
*
|
|
351
|
+
* Segments that ARE punctuation -- ", ", "," -- sit badly beside JSON's own delimiters, and
|
|
352
|
+
* escaping quotes, commas and newlines across 7kB of prose is where it breaks. Another reply
|
|
353
|
+
* showed the model catching itself ("Wait — that response is invalid") and the corrected object
|
|
354
|
+
* failing to parse as well.
|
|
355
|
+
*
|
|
356
|
+
* The INPUT already uses --- SEGMENT n --- markers and the model reads them perfectly. Using the
|
|
357
|
+
* same shape for the OUTPUT removes escaping entirely: a newline is a newline and a segment that
|
|
358
|
+
* is a single comma is a line containing a comma.
|
|
359
|
+
*
|
|
360
|
+
* JSON is still accepted. A model that answers correctly in the harder format should not be
|
|
361
|
+
* refused for it.
|
|
362
|
+
*
|
|
363
|
+
* A FALSE START IS NORMAL. Models correct themselves mid-answer, so the LAST complete set wins.
|
|
364
|
+
*/
|
|
365
|
+
/**
|
|
366
|
+
* THE SEGMENT FORM, WRITTEN ONCE. The generator shows segments in this form and asks for the same
|
|
367
|
+
* form back; the parser below reads it. A second spelling anywhere — the builder's prompt, the
|
|
368
|
+
* rehearsal mock — is a copy that drifts, so both call this.
|
|
369
|
+
*/
|
|
370
|
+
function formatSegments(segments) {
|
|
371
|
+
return segments.map((seg, i) => `--- SEGMENT ${i + 1} ---\n${seg}`).join('\n')
|
|
372
|
+
+ '\n--- END SEGMENTS ---\n';
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function parseSegmentsReply(text, expectedCount) {
|
|
376
|
+
const s = String(text == null ? '' : text);
|
|
377
|
+
|
|
378
|
+
// Delimited form first: split on marker LINES so prose that merely mentions the marker inline
|
|
379
|
+
// cannot be mistaken for one.
|
|
380
|
+
const marker = /^[ \t]*-{2,}[ \t]*SEGMENT[ \t]+(\d+)[ \t]*-{2,}[ \t]*$/gm;
|
|
381
|
+
const hits = [];
|
|
382
|
+
let m = marker.exec(s);
|
|
383
|
+
while (m) { hits.push({ n: Number(m[1]), start: m.index, end: marker.lastIndex }); m = marker.exec(s); }
|
|
384
|
+
if (hits.length) {
|
|
385
|
+
// A reply may contain several attempts. Each time the numbering restarts at 1 a new set begins;
|
|
386
|
+
// the last set is the model's final answer.
|
|
387
|
+
let setStart = 0;
|
|
388
|
+
for (let i = 1; i < hits.length; i += 1) if (hits[i].n === 1) setStart = i;
|
|
389
|
+
const set = hits.slice(setStart);
|
|
390
|
+
const endMarker = /^[ \t]*-{2,}[ \t]*END SEGMENTS[ \t]*-{2,}[ \t]*$/gm;
|
|
391
|
+
endMarker.lastIndex = set[set.length - 1].end;
|
|
392
|
+
const tail = endMarker.exec(s);
|
|
393
|
+
const segs = set.map((h, i) => {
|
|
394
|
+
const from = h.end;
|
|
395
|
+
const to = i + 1 < set.length ? set[i + 1].start : (tail ? tail.index : s.length);
|
|
396
|
+
return s.slice(from, to).replace(/^\n/, '').replace(/\n$/, '');
|
|
397
|
+
});
|
|
398
|
+
if (typeof expectedCount !== 'number' || segs.length === expectedCount) return segs;
|
|
399
|
+
throw new Error(
|
|
400
|
+
`[project-prompt-contract] expected ${expectedCount} segment(s), got ${segs.length}`);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// JSON fallback, for a reply that got the harder format right.
|
|
404
|
+
const viaJson = (() => { try { return extractSegmentsReply(s, expectedCount); } catch { return null; } })();
|
|
405
|
+
if (viaJson) {
|
|
406
|
+
if (typeof expectedCount !== 'number' || viaJson.length === expectedCount) return viaJson;
|
|
407
|
+
throw new Error(
|
|
408
|
+
`[project-prompt-contract] expected ${expectedCount} segment(s), got ${viaJson.length}`);
|
|
409
|
+
}
|
|
410
|
+
throw new Error('[project-prompt-contract] no --- SEGMENT n --- blocks and no usable JSON in the reply');
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
module.exports = { checkGeneratedPrompt, buildGeneratedDoc, generatedTemplateDigest, placeholdersIn, outputFieldsIn,
|
|
414
|
+
splitByPlaceholders, assembleFromSegments, extractSegmentsReply, parseSegmentsReply, formatSegments };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
// A PROJECT'S PROMPTS BROUGHT UP TO ITS CURRENT TEMPLATES, WITHOUT A RUN (operator, 2026-10-21: "only generate the ones
|
|
4
|
+
// needed - I do not want to do a full pipeline run to adjust"). Exactly what the Agents phase does for a codeline whose
|
|
5
|
+
// prompt set is no longer complete for the current templates, and nothing else of that phase: per codeline, the
|
|
6
|
+
// codeline's recorded agents and prompts restored (restoreMinted, as codeline_minted_restore does), then the pipeline's
|
|
7
|
+
// own builder (buildProjectPrompts) under its resume rule, keepExisting — a prompt still the copy of its template is kept
|
|
8
|
+
// byte for byte with no call, one whose template moved on is regenerated and reviewed (lib/prompt-build-calls.js, the
|
|
9
|
+
// mint's own generator and reviewer). The builder records the codeline's completion itself.
|
|
10
|
+
//
|
|
11
|
+
// refresh-project-prompts.js --project <project config dir> --prd <run PRD> [--log-dir <dir>] [--stand-in]
|
|
12
|
+
//
|
|
13
|
+
// The codelines are the PRD's outputDirs, each one's id as the builder files it (codelineFromPrd of a one-codeline PRD).
|
|
14
|
+
// --stand-in: no model is called — the generator answers with the template's own text and the reviewer approves — so
|
|
15
|
+
// the rebuild can be rehearsed on a copy of the project at £0 and its decisions read: which prompts it would regenerate.
|
|
16
|
+
// Run with the project's env loaded (lib/env-file.sh load_project_env), as the mint step is.
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const a = {}; const argv = process.argv.slice(2);
|
|
21
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
22
|
+
if (argv[i] === '--stand-in') a['stand-in'] = true; else { a[argv[i].replace(/^--/, '')] = argv[i + 1]; i += 1; }
|
|
23
|
+
}
|
|
24
|
+
const SCRIPTS = process.env.SCRIPT_DIR || __dirname;
|
|
25
|
+
const ENGINE = path.resolve(SCRIPTS, '..', '..');
|
|
26
|
+
const lib = (f) => require(path.join(SCRIPTS, 'lib', f));
|
|
27
|
+
|
|
28
|
+
function standInCalls(templatesDir) {
|
|
29
|
+
const { splitByPlaceholders } = lib('project-prompt-contract.js');
|
|
30
|
+
const { templateBodyText } = lib('project-prompt-builder.js');
|
|
31
|
+
return {
|
|
32
|
+
runText: async (_prompt, meta) => {
|
|
33
|
+
const tpl = JSON.parse(fs.readFileSync(path.join(templatesDir, `${meta.id}.json`), 'utf8'));
|
|
34
|
+
return splitByPlaceholders(templateBodyText(tpl)).segments.map((s, i) => `--- SEGMENT ${i + 1} ---\n${s}`).join('\n');
|
|
35
|
+
},
|
|
36
|
+
reviewPrompt: async () => ({ ok: true, reviewed: true }),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function main() {
|
|
41
|
+
if (!a.project || !a.prd) throw new Error('usage: refresh-project-prompts.js --project <dir> --prd <run prd.json> [--log-dir <dir>] [--stand-in]');
|
|
42
|
+
const projectConfigDir = path.resolve(a.project);
|
|
43
|
+
const logDir = path.resolve(a['log-dir'] || path.join(projectConfigDir, '..', '..', 'logs', 'refresh-project-prompts'));
|
|
44
|
+
fs.mkdirSync(logDir, { recursive: true });
|
|
45
|
+
const builder = lib('project-prompt-builder.js');
|
|
46
|
+
const promptsLib = lib('prompt-library.js');
|
|
47
|
+
const templatesDir = path.join(ENGINE, promptsLib.TEMPLATE_DIR_REL);
|
|
48
|
+
const prd = JSON.parse(fs.readFileSync(a.prd, 'utf8'));
|
|
49
|
+
const stories = prd.stories || [];
|
|
50
|
+
const dirs = ((prd.project && prd.project.outputDirs) || []).filter((o) => o && o.path);
|
|
51
|
+
if (!dirs.length) throw new Error(`${a.prd} names no codeline (project.outputDirs)`);
|
|
52
|
+
const report = [];
|
|
53
|
+
for (const o of dirs) {
|
|
54
|
+
const slice = path.join(logDir, `prd.${path.basename(o.path)}.json`);
|
|
55
|
+
fs.writeFileSync(slice, JSON.stringify({ ...prd, project: { ...prd.project, outputDirs: [o], outputDir: o.path } }));
|
|
56
|
+
const codeline = builder.codelineFromPrd(slice);
|
|
57
|
+
process.env.EPAM_CODELINE_ID = codeline;
|
|
58
|
+
process.env.EPAM_PROJECT_CONFIG_DIR = projectConfigDir;
|
|
59
|
+
process.env.PRD_FILE = slice;
|
|
60
|
+
const restored = builder.restoreMinted({ projectConfigDir, codeline });
|
|
61
|
+
const roles = lib('project-roster.js').mintedFromRegistries(projectConfigDir);
|
|
62
|
+
const codelines = [{ name: o.codeline, path: o.path }];
|
|
63
|
+
const codelineContext = lib('codeline-context.js').buildCodelineContext({
|
|
64
|
+
codelines, factsFile: path.join(projectConfigDir, 'codeline-facts.json'), surveyed: [] });
|
|
65
|
+
const calls = a['stand-in'] ? standInCalls(templatesDir) : (() => {
|
|
66
|
+
const spec = require(path.join(SCRIPTS, 'spec-mode-runner.js'));
|
|
67
|
+
return lib('prompt-build-calls.js').promptBuildCalls({
|
|
68
|
+
engineRoot: ENGINE, logDir, runClaude: spec.runClaude,
|
|
69
|
+
promptExec: spec.resolvePromptExec(process.env.AI_RUNNER_CMD || path.join(SCRIPTS, 'ai-run.sh')),
|
|
70
|
+
projectConfigDir, codelineContext, stories, mintedDetail: roles,
|
|
71
|
+
toolGrant: lib('agent-tools.js').readOnlyToolGrant(codelines.map((c) => c.path)),
|
|
72
|
+
reviewEnabled: String(process.env.EPAM_PROMPT_REVIEW_ENABLED ?? '1') !== '0',
|
|
73
|
+
});
|
|
74
|
+
})();
|
|
75
|
+
const lines = [];
|
|
76
|
+
const built = await builder.buildProjectPrompts({
|
|
77
|
+
keepExisting: true, templatesDir,
|
|
78
|
+
bootstrapFile: path.join(ENGINE, 'orchestrations', 'prompts', 'bootstrap.json'),
|
|
79
|
+
registryFile: path.join(ENGINE, 'orchestrations', 'agents', 'invocation-profiles.json'),
|
|
80
|
+
projectConfigDir, mode: process.env.EPAM_PROMPT_PROVISION_MODE,
|
|
81
|
+
attempts: Number(process.env.EPAM_PROMPT_ATTEMPTS || 5),
|
|
82
|
+
projectContext: [`Project: ${path.basename(projectConfigDir)}`,
|
|
83
|
+
`Tickets in scope: ${stories.map((t) => `${t.jiraKey || t.id}: ${t.title || ''}`).join(' | ')}`].join('\n'),
|
|
84
|
+
codelineContext,
|
|
85
|
+
mintedRoles: roles.length ? roles.map((m) => `- ${m.name} [${m.kind}] — ${m.rationale || ''}`).join('\n') : '(none minted this run)',
|
|
86
|
+
...calls,
|
|
87
|
+
log: (m) => { lines.push(m); process.stderr.write(`[${codeline}] ${m}\n`); },
|
|
88
|
+
});
|
|
89
|
+
const rebuilt = lines.filter((l) => /template changed since this copy was made/.test(l)).map((l) => (l.match(/\] (\S+?):/) || [])[1]);
|
|
90
|
+
report.push({ codeline, restored, rebuilt, kept: built.generated.length + built.copied.length - rebuilt.length });
|
|
91
|
+
}
|
|
92
|
+
process.stdout.write(`${JSON.stringify(report)}\n`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
module.exports = { standInCalls };
|
|
96
|
+
if (require.main === module) main().catch((e) => { process.stderr.write(`[refresh-project-prompts] FAILED: ${(e && e.stack) || e}\n`); process.exit(1); });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "amsd-pipeline-shared",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.13",
|
|
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": {
|