@tea-agent/loop-agent 0.39.0-beta.1 → 0.39.0-beta.2
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/dist/build-stamp.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.39.0-beta.
|
|
4
|
-
"gitSha": "
|
|
5
|
-
"builtAt": "2026-08-
|
|
3
|
+
"version": "0.39.0-beta.2",
|
|
4
|
+
"gitSha": "cf766ed6e83a3498e13744ffd944ac10d7b1aeca",
|
|
5
|
+
"builtAt": "2026-08-21T07:43:32.268Z"
|
|
6
6
|
}
|
|
@@ -1715,8 +1715,13 @@ async function dropUnresolvedVerificationSymbols(input) {
|
|
|
1715
1715
|
}
|
|
1716
1716
|
return changed;
|
|
1717
1717
|
}
|
|
1718
|
-
function extractSingleOpenspecCitationsBlock(text) {
|
|
1719
|
-
const blocks = text.
|
|
1718
|
+
function extractSingleOpenspecCitationsBlock(text, citationsRequired = true) {
|
|
1719
|
+
const blocks = [...text.matchAll(/^```openspec-citations[ \t]*\r?\n([\s\S]*?)^```[ \t]*$/gim)].map((match) => match[0]);
|
|
1720
|
+
if (!citationsRequired) {
|
|
1721
|
+
if (blocks.length === 0)
|
|
1722
|
+
return "";
|
|
1723
|
+
throw new Error("frontend plan output must not include an openspec-citations block when the generation-frozen OpenSpec candidate set is empty");
|
|
1724
|
+
}
|
|
1720
1725
|
if (blocks.length !== 1) {
|
|
1721
1726
|
throw new Error(`frontend plan patch output must include exactly one fenced openspec-citations block (found ${blocks.length})`);
|
|
1722
1727
|
}
|
|
@@ -1728,8 +1733,8 @@ function extractSingleOpenspecCitationsBlock(text) {
|
|
|
1728
1733
|
* separate so a citation row can never become a competing contract candidate.
|
|
1729
1734
|
* A bare JSON payload is retained only for historical node-output compatibility.
|
|
1730
1735
|
*/
|
|
1731
|
-
export function extractFrontendPlanPayload(text) {
|
|
1732
|
-
const citationsBlock = extractSingleOpenspecCitationsBlock(text);
|
|
1736
|
+
export function extractFrontendPlanPayload(text, options) {
|
|
1737
|
+
const citationsBlock = extractSingleOpenspecCitationsBlock(text, options?.citationsRequired !== false);
|
|
1733
1738
|
return {
|
|
1734
1739
|
payload: extractFrontendPlanPrimaryPayload(text.replace(citationsBlock, "")),
|
|
1735
1740
|
citationsBlock,
|
|
@@ -1798,13 +1803,15 @@ export async function validateFrontendPlanPatchNodeOutput(input) {
|
|
|
1798
1803
|
const skeleton = input.structuredContractOutput?.skeleton;
|
|
1799
1804
|
if (!skeleton)
|
|
1800
1805
|
throw new Error("frontend plan patch validator requires a deterministic runtime skeleton");
|
|
1806
|
+
// Preserve the raw primary candidate even if the separately parsed
|
|
1807
|
+
// citation fence is malformed; the retry report is an audit artifact.
|
|
1801
1808
|
const patch = extractFrontendPlanPrimaryPayload(input.text);
|
|
1802
1809
|
if (!isPlainObject(patch))
|
|
1803
1810
|
throw new Error("frontend plan patch must extract to one JSON object");
|
|
1804
1811
|
extractedPatchPath = path.posix.join(candidateDir, `attempt-${attempt}.extracted.json`);
|
|
1805
1812
|
const extractedArtifact = await writeDeterministicJsonArtifact(input.runDir, extractedPatchPath, patch);
|
|
1806
1813
|
extractedPatchSha256 = extractedArtifact.sha256;
|
|
1807
|
-
const citationsBlock = extractSingleOpenspecCitationsBlock(input.text);
|
|
1814
|
+
const citationsBlock = extractSingleOpenspecCitationsBlock(input.text, input.structuredContractOutput?.citationsRequired !== false);
|
|
1808
1815
|
const citationsArtifactPath = path.posix.join(candidateDir, `attempt-${attempt}.openspec-citations.md`);
|
|
1809
1816
|
await writeTextArtifactFile(path.join(input.runDir, citationsArtifactPath), `${citationsBlock}\n`);
|
|
1810
1817
|
const protectedViolations = frontendPlanPatchProtectedPathViolations(patch);
|
|
@@ -1897,9 +1904,10 @@ export async function validateFrontendContractNodeOutput(input) {
|
|
|
1897
1904
|
/**
|
|
1898
1905
|
* Node-output self-check for the plan revision node, which emits an RFC 7386
|
|
1899
1906
|
* merge-patch delta against the original contract instead of a full contract.
|
|
1900
|
-
* Deliberately loose and format-level:
|
|
1901
|
-
* ```openspec-citations fenced block
|
|
1902
|
-
*
|
|
1907
|
+
* Deliberately loose and format-level: when OpenSpec candidates exist, the
|
|
1908
|
+
* output contains exactly one ```openspec-citations fenced block; otherwise it
|
|
1909
|
+
* contains no citation fence. It must extract (after the citations block is
|
|
1910
|
+
* stripped) to exactly one JSON object. The delta carries no
|
|
1903
1911
|
* schemaVersion/targets, so no full-contract schema/semantic checks run here;
|
|
1904
1912
|
* content parsing, patch application, and merged-contract validation stay with
|
|
1905
1913
|
* the prewrite gate, which remains the only authority.
|
|
@@ -1917,7 +1925,9 @@ export async function validateFrontendRevisionPatchNodeOutput(input) {
|
|
|
1917
1925
|
let citationsBlock;
|
|
1918
1926
|
let patch;
|
|
1919
1927
|
try {
|
|
1920
|
-
const parsed = extractFrontendPlanPayload(input.text
|
|
1928
|
+
const parsed = extractFrontendPlanPayload(input.text, {
|
|
1929
|
+
citationsRequired: input.structuredContractOutput?.citationsRequired,
|
|
1930
|
+
});
|
|
1921
1931
|
citationsBlock = parsed.citationsBlock;
|
|
1922
1932
|
patch = parsed.payload;
|
|
1923
1933
|
}
|
|
@@ -1530,6 +1530,21 @@ function buildSourceContextBlock(sources) {
|
|
|
1530
1530
|
}
|
|
1531
1531
|
return parts.join("\n\n");
|
|
1532
1532
|
}
|
|
1533
|
+
/**
|
|
1534
|
+
* Planner/reviewer nodes already receive the contract/scout artifacts. They
|
|
1535
|
+
* only need stable task-source pointers for an unresolved or conflicting
|
|
1536
|
+
* field, not a second inline copy of every fact-source document.
|
|
1537
|
+
*/
|
|
1538
|
+
function buildArtifactFirstSourceAccessBlock(sources) {
|
|
1539
|
+
const binding = buildDagSourceBinding(sources);
|
|
1540
|
+
return [
|
|
1541
|
+
"## Bound task-source access (read only when upstream artifacts are missing or conflict)",
|
|
1542
|
+
...binding.sources.map((source) => `- ${source.kind}: ${source.path}`),
|
|
1543
|
+
"Use these exact repository-readable paths. Do not search for substitutes or read runtime implementation code to diagnose a format retry.",
|
|
1544
|
+
`- taskId: ${binding.taskId}`,
|
|
1545
|
+
`- allowedPaths: ${sources.taskConfig.allowedPaths.join(", ") || "(none)"}`,
|
|
1546
|
+
].join("\n");
|
|
1547
|
+
}
|
|
1533
1548
|
async function loadMaterializedSourceReferences(sourceDir) {
|
|
1534
1549
|
const referenceDir = path.join(sourceDir, REFERENCE_DIRECTORY);
|
|
1535
1550
|
const referencePaths = [];
|
|
@@ -2523,7 +2538,8 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2523
2538
|
"Emit only editable fields: requirements[], implementationSteps[], targets.routes/publicApiChanges, uiStates[], interactions[], mockApi.strategy/activation/endpoints, verificationTargets[], designEvidence, evidenceGaps[], stylingStrategy, uiComponentChoices[], dependencyPolicy, residualRisks[], realIntegrationGap.",
|
|
2524
2539
|
"Protected and forbidden: schemaVersion, sourceBinding, riskLevel, targets.files, mockApi.productionDefaultOff, schemaId, targetFiles, requirementCoverage.",
|
|
2525
2540
|
"Invariants: every requirement has expectedOutcome; every interaction has trigger + expectedBehavior; applicable uiStates have expectedBehavior + implementationTargets + verificationTargetIds; non-applicable uiStates have notApplicableReason; commandLabel is frozen; mockApi production default remains off.",
|
|
2526
|
-
"
|
|
2541
|
+
"Each behavior belongs to one primary row: requirements cover acceptance, uiStates cover only distinct visible states, and interactions cover only distinct user triggers. Link with IDs; do not restate the same prose in all three. Steps are action plus files, not a prose mirror. Omit optional fields when upstream contract/scout facts already settle them, and omit unchanged revision fields.",
|
|
2542
|
+
"Enums: mock strategy native|browser-intercept|request-adapter|not-needed; uiComponentChoices decision specified|reuse-existing|new. Omit optional empty strings.",
|
|
2527
2543
|
].join("\n");
|
|
2528
2544
|
const sourceContext = [
|
|
2529
2545
|
buildSourceContextBlock(sources),
|
|
@@ -2531,6 +2547,12 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2531
2547
|
]
|
|
2532
2548
|
.filter(Boolean)
|
|
2533
2549
|
.join("\n\n");
|
|
2550
|
+
const artifactFirstSourceContext = [
|
|
2551
|
+
buildArtifactFirstSourceAccessBlock(sources),
|
|
2552
|
+
capabilityContextBlock,
|
|
2553
|
+
]
|
|
2554
|
+
.filter(Boolean)
|
|
2555
|
+
.join("\n\n");
|
|
2534
2556
|
const hasMockVerifyCommands = (taskConfig.frontendMock?.verifyCommands.length ?? 0) > 0 ||
|
|
2535
2557
|
mockCapability.verifyCommands.length > 0;
|
|
2536
2558
|
const requirementIds = frontendSourceBinding.requirementIds;
|
|
@@ -2554,7 +2576,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2554
2576
|
].join("\n");
|
|
2555
2577
|
const plannerSourceReadInstruction = [
|
|
2556
2578
|
"## Source-read policy",
|
|
2557
|
-
"Use
|
|
2579
|
+
"Use frontend-contract-pi and frontend-scout-pi artifacts as primary evidence. Do not repeat reads that those facts already settle.",
|
|
2558
2580
|
"Read a bound source only to resolve a missing or conflicting field; read every applicable OpenSpec candidate directly before citing it. Never infer omitted task fields from a preview.",
|
|
2559
2581
|
].join("\n");
|
|
2560
2582
|
const strategy = resolveDagVerifyStrategy(taskConfig);
|
|
@@ -2705,6 +2727,17 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2705
2727
|
...(classified?.theme ?? []),
|
|
2706
2728
|
...(classified?.rule.components ?? []),
|
|
2707
2729
|
].filter((value, index, array) => array.indexOf(value) === index).sort();
|
|
2730
|
+
const frontendPlanCitationsRequired = openspecGate.openspecCandidatePaths.length > 0 ||
|
|
2731
|
+
componentSpecCandidatePaths.length > 0;
|
|
2732
|
+
const frontendPlanCitationInstruction = frontendPlanCitationsRequired
|
|
2733
|
+
? openspecCitationInstruction
|
|
2734
|
+
: "The generation-frozen OpenSpec/component candidate set is empty. Do not read, cite, or emit an openspec-citations fence; output only the primary JSON patch. The runtime records an empty citation artifact deterministically.";
|
|
2735
|
+
const frontendPlanOutputContract = frontendPlanCitationsRequired
|
|
2736
|
+
? "JSON-only patch output: exactly ONE fenced json object (```json ... ```) containing only the editable RFC 7386 plan patch for the runtime contract skeleton, immediately followed by exactly one ```openspec-citations``` fenced citation block. Omit protected fields: schemaVersion, sourceBinding, riskLevel, targets.files, and mockApi.productionDefaultOff. The runtime applies the patch, validates it, and promotes canonical full-contract JSON for downstream review. Do NOT emit a full contract, Markdown plan explanation, raw JSON, or any other fenced block. No file writes."
|
|
2737
|
+
: "JSON-only patch output: exactly ONE fenced json object (```json ... ```) containing only the editable RFC 7386 plan patch for the runtime contract skeleton. The generation-frozen OpenSpec candidate set is empty, so do NOT emit an openspec-citations fence. Omit protected fields: schemaVersion, sourceBinding, riskLevel, targets.files, and mockApi.productionDefaultOff. The runtime applies the patch, validates it, and promotes canonical full-contract JSON for downstream review. Do NOT emit a full contract, Markdown plan explanation, raw JSON, or any other fenced block. No file writes.";
|
|
2738
|
+
const frontendRevisionOutputContract = frontendPlanCitationsRequired
|
|
2739
|
+
? "When the initial design review requests revision, return exactly ONE fenced json object (```json ... ```) containing an RFC 7386 merge-patch delta against the original frontend-implementation-contract-v1 (only the fields you change; null deletes a key; arrays and scalars replace; plain objects merge recursively), immediately followed by exactly one ```openspec-citations``` fenced citation block. Do NOT emit a full contract, Markdown explanation, or prose — the output is JSON-only; the gate applies the patch on the original contract and renders plan.md deterministically. Apart from the patch JSON fenced block and the openspec-citations block, do not emit any other fenced block or raw JSON. No file writes."
|
|
2740
|
+
: "JSON-only output: when the initial design review requests revision, return exactly ONE fenced json object (```json ... ```) containing an RFC 7386 merge-patch delta against the original frontend-implementation-contract-v1 (only the fields you change; null deletes a key; arrays and scalars replace; plain objects merge recursively). The generation-frozen OpenSpec candidate set is empty, so do NOT emit an openspec-citations fence. Do NOT emit a full contract, Markdown explanation, prose, or another fenced block. No file writes.";
|
|
2708
2741
|
if (openspecGate.openspecPolicy === "cited") {
|
|
2709
2742
|
if (openspecGate.openspecCandidatePaths.length === 0) {
|
|
2710
2743
|
advisories.push("openspec 策略 cited:契约声明的 requiredReadPaths 与任务源引用均为空,prewrite gate 不强制读取 openspec;如需增强规范门禁,请在 task.json.frontendOpenspec.requiredReadPaths 声明必读路径或在任务源中显式引用 openspec 文件。");
|
|
@@ -2790,11 +2823,12 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2790
2823
|
schemaId: FRONTEND_IMPLEMENTATION_CONTRACT_PLAN_PATCH_SCHEMA_ID,
|
|
2791
2824
|
retryOnInvalid: true,
|
|
2792
2825
|
skeleton: frontendContractSkeleton,
|
|
2826
|
+
citationsRequired: frontendPlanCitationsRequired,
|
|
2793
2827
|
},
|
|
2794
2828
|
allowedPaths: readOnlyPaths,
|
|
2795
2829
|
forbiddenPaths,
|
|
2796
2830
|
skills: [],
|
|
2797
|
-
outputContract:
|
|
2831
|
+
outputContract: frontendPlanOutputContract,
|
|
2798
2832
|
subtask_prompt: [
|
|
2799
2833
|
"Use frontend-contract-pi, frontend-scout-pi, task sources, and the generation-time Mock capability evidence to fill the runtime-owned frontend contract skeleton. Return JSON-only output containing only an editable RFC 7386 plan patch. The runtime already owns schemaVersion, sourceBinding, riskLevel, targets.files, and mockApi.productionDefaultOff; omit those protected paths even when their values look obvious.",
|
|
2800
2834
|
"The patch fields become the complete implementation plan after deterministic merge. Do not produce a separate plan document, prose mirror, or full contract.",
|
|
@@ -2802,7 +2836,10 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2802
2836
|
"Encode ordered steps (implementationSteps), target files, UI state handling, styling/component strategy (stylingStrategy), interaction notes, Mock/API strategy, dependency policy (dependencyPolicy), deterministic verification entrypoints, Real Integration Gap (realIntegrationGap), and residual risks (residualRisks) into the contract JSON fields. Use only the fixed entrypoints below; implementation may add tests behind them but cannot replace them.",
|
|
2803
2837
|
"Every target file and verification target must be selected from the current target workspace and task scope. Do not reuse paths or symbols from examples, prior tasks, or loop-agent itself; if the project uses app/, packages/, spec/, __tests__, or another layout, preserve that layout.",
|
|
2804
2838
|
"Consume the Scout TARGET_SURFACE evidence before selecting files. Preserve the discovered existing entrypoint and data source. If implementationPaths or testPaths are outside task allowedPaths, record a blocking scope conflict; do not substitute a new page or silently broaden the writeSet.",
|
|
2805
|
-
|
|
2839
|
+
frontendPlanCitationsRequired
|
|
2840
|
+
? "Output exactly two adjacent blocks: (1) one fenced json object containing the editable plan patch; (2) one openspec-citations citation block."
|
|
2841
|
+
: "Output exactly one fenced json object containing the editable plan patch; do not emit an openspec-citations block.",
|
|
2842
|
+
"Do NOT emit protected skeleton fields, a full contract, Markdown plan explanation, raw JSON, or any other fenced block.",
|
|
2806
2843
|
"Each requirement must state its user-observable or logic-observable expectedOutcome. Each interaction must state its trigger and expectedBehavior. IDs plus file paths are not sufficient behavior semantics.",
|
|
2807
2844
|
requirementCoverageInstruction,
|
|
2808
2845
|
"verificationTargets[].commandLabel MUST be one of the frozen command labels listed above. Any other value will be rejected at contract materialization.",
|
|
@@ -2810,11 +2847,11 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2810
2847
|
"Read-only: do not modify code, docs, artifacts, or repository files.",
|
|
2811
2848
|
plannerSourceReadInstruction,
|
|
2812
2849
|
fixedVerificationContext,
|
|
2813
|
-
|
|
2850
|
+
artifactFirstSourceContext,
|
|
2814
2851
|
mockContextBlock,
|
|
2815
2852
|
frontendPlanFieldGuide,
|
|
2816
2853
|
frontendComponentConformanceInstruction,
|
|
2817
|
-
|
|
2854
|
+
frontendPlanCitationInstruction,
|
|
2818
2855
|
].join("\n\n"),
|
|
2819
2856
|
},
|
|
2820
2857
|
{
|
|
@@ -2838,7 +2875,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2838
2875
|
"Component selection conformance is a hard blocking condition: VERDICT: request-revision when the frontend spec (component/theme/rule.components bucket) already defines a component for a purpose but the plan selects another or self-invents one without a declared deviation; when uiComponentChoices is missing/empty for UI-visible work while the frozen component/theme bucket is non-empty; or when any uiComponentChoices specReference.path is not cited in the openspec-citations block or has no successful read event.",
|
|
2839
2876
|
"Read-only: do not modify repository files.",
|
|
2840
2877
|
fixedVerificationContext,
|
|
2841
|
-
|
|
2878
|
+
artifactFirstSourceContext,
|
|
2842
2879
|
frontendContractFieldSummary,
|
|
2843
2880
|
mockContextBlock,
|
|
2844
2881
|
].join("\n\n"),
|
|
@@ -2857,11 +2894,12 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2857
2894
|
structuredContractOutput: {
|
|
2858
2895
|
schemaId: "frontend-implementation-contract-revision-patch-v1",
|
|
2859
2896
|
retryOnInvalid: true,
|
|
2897
|
+
citationsRequired: frontendPlanCitationsRequired,
|
|
2860
2898
|
},
|
|
2861
2899
|
allowedPaths: readOnlyPaths,
|
|
2862
2900
|
forbiddenPaths,
|
|
2863
2901
|
skills: [],
|
|
2864
|
-
outputContract:
|
|
2902
|
+
outputContract: frontendRevisionOutputContract,
|
|
2865
2903
|
subtask_prompt: [
|
|
2866
2904
|
"Consume the hash-bound frontend-plan-pi contract artifact and frontend-design-review-pi findings. Read the artifact only for fields affected by a Required Plan Correction.",
|
|
2867
2905
|
"This node runs only when frontend-design-review-pi emitted VERDICT: request-revision. Produce an RFC 7386 merge-patch delta against the original contract JSON that addresses every Required Plan Correction from the design findings.",
|
|
@@ -2869,16 +2907,19 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2869
2907
|
"Only include changed fields. For a changed requirement, preserve its expectedOutcome, implementationTargets, and verificationTargetIds; do not resend unchanged requirements.",
|
|
2870
2908
|
"Do not turn MOCK_STRATEGY: blocked into an implementable strategy without new repository or contract evidence that resolves every blocker.",
|
|
2871
2909
|
"Read-only: do not modify code, docs, artifacts, or repository files. This node revises the plan only.",
|
|
2872
|
-
|
|
2910
|
+
frontendPlanCitationsRequired
|
|
2911
|
+
? "Output exactly two adjacent blocks: (1) one fenced json object containing the merge-patch delta; (2) one openspec-citations citation block."
|
|
2912
|
+
: "JSON-only: output exactly one fenced json object containing the merge-patch delta; do not emit an openspec-citations block.",
|
|
2913
|
+
"Do NOT emit a full contract, Markdown explanation, prose, raw JSON or JSON objects outside the fenced payload, secrets, or unsafe paths.",
|
|
2873
2914
|
"Preserve each requirement expectedOutcome and each interaction trigger/expectedBehavior in the effective (merged) contract; do not reduce behavior semantics to IDs and paths.",
|
|
2874
2915
|
"verificationTargets[].commandLabel MUST be one of the frozen command labels listed above. Any other value will be rejected at contract materialization.",
|
|
2875
2916
|
verificationTargetFileInstruction,
|
|
2876
2917
|
fixedVerificationContext,
|
|
2877
|
-
|
|
2918
|
+
artifactFirstSourceContext,
|
|
2878
2919
|
frontendPlanFieldGuide,
|
|
2879
2920
|
mockContextBlock,
|
|
2880
2921
|
frontendComponentConformanceInstruction,
|
|
2881
|
-
|
|
2922
|
+
frontendPlanCitationInstruction,
|
|
2882
2923
|
].join("\n\n"),
|
|
2883
2924
|
},
|
|
2884
2925
|
{
|
|
@@ -2913,7 +2954,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2913
2954
|
"Also request revision for component selection non-conformance: spec-defined components silently replaced or self-invented without a declared deviation, uiComponentChoices missing for UI-visible work, or a uiComponentChoices specReference.path not cited in the openspec-citations block / not actually read.",
|
|
2914
2955
|
"Read-only: do not modify repository files.",
|
|
2915
2956
|
fixedVerificationContext,
|
|
2916
|
-
|
|
2957
|
+
artifactFirstSourceContext,
|
|
2917
2958
|
frontendContractFieldSummary,
|
|
2918
2959
|
mockContextBlock,
|
|
2919
2960
|
].join("\n\n"),
|
|
@@ -129,17 +129,24 @@ export function buildNodePrompt(spec, task, upstream, options) {
|
|
|
129
129
|
convergenceFeedback: options?.convergenceFeedback,
|
|
130
130
|
});
|
|
131
131
|
}
|
|
132
|
-
function frontendStructuredArtifactRetryGuidance(schemaId) {
|
|
132
|
+
function frontendStructuredArtifactRetryGuidance(schemaId, citationsRequired = true) {
|
|
133
|
+
const format = citationsRequired
|
|
134
|
+
? "one fenced json block immediately followed by one fenced openspec-citations block"
|
|
135
|
+
: "one fenced json block and no openspec-citations block";
|
|
133
136
|
if (schemaId === "frontend-implementation-contract-plan-patch-v1") {
|
|
134
137
|
return [
|
|
135
|
-
|
|
138
|
+
citationsRequired
|
|
139
|
+
? "Return exactly one fenced json block containing only the editable RFC 7386 plan patch, immediately followed by one fenced openspec-citations block, and nothing else."
|
|
140
|
+
: `Return exactly ${format} and nothing else; the json block contains only the editable RFC 7386 plan patch.`,
|
|
136
141
|
"The runtime applies your patch to its protected contract skeleton. Do not emit schemaVersion, sourceBinding, riskLevel, targets.files, or mockApi.productionDefaultOff; do not emit the full contract.",
|
|
137
142
|
"Do not emit Markdown headings, bullets, plan prose, explanations, raw JSON, or any other fenced block.",
|
|
138
143
|
];
|
|
139
144
|
}
|
|
140
145
|
if (schemaId === "frontend-implementation-contract-revision-patch-v1") {
|
|
141
146
|
return [
|
|
142
|
-
|
|
147
|
+
citationsRequired
|
|
148
|
+
? "Return exactly one fenced json block containing the RFC 7386 merge-patch delta, immediately followed by one fenced openspec-citations block, and nothing else."
|
|
149
|
+
: `Return exactly ${format} and nothing else; the json block contains the RFC 7386 merge-patch delta.`,
|
|
143
150
|
"The delta contains only the fields you change; null deletes a key, arrays and scalars replace, and plain objects merge recursively. Do not emit a full contract or a schemaVersion/targets section.",
|
|
144
151
|
"Do not emit Markdown headings, bullets, plan prose, explanations, raw JSON, or any other fenced block.",
|
|
145
152
|
];
|
|
@@ -163,7 +170,7 @@ function buildFrontendPlanArtifactRecoveryPrompt(options) {
|
|
|
163
170
|
"<retry_instruction>",
|
|
164
171
|
cause,
|
|
165
172
|
options.reason ? `Validation error: ${options.reason}` : "",
|
|
166
|
-
...frontendStructuredArtifactRetryGuidance(options.schemaId),
|
|
173
|
+
...frontendStructuredArtifactRetryGuidance(options.schemaId, options.citationsRequired),
|
|
167
174
|
"Use concise values and reference paths/IDs instead of copying source prose. Do not omit required semantics.",
|
|
168
175
|
"</retry_instruction>",
|
|
169
176
|
]
|
|
@@ -190,6 +197,7 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
|
|
|
190
197
|
failure: "invalid",
|
|
191
198
|
reason: previousProtocolReason,
|
|
192
199
|
schemaId: task.structuredContractOutput.schemaId,
|
|
200
|
+
citationsRequired: task.structuredContractOutput.citationsRequired,
|
|
193
201
|
});
|
|
194
202
|
}
|
|
195
203
|
return [
|
|
@@ -211,6 +219,7 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
|
|
|
211
219
|
failure: "truncated",
|
|
212
220
|
reason: previousProtocolReason,
|
|
213
221
|
schemaId,
|
|
222
|
+
citationsRequired: task.structuredContractOutput.citationsRequired,
|
|
214
223
|
});
|
|
215
224
|
}
|
|
216
225
|
return [
|
|
@@ -293,6 +302,7 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
|
|
|
293
302
|
return buildFrontendPlanArtifactRecoveryPrompt({
|
|
294
303
|
failure: "too-large",
|
|
295
304
|
schemaId: task.structuredContractOutput.schemaId,
|
|
305
|
+
citationsRequired: task.structuredContractOutput.citationsRequired,
|
|
296
306
|
});
|
|
297
307
|
}
|
|
298
308
|
return [
|
|
@@ -782,6 +782,10 @@ export const dagTaskSchema = z.object({ id: z.string().regex(/^[a-z][a-z0-9-]*$/
|
|
|
782
782
|
* this run-owned skeleton before strict contract validation.
|
|
783
783
|
*/
|
|
784
784
|
skeleton: z.record(z.string(), z.unknown()).optional(),
|
|
785
|
+
/** Frontend plan/revision citation fence is only needed for a
|
|
786
|
+
* generation-frozen OpenSpec candidate set. Undefined preserves the
|
|
787
|
+
* historical required-fence protocol. */
|
|
788
|
+
citationsRequired: z.boolean().optional(),
|
|
785
789
|
})
|
|
786
790
|
.strict()
|
|
787
791
|
.superRefine((value, ctx) => {
|