@tea-agent/loop-agent 0.42.0-next.1 → 0.42.0-next.10
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/AGENTS.md +1 -1
- package/CHANGELOG.md +319 -1
- package/dist/application/dag/generate-task-dag.js +75 -19
- package/dist/application/dag/run-dag.js +41 -0
- package/dist/application/task-lifecycle/advance.js +24 -5
- package/dist/application/task-lifecycle/observe.js +171 -17
- package/dist/application/task-lifecycle/plan-transitions.js +42 -7
- package/dist/application/task-lifecycle/recommendations.js +13 -2
- package/dist/build-stamp.json +3 -3
- package/dist/cli/command-definitions.js +7 -0
- package/dist/cli/program.js +6 -1
- package/dist/commands/client-recovery.js +3 -0
- package/dist/commands/dag-follow-up.js +138 -0
- package/dist/commands/dag-rerun-task.js +2 -0
- package/dist/commands/init.js +27 -1
- package/dist/commands/task-advance.js +19 -0
- package/dist/executors/dag-pi-executor.js +3976 -76
- package/dist/executors/pi-executor.js +15 -4
- package/dist/executors/pi-read-budget-policy.js +239 -0
- package/dist/executors/shell-executor.js +978 -188
- package/dist/executors/shell-write-guard.js +7 -0
- package/dist/infrastructure/console/operation-store.js +226 -6
- package/dist/shared/dag-failure-category.js +12 -0
- package/dist/shared/openspec-spec.js +70 -4
- package/dist/shared/operator/capabilities.js +7 -0
- package/dist/task/config-types.js +105 -5
- package/dist/task/contract/adopt.js +4 -0
- package/dist/task/contract/import-revision.js +4 -0
- package/dist/task/contract/project.js +3 -0
- package/dist/task/contract/schema.js +2 -1
- package/dist/task/frontend-project-capability.js +203 -20
- package/dist/task/runtime.js +5 -2
- package/dist/task/source-prepare/build-draft.js +3 -3
- package/dist/task/source-prepare/fragment-inventory.js +64 -25
- package/dist/task/source-prepare/prepare.js +126 -21
- package/dist/task/source-prepare/semantic-intake.js +6 -2
- package/dist/task/source-references.js +22 -1
- package/dist/worker/console/chat/chat-ui-policy.js +4 -2
- package/dist/worker/console/chat/model-resolver.js +7 -4
- package/dist/worker/console/chat/pi-runtime.js +258 -11
- package/dist/worker/console/chat/repo-browser.js +10 -3
- package/dist/worker/console/chat/routes.js +165 -71
- package/dist/worker/console/chat/sdd-data-alignment.js +222 -0
- package/dist/worker/console/chat/session-catalog.js +2 -0
- package/dist/worker/console/chat/session-store.js +159 -70
- package/dist/worker/console/chat/shortcuts.js +19 -3
- package/dist/worker/console/chat/turn-process.js +111 -56
- package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
- package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
- package/dist/worker/console/index.js +3 -0
- package/dist/worker/console/operator-actions.js +169 -7
- package/dist/worker/console/pi-readiness.js +3 -2
- package/dist/worker/console/server.js +23 -1
- package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-DZZ8m3PO.js → abnfDiagram-N423BO3Z-CcS17TBr.js} +1 -1
- package/dist/worker/console/static/assets/{arc-D6PvaVd-.js → arc-COptKq2S.js} +1 -1
- package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-B_OTOiI8.js → architectureDiagram-T3A2C74G-h4LKMHjP.js} +1 -1
- package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-Bv6rqHBg.js → blockDiagram-VBNYF7ZC-COA1MOH0.js} +1 -1
- package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-B8eHr0oz.js → c4Diagram-5PPSVZJV-DJUf0QPm.js} +1 -1
- package/dist/worker/console/static/assets/channel-DdBCaOJ6.js +1 -0
- package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-DYwR0im2.js → chunk-2GRJ4B5K-mtWfKrUX.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-D2WPGqXt.js → chunk-2Q5K7J3B-B8pXsxDQ.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5RXB4S5H-CQISJ_I7.js → chunk-5RXB4S5H-ipKzByl1.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-5VM5RSS4-C0o2Du1e.js → chunk-5VM5RSS4-DYi3Ald_.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-4f8kr-U7.js → chunk-6Q2QTUOP-DQtGYoty.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-GF5L2VYU-D_OWvXzX.js → chunk-GF5L2VYU-BU0qS2YV.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-JWPE2WC7-CasdPz5X.js → chunk-JWPE2WC7-DVH9dIGE.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-KBJHAD2P-Cj8lRrla.js → chunk-KBJHAD2P-Dt60SmgM.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-RYQCIY6F-CUvD1FSd.js → chunk-RYQCIY6F-BoaUGuEY.js} +1 -1
- package/dist/worker/console/static/assets/{chunk-XXDRQBXY-CqLw_eqB.js → chunk-XXDRQBXY-_WHFDTp2.js} +1 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DZFra1GO.js +1 -0
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DZFra1GO.js +1 -0
- package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-ku-WqJPx.js → cose-bilkent-JH36ORCC-BxkbTIRd.js} +1 -1
- package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-Bq_PYDhl.js → cynefin-VYW2F7L2-Bf2UVnoG.js} +1 -1
- package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-BiGZV641.js → cynefinDiagram-MW4NZA55-Bo23q1J_.js} +1 -1
- package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-C4JRlglF.js → dagre-VZM6K2ZE-Dd_UU49i.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-7IWD3JNH-B0dNeWYG.js → diagram-7IWD3JNH-ebUa1a9y.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-Cj35YvYM.js → diagram-B4RE2ZJO-BucthU8r.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-LBJQPF4R-uzQoJ2-8.js → diagram-LBJQPF4R-BgqNpAkm.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D1a-Buoz.js → diagram-Q27KOJAE-DC4q5NGa.js} +1 -1
- package/dist/worker/console/static/assets/{diagram-UB23O5K3-XjRrLRSs.js → diagram-UB23O5K3-CBqCaMeb.js} +1 -1
- package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-Da_O24cW.js → ebnfDiagram-BXEA7PRR-CGtnbQ3-.js} +1 -1
- package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-BLJ8jrYU.js → erDiagram-JOGREHBK-CG3LUao5.js} +1 -1
- package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-B9GrLjM0.js → flowDiagram-UKHOOZJN-D2ZVgoFS.js} +1 -1
- package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CeJ0TqiK.js → ganttDiagram-PKOTCBZU-DoYFZiKt.js} +1 -1
- package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Bm2eNNFX.js → gitGraphDiagram-DS77QQ5N-CKoP1s6j.js} +1 -1
- package/dist/worker/console/static/assets/index-CAZ2fC_X.css +1 -0
- package/dist/worker/console/static/assets/index-vbTcFnFs.js +449 -0
- package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DB26i3d8.js → infoDiagram-6WML65LV-Duofv8p2.js} +1 -1
- package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-BOGRyeZT.js → ishikawaDiagram-WSZJBQD7-D2nlCkA1.js} +1 -1
- package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-CZRoBO_6.js → journeyDiagram-NVQOT4AX-Dd4IHdus.js} +1 -1
- package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-qlWhJyoB.js → kanban-definition-27J2QSJJ-Bi20AOdt.js} +1 -1
- package/dist/worker/console/static/assets/{linear-CfUiDDB3.js → linear-D59YJ9kB.js} +1 -1
- package/dist/worker/console/static/assets/{mermaid.core-BQe6fpqj.js → mermaid.core-Bl13LpNa.js} +5 -5
- package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-zFzWHw64.js → mindmap-definition-FAOFIHXS-C6DLP5eY.js} +1 -1
- package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-DFGUqjZO.js → pegDiagram-VL7TDLO6-BjO4cy64.js} +1 -1
- package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-BB5i0l8Z.js → pieDiagram-7S7Q4E2Y-D7i2qZTG.js} +1 -1
- package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-obbVZ_X5.js → quadrantDiagram-CIZ2JOQS-Dx8o_h0y.js} +1 -1
- package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-BwdaNzc1.js → railroadDiagram-AXF67PYL-B55orI_t.js} +1 -1
- package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-CkTJaugh.js → requirementDiagram-LRYGKXZP-zWaehp6f.js} +1 -1
- package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-BwJ-hIgq.js → sankeyDiagram-W5VNT64P-CcA-pjvD.js} +1 -1
- package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-BzSgmm7w.js → sequenceDiagram-SI44F4Z6-BOFbzNHI.js} +1 -1
- package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-Bn3oj5Q1.js → sizeCapture-X5ZJPWSS-BjAejah1.js} +1 -1
- package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-dxhGtL8K.js → stateDiagram-OKZ733FA-DXYwxJwZ.js} +1 -1
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Clg3V9t1.js +1 -0
- package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-BLpEInya.js → swimlanes-SLNWSIFB-OYiOch8n.js} +2 -2
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-DX1dxAAW.js +8 -0
- package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-ChJGYcXy.js → timeline-definition-Z64GVDOM-cZfH3nmU.js} +1 -1
- package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-DK-qjRer.js → vennDiagram-T6HMQDX7-BDE7b3E1.js} +1 -1
- package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-3qeaWAg-.js → wardleyDiagram-T6FBY63Y-kyyGy9WJ.js} +1 -1
- package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CootlyP9.js → xychartDiagram-ELKLHX3M-CJj6VTog.js} +1 -1
- package/dist/worker/console/static/index.html +2 -2
- package/dist/worker/console/static-src/active-run-badge.js +17 -0
- package/dist/worker/console/static-src/app/useRecoveryConsole.js +5 -5
- package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +19 -8
- package/dist/worker/console/static-src/operator-chat/input-history.js +8 -6
- package/dist/worker/console/static-src/operator-chat/open-preview-in-browser.js +15 -7
- package/dist/worker/console/static-src/operator-chat/runtime-snapshot-store.js +10 -0
- package/dist/worker/console/static-src/operator-chat/useChatSessions.js +258 -23
- package/dist/worker/console/static-src/operator-chat/useChatThread.js +112 -4
- package/dist/worker/console/static-src/operator-chat/useComposer.js +13 -4
- package/dist/worker/console/static-src/operator-chat/useRuntimeControls.js +29 -6
- package/dist/worker/console/static-src/operator-chat/useRuntimeSnapshot.js +8 -2
- package/dist/worker/console/static-src/shell/console-update-reload.js +36 -9
- package/dist/worker/console/static-src/shell/workspace-route.js +11 -0
- package/dist/worker/console/workspace-context.js +115 -1
- package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
- package/dist/worker/observe/routes.js +4 -0
- package/dist/worker/observe/static/dag-helpers.js +4 -2
- package/dist/worker/observe/static/dag-node-purpose.js +5 -0
- package/dist/worker/observe/static/inspect-workspace.js +23 -0
- package/dist/worker/observe/static/kpi.js +2 -2
- package/dist/worker/observe/static/operator-chrome.d.ts +10 -2
- package/dist/worker/observe/static/operator-chrome.js +37 -23
- package/dist/worker/observe/static/relations.js +2 -2
- package/dist/worker/observe/static/router.d.ts +12 -1
- package/dist/worker/observe/static/router.js +48 -6
- package/dist/worker/observe/static/run-processing.js +4 -2
- package/dist/worker/observe/static/shell-chrome.js +2 -2
- package/dist/worker/observe/static/state.js +5 -0
- package/dist/worker/observe/static/styles.css +95 -0
- package/dist/worker/observe/static/views/dag-inspector.js +14 -7
- package/dist/worker/observe/static/views/dag.js +10 -2
- package/dist/worker/observe/static/views/dags.js +3 -1
- package/dist/worker/observe/static/views/dashboard.js +14 -6
- package/dist/worker/observe/static/views/pool.js +7 -2
- package/dist/worker/observe/static/views/run.js +12 -3
- package/dist/worker/observe/static/views/session-timeline.js +69 -10
- package/dist/worker/observe/static/views/task.js +7 -2
- package/dist/workflows/dag/backend-test-case-coverage-analysis.js +215 -28
- package/dist/workflows/dag/backend-test-markdown-workflow.js +13 -1
- package/dist/workflows/dag/backend-test-plan-protocol.js +106 -0
- package/dist/workflows/dag/backend-test-scenario-param.js +172 -25
- package/dist/workflows/dag/backend-test-scenario-partitions.js +62 -1
- package/dist/workflows/dag/backend-test-writer-completeness.js +104 -12
- package/dist/workflows/dag/contract-validator-registrations.js +1 -2
- package/dist/workflows/dag/dag-retry-schema.js +138 -0
- package/dist/workflows/dag/frontend-closeout.js +221 -0
- package/dist/workflows/dag/frontend-design-policy.js +400 -0
- package/dist/workflows/dag/frontend-human-decision.js +182 -0
- package/dist/workflows/dag/frontend-implementation-contract.js +1237 -192
- package/dist/workflows/dag/frontend-plan-render.js +2 -1
- package/dist/workflows/dag/frontend-prewrite-gate.js +256 -350
- package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
- package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
- package/dist/workflows/dag/frontend-recovery-controller.js +226 -0
- package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
- package/dist/workflows/dag/frontend-recovery-plan.js +21 -10
- package/dist/workflows/dag/frontend-recovery-run.js +166 -34
- package/dist/workflows/dag/frontend-repair.js +1 -432
- package/dist/workflows/dag/frontend-review-context.js +261 -15
- package/dist/workflows/dag/frontend-review-findings.js +270 -0
- package/dist/workflows/dag/frontend-shadow-dual-write.js +941 -0
- package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
- package/dist/workflows/dag/frontend-shape-facts.js +419 -0
- package/dist/workflows/dag/frontend-shape.js +435 -0
- package/dist/workflows/dag/frontend-source-fidelity-ledger.js +108 -0
- package/dist/workflows/dag/frontend-split-application-service.js +203 -0
- package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
- package/dist/workflows/dag/frontend-typed-event-store.js +452 -0
- package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
- package/dist/workflows/dag/frontend-verification-trace.js +252 -25
- package/dist/workflows/dag/frontend-worktree-diff.js +250 -17
- package/dist/workflows/dag/frontend-writer-admission.js +319 -0
- package/dist/workflows/dag/frontend-writer-status.js +256 -0
- package/dist/workflows/dag/init-hybrid.js +1028 -562
- package/dist/workflows/dag/interrupt-request.js +7 -0
- package/dist/workflows/dag/node-execution.js +828 -4
- package/dist/workflows/dag/prompt.js +70 -3
- package/dist/workflows/dag/recovery-lease.js +170 -0
- package/dist/workflows/dag/report.js +37 -1
- package/dist/workflows/dag/rerun-feedback.js +315 -1
- package/dist/workflows/dag/rerun-plan.js +17 -6
- package/dist/workflows/dag/rerun-run.js +44 -6
- package/dist/workflows/dag/rerun-task.js +251 -13
- package/dist/workflows/dag/retry-policy.js +219 -104
- package/dist/workflows/dag/runner.js +580 -124
- package/dist/workflows/dag/scheduler.js +133 -20
- package/dist/workflows/dag/types.js +244 -16
- package/dist/workflows/dag/validate.js +28 -12
- package/docs/examples/README.md +5 -0
- package/docs/init-surface.manifest.json +30 -12
- package/docs/skills/vetted-skill-registry.md +4 -2
- package/docs/templates/README.md +2 -0
- package/docs/templates/agent-dag-report.schema.json +8 -2
- package/docs/templates/agent-dag.schema.json +1 -1
- package/docs/templates/backend-test-dag.json +23 -20
- package/docs/templates/frontend-implementation-contract.schema.json +4 -1
- package/docs/templates/frontend-implementation-dag.json +89 -0
- package/docs/templates/spec-registry.schema.json +45 -0
- package/harness.json +1 -1
- package/package.json +4 -3
- package/skills/frontend-bounded-implement/SKILL.md +15 -14
- package/skills/frontend-bounded-implement/references/code-standards.md +19 -0
- package/skills/frontend-contract/SKILL.md +23 -0
- package/skills/frontend-contract/references/contract-protocol.md +34 -0
- package/skills/frontend-design-review/SKILL.md +22 -41
- package/skills/frontend-plan/SKILL.md +26 -0
- package/skills/frontend-plan/references/decision-contract.md +37 -0
- package/skills/frontend-plan/references/design-decisions.md +17 -0
- package/skills/frontend-review/SKILL.md +20 -15
- package/skills/frontend-review/references/review-findings.md +6 -7
- package/skills/frontend-scout/SKILL.md +25 -0
- package/skills/frontend-scout/references/design-evidence.md +16 -0
- package/skills/frontend-scout/references/scout-evidence.md +23 -0
- package/skills/frontend-verification/SKILL.md +1 -1
- package/skills/loop-agent/references/command-reference.md +1 -0
- package/skills/loop-agent/references/hybrid-dag.md +2 -2
- package/dist/worker/console/static/assets/channel-BU5gOilw.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DIzKJGHr.js +0 -1
- package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DIzKJGHr.js +0 -1
- package/dist/worker/console/static/assets/index-H9rFJiGL.css +0 -1
- package/dist/worker/console/static/assets/index-xwu9GxEc.js +0 -451
- package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-Cld2qK9v.js +0 -1
- package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-TIHpiT7w.js +0 -8
- package/skills/frontend-implementation/SKILL.md +0 -52
- package/skills/frontend-implementation/references/code-standards.md +0 -33
- package/skills/frontend-implementation/references/design-spec.md +0 -56
- package/skills/frontend-implementation/references/node-contracts.md +0 -31
|
@@ -4,11 +4,166 @@ import path from "node:path";
|
|
|
4
4
|
import { writeDagRunJsonArtifact } from "../../infrastructure/harness/artifact-store.js";
|
|
5
5
|
import { formatFrontendWorktreeDiffStdout, runFrontendWorktreeDiffGate, } from "./frontend-worktree-diff.js";
|
|
6
6
|
import { frontendLintAssessmentArtifactSchema } from "./frontend-lint-baseline.js";
|
|
7
|
-
import { frontendImplementationContractSchema, } from "./frontend-implementation-contract.js";
|
|
8
|
-
import { frontendRepairAssessmentSchema } from "./frontend-repair.js";
|
|
7
|
+
import { deterministicSha256, FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID, frontendImplementationContractSchema, writeFrontendContractReviewArtifacts, } from "./frontend-implementation-contract.js";
|
|
9
8
|
import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
|
|
10
|
-
import { ledgerStatsLine, parseLedgerJson
|
|
11
|
-
export const FRONTEND_REVIEW_CONTEXT_SCHEMA_ID = "frontend-review-context-
|
|
9
|
+
import { ledgerStatsLine, parseLedgerJson } from "../../task/source-prepare/ledger.js";
|
|
10
|
+
export const FRONTEND_REVIEW_CONTEXT_SCHEMA_ID = "frontend-review-context-v2";
|
|
11
|
+
/**
|
|
12
|
+
* Deterministically assemble the cumulative diff from a task baseline and the
|
|
13
|
+
* current candidate snapshot. Same input always yields the same output:
|
|
14
|
+
* paths are sorted, statuses are derived from a fixed decision table, and both
|
|
15
|
+
* digests use canonical JSON hashing.
|
|
16
|
+
*/
|
|
17
|
+
export function assembleCumulativeDiffContext(input) {
|
|
18
|
+
const baselineByPath = new Map(input.baselineEntries.map((entry) => [entry.path, entry.sha256]));
|
|
19
|
+
const currentByPath = new Map(input.currentEntries.map((entry) => [entry.path, entry.sha256]));
|
|
20
|
+
const allPaths = new Set([
|
|
21
|
+
...baselineByPath.keys(),
|
|
22
|
+
...currentByPath.keys(),
|
|
23
|
+
]);
|
|
24
|
+
const sortedPaths = [...allPaths].sort();
|
|
25
|
+
const perPath = sortedPaths
|
|
26
|
+
.map((p) => {
|
|
27
|
+
const baselineSha256 = baselineByPath.get(p) ?? null;
|
|
28
|
+
const hasCurrent = currentByPath.has(p);
|
|
29
|
+
const currentSha256 = hasCurrent
|
|
30
|
+
? (currentByPath.get(p) ?? null)
|
|
31
|
+
: null;
|
|
32
|
+
let status;
|
|
33
|
+
if (baselineSha256 === null) {
|
|
34
|
+
status = currentSha256 === null ? "deleted" : "added";
|
|
35
|
+
}
|
|
36
|
+
else if (currentSha256 === null) {
|
|
37
|
+
status = hasCurrent ? "deleted" : "unchanged";
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
status =
|
|
41
|
+
currentSha256 === baselineSha256 ? "unchanged" : "modified";
|
|
42
|
+
}
|
|
43
|
+
return { path: p, status, baselineSha256, currentSha256 };
|
|
44
|
+
})
|
|
45
|
+
// Cumulative diff carries only actual changes. The worktree baseline is
|
|
46
|
+
// the full repository inventory (git ls-files -co), so an unfiltered
|
|
47
|
+
// perPath enumerates thousands of unchanged hashes (e.g. 3000+ entries
|
|
48
|
+
// ≈ 700KB), ballooning the review context and blowing the review node
|
|
49
|
+
// read budget on a single read. Unchanged entries add no diff evidence.
|
|
50
|
+
.filter((entry) => entry.status !== "unchanged");
|
|
51
|
+
const baselineDigest = deterministicSha256([...input.baselineEntries].sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0));
|
|
52
|
+
const cumulativeDigest = deterministicSha256(perPath);
|
|
53
|
+
return {
|
|
54
|
+
schemaId: "frontend-cumulative-diff-context-v1",
|
|
55
|
+
baselineDigest,
|
|
56
|
+
perPath,
|
|
57
|
+
cumulativeDigest,
|
|
58
|
+
empty: perPath.length === 0,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async function sha256File(filePath) {
|
|
62
|
+
try {
|
|
63
|
+
const bytes = await readFile(filePath);
|
|
64
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Capture the cumulative diff from the frozen worktree baseline and the
|
|
72
|
+
* run-owned diff gate output. Pure status/digest logic lives in
|
|
73
|
+
* `assembleCumulativeDiffContext`; this wrapper only does IO.
|
|
74
|
+
*/
|
|
75
|
+
export async function captureCumulativeDiffContext(input) {
|
|
76
|
+
const root = path.resolve(input.workspaceRoot);
|
|
77
|
+
let baselineEntries = [];
|
|
78
|
+
try {
|
|
79
|
+
const raw = JSON.parse(await readFile(path.join(input.runDir, "contracts/frontend-worktree-baseline.json"), "utf8"));
|
|
80
|
+
baselineEntries = (raw.files ?? [])
|
|
81
|
+
.map((file) => ({ path: file, sha256: raw.hashes?.[file] ?? null }))
|
|
82
|
+
.filter((entry) => entry.sha256 !== null);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// Missing baseline → empty baseline entries (still deterministic).
|
|
86
|
+
}
|
|
87
|
+
const candidatePaths = [
|
|
88
|
+
...new Set([...input.diff.changedFiles, ...input.diff.untrackedFiles]),
|
|
89
|
+
];
|
|
90
|
+
const currentEntries = [];
|
|
91
|
+
for (const file of candidatePaths) {
|
|
92
|
+
currentEntries.push({
|
|
93
|
+
path: file,
|
|
94
|
+
sha256: await sha256File(path.join(root, file)),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return assembleCumulativeDiffContext({ baselineEntries, currentEntries });
|
|
98
|
+
}
|
|
99
|
+
const TYPED_TO_LEGACY = {
|
|
100
|
+
approve_review: "pass",
|
|
101
|
+
request_review_changes: "request-revision",
|
|
102
|
+
};
|
|
103
|
+
function isTypedReviewTerminalVerdict(value) {
|
|
104
|
+
return value === "approve_review" || value === "request_review_changes";
|
|
105
|
+
}
|
|
106
|
+
function isLegacyReviewVerdict(value) {
|
|
107
|
+
return value === "pass" || value === "request-revision";
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Deterministic, fail-closed equivalence between the typed review terminal
|
|
111
|
+
* verdict(s) observed in the attempt and the legacy JSON verdict.
|
|
112
|
+
*
|
|
113
|
+
* Fail-closed cases (all yield `match: false` with an explicit reason):
|
|
114
|
+
* - no typed terminal fact;
|
|
115
|
+
* - more than one typed terminal fact (conflicting);
|
|
116
|
+
* - an unknown typed terminal kind;
|
|
117
|
+
* - a missing/illegal legacy JSON verdict.
|
|
118
|
+
*/
|
|
119
|
+
export function compareTypedReviewToLegacyJsonVerdict(input) {
|
|
120
|
+
const kinds = [...input.typedKinds];
|
|
121
|
+
const legacyVerdict = isLegacyReviewVerdict(input.legacyVerdict ?? "")
|
|
122
|
+
? input.legacyVerdict
|
|
123
|
+
: undefined;
|
|
124
|
+
if (kinds.length === 0) {
|
|
125
|
+
return {
|
|
126
|
+
typedVerdict: undefined,
|
|
127
|
+
legacyVerdict,
|
|
128
|
+
match: false,
|
|
129
|
+
reason: "typed review terminal fact missing",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (kinds.length > 1) {
|
|
133
|
+
return {
|
|
134
|
+
typedVerdict: undefined,
|
|
135
|
+
legacyVerdict,
|
|
136
|
+
match: false,
|
|
137
|
+
reason: `conflicting typed review terminal facts: ${[...new Set(kinds)].join(",")}`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
const typedVerdict = kinds[0];
|
|
141
|
+
if (!isTypedReviewTerminalVerdict(typedVerdict)) {
|
|
142
|
+
return {
|
|
143
|
+
typedVerdict: undefined,
|
|
144
|
+
legacyVerdict,
|
|
145
|
+
match: false,
|
|
146
|
+
reason: `unknown typed review terminal fact kind: ${typedVerdict}`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (!legacyVerdict) {
|
|
150
|
+
return {
|
|
151
|
+
typedVerdict,
|
|
152
|
+
legacyVerdict: undefined,
|
|
153
|
+
match: false,
|
|
154
|
+
reason: "legacy JSON verdict missing or not pass|request-revision",
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
const expected = TYPED_TO_LEGACY[typedVerdict];
|
|
158
|
+
return {
|
|
159
|
+
typedVerdict,
|
|
160
|
+
legacyVerdict,
|
|
161
|
+
match: expected === legacyVerdict,
|
|
162
|
+
reason: expected === legacyVerdict
|
|
163
|
+
? "typed review verdict matches legacy JSON verdict"
|
|
164
|
+
: `typed review verdict ${typedVerdict} maps to ${expected} but legacy JSON verdict is ${legacyVerdict}`,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
12
167
|
/**
|
|
13
168
|
* review-context 稳定失败枚举(AC-011 / AC-HARD-005):v2 drift / manifest 失败返回
|
|
14
169
|
* 结构化、可消费的 failure code(稳定枚举 + reason 的 envelope),既有调用方无需字符串
|
|
@@ -52,6 +207,37 @@ async function readOptionalLintAssessment(runDir) {
|
|
|
52
207
|
throw new Error(`frontend review context invalid ${relativePath}`);
|
|
53
208
|
}
|
|
54
209
|
}
|
|
210
|
+
async function readOptionalRepairAssessment(runDir) {
|
|
211
|
+
const relativePath = "contracts/frontend-repair-assessment.json";
|
|
212
|
+
try {
|
|
213
|
+
return JSON.parse(await readFile(path.join(runDir, relativePath), "utf8"));
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
if (error &&
|
|
217
|
+
typeof error === "object" &&
|
|
218
|
+
"code" in error &&
|
|
219
|
+
error.code === "ENOENT") {
|
|
220
|
+
return undefined;
|
|
221
|
+
}
|
|
222
|
+
throw new Error(`frontend review context invalid ${relativePath}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/** M8: verify failure ownership facts written by frontend-verify-shell. */
|
|
226
|
+
async function readOptionalVerifyFailure(runDir) {
|
|
227
|
+
const relativePath = "contracts/frontend-verify-failure.json";
|
|
228
|
+
try {
|
|
229
|
+
return JSON.parse(await readFile(path.join(runDir, relativePath), "utf8"));
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
if (error &&
|
|
233
|
+
typeof error === "object" &&
|
|
234
|
+
"code" in error &&
|
|
235
|
+
error.code === "ENOENT") {
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
throw new Error(`frontend review context invalid ${relativePath}`);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
55
241
|
function assertReviewEvidence(input) {
|
|
56
242
|
const contract = frontendImplementationContractSchema.safeParse(input.contract);
|
|
57
243
|
if (!contract.success) {
|
|
@@ -94,47 +280,105 @@ function assertReviewEvidence(input) {
|
|
|
94
280
|
trace.mockStrategy !== contract.data.mockApi.strategy) {
|
|
95
281
|
throw new FrontendReviewContextFailure("review-context-verification-unbound", "frontend review context verification trace is not bound to a passing contract");
|
|
96
282
|
}
|
|
97
|
-
const assessment = frontendRepairAssessmentSchema.safeParse(input.repairAssessment);
|
|
98
|
-
if (!assessment.success) {
|
|
99
|
-
throw new FrontendReviewContextFailure("review-context-invalid-contract", "frontend review context invalid repair assessment");
|
|
100
|
-
}
|
|
101
283
|
}
|
|
102
284
|
export async function runFrontendReviewContextGate(input) {
|
|
103
|
-
const
|
|
285
|
+
const contractRelativePath = "contracts/frontend-implementation-contract.json";
|
|
286
|
+
let contractRaw;
|
|
287
|
+
try {
|
|
288
|
+
contractRaw = await readFile(path.join(input.runDir, contractRelativePath), "utf8");
|
|
289
|
+
}
|
|
290
|
+
catch (error) {
|
|
291
|
+
throw new FrontendReviewContextFailure("review-context-manifest-missing", `frontend review context missing or invalid ${contractRelativePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
292
|
+
}
|
|
293
|
+
let contract;
|
|
294
|
+
try {
|
|
295
|
+
contract = JSON.parse(contractRaw);
|
|
296
|
+
}
|
|
297
|
+
catch (error) {
|
|
298
|
+
throw new FrontendReviewContextFailure("review-context-manifest-missing", `frontend review context missing or invalid ${contractRelativePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
299
|
+
}
|
|
104
300
|
const parsedContract = frontendImplementationContractSchema.safeParse(contract);
|
|
105
301
|
if (!parsedContract.success) {
|
|
106
302
|
throw new FrontendReviewContextFailure("review-context-invalid-contract", "frontend review context invalid implementation contract");
|
|
107
303
|
}
|
|
304
|
+
// Authorized diff surface = deliverable files + every verification-target
|
|
305
|
+
// file the contract references. The writer legitimately writes VT test
|
|
306
|
+
// artifacts (behavior verification), and the reviewer must audit their
|
|
307
|
+
// diffs; scoping this to targets.files alone failed the baseline-overlap
|
|
308
|
+
// check on app.test.js (r19 extreme smoke).
|
|
309
|
+
const authorizedChangedPaths = [
|
|
310
|
+
...new Set([
|
|
311
|
+
...parsedContract.data.targets.files,
|
|
312
|
+
...parsedContract.data.verificationTargets.map((target) => target.file),
|
|
313
|
+
]),
|
|
314
|
+
];
|
|
108
315
|
const diff = await runFrontendWorktreeDiffGate({
|
|
109
316
|
...input,
|
|
110
|
-
authorizedChangedPaths
|
|
317
|
+
authorizedChangedPaths,
|
|
111
318
|
});
|
|
112
319
|
const verificationTrace = await readRequiredJson(input.runDir, "contracts/frontend-verification-trace.json");
|
|
113
|
-
const repairAssessment = await
|
|
320
|
+
const repairAssessment = await readOptionalRepairAssessment(input.runDir);
|
|
321
|
+
const verifyFailure = await readOptionalVerifyFailure(input.runDir);
|
|
114
322
|
const lintAssessment = await readOptionalLintAssessment(input.runDir);
|
|
323
|
+
const cumulativeDiff = await captureCumulativeDiffContext({
|
|
324
|
+
runDir: input.runDir,
|
|
325
|
+
workspaceRoot: input.workspaceRoot,
|
|
326
|
+
diff,
|
|
327
|
+
});
|
|
115
328
|
assertReviewEvidence({ contract, verificationTrace, repairAssessment });
|
|
116
329
|
// Source fidelity ledger 核对(AC-008 / AC-FIDELITY-008):ledger 缺失/漂移即阻断;
|
|
117
330
|
// 同时把核对清单注入 review-context 供最终 reviewer 机械化核对。
|
|
118
331
|
const sourceFidelity = await buildReviewSourceFidelityChecklist(input, parsedContract.data);
|
|
332
|
+
const contractReviewArtifacts = await writeFrontendContractReviewArtifacts({
|
|
333
|
+
runDir: input.runDir,
|
|
334
|
+
outputDir: "contracts",
|
|
335
|
+
artifactName: "frontend-implementation-contract.json",
|
|
336
|
+
canonical: parsedContract.data,
|
|
337
|
+
canonicalArtifact: {
|
|
338
|
+
path: path.join(input.runDir, contractRelativePath),
|
|
339
|
+
sha256: createHash("sha256").update(contractRaw).digest("hex"),
|
|
340
|
+
bytes: Buffer.byteLength(contractRaw, "utf8"),
|
|
341
|
+
},
|
|
342
|
+
});
|
|
119
343
|
const payload = {
|
|
120
|
-
schemaVersion:
|
|
344
|
+
schemaVersion: 2,
|
|
121
345
|
schemaId: FRONTEND_REVIEW_CONTEXT_SCHEMA_ID,
|
|
122
|
-
|
|
346
|
+
contractRef: {
|
|
347
|
+
path: contractRelativePath,
|
|
348
|
+
schemaId: FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID,
|
|
349
|
+
sha256: contractReviewArtifacts.index.contract.sha256,
|
|
350
|
+
bytes: contractReviewArtifacts.index.contract.bytes,
|
|
351
|
+
indexPath: contractReviewArtifacts.index.relativePath,
|
|
352
|
+
indexSha256: contractReviewArtifacts.index.artifactSha256,
|
|
353
|
+
capacityDiagnosticPath: contractReviewArtifacts.capacity.relativePath,
|
|
354
|
+
capacityDiagnosticSha256: contractReviewArtifacts.capacity.artifactSha256,
|
|
355
|
+
sections: contractReviewArtifacts.index.sections,
|
|
356
|
+
},
|
|
123
357
|
verificationTrace,
|
|
124
|
-
repairAssessment,
|
|
358
|
+
...(repairAssessment ? { repairAssessment } : {}),
|
|
359
|
+
...(verifyFailure ? { verifyFailure } : {}),
|
|
125
360
|
...(lintAssessment ? { lintAssessment } : {}),
|
|
126
361
|
sourceFidelity,
|
|
127
362
|
diff: {
|
|
128
363
|
schemaId: diff.schemaId,
|
|
129
364
|
patchPath: diff.patchPath,
|
|
130
365
|
patchSha256: diff.patchSha256,
|
|
366
|
+
reviewSummaryPath: diff.reviewSummaryPath,
|
|
367
|
+
reviewIndexPath: diff.reviewIndexPath,
|
|
131
368
|
changedFiles: diff.changedFiles,
|
|
132
369
|
untrackedFiles: diff.untrackedFiles,
|
|
133
370
|
empty: diff.empty,
|
|
134
371
|
},
|
|
372
|
+
cumulativeDiff,
|
|
135
373
|
};
|
|
136
374
|
const artifactPath = await writeDagRunJsonArtifact(input.runDir, "contracts/frontend-review-context.json", payload);
|
|
137
|
-
return {
|
|
375
|
+
return {
|
|
376
|
+
ok: true,
|
|
377
|
+
artifactPath,
|
|
378
|
+
diff,
|
|
379
|
+
contractRef: payload.contractRef,
|
|
380
|
+
capacity: contractReviewArtifacts.capacity,
|
|
381
|
+
};
|
|
138
382
|
}
|
|
139
383
|
/**
|
|
140
384
|
* 最终 review 核对清单(机械化):ledger 缺失/漂移、canonical requirement 漏项、
|
|
@@ -193,6 +437,8 @@ export function formatFrontendReviewContextStdout(result) {
|
|
|
193
437
|
return [
|
|
194
438
|
"Frontend review context: pass",
|
|
195
439
|
`Artifact: ${result.artifactPath}`,
|
|
440
|
+
`Contract index: ${result.contractRef.indexPath}`,
|
|
441
|
+
`Capacity: ${result.capacity.overallLevel} (${result.capacity.relativePath}, non-blocking)`,
|
|
196
442
|
formatFrontendWorktreeDiffStdout(result.diff),
|
|
197
443
|
].join("\n");
|
|
198
444
|
}
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { readTypedEventStoreFromJsonl, } from "./frontend-typed-event-store.js";
|
|
4
|
+
/**
|
|
5
|
+
* Frontend review findings → downstream work (direction A).
|
|
6
|
+
*
|
|
7
|
+
* When `frontend-review-pi` commits `request_review_changes`, the implementation
|
|
8
|
+
* must not silently finish: the run becomes "not passed" (partial_failed +
|
|
9
|
+
* terminalReason=review-request-changes), and the committed findings are
|
|
10
|
+
* rendered into a downstream-task PRD draft that a later DAG round can consume
|
|
11
|
+
* instead of repeating the previous flow. Rendering is deterministic (pure);
|
|
12
|
+
* only the JSONL read and PRD write are IO.
|
|
13
|
+
*
|
|
14
|
+
* The design-review analog (`request_design_changes`) reuses the same PRD
|
|
15
|
+
* rendering and typed-fact channel: a design rejection is a definite finding
|
|
16
|
+
* (not a protocol/environment error), so it auto-re-enters the next plan round
|
|
17
|
+
* with the committed design findings instead of terminating the run.
|
|
18
|
+
*/
|
|
19
|
+
export const FRONTEND_REVIEW_FINDINGS_PRD_REL = "contracts/frontend-review-findings-prd.md";
|
|
20
|
+
/** Review node id whose committed typed facts drive the findings PRD. */
|
|
21
|
+
export const FRONTEND_REVIEW_NODE_ID = "frontend-review-pi";
|
|
22
|
+
/** Design review node id whose committed typed facts drive the findings PRD. */
|
|
23
|
+
export const FRONTEND_DESIGN_REVIEW_NODE_ID = "frontend-design-review-pi";
|
|
24
|
+
/** Deterministic issueCategory → failure owner mapping (A+B AC-009 §4.10). */
|
|
25
|
+
export function classifyReviewIssueCategoryFailureOwner(issueCategory) {
|
|
26
|
+
switch (issueCategory) {
|
|
27
|
+
case "implementation-mismatch":
|
|
28
|
+
return "implementation";
|
|
29
|
+
case "approved-design-defect":
|
|
30
|
+
return "plan";
|
|
31
|
+
case "target-surface-defect":
|
|
32
|
+
return "scout";
|
|
33
|
+
case "contract-requirement-gap":
|
|
34
|
+
return "contract";
|
|
35
|
+
case "unknown":
|
|
36
|
+
default:
|
|
37
|
+
return "unknown";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const API_SIGNAL_RE = /\b(api|fetch|axios|graphql|endpoint|远程|接口)\b/i;
|
|
41
|
+
/** Negations for the API signal. CJK "无 API/无接口" has no \b boundary, so
|
|
42
|
+
* it is matched separately from the ASCII no api/not-needed forms. */
|
|
43
|
+
const API_NEGATION_RE = /(?:^|[^\w])(?:no\s+api|not-needed)(?:$|[^\w])|无\s*api|无\s*接口/i;
|
|
44
|
+
const SMALL_SIGNAL_WORDS = [
|
|
45
|
+
"单文件局部修复",
|
|
46
|
+
"局部样式",
|
|
47
|
+
"文案调整",
|
|
48
|
+
"无远程接口",
|
|
49
|
+
];
|
|
50
|
+
function topLevelDir(p) {
|
|
51
|
+
const normalized = p.replace(/\\/g, "/");
|
|
52
|
+
const first = normalized.split("/").filter(Boolean)[0] ?? "";
|
|
53
|
+
return first;
|
|
54
|
+
}
|
|
55
|
+
/** Collect unique non-empty finding `file` values (sorted, de-duplicated). */
|
|
56
|
+
function findingTargetFiles(fact) {
|
|
57
|
+
const files = new Set();
|
|
58
|
+
for (const finding of fact.findings) {
|
|
59
|
+
if (typeof finding.file === "string" && finding.file.trim()) {
|
|
60
|
+
files.add(finding.file.trim());
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return [...files].sort();
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Deterministically derive the fix scope for the downstream task. Same fact
|
|
67
|
+
* always yields the same scope. The shape mirrors `resolveFrontendTaskShape`
|
|
68
|
+
* signals (concentrated paths, no remote API → micro/small) so that when the
|
|
69
|
+
* PRD draft is used as the next task's requirement, the frontend DAG generator
|
|
70
|
+
* routes a small fix to micro/small instead of the full 11-node standard
|
|
71
|
+
* topology.
|
|
72
|
+
*/
|
|
73
|
+
export function deriveFrontendReviewFixScope(fact) {
|
|
74
|
+
const targetFiles = findingTargetFiles(fact);
|
|
75
|
+
const blob = fact.findings
|
|
76
|
+
.map((f) => `${f.issue} ${f.requiredChange ?? ""} ${f.file ?? ""}`)
|
|
77
|
+
.join("\n");
|
|
78
|
+
const hasApiSignal = API_SIGNAL_RE.test(blob) && !API_NEGATION_RE.test(blob);
|
|
79
|
+
const topDirs = new Set(targetFiles.map(topLevelDir));
|
|
80
|
+
const concentrated = targetFiles.length > 0 && topDirs.size <= 1 && targetFiles.length <= 4;
|
|
81
|
+
let shape;
|
|
82
|
+
let complexity;
|
|
83
|
+
let reason;
|
|
84
|
+
if (targetFiles.length === 0) {
|
|
85
|
+
shape = "standard";
|
|
86
|
+
complexity = "medium";
|
|
87
|
+
reason =
|
|
88
|
+
"findings carry no file references; scope unknown, keep standard topology";
|
|
89
|
+
}
|
|
90
|
+
else if (hasApiSignal) {
|
|
91
|
+
shape = "standard";
|
|
92
|
+
complexity = "medium";
|
|
93
|
+
reason = `findings reference a remote API signal; standard topology required (files=${targetFiles.length})`;
|
|
94
|
+
}
|
|
95
|
+
else if (concentrated && targetFiles.length <= 1) {
|
|
96
|
+
shape = "micro";
|
|
97
|
+
complexity = "trivial";
|
|
98
|
+
reason = "single-file concentrated fix without remote API";
|
|
99
|
+
}
|
|
100
|
+
else if (concentrated) {
|
|
101
|
+
shape = "small";
|
|
102
|
+
complexity = "low";
|
|
103
|
+
reason = `concentrated fix across ${targetFiles.length} file(s) in one top-level directory`;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
shape = "standard";
|
|
107
|
+
complexity = "medium";
|
|
108
|
+
reason = `fix spans ${targetFiles.length} file(s) across ${topDirs.size} top-level directories; standard topology`;
|
|
109
|
+
}
|
|
110
|
+
const allowedPaths = [
|
|
111
|
+
...new Set([
|
|
112
|
+
...targetFiles,
|
|
113
|
+
...targetFiles.map((f) => `${topLevelDir(f)}/**`),
|
|
114
|
+
]),
|
|
115
|
+
].sort();
|
|
116
|
+
return {
|
|
117
|
+
shape,
|
|
118
|
+
complexity,
|
|
119
|
+
targetFiles,
|
|
120
|
+
allowedPaths,
|
|
121
|
+
smallSignals: [...SMALL_SIGNAL_WORDS],
|
|
122
|
+
hasApiSignal,
|
|
123
|
+
reason,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Read the committed `request_review_changes` fact from the review node's typed
|
|
128
|
+
* event store. Returns undefined when no committed request fact exists (e.g.
|
|
129
|
+
* approve_review, missing store, or an unreadable store).
|
|
130
|
+
*/
|
|
131
|
+
export async function readCommittedReviewRequestFact(input) {
|
|
132
|
+
const { runDir: reviewRunDir } = input;
|
|
133
|
+
const reviewNodeId = input.reviewNodeId ?? FRONTEND_REVIEW_NODE_ID;
|
|
134
|
+
return readCommittedRequestFactByKind({
|
|
135
|
+
runDir: reviewRunDir,
|
|
136
|
+
nodeId: reviewNodeId,
|
|
137
|
+
factsFile: "review-typed-facts.jsonl",
|
|
138
|
+
kind: "request_review_changes",
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Read the committed `request_design_changes` fact from the design review
|
|
143
|
+
* node's typed event store. Returns undefined when no committed request fact
|
|
144
|
+
* exists (e.g. approve_design, missing store, or an unreadable store).
|
|
145
|
+
*/
|
|
146
|
+
export async function readCommittedDesignRequestFact(input) {
|
|
147
|
+
const { runDir: designRunDir } = input;
|
|
148
|
+
const designNodeId = input.designNodeId ?? FRONTEND_DESIGN_REVIEW_NODE_ID;
|
|
149
|
+
return readCommittedRequestFactByKind({
|
|
150
|
+
runDir: designRunDir,
|
|
151
|
+
nodeId: designNodeId,
|
|
152
|
+
factsFile: "design-typed-facts.jsonl",
|
|
153
|
+
kind: "request_design_changes",
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
async function readCommittedRequestFactByKind(input) {
|
|
157
|
+
let records;
|
|
158
|
+
try {
|
|
159
|
+
records = await readTypedEventStoreFromJsonl(path.join(input.runDir, input.nodeId, input.factsFile));
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
const committed = records.filter((record) => record.phase === "committed");
|
|
165
|
+
if (committed.length === 0)
|
|
166
|
+
return undefined;
|
|
167
|
+
const request = committed.find((record) => record.fact.kind === input.kind);
|
|
168
|
+
if (!request)
|
|
169
|
+
return undefined;
|
|
170
|
+
const fact = request.fact;
|
|
171
|
+
if (typeof fact !== "object" || fact === null)
|
|
172
|
+
return undefined;
|
|
173
|
+
return fact;
|
|
174
|
+
}
|
|
175
|
+
function bulletList(items) {
|
|
176
|
+
if (!items || items.length === 0)
|
|
177
|
+
return "_(none)_";
|
|
178
|
+
return items.map((item) => `- ${item}`).join("\n");
|
|
179
|
+
}
|
|
180
|
+
function findingLines(findings) {
|
|
181
|
+
if (findings.length === 0)
|
|
182
|
+
return "_(none)_";
|
|
183
|
+
return findings
|
|
184
|
+
.map((finding) => {
|
|
185
|
+
const location = [finding.file, finding.line]
|
|
186
|
+
.filter((value) => value !== undefined)
|
|
187
|
+
.join(":");
|
|
188
|
+
const prefix = location ? `[${location}] ` : "";
|
|
189
|
+
const requiredChange = finding.requiredChange
|
|
190
|
+
? `\n - requiredChange: ${finding.requiredChange}`
|
|
191
|
+
: "";
|
|
192
|
+
return `- ${prefix}${finding.severity}: ${finding.issue}${requiredChange}`;
|
|
193
|
+
})
|
|
194
|
+
.join("\n");
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Deterministically render a downstream-task PRD draft from a committed
|
|
198
|
+
* `request_review_changes` / `request_design_changes` fact. Same fact always
|
|
199
|
+
* renders byte-identical output; the parent run id and review node id are the
|
|
200
|
+
* only context.
|
|
201
|
+
*/
|
|
202
|
+
export function renderFrontendReviewFindingsPrd(input) {
|
|
203
|
+
const { parentRunId, fact } = input;
|
|
204
|
+
const isDesign = fact.kind === "request_design_changes";
|
|
205
|
+
const reviewNodeId = input.reviewNodeId ??
|
|
206
|
+
(isDesign ? FRONTEND_DESIGN_REVIEW_NODE_ID : FRONTEND_REVIEW_NODE_ID);
|
|
207
|
+
const restartPhase = classifyReviewIssueCategoryFailureOwner(fact.issueCategory);
|
|
208
|
+
const scope = deriveFrontendReviewFixScope(fact);
|
|
209
|
+
const targetSummary = scope.targetFiles.length > 0
|
|
210
|
+
? scope.targetFiles.join(", ")
|
|
211
|
+
: fact.issueCategory;
|
|
212
|
+
return [
|
|
213
|
+
`# 修复 ${isDesign ? "设计" : "review"} 发现(${scope.shape}):${targetSummary}`,
|
|
214
|
+
"",
|
|
215
|
+
"## 背景",
|
|
216
|
+
"",
|
|
217
|
+
`- parentRunId: \`${parentRunId}\``,
|
|
218
|
+
`- reviewNodeId: \`${reviewNodeId}\``,
|
|
219
|
+
`- typedVerdict: \`${fact.kind}\``,
|
|
220
|
+
`- issueCategory: \`${fact.issueCategory}\``,
|
|
221
|
+
`- restartPhase/failureOwner: \`${restartPhase}\``,
|
|
222
|
+
"",
|
|
223
|
+
"## 修复范围(供下一轮任务生成路由)",
|
|
224
|
+
"",
|
|
225
|
+
`- 建议 shape:\`${scope.shape}\`(micro=7 / small=10 / standard=11 节点)`,
|
|
226
|
+
`- 建议 complexity:\`${scope.complexity}\``,
|
|
227
|
+
`- 推导依据:${scope.reason}`,
|
|
228
|
+
...(scope.hasApiSignal ? ["- ⚠️ findings 含远程 API/接口信号,走 standard 拓扑"] : []),
|
|
229
|
+
scope.targetFiles.length > 0
|
|
230
|
+
? [`- 目标文件:${scope.targetFiles.map((f) => `\`${f}\``).join(", ")}`]
|
|
231
|
+
: ["- 目标文件:_(findings 未提供 file 引用,需人工补全)_"],
|
|
232
|
+
scope.allowedPaths.length > 0
|
|
233
|
+
? [`- 建议 allowedPaths:${scope.allowedPaths.map((p) => `\`${p}\``).join(", ")}`]
|
|
234
|
+
: [],
|
|
235
|
+
scope.shape === "micro" || scope.shape === "small"
|
|
236
|
+
? [`- 范围信号:${scope.smallSignals.join("、")}(供 shape 分类器识别,避免退化为全节点流程)`]
|
|
237
|
+
: [],
|
|
238
|
+
"",
|
|
239
|
+
"## 问题清单",
|
|
240
|
+
"",
|
|
241
|
+
findingLines(fact.findings),
|
|
242
|
+
"",
|
|
243
|
+
"## 证据引用",
|
|
244
|
+
"",
|
|
245
|
+
bulletList(fact.evidenceRefs),
|
|
246
|
+
"",
|
|
247
|
+
"## 修复目标",
|
|
248
|
+
"",
|
|
249
|
+
`- 按 issueCategory 映射的重启阶段(${restartPhase})定点修复,不复用上一轮未通过的实现。`,
|
|
250
|
+
"- 逐条关闭上面「问题清单」中的 findings,并保留上一轮已通过的验证目标继续有效。",
|
|
251
|
+
`- 完成修复后重新走 frontend DAG 的 ${isDesign ? "plan → design" : "implement → verify → review"} 链,直到${isDesign ? " design-review 提交 approve_design" : " review 提交 approve_review"}。`,
|
|
252
|
+
"",
|
|
253
|
+
].join("\n");
|
|
254
|
+
}
|
|
255
|
+
/** Write the findings PRD draft under the run dir (run-owned artifact). */
|
|
256
|
+
export async function writeFrontendReviewFindingsPrd(input) {
|
|
257
|
+
const markdown = renderFrontendReviewFindingsPrd(input);
|
|
258
|
+
const abs = path.join(input.runDir, FRONTEND_REVIEW_FINDINGS_PRD_REL);
|
|
259
|
+
await mkdir(path.dirname(abs), { recursive: true });
|
|
260
|
+
await writeFile(abs, markdown, "utf8");
|
|
261
|
+
return FRONTEND_REVIEW_FINDINGS_PRD_REL;
|
|
262
|
+
}
|
|
263
|
+
export async function readFrontendReviewFindingsPrd(runDir) {
|
|
264
|
+
try {
|
|
265
|
+
return await readFile(path.join(runDir, FRONTEND_REVIEW_FINDINGS_PRD_REL), "utf8");
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
}
|