@tea-agent/loop-agent 0.38.1 → 0.38.3
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/CHANGELOG.md +126 -12
- package/README.md +2 -0
- package/bin/loop-agent.js +7 -3
- package/dist/application/dag/generate-task-dag.js +9 -0
- package/dist/application/task-lifecycle/advance.js +4 -0
- package/dist/build-stamp.json +3 -3
- package/dist/commands/init.js +2 -14
- package/dist/commands/task-advance.js +31 -0
- package/dist/executors/dag-pi-executor.js +99 -5
- package/dist/executors/pi-event-serializer.js +42 -1
- package/dist/executors/pi-executor.js +51 -0
- package/dist/executors/pi-extension-resolver.js +233 -0
- package/dist/executors/pi-sdk-executor.js +187 -59
- package/dist/executors/shell-executor.js +324 -16
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/governance/manifest-types.js +0 -2
- package/dist/shared/operator/capabilities.js +87 -22
- package/dist/task/config-types.js +22 -0
- package/dist/task/contract/constants.js +1 -0
- package/dist/task/contract/diff.js +26 -0
- package/dist/task/contract/project.js +5 -0
- package/dist/task/contract/schema.js +6 -1
- package/dist/task/source-prepare/build-draft.js +15 -0
- package/dist/worker/console/chat/deferred-turn.js +91 -0
- package/dist/worker/console/chat/pi-runtime.js +10 -5
- package/dist/worker/console/chat/routes.js +458 -47
- package/dist/worker/console/chat/semantic-activity.js +13 -13
- package/dist/worker/console/chat/session-store.js +146 -2
- package/dist/worker/console/harness-pi-model-matrix.js +2 -11
- package/dist/worker/console/operator-actions.js +5 -4
- package/dist/worker/console/pi-readiness.js +5 -4
- package/dist/worker/console/recovery-cta.js +4 -4
- package/dist/worker/console/recovery-error-copy.js +4 -4
- package/dist/worker/console/server.js +6 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-Hfsot6rP.js → abnfDiagram-N423BO3Z-C4kVYweA.js} +1 -1
- package/dist/worker/console/static/assets/{arc-YGwEASin.js → arc-Bj2M8iO5.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CmOM0F92.js → architectureDiagram-T3A2C74G-CLlXe4-6.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-DDLuaHC5.js → blockDiagram-VBNYF7ZC-psEp5xH0.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B0Um43W4.js → c4Diagram-5PPSVZJV-DeKcOCGJ.js} +1 -1
- package/dist/worker/console/static/assets/channel-CPF4N7pf.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-DvBN4JZc.js → chunk-2GRJ4B5K-K0C744p1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-B1UVgW4E.js → chunk-2Q5K7J3B-xbNw4J9-.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-Cf0GgXzX.js → chunk-5RXB4S5H-DjLaSDUO.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-DbNA24sj.js → chunk-5VM5RSS4-CtiPlKel.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-Bi_0Be06.js → chunk-6Q2QTUOP-DZtolir7.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-C8pPGvRd.js → chunk-GF5L2VYU-CPcdNeew.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-BitQY8Z-.js → chunk-JWPE2WC7-NgHc6V37.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-U67GCiEo.js → chunk-KBJHAD2P-DQ_T-hg8.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-IoJwXPBA.js → chunk-RYQCIY6F-DgLsxcVP.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-YDZ_UHgE.js → chunk-XXDRQBXY-UrVoM9z1.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CUZgsiRC.js → cose-bilkent-JH36ORCC-CLkYvTb3.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-COmCh4pT.js → cynefin-VYW2F7L2-BwT_xKrE.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Ta6FSm68.js → cynefinDiagram-MW4NZA55-Bi9tuzif.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-UdcR-VW9.js → dagre-VZM6K2ZE-BCIqkWBV.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-DybcO3Et.js → diagram-7IWD3JNH-Dip6l9_Z.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-wErDFWLw.js → diagram-B4RE2ZJO-B-xkh_wK.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DkVHZWqz.js → diagram-LBJQPF4R-DZO0kTt_.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-DdqWs7QN.js → diagram-Q27KOJAE-D6ZplNbZ.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-C9dMVmDo.js → diagram-UB23O5K3-BPekbhoS.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-B2T2WcBK.js → ebnfDiagram-BXEA7PRR-BtmaJpK5.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-Bt9rjDFq.js → erDiagram-JOGREHBK-6DS0Xc44.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-U8ic_tvi.js → flowDiagram-UKHOOZJN-CihnROSm.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-BfQJdQoO.js → ganttDiagram-PKOTCBZU-CZC4zOE2.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-CpMftH4O.js → gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js} +1 -1
- package/dist/worker/console/static/assets/index-DTZOKgAn.js +404 -0
- package/dist/worker/console/static/assets/index-Ya5FE7cD.css +1 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DzqWXNO_.js → infoDiagram-6WML65LV-DlSl4BGx.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-CJmGQ5Yp.js → ishikawaDiagram-WSZJBQD7-snnzFl_U.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-ChPtKFLz.js → journeyDiagram-NVQOT4AX-BDE7qpMx.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-PRe7wB5u.js → kanban-definition-27J2QSJJ-CD2Ci04G.js} +1 -1
- package/dist/worker/console/static/assets/{linear-DpKX6QNp.js → linear-DdnapKIH.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-Dnl76b7Y.js → mermaid.core-BVjAT9b8.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-S9PCXc3J.js → mindmap-definition-FAOFIHXS-D0yJk-zA.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-Du8e-FSX.js → pegDiagram-VL7TDLO6-BVo9tFP-.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-CY6OmHTc.js → pieDiagram-7S7Q4E2Y-DnOV-mZ_.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-C65GPhpj.js → quadrantDiagram-CIZ2JOQS-CzUJo58i.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-U6IUiTCL.js → railroadDiagram-AXF67PYL-Bvikrolh.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-D-I_9Qhi.js → requirementDiagram-LRYGKXZP-DtaSVCap.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-B57Fhed0.js → sankeyDiagram-W5VNT64P-C2c9A0wW.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-CJOtiw21.js → sequenceDiagram-SI44F4Z6-DmXJcU7r.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-FyX8zxSh.js → sizeCapture-X5ZJPWSS-B4GUFW92.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-eF-pbs9t.js → stateDiagram-OKZ733FA-Dsad2MXf.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-CcfkA-Dq.js → swimlanes-SLNWSIFB-DGq48Fbi.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-DH8FjnOK.js → timeline-definition-Z64GVDOM-CSSi6OFf.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-beoTagJg.js → vennDiagram-T6HMQDX7-BqyzPrWv.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-CQSfw4cK.js → wardleyDiagram-T6FBY63Y-DlznVb6S.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CQgzQ6jn.js → xychartDiagram-ELKLHX3M-CfBcgJ_K.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/app/console-types.js +0 -12
- package/dist/worker/console/static-src/app/useOperatorActions.js +1 -1
- package/dist/worker/console/static-src/app/useRecoveryActions.js +2 -2
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +38 -1
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +328 -0
- package/dist/worker/console/static-src/operator-chat/turn-stream-controller.js +8 -1
- package/dist/worker/console/static-src/operator-chat/turn-submission.js +42 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +3 -0
- package/dist/worker/console/static-src/operator-chat/useChatStream.js +204 -11
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +23 -1
- package/dist/worker/observe/node-input.js +72 -3
- package/dist/worker/observe/static/dag-history-labels.js +4 -0
- package/dist/worker/observe/static/state.js +2 -2
- package/dist/worker/observe/static/views/dag-inspector.js +51 -0
- package/dist/worker/observe/static/views/session-timeline.js +18 -5
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +809 -53
- package/dist/workflows/dag/backend-test-case-manifest.js +4 -0
- package/dist/workflows/dag/backend-test-execution-contract.js +18 -9
- package/dist/workflows/dag/backend-test-gap-fill.js +205 -0
- package/dist/workflows/dag/backend-test-intake-context.js +32 -0
- package/dist/workflows/dag/backend-test-layout.js +133 -0
- package/dist/workflows/dag/backend-test-markdown-workflow.js +167 -32
- package/dist/workflows/dag/backend-test-module-stem.js +5 -0
- package/dist/workflows/dag/backend-test-pytest-collection.js +368 -33
- package/dist/workflows/dag/backend-test-result-contract.js +1 -1
- package/dist/workflows/dag/backend-test-scenario-param.js +884 -140
- package/dist/workflows/dag/backend-test-scenario-partitions.js +321 -0
- package/dist/workflows/dag/backend-test-writer-completeness.js +100 -56
- package/dist/workflows/dag/contract-output-registry.js +15 -0
- package/dist/workflows/dag/contract-validator-registrations.js +2 -1
- package/dist/workflows/dag/convergence/controller.js +141 -0
- package/dist/workflows/dag/dynamic-runtime/map.js +24 -8
- package/dist/workflows/dag/dynamic-runtime/shared.js +1 -1
- package/dist/workflows/dag/failure-category.js +4 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +355 -26
- package/dist/workflows/dag/frontend-prewrite-gate.js +96 -84
- package/dist/workflows/dag/frontend-test-case-quality.js +5 -13
- package/dist/workflows/dag/frontend-test-environment-probe.js +227 -0
- package/dist/workflows/dag/frontend-test-markdown.js +61 -0
- package/dist/workflows/dag/frontend-test-result-contract.js +10 -18
- package/dist/workflows/dag/frontend-test-standard-scenarios.js +68 -0
- package/dist/workflows/dag/init-hybrid.js +720 -177
- package/dist/workflows/dag/node-execution.js +164 -26
- package/dist/workflows/dag/prompt.js +60 -1
- package/dist/workflows/dag/rerun-feedback.js +212 -0
- package/dist/workflows/dag/rerun-plan.js +112 -27
- package/dist/workflows/dag/rerun-run.js +3 -0
- package/dist/workflows/dag/rerun-task.js +61 -2
- package/dist/workflows/dag/retry-policy.js +18 -0
- package/dist/workflows/dag/structured-output-repair.js +712 -0
- package/dist/workflows/dag/types.js +121 -0
- package/dist/workflows/dag/validate.js +41 -1
- package/docs/operations/local-development-environment.md +1 -5
- package/docs/skills/vetted-skill-registry.md +14 -0
- package/docs/templates/README.md +1 -1
- package/docs/templates/agent-dag.schema.json +44 -0
- package/docs/templates/backend-test-dag.json +60 -35
- package/docs/templates/frontend-test-dag.json +8 -10
- package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
- package/docs/templates/harness.schema.json +0 -8
- package/harness.json +1 -8
- package/package.json +8 -4
- package/skills/analyze-product-requirements/SKILL.md +8 -5
- package/skills/analyze-product-requirements/references/acceptance-criteria.md +1 -1
- package/skills/analyze-product-requirements/references/clarification-and-knowledge.md +10 -6
- package/skills/analyze-product-requirements/references/example.md +50 -0
- package/skills/analyze-product-requirements/references/forward-test-cases.md +11 -7
- package/skills/analyze-product-requirements/references/kb-integration.md +5 -5
- package/skills/analyze-product-requirements/references/product-analysis-schema.md +8 -4
- package/skills/analyze-product-requirements/references/product-requirement-schema.md +2 -2
- package/skills/analyze-product-requirements/references/requirement-clarification-schema.md +1 -1
- package/skills/analyze-product-requirements/scripts/test-validators.mjs +11 -1
- package/skills/analyze-product-requirements/scripts/validate-product-analysis.mjs +20 -1
- package/skills/analyze-product-requirements/scripts/validate-requirement-clarification.mjs +9 -0
- package/skills/codebase-scout/SKILL.md +1 -1
- package/skills/frontend-implementation/references/node-contracts.md +2 -2
- package/skills/improve-codebase-architecture/SKILL.md +81 -0
- package/skills/improve-codebase-architecture/deepening.md +37 -0
- package/skills/improve-codebase-architecture/html-report.md +123 -0
- package/skills/improve-codebase-architecture/interface-design.md +44 -0
- package/skills/improve-codebase-architecture/language.md +53 -0
- package/dist/worker/console/static/assets/channel-Dp_2Aqf1.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL--CODy37j.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE--CODy37j.js +0 -1
- package/dist/worker/console/static/assets/index-BA5-N5ER.css +0 -1
- package/dist/worker/console/static/assets/index-DKPXBrdc.js +0 -325
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B-mvnLEc.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-JtpBmIv4.js +0 -8
|
@@ -8,11 +8,13 @@ import { writeTextArtifactFile } from "../../infrastructure/harness/artifact-sto
|
|
|
8
8
|
import { findPackageRoot } from "../../shared/package-metadata.js";
|
|
9
9
|
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
10
10
|
import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
|
|
11
|
+
import { canonicalContractRel, classifyStructuredContractFailure, formatStructuredArtifactPointer, openspecCitationsRel, parseOpenspecCitationsBlock, persistRuntimeSkeleton, } from "./structured-output-repair.js";
|
|
11
12
|
export const frontendNormalizationActionSchema = z.enum([
|
|
12
13
|
"remove-trailing-commas",
|
|
13
14
|
"strip-comments",
|
|
14
15
|
"canonicalize-requirement-ids",
|
|
15
16
|
"canonicalize-verification-alias",
|
|
17
|
+
"drop-unresolved-verification-symbols",
|
|
16
18
|
"inject-source-binding",
|
|
17
19
|
]);
|
|
18
20
|
export function sha256Hex(input) {
|
|
@@ -102,6 +104,21 @@ async function deriveFrozenCommandLabelsFromRun(runDir) {
|
|
|
102
104
|
return [...labels];
|
|
103
105
|
}
|
|
104
106
|
export const FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID = "frontend-implementation-contract-v1";
|
|
107
|
+
export const FRONTEND_IMPLEMENTATION_CONTRACT_PLAN_PATCH_SCHEMA_ID = "frontend-implementation-contract-plan-patch-v1";
|
|
108
|
+
/**
|
|
109
|
+
* Build the deterministic, run-owned portion of the frontend contract. The
|
|
110
|
+
* planner is allowed to fill semantic fields with an RFC 7386 merge patch but
|
|
111
|
+
* cannot redefine task identity, risk, or the implementation write boundary.
|
|
112
|
+
*/
|
|
113
|
+
export function buildFrontendImplementationContractSkeleton(input) {
|
|
114
|
+
return {
|
|
115
|
+
schemaVersion: 1,
|
|
116
|
+
sourceBinding: canonicalFrontendContractSourceBinding(input.sourceBinding),
|
|
117
|
+
riskLevel: input.riskLevel,
|
|
118
|
+
targets: { files: [...input.targetFiles] },
|
|
119
|
+
mockApi: { productionDefaultOff: true },
|
|
120
|
+
};
|
|
121
|
+
}
|
|
105
122
|
/**
|
|
106
123
|
* Load the canonical frontend-implementation-contract-v1 JSON Schema from the
|
|
107
124
|
* installed loop-agent package docs/templates/ path. Package-root discovery
|
|
@@ -499,9 +516,9 @@ function isPlainObject(value) {
|
|
|
499
516
|
* ever materialized, so a patch that removes a required field still fails
|
|
500
517
|
* closed at the gate.
|
|
501
518
|
*/
|
|
502
|
-
export function
|
|
519
|
+
export function applyFrontendContractMergePatch(target, patch) {
|
|
503
520
|
if (!isPlainObject(patch)) {
|
|
504
|
-
throw new Error("frontend
|
|
521
|
+
throw new Error("frontend contract patch must be a JSON object");
|
|
505
522
|
}
|
|
506
523
|
const merge = (base, delta) => {
|
|
507
524
|
if (delta === null)
|
|
@@ -526,6 +543,9 @@ export function applyFrontendImplementationContractPatch(target, patch) {
|
|
|
526
543
|
};
|
|
527
544
|
return merge(target, patch);
|
|
528
545
|
}
|
|
546
|
+
export function applyFrontendImplementationContractPatch(target, patch) {
|
|
547
|
+
return applyFrontendContractMergePatch(target, patch);
|
|
548
|
+
}
|
|
529
549
|
export async function assertFrontendSourceBindingFresh(input) {
|
|
530
550
|
for (const source of input.binding.sources) {
|
|
531
551
|
const absolute = resolveDagTaskSourcePath({
|
|
@@ -1466,7 +1486,13 @@ export async function analyzeFrontendImplementationContract(input) {
|
|
|
1466
1486
|
rawOutput = input.rawContractText;
|
|
1467
1487
|
}
|
|
1468
1488
|
else if (input.fromNodeId !== undefined) {
|
|
1469
|
-
|
|
1489
|
+
let record;
|
|
1490
|
+
try {
|
|
1491
|
+
record = JSON.parse(await readFile(path.join(input.runDir, `${input.fromNodeId}.json`), "utf8"));
|
|
1492
|
+
}
|
|
1493
|
+
catch (error) {
|
|
1494
|
+
throw new Error(`cannot parse node record ${input.fromNodeId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1495
|
+
}
|
|
1470
1496
|
rawOutput = record.assistantText ?? record.stdout ?? "";
|
|
1471
1497
|
}
|
|
1472
1498
|
else {
|
|
@@ -1608,6 +1634,239 @@ export async function writeFrontendImplementationContractArtifact(input) {
|
|
|
1608
1634
|
schemaId: FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID,
|
|
1609
1635
|
};
|
|
1610
1636
|
}
|
|
1637
|
+
const FRONTEND_PLAN_PATCH_PROTECTED_PATHS = [
|
|
1638
|
+
"/schemaVersion",
|
|
1639
|
+
"/sourceBinding",
|
|
1640
|
+
"/riskLevel",
|
|
1641
|
+
"/targets/files",
|
|
1642
|
+
"/mockApi/productionDefaultOff",
|
|
1643
|
+
];
|
|
1644
|
+
function frontendPlanPatchProtectedPathViolations(patch) {
|
|
1645
|
+
const record = asRecord(patch);
|
|
1646
|
+
if (!record)
|
|
1647
|
+
return ["/"];
|
|
1648
|
+
const violations = [];
|
|
1649
|
+
for (const key of ["schemaVersion", "sourceBinding", "riskLevel"]) {
|
|
1650
|
+
if (Object.hasOwn(record, key))
|
|
1651
|
+
violations.push(`/${key}`);
|
|
1652
|
+
}
|
|
1653
|
+
const targets = asRecord(record.targets);
|
|
1654
|
+
if (targets && Object.hasOwn(targets, "files"))
|
|
1655
|
+
violations.push("/targets/files");
|
|
1656
|
+
const mockApi = asRecord(record.mockApi);
|
|
1657
|
+
if (mockApi && Object.hasOwn(mockApi, "productionDefaultOff"))
|
|
1658
|
+
violations.push("/mockApi/productionDefaultOff");
|
|
1659
|
+
return violations;
|
|
1660
|
+
}
|
|
1661
|
+
function workspaceRootFromDagRunDir(runDir) {
|
|
1662
|
+
const absolute = path.resolve(runDir);
|
|
1663
|
+
const segments = absolute.split(path.sep);
|
|
1664
|
+
const harnessIndex = segments.lastIndexOf(".harness");
|
|
1665
|
+
if (harnessIndex < 0 ||
|
|
1666
|
+
segments[harnessIndex + 1] !== "dag-runs" ||
|
|
1667
|
+
harnessIndex + 3 >= segments.length) {
|
|
1668
|
+
return null;
|
|
1669
|
+
}
|
|
1670
|
+
const joined = segments.slice(0, harnessIndex).join(path.sep);
|
|
1671
|
+
return joined || path.parse(absolute).root;
|
|
1672
|
+
}
|
|
1673
|
+
function verificationSymbolMatchesFile(symbol, content) {
|
|
1674
|
+
if (symbol.trim().toLowerCase() === "all describe blocks")
|
|
1675
|
+
return content.includes("describe(");
|
|
1676
|
+
const symbols = symbol.includes(" / ")
|
|
1677
|
+
? symbol
|
|
1678
|
+
.split(/[((]/, 1)[0]
|
|
1679
|
+
.split("/")
|
|
1680
|
+
.map((item) => item.trim())
|
|
1681
|
+
.filter(Boolean)
|
|
1682
|
+
: [symbol];
|
|
1683
|
+
return symbols.every((item) => {
|
|
1684
|
+
const describeTitle = item.match(/^describe\((?:['"])(.+?)(?:['"])/i)?.[1];
|
|
1685
|
+
return [item, describeTitle, describeTitle ? "describe(" : undefined]
|
|
1686
|
+
.filter((candidate) => Boolean(candidate))
|
|
1687
|
+
.some((candidate) => content.includes(candidate));
|
|
1688
|
+
});
|
|
1689
|
+
}
|
|
1690
|
+
async function dropUnresolvedVerificationSymbols(input) {
|
|
1691
|
+
const workspaceRoot = workspaceRootFromDagRunDir(input.runDir);
|
|
1692
|
+
const targets = asRecord(input.contract)?.verificationTargets;
|
|
1693
|
+
if (!workspaceRoot || !Array.isArray(targets))
|
|
1694
|
+
return false;
|
|
1695
|
+
let changed = false;
|
|
1696
|
+
for (const targetValue of targets) {
|
|
1697
|
+
const target = asRecord(targetValue);
|
|
1698
|
+
const file = asString(target?.file);
|
|
1699
|
+
const symbol = asString(target?.symbol);
|
|
1700
|
+
if (!target || !file || !symbol)
|
|
1701
|
+
continue;
|
|
1702
|
+
const absolute = path.resolve(workspaceRoot, file);
|
|
1703
|
+
const relative = path.relative(workspaceRoot, absolute);
|
|
1704
|
+
if (relative.startsWith("..") ||
|
|
1705
|
+
path.isAbsolute(relative) ||
|
|
1706
|
+
relative.includes("..")) {
|
|
1707
|
+
continue;
|
|
1708
|
+
}
|
|
1709
|
+
let content;
|
|
1710
|
+
try {
|
|
1711
|
+
content = await readFile(absolute, "utf8");
|
|
1712
|
+
}
|
|
1713
|
+
catch {
|
|
1714
|
+
// Missing files remain a trace-time hard failure; do not hide them by
|
|
1715
|
+
// changing the optional symbol during candidate normalization.
|
|
1716
|
+
continue;
|
|
1717
|
+
}
|
|
1718
|
+
if (verificationSymbolMatchesFile(symbol, content))
|
|
1719
|
+
continue;
|
|
1720
|
+
delete target.symbol;
|
|
1721
|
+
changed = true;
|
|
1722
|
+
}
|
|
1723
|
+
return changed;
|
|
1724
|
+
}
|
|
1725
|
+
function extractSingleOpenspecCitationsBlock(text) {
|
|
1726
|
+
const blocks = text.match(/```openspec-citations[ \t]*\r?\n[\s\S]*?\r?\n```/g) ?? [];
|
|
1727
|
+
if (blocks.length !== 1) {
|
|
1728
|
+
throw new Error(`frontend plan patch output must include exactly one fenced openspec-citations block (found ${blocks.length})`);
|
|
1729
|
+
}
|
|
1730
|
+
return blocks[0];
|
|
1731
|
+
}
|
|
1732
|
+
async function writeFrontendPlanCandidateRaw(input) {
|
|
1733
|
+
const relativePath = path.posix.join("contracts", "candidates", input.nodeId, `attempt-${input.attempt}.raw.md`);
|
|
1734
|
+
await writeTextArtifactFile(path.join(input.runDir, relativePath), input.text.endsWith("\n") ? input.text : `${input.text}\n`);
|
|
1735
|
+
return relativePath;
|
|
1736
|
+
}
|
|
1737
|
+
/**
|
|
1738
|
+
* Compile the planner's editable RFC 7386 patch against the deterministic
|
|
1739
|
+
* runtime skeleton. Every attempt is preserved under contracts/candidates so
|
|
1740
|
+
* format/schema failures can be inspected and replayed without trusting model
|
|
1741
|
+
* prose. A successful result returns a path-only pointer plus the hash-bound canonical artifact for
|
|
1742
|
+
* downstream review and prewrite nodes.
|
|
1743
|
+
*/
|
|
1744
|
+
export async function validateFrontendPlanPatchNodeOutput(input) {
|
|
1745
|
+
const nodeId = input.nodeId ?? "frontend-plan-pi";
|
|
1746
|
+
const attempt = input.attempt ?? 1;
|
|
1747
|
+
const candidateDir = path.posix.join("contracts", "candidates", nodeId);
|
|
1748
|
+
const rawPath = await writeFrontendPlanCandidateRaw({
|
|
1749
|
+
runDir: input.runDir,
|
|
1750
|
+
nodeId,
|
|
1751
|
+
attempt,
|
|
1752
|
+
text: input.text,
|
|
1753
|
+
});
|
|
1754
|
+
const reportBase = {
|
|
1755
|
+
schemaVersion: 1,
|
|
1756
|
+
schemaId: FRONTEND_IMPLEMENTATION_CONTRACT_PLAN_PATCH_SCHEMA_ID,
|
|
1757
|
+
nodeId,
|
|
1758
|
+
attempt,
|
|
1759
|
+
rawPath,
|
|
1760
|
+
protectedPaths: [...FRONTEND_PLAN_PATCH_PROTECTED_PATHS],
|
|
1761
|
+
candidateRawSha256: sha256Hex(input.text),
|
|
1762
|
+
};
|
|
1763
|
+
let extractedPatchPath;
|
|
1764
|
+
let extractedPatchSha256;
|
|
1765
|
+
const writeReport = async (value) => {
|
|
1766
|
+
await writeDeterministicJsonArtifact(input.runDir, path.posix.join(candidateDir, `attempt-${attempt}.validation.json`), {
|
|
1767
|
+
...reportBase,
|
|
1768
|
+
...(extractedPatchPath
|
|
1769
|
+
? { extractedPatchPath, extractedPatchSha256 }
|
|
1770
|
+
: {}),
|
|
1771
|
+
...value,
|
|
1772
|
+
});
|
|
1773
|
+
};
|
|
1774
|
+
try {
|
|
1775
|
+
if (!input.sourceBinding)
|
|
1776
|
+
throw new Error("frontend plan patch validator requires DAG sourceBinding");
|
|
1777
|
+
const skeleton = input.structuredContractOutput?.skeleton;
|
|
1778
|
+
if (!skeleton)
|
|
1779
|
+
throw new Error("frontend plan patch validator requires a deterministic runtime skeleton");
|
|
1780
|
+
const patch = extractFrontendImplementationJson(input.text);
|
|
1781
|
+
if (!isPlainObject(patch))
|
|
1782
|
+
throw new Error("frontend plan patch must extract to one JSON object");
|
|
1783
|
+
extractedPatchPath = path.posix.join(candidateDir, `attempt-${attempt}.extracted.json`);
|
|
1784
|
+
const extractedArtifact = await writeDeterministicJsonArtifact(input.runDir, extractedPatchPath, patch);
|
|
1785
|
+
extractedPatchSha256 = extractedArtifact.sha256;
|
|
1786
|
+
const citationsBlock = extractSingleOpenspecCitationsBlock(input.text);
|
|
1787
|
+
const protectedViolations = frontendPlanPatchProtectedPathViolations(patch);
|
|
1788
|
+
if (protectedViolations.length > 0)
|
|
1789
|
+
throw new Error(`frontend plan patch must not modify runtime-protected paths: ${protectedViolations.join(", ")}`);
|
|
1790
|
+
const merged = applyFrontendContractMergePatch(skeleton, patch);
|
|
1791
|
+
const droppedUnresolvedSymbols = await dropUnresolvedVerificationSymbols({
|
|
1792
|
+
runDir: input.runDir,
|
|
1793
|
+
contract: merged,
|
|
1794
|
+
});
|
|
1795
|
+
const analysis = await analyzeFrontendImplementationContract({
|
|
1796
|
+
runDir: input.runDir,
|
|
1797
|
+
rawContractText: serializeDeterministicJson(merged),
|
|
1798
|
+
sourceBinding: input.sourceBinding,
|
|
1799
|
+
});
|
|
1800
|
+
const normalizedArtifact = await writeDeterministicJsonArtifact(input.runDir, path.posix.join(candidateDir, `attempt-${attempt}.normalized.json`), analysis.canonical);
|
|
1801
|
+
await writeReport({
|
|
1802
|
+
classification: "accepted-normalized",
|
|
1803
|
+
normalizedContractPath: path.posix.relative(input.runDir.replaceAll(path.sep, "/"), normalizedArtifact.path.replaceAll(path.sep, "/")),
|
|
1804
|
+
normalizedContractSha256: normalizedArtifact.sha256,
|
|
1805
|
+
normalizationActions: [
|
|
1806
|
+
"apply-runtime-skeleton",
|
|
1807
|
+
...(droppedUnresolvedSymbols
|
|
1808
|
+
? ["drop-unresolved-verification-symbols"]
|
|
1809
|
+
: []),
|
|
1810
|
+
...analysis.normalizationActions,
|
|
1811
|
+
],
|
|
1812
|
+
errors: [],
|
|
1813
|
+
});
|
|
1814
|
+
const canonicalRel = canonicalContractRel(nodeId);
|
|
1815
|
+
const canonicalArtifact = await writeDeterministicJsonArtifact(input.runDir, canonicalRel, analysis.canonical);
|
|
1816
|
+
const parsedCitations = parseOpenspecCitationsBlock(citationsBlock);
|
|
1817
|
+
await writeDeterministicJsonArtifact(input.runDir, openspecCitationsRel(nodeId), {
|
|
1818
|
+
citations: parsedCitations.citations,
|
|
1819
|
+
block: citationsBlock,
|
|
1820
|
+
});
|
|
1821
|
+
await persistRuntimeSkeleton({
|
|
1822
|
+
runDir: input.runDir,
|
|
1823
|
+
nodeId,
|
|
1824
|
+
skeleton,
|
|
1825
|
+
});
|
|
1826
|
+
const artifact = {
|
|
1827
|
+
path: canonicalArtifact.path,
|
|
1828
|
+
sha256: canonicalArtifact.sha256,
|
|
1829
|
+
schemaId: FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID,
|
|
1830
|
+
};
|
|
1831
|
+
return {
|
|
1832
|
+
ok: true,
|
|
1833
|
+
contract: analysis.canonical,
|
|
1834
|
+
artifact,
|
|
1835
|
+
normalizedText: formatStructuredArtifactPointer(artifact),
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
catch (error) {
|
|
1839
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1840
|
+
const classified = classifyStructuredContractFailure({
|
|
1841
|
+
reason,
|
|
1842
|
+
kind: error instanceof FrontendContractFailure ? error.kind : undefined,
|
|
1843
|
+
});
|
|
1844
|
+
await writeReport({
|
|
1845
|
+
classification: classified.classification,
|
|
1846
|
+
normalizationActions: [],
|
|
1847
|
+
errors: classified.errors,
|
|
1848
|
+
});
|
|
1849
|
+
return {
|
|
1850
|
+
ok: false,
|
|
1851
|
+
reason,
|
|
1852
|
+
classification: classified.classification,
|
|
1853
|
+
errors: classified.errors,
|
|
1854
|
+
};
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
export async function readFrontendCanonicalCandidate(runDir, nodeId) {
|
|
1858
|
+
const candidatePath = path.join(runDir, canonicalContractRel(nodeId));
|
|
1859
|
+
try {
|
|
1860
|
+
const raw = await readFile(candidatePath, "utf8");
|
|
1861
|
+
const parsed = frontendImplementationContractSchema.safeParse(JSON.parse(raw));
|
|
1862
|
+
return parsed.success ? parsed.data : undefined;
|
|
1863
|
+
}
|
|
1864
|
+
catch (error) {
|
|
1865
|
+
if (error.code === "ENOENT")
|
|
1866
|
+
return undefined;
|
|
1867
|
+
return undefined;
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1611
1870
|
/**
|
|
1612
1871
|
* Node-output self-check for plan nodes that produce the implementation
|
|
1613
1872
|
* contract. Mirrors the prewrite gate validation so schema/typo/null violations
|
|
@@ -1626,51 +1885,121 @@ export async function validateFrontendContractNodeOutput(input) {
|
|
|
1626
1885
|
return { ok: true, contract: analysis.canonical };
|
|
1627
1886
|
}
|
|
1628
1887
|
catch (error) {
|
|
1888
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1889
|
+
const classified = classifyStructuredContractFailure({
|
|
1890
|
+
reason,
|
|
1891
|
+
kind: error instanceof FrontendContractFailure ? error.kind : undefined,
|
|
1892
|
+
});
|
|
1629
1893
|
return {
|
|
1630
1894
|
ok: false,
|
|
1631
|
-
reason
|
|
1895
|
+
reason,
|
|
1896
|
+
classification: classified.classification,
|
|
1897
|
+
errors: classified.errors,
|
|
1632
1898
|
};
|
|
1633
1899
|
}
|
|
1634
1900
|
}
|
|
1635
1901
|
/**
|
|
1636
|
-
* Node-output self-check for the plan revision node
|
|
1637
|
-
*
|
|
1638
|
-
*
|
|
1639
|
-
*
|
|
1640
|
-
*
|
|
1641
|
-
* schemaVersion/targets, so no full-contract schema/semantic checks run here;
|
|
1642
|
-
* content parsing, patch application, and merged-contract validation stay with
|
|
1643
|
-
* the prewrite gate, which remains the only authority.
|
|
1902
|
+
* Node-output self-check for the plan revision node. Format-level checks still
|
|
1903
|
+
* require one openspec-citations fence and one JSON object. When the original
|
|
1904
|
+
* canonical contract and sourceBinding are present, this node is the merge
|
|
1905
|
+
* authority: it applies the RFC 7386 patch, validates the full contract, and
|
|
1906
|
+
* writes the revision node's own hash-bound canonical artifact.
|
|
1644
1907
|
*/
|
|
1645
1908
|
export async function validateFrontendRevisionPatchNodeOutput(input) {
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
if (!/```openspec-citations[ \t]*\r?\n[\s\S]*?\r?\n```/.test(input.text)) {
|
|
1909
|
+
const fail = (reason) => {
|
|
1910
|
+
const classified = classifyStructuredContractFailure({ reason });
|
|
1649
1911
|
return {
|
|
1650
1912
|
ok: false,
|
|
1651
|
-
reason
|
|
1913
|
+
reason,
|
|
1914
|
+
classification: classified.classification,
|
|
1915
|
+
errors: classified.errors,
|
|
1652
1916
|
};
|
|
1917
|
+
};
|
|
1918
|
+
const nodeId = input.nodeId ?? "frontend-plan-revision-pi";
|
|
1919
|
+
const attempt = input.attempt ?? 1;
|
|
1920
|
+
await writeFrontendPlanCandidateRaw({
|
|
1921
|
+
runDir: input.runDir,
|
|
1922
|
+
nodeId,
|
|
1923
|
+
attempt,
|
|
1924
|
+
text: input.text,
|
|
1925
|
+
});
|
|
1926
|
+
let citationsBlock;
|
|
1927
|
+
try {
|
|
1928
|
+
citationsBlock = extractSingleOpenspecCitationsBlock(input.text);
|
|
1653
1929
|
}
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1930
|
+
catch (error) {
|
|
1931
|
+
return fail(error instanceof Error ? error.message : String(error));
|
|
1932
|
+
}
|
|
1933
|
+
const textWithoutCitations = input.text.replace(citationsBlock, "");
|
|
1657
1934
|
let patch;
|
|
1658
1935
|
try {
|
|
1659
1936
|
patch = extractFrontendImplementationJson(textWithoutCitations);
|
|
1660
1937
|
}
|
|
1661
1938
|
catch (error) {
|
|
1662
|
-
return {
|
|
1663
|
-
ok: false,
|
|
1664
|
-
reason: `revision patch output must contain exactly one valid JSON object: ${error instanceof Error ? error.message : String(error)}`,
|
|
1665
|
-
};
|
|
1939
|
+
return fail(`revision patch output must contain exactly one valid JSON object: ${error instanceof Error ? error.message : String(error)}`);
|
|
1666
1940
|
}
|
|
1667
1941
|
if (!patch || typeof patch !== "object" || Array.isArray(patch)) {
|
|
1942
|
+
return fail("revision patch output must extract to exactly one JSON object (RFC 7386 merge-patch delta)");
|
|
1943
|
+
}
|
|
1944
|
+
const protectedViolations = frontendPlanPatchProtectedPathViolations(patch);
|
|
1945
|
+
if (protectedViolations.length > 0) {
|
|
1946
|
+
return fail(`frontend revision patch must not modify runtime-protected paths: ${protectedViolations.join(", ")}`);
|
|
1947
|
+
}
|
|
1948
|
+
const original = (await readFrontendCanonicalCandidate(input.runDir, "frontend-plan-pi")) ??
|
|
1949
|
+
(await readFrontendCanonicalCandidate(input.runDir, nodeId));
|
|
1950
|
+
if (!input.sourceBinding) {
|
|
1951
|
+
return { ok: true, contract: patch };
|
|
1952
|
+
}
|
|
1953
|
+
if (!original) {
|
|
1954
|
+
return fail("frontend revision patch requires the original frontend-plan-pi canonical contract");
|
|
1955
|
+
}
|
|
1956
|
+
try {
|
|
1957
|
+
const merged = applyFrontendContractMergePatch(original, patch);
|
|
1958
|
+
const droppedUnresolvedSymbols = await dropUnresolvedVerificationSymbols({
|
|
1959
|
+
runDir: input.runDir,
|
|
1960
|
+
contract: merged,
|
|
1961
|
+
});
|
|
1962
|
+
const analysis = await analyzeFrontendImplementationContract({
|
|
1963
|
+
runDir: input.runDir,
|
|
1964
|
+
rawContractText: serializeDeterministicJson(merged),
|
|
1965
|
+
sourceBinding: input.sourceBinding,
|
|
1966
|
+
});
|
|
1967
|
+
const candidateDir = path.posix.join("contracts", "candidates", nodeId);
|
|
1968
|
+
await writeDeterministicJsonArtifact(input.runDir, path.posix.join(candidateDir, `attempt-${attempt}.extracted.json`), patch);
|
|
1969
|
+
await writeDeterministicJsonArtifact(input.runDir, path.posix.join(candidateDir, `attempt-${attempt}.normalized.json`), analysis.canonical);
|
|
1970
|
+
await writeDeterministicJsonArtifact(input.runDir, path.posix.join(candidateDir, `attempt-${attempt}.validation.json`), {
|
|
1971
|
+
classification: "accepted-normalized",
|
|
1972
|
+
normalizationActions: [
|
|
1973
|
+
"apply-original-canonical",
|
|
1974
|
+
...(droppedUnresolvedSymbols
|
|
1975
|
+
? ["drop-unresolved-verification-symbols"]
|
|
1976
|
+
: []),
|
|
1977
|
+
...analysis.normalizationActions,
|
|
1978
|
+
],
|
|
1979
|
+
errors: [],
|
|
1980
|
+
});
|
|
1981
|
+
const canonicalArtifact = await writeDeterministicJsonArtifact(input.runDir, canonicalContractRel(nodeId), analysis.canonical);
|
|
1982
|
+
const parsedCitations = parseOpenspecCitationsBlock(citationsBlock);
|
|
1983
|
+
await writeDeterministicJsonArtifact(input.runDir, openspecCitationsRel(nodeId), {
|
|
1984
|
+
citations: parsedCitations.citations,
|
|
1985
|
+
block: citationsBlock,
|
|
1986
|
+
});
|
|
1987
|
+
const artifact = {
|
|
1988
|
+
path: canonicalArtifact.path,
|
|
1989
|
+
sha256: canonicalArtifact.sha256,
|
|
1990
|
+
schemaId: FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID,
|
|
1991
|
+
};
|
|
1668
1992
|
return {
|
|
1669
|
-
ok:
|
|
1670
|
-
|
|
1993
|
+
ok: true,
|
|
1994
|
+
contract: analysis.canonical,
|
|
1995
|
+
artifact,
|
|
1996
|
+
normalizedText: formatStructuredArtifactPointer(artifact),
|
|
1671
1997
|
};
|
|
1672
1998
|
}
|
|
1673
|
-
|
|
1999
|
+
catch (error) {
|
|
2000
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
2001
|
+
return fail(reason);
|
|
2002
|
+
}
|
|
1674
2003
|
}
|
|
1675
2004
|
export async function materializeFrontendImplementationContract(input) {
|
|
1676
2005
|
const analysis = await analyzeFrontendImplementationContract(input);
|