@tea-agent/loop-agent 0.16.25 → 0.17.0

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.
Files changed (115) hide show
  1. package/CHANGELOG.md +46 -0
  2. package/README.md +14 -3
  3. package/dist/cli/command-definitions.js +43 -0
  4. package/dist/cli/program.js +26 -0
  5. package/dist/commands/dag-approve.js +4 -0
  6. package/dist/commands/dag-resume.js +1 -0
  7. package/dist/commands/dag-validate.js +6 -0
  8. package/dist/commands/operator.js +44 -0
  9. package/dist/commands/task-contract.js +271 -0
  10. package/dist/executors/dag-pi-executor.js +118 -16
  11. package/dist/executors/pi-executor.js +206 -13
  12. package/dist/executors/pi-sdk-executor.js +21 -6
  13. package/dist/executors/shell-executor.js +85 -8
  14. package/dist/executors/shell-presets.js +16 -3
  15. package/dist/executors/shell-write-guard.js +64 -2
  16. package/dist/shared/operator/capabilities.js +255 -0
  17. package/dist/shared/operator/envelope.js +59 -0
  18. package/dist/shared/operator/index.js +4 -0
  19. package/dist/shared/operator/registry.js +38 -0
  20. package/dist/shared/operator/types.js +5 -0
  21. package/dist/task/contract/adopt.js +166 -0
  22. package/dist/task/contract/apply.js +326 -0
  23. package/dist/task/contract/canonicalize.js +60 -0
  24. package/dist/task/contract/constants.js +29 -0
  25. package/dist/task/contract/diff.js +177 -0
  26. package/dist/task/contract/hash.js +42 -0
  27. package/dist/task/contract/import-revision.js +96 -0
  28. package/dist/task/contract/index.js +17 -0
  29. package/dist/task/contract/journal.js +155 -0
  30. package/dist/task/contract/lock.js +153 -0
  31. package/dist/task/contract/observe.js +296 -0
  32. package/dist/task/contract/paths.js +19 -0
  33. package/dist/task/contract/project.js +170 -0
  34. package/dist/task/contract/recover.js +312 -0
  35. package/dist/task/contract/request-ledger.js +37 -0
  36. package/dist/task/contract/schema.js +151 -0
  37. package/dist/task/contract/transaction.js +160 -0
  38. package/dist/task/contract/types.js +1 -0
  39. package/dist/task/contract/validate-draft.js +106 -0
  40. package/dist/task/index.js +3 -0
  41. package/dist/task/operator/capabilities.js +6 -0
  42. package/dist/task/operator/envelope.js +2 -0
  43. package/dist/task/operator/index.js +5 -0
  44. package/dist/task/operator/registry.js +2 -0
  45. package/dist/task/operator/types.js +1 -0
  46. package/dist/task/runtime.js +5 -1
  47. package/dist/task/source-references.js +7 -0
  48. package/dist/worker/cli.js +150 -32
  49. package/dist/worker/console/app-data.js +185 -0
  50. package/dist/worker/console/dag-confirmation.js +313 -0
  51. package/dist/worker/console/doctor.js +169 -0
  52. package/dist/worker/console/draft-store.js +80 -0
  53. package/dist/worker/console/index.js +15 -0
  54. package/dist/worker/console/interview/assessment.js +67 -0
  55. package/dist/worker/console/interview/session.js +100 -0
  56. package/dist/worker/console/interview/tools.js +109 -0
  57. package/dist/worker/console/loopback.js +16 -0
  58. package/dist/worker/console/observe-health-match.js +174 -0
  59. package/dist/worker/console/observe-link.js +33 -0
  60. package/dist/worker/console/operation-runner.js +166 -0
  61. package/dist/worker/console/operation-sse.js +158 -0
  62. package/dist/worker/console/operation-store.js +147 -0
  63. package/dist/worker/console/operator-actions.js +769 -0
  64. package/dist/worker/console/pi-readiness.js +94 -0
  65. package/dist/worker/console/recovery-cta.js +133 -0
  66. package/dist/worker/console/repo-fingerprint.js +29 -0
  67. package/dist/worker/console/resource-loader.js +95 -0
  68. package/dist/worker/console/routes.js +368 -0
  69. package/dist/worker/console/security.js +126 -0
  70. package/dist/worker/console/server.js +149 -0
  71. package/dist/worker/console/sibling-controller.js +28 -0
  72. package/dist/worker/console/static/assets/index-CbnMgdWa.js +9 -0
  73. package/dist/worker/console/static/assets/index-Dnj0RVs8.css +1 -0
  74. package/dist/worker/console/static/index.html +13 -0
  75. package/dist/worker/console/vite.config.js +27 -0
  76. package/dist/worker/delivery/git-transaction.js +43 -8
  77. package/dist/worker/observe/health.js +57 -0
  78. package/dist/worker/observe/paths.js +81 -0
  79. package/dist/worker/observe/routes.js +142 -27
  80. package/dist/worker/observe/spec-evidence.js +84 -0
  81. package/dist/worker/observe/static/api.js +23 -0
  82. package/dist/worker/observe/static/state.js +26 -0
  83. package/dist/worker/observe/static/styles.css +10 -0
  84. package/dist/worker/observe/static/views/dag-inspector.js +173 -6
  85. package/dist/workflows/dag/backend-test-analysis-contract.js +34 -9
  86. package/dist/workflows/dag/dynamic-runtime/shared.js +1 -0
  87. package/dist/workflows/dag/frontend-repair.js +1 -10
  88. package/dist/workflows/dag/init-hybrid.js +372 -115
  89. package/dist/workflows/dag/node-execution.js +57 -6
  90. package/dist/workflows/dag/project-governance-context.js +508 -0
  91. package/dist/workflows/dag/prompt.js +46 -1
  92. package/dist/workflows/dag/retry-policy.js +16 -1
  93. package/dist/workflows/dag/runner.js +9 -0
  94. package/dist/workflows/dag/skill-snapshot.js +1 -0
  95. package/dist/workflows/dag/task-contract-binding.js +138 -0
  96. package/dist/workflows/dag/types.js +84 -10
  97. package/dist/workflows/dag/validate.js +53 -7
  98. package/docs/README.md +2 -0
  99. package/docs/architecture/evolution.md +2 -0
  100. package/docs/architecture/system-overview.md +6 -0
  101. package/docs/architecture/worker-and-feature.md +7 -0
  102. package/docs/templates/agent-dag.schema.json +64 -2
  103. package/docs/templates/agent-dag.supervised-implementation.json +1 -0
  104. package/docs/templates/backend-test-dag.classify.prompt.md +1 -1
  105. package/docs/templates/backend-test-dag.generate-pytest.prompt.md +5 -5
  106. package/docs/templates/backend-test-dag.json +26 -154
  107. package/docs/templates/backend-test-dag.retrospect.prompt.md +1 -1
  108. package/docs/templates/backend-test-dag.review-cases.prompt.md +2 -2
  109. package/package.json +8 -2
  110. package/skills/agent-worker/SKILL.md +1 -0
  111. package/skills/agent-worker/references/agent-worker-operator.md +3 -2
  112. package/skills/frontend-design-review/SKILL.md +25 -16
  113. package/skills/frontend-implementation/references/node-contracts.md +5 -5
  114. package/skills/loop-agent/references/command-reference.md +48 -1
  115. package/skills/loop-agent/references/hybrid-dag.md +4 -4
@@ -7,13 +7,15 @@ import { DAG_AGENT_RUNTIME_PI_ONLY, DAG_REPAIR_WRITER_PROTOCOL_EXPLICIT_NODE_V1,
7
7
  import { pathMatchesPattern } from "../../shared/git-progress.js";
8
8
  import { BASELINE_FORBIDDEN_PATHS } from "./governance-constants.js";
9
9
  import { buildDecisionEnvelopePromptContract } from "./decision-envelope.js";
10
- import { DEFAULT_READ_ONLY_PI_RETRY_POLICY, isSafeReadOnlyPiRetryCandidate, } from "./retry-policy.js";
10
+ import { DEFAULT_READ_ONLY_PI_RETRY_POLICY, STRUCTURED_REQUIRED_PI_RETRY_POLICY, isSafeReadOnlyPiRetryCandidate, } from "./retry-policy.js";
11
11
  import { resolveAdapter } from "../../adapters/index.js";
12
12
  import { loadHarnessManifest } from "../../governance/harness.js";
13
13
  import { buildAuthoritySurfaceAuditNode, buildAuthoritySurfaceGateNode, resolveAuthoritySurfaceAudit, } from "./authority-surface.js";
14
14
  import { applySddEmbeddedEnhancements, probeRepoLocalSddSkills, } from "./sdd-embedded.js";
15
+ import { discoverProjectGovernancePresence } from "./project-governance-context.js";
15
16
  import { getTaskPaths, loadTaskConfig } from "../../task/runtime.js";
16
17
  import { materializeTaskReferenceDocs } from "../../task/source-references.js";
18
+ import { observeTaskContract } from "../../task/contract/observe.js";
17
19
  import { resolveVerifyPreset } from "../../executors/shell-verification.js";
18
20
  import { resolveExecutorModelMatrices } from "../../executors/model-routing.js";
19
21
  import { normalizeTaskRequirementText, resolveTaskDagTemplateSelection, } from "./task-demand-routing.js";
@@ -1105,6 +1107,7 @@ export async function loadTaskHybridSources(repoRoot, taskId) {
1105
1107
  executorModelMatrix: resolveExecutorModelMatrices(manifest),
1106
1108
  verifyCommands,
1107
1109
  sddEmbeddedSkills: await probeRepoLocalSddSkills(repoRoot),
1110
+ projectGovernancePresent: await discoverProjectGovernancePresence(repoRoot),
1108
1111
  };
1109
1112
  return sources;
1110
1113
  }
@@ -1377,6 +1380,7 @@ function buildFrontendMockAssessNode(sources, sourceContext, mockContextBlock, f
1377
1380
  subtask_prompt: [
1378
1381
  "Perform read-only Mock assessment and select one safe frontend data strategy.",
1379
1382
  "The first line must be exactly one of: MOCK_STRATEGY: native, MOCK_STRATEGY: browser-intercept, MOCK_STRATEGY: request-adapter, MOCK_STRATEGY: not-needed, or MOCK_STRATEGY: blocked. Do not emit blank lines, headings, or explanatory preamble before it.",
1383
+ "Do not wrap the first line in Markdown bullets, quotes, emphasis, code fences, inline code, Chinese punctuation, or a split label/value form. Put any headings or explanation only after the exact first line.",
1380
1384
  "Prefer an existing native Mock facility. Use browser-intercept only with an existing browser/e2e harness. When no Mock exists but the API layer is writable, use request-adapter by adding a minimal reversible adapter/DI seam within the approved writeSet; the real adapter must remain the production default.",
1381
1385
  autoMaySkipMissingMock
1382
1386
  ? "Auto mode may skip Mock when no project Mock capability is confirmed. Select not-needed with positive evidence from contract/scout that no project Mock capability is confirmed, continue without adding Mock files or dependencies, run the fixed verification entrypoints, and record any unproved real API behavior in Real Integration Gap. Do not block solely because no project Mock capability, browser interception harness, or request adapter exists."
@@ -1400,7 +1404,9 @@ function buildFrontendMockAssessNode(sources, sourceContext, mockContextBlock, f
1400
1404
  "- Do not infer Mock service from lockfile-only or transitive dependency evidence.",
1401
1405
  "- Output MOCK_STRATEGY: blocked if capability evidence conflicts, contract fields are missing/conflicting, paths or dependencies are unauthorized, specs were not actually read, sources conflict, production-default-off cannot be proven, the API layer is not writable for a new adapter, or the frozen entrypoints cannot verify the selected strategy.",
1402
1406
  ...(autoMaySkipMissingMock
1403
- ? ["- In auto mode with no confirmed project Mock capability, do not block solely because Mock is unavailable or Mock-specific verification is absent; choose not-needed and make the Real Integration Gap explicit unless project specs require Mock or another safety rule is violated."]
1407
+ ? [
1408
+ "- In auto mode with no confirmed project Mock capability, do not block solely because Mock is unavailable or Mock-specific verification is absent; choose not-needed and make the Real Integration Gap explicit unless project specs require Mock or another safety rule is violated.",
1409
+ ]
1404
1410
  : []),
1405
1411
  "- Never comment out or replace the real request with inline data, hard-code Mock enablement, import test mocks from a production entrypoint, invent API fields, or place secrets/real user data in fixtures.",
1406
1412
  "- Mock-backed behavior evidence proves the documented frontend contract only; it never proves real API integration.",
@@ -1648,11 +1654,10 @@ function pruneFrontendTasksForRisk(tasks, risk) {
1648
1654
  if (risk.forceFullGates || risk.selectedRisk !== "small") {
1649
1655
  return tasks;
1650
1656
  }
1651
- // small topology: drop first design gate + plan revision + second design cycle;
1657
+ // small topology: drop the initial design review + conditional revision cycle;
1652
1658
  // keep mock assess/gate, single design review (final), contract, implement, verify, repair chain, review.
1653
1659
  const drop = new Set([
1654
1660
  "frontend-design-gate-pi",
1655
- "frontend-first-design-gate-shell",
1656
1661
  "frontend-plan-revision-pi",
1657
1662
  ]);
1658
1663
  // If we drop plan-revision, contract shell must depend on plan-pi instead; final design review depends on plan.
@@ -1668,8 +1673,7 @@ function pruneFrontendTasksForRisk(tasks, risk) {
1668
1673
  next.push("frontend-plan-pi");
1669
1674
  continue;
1670
1675
  }
1671
- if (dep === "frontend-first-design-gate-shell" ||
1672
- dep === "frontend-design-gate-pi") {
1676
+ if (dep === "frontend-design-gate-pi") {
1673
1677
  // skip removed gates
1674
1678
  continue;
1675
1679
  }
@@ -1690,6 +1694,12 @@ function pruneFrontendTasksForRisk(tasks, risk) {
1690
1694
  if (byId.has(need) && !depends_on.includes(need))
1691
1695
  depends_on.push(need);
1692
1696
  }
1697
+ return {
1698
+ ...task,
1699
+ depends_on,
1700
+ dependsPolicy: "all",
1701
+ runIf: undefined,
1702
+ };
1693
1703
  }
1694
1704
  if (task.id === "frontend-requirement-coverage-shell") {
1695
1705
  const shell = task.shell?.requirementCoverageGate
@@ -1700,6 +1710,7 @@ function pruneFrontendTasksForRisk(tasks, risk) {
1700
1710
  fromNodeIds: task.shell.requirementCoverageGate.fromNodeIds.map((nodeId) => nodeId === "frontend-plan-revision-pi"
1701
1711
  ? "frontend-plan-pi"
1702
1712
  : nodeId),
1713
+ fallbackFromNodeIds: undefined,
1703
1714
  },
1704
1715
  }
1705
1716
  : task.shell;
@@ -1718,12 +1729,25 @@ function pruneFrontendTasksForRisk(tasks, risk) {
1718
1729
  ? {
1719
1730
  ...task.shell.jsonArtifactGate,
1720
1731
  fromNodeId: "frontend-plan-pi",
1732
+ fallbackFromNodeIds: undefined,
1721
1733
  }
1722
1734
  : task.shell.jsonArtifactGate,
1723
1735
  }
1724
1736
  : task.shell;
1725
1737
  return { ...task, depends_on: nextDeps, shell };
1726
1738
  }
1739
+ if (task.id === "frontend-final-design-gate-shell") {
1740
+ const shell = task.shell?.verdictGate
1741
+ ? {
1742
+ ...task.shell,
1743
+ verdictGate: {
1744
+ ...task.shell.verdictGate,
1745
+ fallbackFromNodeIds: undefined,
1746
+ },
1747
+ }
1748
+ : task.shell;
1749
+ return { ...task, depends_on, dependsPolicy: "all", shell };
1750
+ }
1727
1751
  if (task.id === "frontend-implement-pi") {
1728
1752
  // still requires final design gate
1729
1753
  for (const need of [
@@ -1828,7 +1852,7 @@ function buildFrontendHybridDagFromTask(sources) {
1828
1852
  ? [`See 执行约束.md in task source (${sources.taskId})`]
1829
1853
  : []),
1830
1854
  ...STANDARD_GLOBAL_CONSTRAINTS,
1831
- "Frontend implementation DAGs must pass the final design verdict gate before any write node executes; the first design gate also accepts request-revision for plan revision only.",
1855
+ "Frontend implementation DAGs must pass the effective final design verdict gate before any write node executes; an initial pass uses the original plan, while request-revision selects the read-only revision and final-review branch.",
1832
1856
  "Final design gate pass is the only authorization for frontend implementation writes.",
1833
1857
  "Plan revision remains read-only and never edits business code.",
1834
1858
  "Design revision failures route to replan-and-rerun, never dev-fix.",
@@ -1960,6 +1984,8 @@ function buildFrontendHybridDagFromTask(sources) {
1960
1984
  executor: "pi",
1961
1985
  complexity: "MED",
1962
1986
  writePolicy: "read-only",
1987
+ outputMode: "structured-required",
1988
+ retryPolicy: STRUCTURED_REQUIRED_PI_RETRY_POLICY,
1963
1989
  allowedPaths: readOnlyPaths,
1964
1990
  forbiddenPaths,
1965
1991
  skills: FRONTEND_IMPLEMENTATION_SKILLS,
@@ -1998,53 +2024,27 @@ function buildFrontendHybridDagFromTask(sources) {
1998
2024
  sourceContext,
1999
2025
  ].join("\n\n"),
2000
2026
  },
2001
- {
2002
- id: "frontend-first-design-gate-shell",
2003
- depends_on: ["frontend-design-gate-pi"],
2004
- role: "verifier",
2005
- executor: "shell",
2006
- complexity: "LOW",
2007
- writePolicy: "read-only",
2008
- allowedPaths: readOnlyPaths,
2009
- forbiddenPaths,
2010
- outputContract: "Deterministic first design verdict gate: exit 0 when frontend-design-gate-pi emits VERDICT: pass or VERDICT: request-revision. Does not authorize code writes.",
2011
- subtask_prompt: "Deterministic gate: validate frontend-design-gate-pi first-line VERDICT is pass or request-revision; block downstream only on malformed/unexpected verdict.",
2012
- shell: {
2013
- commands: [],
2014
- verdictGate: {
2015
- fromNodeId: "frontend-design-gate-pi",
2016
- accept: ["VERDICT: pass", "VERDICT: request-revision"],
2017
- label: "frontend first design gate",
2018
- lineMode: "first-verdict-line",
2019
- },
2020
- cwd: ".",
2021
- timeoutMs: 60000,
2022
- },
2023
- },
2024
2027
  {
2025
2028
  id: "frontend-plan-revision-pi",
2026
2029
  depends_on: [
2027
- "frontend-first-design-gate-shell",
2028
2030
  "frontend-plan-pi",
2029
2031
  "frontend-design-gate-pi",
2030
2032
  "frontend-mock-assess-pi",
2031
2033
  ],
2034
+ runIf: "$.nodes['frontend-design-gate-pi'].firstVerdictLine == 'VERDICT: request-revision'",
2032
2035
  role: "planner",
2033
2036
  executor: "pi",
2034
2037
  complexity: "MED",
2035
2038
  writePolicy: "read-only",
2039
+ outputMode: "structured-required",
2040
+ retryPolicy: STRUCTURED_REQUIRED_PI_RETRY_POLICY,
2036
2041
  allowedPaths: readOnlyPaths,
2037
2042
  forbiddenPaths,
2038
2043
  skills: FRONTEND_IMPLEMENTATION_SKILLS,
2039
- outputContract: "Markdown revision plan followed by exactly one fenced json object conforming to frontend-implementation-contract-v1. The JSON is the authoritative materialization input. No file writes.",
2044
+ outputContract: "When the initial design review requests revision, return a complete Markdown revision plan followed by exactly one fenced json object conforming to frontend-implementation-contract-v1. The JSON is the authoritative materialization input. No file writes.",
2040
2045
  subtask_prompt: [
2041
2046
  "Consume frontend-plan-pi (original plan) and frontend-design-gate-pi (first design review findings).",
2042
- "If the first design gate passed (VERDICT: pass from frontend-design-gate-pi), output exactly:",
2043
- "PASS_NO_REVISION_NEEDED",
2044
- "The original plan from frontend-plan-pi is confirmed and does not require changes.",
2045
- "Then reproduce a complete Requirement Coverage section containing every explicit REQ-/BR-/AC- identifier from the authoritative task sources so this node is the single effective-plan evidence source for the deterministic coverage gate.",
2046
- "",
2047
- "If the first design gate requested revision (VERDICT: request-revision), produce a complete revised implementation plan that addresses every Required Plan Correction from the design findings.",
2047
+ "This node runs only when frontend-design-gate-pi emitted VERDICT: request-revision. Produce a complete revised implementation plan that addresses every Required Plan Correction from the design findings.",
2048
2048
  "The revised plan must include Requirement Coverage, Implementation Steps, Target Files, UI State Handling, Styling / Component Strategy, Interaction Notes, Mock / API Strategy, Dependency Policy, Verification Plan, Real Integration Gap, and Residual Risks.",
2049
2049
  requirementCoverageInstruction,
2050
2050
  "Do not turn MOCK_STRATEGY: blocked into an implementable strategy without new repository or contract evidence that resolves every blocker.",
@@ -2058,7 +2058,8 @@ function buildFrontendHybridDagFromTask(sources) {
2058
2058
  ? [
2059
2059
  {
2060
2060
  id: "frontend-requirement-coverage-shell",
2061
- depends_on: ["frontend-plan-revision-pi"],
2061
+ depends_on: ["frontend-plan-revision-pi", "frontend-plan-pi"],
2062
+ dependsPolicy: "all-or-condition-skip",
2062
2063
  role: "verifier",
2063
2064
  executor: "shell",
2064
2065
  complexity: "LOW",
@@ -2071,6 +2072,7 @@ function buildFrontendHybridDagFromTask(sources) {
2071
2072
  commands: [],
2072
2073
  requirementCoverageGate: {
2073
2074
  fromNodeIds: ["frontend-plan-revision-pi"],
2075
+ fallbackFromNodeIds: ["frontend-plan-pi"],
2074
2076
  requiredIds: requirementIds,
2075
2077
  label: "frontend requirement coverage",
2076
2078
  },
@@ -2082,7 +2084,8 @@ function buildFrontendHybridDagFromTask(sources) {
2082
2084
  : []),
2083
2085
  {
2084
2086
  id: "frontend-implementation-contract-shell",
2085
- depends_on: ["frontend-plan-revision-pi"],
2087
+ depends_on: ["frontend-plan-revision-pi", "frontend-plan-pi"],
2088
+ dependsPolicy: "all-or-condition-skip",
2086
2089
  role: "verifier",
2087
2090
  executor: "shell",
2088
2091
  complexity: "LOW",
@@ -2095,6 +2098,7 @@ function buildFrontendHybridDagFromTask(sources) {
2095
2098
  commands: [],
2096
2099
  jsonArtifactGate: {
2097
2100
  fromNodeId: "frontend-plan-revision-pi",
2101
+ fallbackFromNodeIds: ["frontend-plan-pi"],
2098
2102
  schemaId: "frontend-implementation-contract-v1",
2099
2103
  artifactName: "frontend-implementation-contract.json",
2100
2104
  outputDir: "contracts",
@@ -2115,6 +2119,8 @@ function buildFrontendHybridDagFromTask(sources) {
2115
2119
  ? ["frontend-requirement-coverage-shell"]
2116
2120
  : []),
2117
2121
  ],
2122
+ dependsPolicy: "all-or-condition-skip",
2123
+ runIf: "$.nodes['frontend-design-gate-pi'].firstVerdictLine == 'VERDICT: request-revision'",
2118
2124
  role: "reviewer",
2119
2125
  executor: "pi",
2120
2126
  complexity: "MED",
@@ -2122,12 +2128,11 @@ function buildFrontendHybridDagFromTask(sources) {
2122
2128
  allowedPaths: readOnlyPaths,
2123
2129
  forbiddenPaths,
2124
2130
  skills: FRONTEND_DESIGN_REVIEW_SKILLS,
2125
- outputContract: "Plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision, followed by Findings and Checked Items. No file writes.",
2131
+ outputContract: "For the effective frontend plan, return plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision, followed by Findings and Checked Items. No file writes.",
2126
2132
  subtask_prompt: [
2127
- "Audit the revised (or confirmed) frontend plan from frontend-plan-revision-pi before implementation.",
2133
+ "Audit the effective frontend plan before implementation. In the standard topology this node runs after request-revision and consumes frontend-plan-revision-pi; in a small-risk topology where that branch is pruned, audit frontend-plan-pi directly.",
2128
2134
  "First non-empty line must be exactly VERDICT: pass or VERDICT: request-revision.",
2129
- "If frontend-plan-revision-pi returned PASS_NO_REVISION_NEEDED, confirm the original plan against all design constraints and task requirements. Re-verify that all applicable UI states are covered, dependencies are authorized, and deterministic verification commands are present.",
2130
- "If frontend-plan-revision-pi revised the plan, verify that every Required Plan Correction from the first design review has been fully addressed.",
2135
+ "When an initial design review requested revision, verify that every Required Plan Correction has been fully addressed. In the small-risk topology, independently audit the original plan against the full checklist because there is no initial-review finding set.",
2131
2136
  "Recheck the selected Mock / API strategy, contract-to-fixture mapping, authorized paths/dependencies, explicit activation, production-default-off behavior, behavior verification, and Real Integration Gap. MOCK_STRATEGY: blocked cannot receive VERDICT: pass.",
2132
2137
  "The frontend requirement coverage gate has verified that every explicit REQ-/BR-/AC- identifier remains present in the current-run plan evidence; review the mapped behavior rather than accepting identifier presence alone.",
2133
2138
  "Request revision if any design gap remains, if corrections are incomplete, or if the revised plan introduces new unaddressed issues.",
@@ -2138,19 +2143,24 @@ function buildFrontendHybridDagFromTask(sources) {
2138
2143
  },
2139
2144
  {
2140
2145
  id: "frontend-final-design-gate-shell",
2141
- depends_on: ["frontend-final-design-review-pi"],
2146
+ depends_on: [
2147
+ "frontend-final-design-review-pi",
2148
+ "frontend-design-gate-pi",
2149
+ ],
2150
+ dependsPolicy: "all-or-condition-skip",
2142
2151
  role: "verifier",
2143
2152
  executor: "shell",
2144
2153
  complexity: "LOW",
2145
2154
  writePolicy: "read-only",
2146
2155
  allowedPaths: readOnlyPaths,
2147
2156
  forbiddenPaths,
2148
- outputContract: "Deterministic final design verdict gate: exit 0 only when frontend-final-design-review-pi emits VERDICT: pass. This is the sole authorization for frontend implementation writes.",
2149
- subtask_prompt: "Deterministic gate: block frontend implementation unless frontend-final-design-review-pi emitted VERDICT: pass.",
2157
+ outputContract: "Deterministic effective design verdict gate: resolve the selected design review output and exit 0 only on VERDICT: pass. This is the sole authorization for frontend implementation writes.",
2158
+ subtask_prompt: "Deterministic gate: block frontend implementation unless the effective design review emitted VERDICT: pass.",
2150
2159
  shell: {
2151
2160
  commands: [],
2152
2161
  verdictGate: {
2153
2162
  fromNodeId: "frontend-final-design-review-pi",
2163
+ fallbackFromNodeIds: ["frontend-design-gate-pi"],
2154
2164
  accept: ["VERDICT: pass"],
2155
2165
  label: "frontend final design gate",
2156
2166
  lineMode: "first-verdict-line",
@@ -2169,6 +2179,7 @@ function buildFrontendHybridDagFromTask(sources) {
2169
2179
  "frontend-plan-pi",
2170
2180
  "frontend-mock-assess-pi",
2171
2181
  ],
2182
+ dependsPolicy: "all-or-condition-skip",
2172
2183
  role: "implementer",
2173
2184
  executor: "pi",
2174
2185
  toolProfile: "write",
@@ -2184,7 +2195,7 @@ function buildFrontendHybridDagFromTask(sources) {
2184
2195
  "Execute in fixed stages and report each in the delivery summary: (1) Contract confirm, (2) Tests sync, (3) Component/UI state implementation, (4) API/Mock wiring per contract.mockApi, (5) Focused checks behind frozen entrypoints only, (6) Diff cleanup.",
2185
2196
  "Map every requirement id and applicable UI state from the contract to concrete files. Do not invent shell verification commands; only frozen static/behavior entrypoints will run.",
2186
2197
  "Implement only the approved Mock strategy from frontend-mock-assess-pi as carried in the contract. Preserve the real request path as the default, require explicit test/dev activation, and never comment out or replace the real request with inline data.",
2187
- "The frontend-final-design-review-pi verdict confirmed the plan is ready. Stay within writeSet and preserve unrelated files.",
2198
+ "frontend-final-design-gate-shell confirmed the effective initial-or-final design verdict is pass. Stay within writeSet and preserve unrelated files.",
2188
2199
  "For native, browser-intercept, or request-adapter, implement contract-aligned fixtures/states and a dev/test-only activation boundary in this same writer. For not-needed, do not add Mock files or a framework and state the positive reason.",
2189
2200
  "Do not write root artifacts/** unless explicitly included in writeSet. Do not claim Browser/visual verification.",
2190
2201
  writerDeliveryContract(taskConfig),
@@ -2309,6 +2320,7 @@ function buildFrontendHybridDagFromTask(sources) {
2309
2320
  "frontend-failure-assess-shell",
2310
2321
  implementId,
2311
2322
  ],
2323
+ runIf: "$.nodes['frontend-failure-assess-shell'].json.eligible == true",
2312
2324
  role: "implementer",
2313
2325
  executor: "pi",
2314
2326
  toolProfile: "write",
@@ -2318,10 +2330,10 @@ function buildFrontendHybridDagFromTask(sources) {
2318
2330
  allowedPaths: implementPaths.allowedPaths,
2319
2331
  forbiddenPaths,
2320
2332
  skills: FRONTEND_IMPLEMENTATION_SKILLS,
2321
- outputContract: "Repair summary or explicit no-op when assessment has no failure/eligible=false path already gated. Must not expand writeSet, re-interpret requirements, skip tests, or enable Mock by default.",
2333
+ outputContract: "Repair summary for an eligible repairable assessment. Must not expand writeSet, re-interpret requirements, skip tests, or enable Mock by default.",
2322
2334
  subtask_prompt: [
2323
2335
  "Read contracts/frontend-repair-assessment.json and the validated frontend implementation contract.",
2324
- "If failedNodeIds is empty, return no-op with evidence. If eligible repairable failure, apply the smallest fix inside the original implement writeSet only.",
2336
+ "This node runs only for eligible=true. Apply the smallest fix for the classified repairable failure inside the original implement writeSet only.",
2325
2337
  "Do not change lint/type/test config, do not add .skip/.only, do not comment out real requests, do not default-enable Mock, do not add dependencies.",
2326
2338
  "Do not re-plan requirements or expand allowed paths. Browser/visual remain not-run.",
2327
2339
  writerDeliveryContract(taskConfig),
@@ -2397,6 +2409,7 @@ function buildFrontendHybridDagFromTask(sources) {
2397
2409
  "frontend-repair-pi",
2398
2410
  implementId,
2399
2411
  ],
2412
+ dependsPolicy: "all-or-condition-skip",
2400
2413
  role: "verifier",
2401
2414
  executor: "shell",
2402
2415
  complexity: "LOW",
@@ -2404,7 +2417,7 @@ function buildFrontendHybridDagFromTask(sources) {
2404
2417
  allowedPaths: readOnlyPaths,
2405
2418
  forbiddenPaths,
2406
2419
  outputContract: "Run-owned actual worktree diff_patch (artifacts/diff_patch.patch) plus contracts/frontend-worktree-diff.json inventory/hash for review. No product worktree writes.",
2407
- subtask_prompt: "Capture the authoritative actual diff after implement/repair/reverify so frontend-review-pi can audit changed files without relying on failure-path patches or model summaries.",
2420
+ subtask_prompt: "Capture the authoritative actual diff after implementation and any selected repair/reverify branch so frontend-review-pi can audit changed files without relying on failure-path patches or model summaries.",
2408
2421
  shell: {
2409
2422
  commands: ["frontend-worktree-diff-gate"],
2410
2423
  cwd: ".",
@@ -2419,11 +2432,15 @@ function buildFrontendHybridDagFromTask(sources) {
2419
2432
  "frontend-static-reverify-shell",
2420
2433
  "frontend-behavior-reverify-shell",
2421
2434
  "frontend-repair-pi",
2435
+ "frontend-verification-trace-shell",
2436
+ "frontend-static-verify-shell",
2437
+ "frontend-behavior-verify-shell",
2422
2438
  "frontend-failure-assess-shell",
2423
2439
  implementId,
2424
2440
  "frontend-implementation-contract-shell",
2425
2441
  "frontend-contract-pi",
2426
2442
  "frontend-plan-pi",
2443
+ "frontend-design-gate-pi",
2427
2444
  "frontend-plan-revision-pi",
2428
2445
  "frontend-final-design-review-pi",
2429
2446
  "frontend-mock-assess-pi",
@@ -2431,6 +2448,7 @@ function buildFrontendHybridDagFromTask(sources) {
2431
2448
  ? ["frontend-mock-verify-shell"]
2432
2449
  : []),
2433
2450
  ],
2451
+ dependsPolicy: "all-or-condition-skip",
2434
2452
  role: "reviewer",
2435
2453
  executor: "pi",
2436
2454
  complexity: "HIGH",
@@ -2443,9 +2461,9 @@ function buildFrontendHybridDagFromTask(sources) {
2443
2461
  "Review the frontend implementation and verification evidence.",
2444
2462
  "First non-empty line must be exactly VERDICT: pass or VERDICT: request-revision.",
2445
2463
  "Any Critical or Important finding must force VERDICT: request-revision.",
2446
- "Read the validated frontend-implementation-contract, frontend-verification-trace evidence, static/behavior shell facts, and the run-owned actual diff from frontend-worktree-diff-shell (contracts/frontend-worktree-diff.json + artifacts/diff_patch.patch). Do not claim actual diff is missing when those artifacts exist; do not invent a diff from the implementation summary alone. Trace proves command/file/symbol binding only—not semantic correctness.",
2464
+ "Read the validated frontend-implementation-contract, the effective initial-or-post-repair frontend verification trace, the corresponding static/behavior shell facts, and the run-owned actual diff from frontend-worktree-diff-shell (contracts/frontend-worktree-diff.json + artifacts/diff_patch.patch). Do not claim actual diff is missing when those artifacts exist; do not invent a diff from the implementation summary alone. Trace proves command/file/symbol binding only—not semantic correctness.",
2447
2465
  "Flag .skip/.only, deleted or weakened tests, unauthorized config changes, Mock-only evidence claimed as real integration, and Browser/visual claims (always not-run in this workflow).",
2448
- "Use the direct contract, original plan, revision/no-op result, and final design review to reconstruct the approved plan and design verdict; do not infer them from the implementation summary.",
2466
+ "Use the direct contract and the effective plan/design branch: original plan plus initial pass when revision was skipped, or revised plan plus final design review when revision ran. Do not infer them from the implementation summary.",
2449
2467
  "Treat a commented-out real request, default-enabled Mock, production entrypoint importing test mocks, API/fixture contract drift, unauthorized Mock dependency/path, or missing behavior evidence for the selected strategy as at least Important. Mock strategies require Mock-backed evidence. not-needed requires applicable real/no-remote behavior evidence unless auto mode explicitly skipped Mock because no project Mock capability exists; in that case verify that the real request remains the default and the Real Integration Gap is preserved.",
2450
2468
  "Inspect the production/default-real-path static evidence directly and require Mock activation to be off for that check.",
2451
2469
  "Distinguish Mock-backed evidence from real API integration evidence and preserve the Real Integration Gap when the backend was not exercised.",
@@ -2486,6 +2504,9 @@ function buildFrontendHybridDagFromTask(sources) {
2486
2504
  "frontend-static-reverify-shell",
2487
2505
  "frontend-behavior-reverify-shell",
2488
2506
  "frontend-repair-pi",
2507
+ "frontend-verification-trace-shell",
2508
+ "frontend-static-verify-shell",
2509
+ "frontend-behavior-verify-shell",
2489
2510
  "frontend-failure-assess-shell",
2490
2511
  "frontend-implementation-contract-shell",
2491
2512
  "frontend-mock-assess-pi",
@@ -2493,6 +2514,7 @@ function buildFrontendHybridDagFromTask(sources) {
2493
2514
  ? ["frontend-mock-verify-shell"]
2494
2515
  : []),
2495
2516
  ],
2517
+ dependsPolicy: "all-or-condition-skip",
2496
2518
  role: "closeout",
2497
2519
  executor: "pi",
2498
2520
  complexity: "MED",
@@ -2505,7 +2527,7 @@ function buildFrontendHybridDagFromTask(sources) {
2505
2527
  outputContract: "Markdown closeout summary with Changes, Mock Decision / Strategy / Files / Verification / Production Boundary, Verification Evidence, Review Result, Frontend Status, Real Integration Status, Known Risks, and Follow-up. No file writes.",
2506
2528
  subtask_prompt: [
2507
2529
  "Return a frontend closeout summary covering Mock decision/strategy/files/verification/production boundary, changes, verification evidence, review result, known risks, and follow-up.",
2508
- "Include a coverage matrix for each requirement id, applicable UI state, and verification target/check with status passed|failed|not-run|blocked|unavailable. Always state Browser accessibility verification: not-run and Visual regression: not-run. Use frontend-verification-trace facts; do not invent Browser evidence from component tests.",
2530
+ "Include a coverage matrix for each requirement id, applicable UI state, and verification target/check with status passed|failed|not-run|blocked|unavailable. Always state Browser accessibility verification: not-run and Visual regression: not-run. Use the effective initial-or-post-repair frontend verification trace facts; do not invent Browser evidence from component tests.",
2509
2531
  `When only Mock-backed evidence passed, state exactly Frontend status: mock-validated and Real integration: pending, summarize the Real Integration Gap, and name ${taskConfig.taskId}-real-api-integration-verify as the explicit follow-up task to create/run after backend readiness. This follow-up is not auto-created or auto-executed. Never describe Mock evidence as real API integration.`,
2510
2532
  `When Mock was skipped in auto mode and no real API evidence passed, state exactly Frontend status: locally-validated and Real integration: pending, summarize the Real Integration Gap, and name ${taskConfig.taskId}-real-api-integration-verify as the explicit follow-up task when backend readiness matters.`,
2511
2533
  "Read-only: do not modify code, docs, artifacts, or .harness/dag-runs/.",
@@ -2707,7 +2729,7 @@ const BACKEND_TEST_CASE_MANIFEST_OUTPUT_INSTRUCTIONS = [
2707
2729
  "Every case must map to at least one semantically applicable explicit AC-* in acIds. If no AC applies, omit that case and bind an evidence gap to the nearest applicable acId or caseId; never emit an unbound informational gap.",
2708
2730
  "acIds MUST exactly match the explicit AC-* values in the written case body; do not infer ACs from Business Rules or summary matrices.",
2709
2731
  "Use full BE-<MODULE>-<NNN> caseId strings. Do not invent coverage percentages and do not emit coverageSummary; shell always writes the canonical summary.",
2710
- "Minimal shape example: {\"schemaVersion\":1,\"sourceBinding\":{\"taskId\":\"...\",\"requirementPath\":\"source/需求.md\",\"requirementSha256\":\"<64 lowercase hex>\",\"referencePaths\":[],\"requirementIds\":[\"AC-001\"]},\"cases\":[{\"caseId\":\"BE-MODULE-001\",\"acIds\":[\"AC-001\"],\"title\":\"...\",\"category\":\"positive\",\"automationStatus\":\"planned\",\"evidenceRef\":\"testcase/md/module.md\"}],\"evidenceGaps\":[]}",
2732
+ 'Minimal shape example: {"schemaVersion":1,"sourceBinding":{"taskId":"...","requirementPath":"source/需求.md","requirementSha256":"<64 lowercase hex>","referencePaths":[],"requirementIds":["AC-001"]},"cases":[{"caseId":"BE-MODULE-001","acIds":["AC-001"],"title":"...","category":"positive","automationStatus":"planned","evidenceRef":"testcase/md/module.md"}],"evidenceGaps":[]}',
2711
2733
  ].join("\n\n");
2712
2734
  function buildEmitBackendCaseManifestNode(sources, options) {
2713
2735
  return {
@@ -2797,7 +2819,7 @@ function buildReviewBackendCasesNode(sources, options) {
2797
2819
  writePolicy: "read-only",
2798
2820
  allowedPaths: commonReadOnlyPaths(sources),
2799
2821
  forbiddenPaths: commonForbiddenPaths(sources),
2800
- outputContract: "Plain Markdown whose first non-empty line is VERDICT: pass or VERDICT: request-revision; followed by Findings and Coverage Assessment. No file writes. The deterministic gate accepts pass only; request-revision ends this run.",
2822
+ outputContract: "advisory case review evidence whose first non-empty line is VERDICT: pass or VERDICT: request-revision; followed by Findings and Coverage Assessment. No file writes; this review neither authorizes nor blocks pytest generation.",
2801
2823
  subtask_prompt: [
2802
2824
  "Review the generated backend functional test cases under testcase/md/ and the validated Case Manifest v1.",
2803
2825
  "",
@@ -2828,7 +2850,7 @@ function buildReviewBackendCasesNode(sources, options) {
2828
2850
  "## Verdict Rules:",
2829
2851
  "- All Critical checks pass + Important findings ≤ 2 → VERDICT: pass",
2830
2852
  "- Any Critical fails OR Important > 2 → VERDICT: request-revision",
2831
- "- Any request-revision verdict ends the current run at the deterministic gate; describe findings clearly for an independent follow-up task.",
2853
+ "- Any request-revision verdict is advisory evidence for canonical context, retrospective, and L-5; it does not authorize or block the pytest writer.",
2832
2854
  "",
2833
2855
  "## Output After Verdict:",
2834
2856
  "1. Coverage Assessment table (AC → full BE-* case IDs) using manifest + MD",
@@ -2884,8 +2906,8 @@ function buildGenerateBackendPytestNode(sources) {
2884
2906
  return {
2885
2907
  id: "generate-backend-pytest-pi",
2886
2908
  depends_on: [
2887
- "review-backend-cases-gate-shell",
2888
- "backend-test-execution-contract-shell",
2909
+ "backend-test-case-manifest-shell",
2910
+ "validate-backend-test-contracts-shell",
2889
2911
  ],
2890
2912
  role: "implementer",
2891
2913
  executor: "pi",
@@ -2905,11 +2927,11 @@ function buildGenerateBackendPytestNode(sources) {
2905
2927
  // 注意:Pi 节点超时由 executor 层控制(默认 30 分钟)
2906
2928
  // 如需调整,在 harness.json 的 executors.pi 中配置 modelConfig.timeoutMs
2907
2929
  subtask_prompt: [
2908
- "Convert the reviewed test cases under testcase/md/ into pytest automation code.",
2930
+ "Convert the validated test cases under testcase/md/ into pytest automation code.",
2909
2931
  "",
2910
2932
  "## Inputs (MUST use validated contracts):",
2911
- "- Reviewed cases under testcase/md/ (after review-backend-cases-gate-shell).",
2912
- "- Validated Backend Test Analysis v1 under the current run contracts/ (analysis gate).",
2933
+ "- Validated cases under testcase/md/ and contracts/backend-test-case-manifest.json (case review runs independently as advisory evidence).",
2934
+ "- Validated Backend Test Analysis v2 under contracts/backend-test-analysis.json.",
2913
2935
  "- Validated Backend Test Execution Contract v1 under contracts/backend-test-execution.json (execution gate).",
2914
2936
  "Use only fixture names, env NAMES, testRoot, targetMode, and field/API facts already present in those contracts or reviewed cases. Do not invent production credentials or secret values.",
2915
2937
  "When targetMode is in-process (including demoted local npm/node managed servers): bootstrap the service inside function-scoped pytest fixtures under testcase/** — e.g. subprocess node server.js / startWelcomeServer with PORT=0 — and never require host-injected base URL env vars (clean-env shell will not provide WELCOME_BASE_URL / API_BASE_URL).",
@@ -3025,7 +3047,7 @@ function buildBackendTestSemanticReviewNode(sources, options = {}) {
3025
3047
  "Return exactly one pure JSON object with only verdict, findings, summary; no Markdown fence or surrounding prose.",
3026
3048
  "verdict must be pass or request-revision. Each findings[] item must contain exactly severity, caseId, testFile, testSymbol, contractRefs, issue, requiredChange.",
3027
3049
  "severity must be exactly Critical, Important, or Informational; contractRefs must be a non-empty string array. A request-revision verdict requires at least one finding; pass must not contain Critical findings.",
3028
- "Minimal shape: {\"verdict\":\"pass\",\"findings\":[],\"summary\":\"No contract-backed semantic contradiction found.\"}",
3050
+ 'Minimal shape: {"verdict":"pass","findings":[],"summary":"No contract-backed semantic contradiction found."}',
3029
3051
  "Check responseBody.kind (array vs object/items), ordering, field comparison (especially parseable-only date-time precision), documented status/error fields, and each caseId→symbol assertion meaning.",
3030
3052
  "Do not use aliases such as file, symbol, refs, finding, or requiredFix; the strict contract requires testFile, testSymbol, contractRefs, issue, requiredChange.",
3031
3053
  "request-revision only for concrete semantic contradiction with reviewed cases/formal analysis evidence. No style findings.",
@@ -3123,7 +3145,7 @@ function buildClassifyBackendTestResultNode(sources) {
3123
3145
  "Read-only classifier for Backend Test Result v1.",
3124
3146
  "Return exactly one JSON object (prefer pure JSON; single fenced json block tolerated; no trailing prose).",
3125
3147
  "The object must contain exactly category, evidence, confidence, notes. evidence must be a non-empty array of strings, confidence must be a number from 0 through 1, and notes must be a non-empty string. Do not emit schemaVersion or custom fields.",
3126
- "Minimal shape: {\"category\":\"Unknown\",\"evidence\":[\"outcome=completed-with-failures\"],\"confidence\":0.5,\"notes\":\"Single-run evidence is insufficient for a stronger classification.\"}",
3148
+ 'Minimal shape: {"category":"Unknown","evidence":["outcome=completed-with-failures"],"confidence":0.5,"notes":"Single-run evidence is insufficient for a stronger classification."}',
3127
3149
  "Read contracts/backend-test-result-initial.json (run-owned initial Result v1). Do NOT invent pass rates from raw logs.",
3128
3150
  "category must be one of: ProductBug, TestBug, EnvFailure, ContractMismatch, FlakyTest, Unknown.",
3129
3151
  "Hard constraints:",
@@ -3176,7 +3198,7 @@ function buildTestRetrospectNode(sources) {
3176
3198
  "- Code coverage MUST come only from the validated contracts/code-coverage-v1.json artifact generated by coverage.py/pytest-cov or JaCoCo. Show line, branch, function/method, covered, total, ratio, threshold, status, source scope, requirement IDs, tool, commit, and artifact hash.",
3177
3199
  "- Stability MUST come from independent Stability Evidence: use successfulRuns / recordedRuns, same suite/version, and require n≥5; a single run is unavailable.",
3178
3200
  "- Use classify-backend-test-result-pi JSON as interpretive evidence only.",
3179
- "- NEVER rewrite a failed result as passed. Outcome gate (not this report) is authoritative for task success.",
3201
+ "- NEVER rewrite a failed result as passed. Result v1 is authoritative for testOutcome; pipeline completion and L-5 readiness are separate conclusions.",
3180
3202
  "",
3181
3203
  "## Report Structure:",
3182
3204
  "1. Maturity Rating with rationale",
@@ -3243,7 +3265,7 @@ function buildL5MetricsNode(sources) {
3243
3265
  "The direct upstream test-retrospect-pi output is the primary report to assess. Read it together with the run-owned Result v1, Case Manifest v1, Code Coverage v1, and Stability Evidence artifacts when present.",
3244
3266
  "Do not create a new DAG, rewrite the retrospective report, change test outcome, or modify any repository file.",
3245
3267
  "Return exactly one JSON object and no surrounding prose.",
3246
- "Required shape: {\"status\":\"ready\"|\"not-ready\",\"metrics\":{\"passRate\":metric,\"acCoverage\":metric,\"automationCoverage\":metric,\"stability\":metric,\"lineCoverage\":metric,\"branchCoverage\":metric,\"skipped\":metric,\"criticalRisks\":metric},\"blockingItems\":[string]}.",
3268
+ 'Required shape: {"status":"ready"|"not-ready","metrics":{"passRate":metric,"acCoverage":metric,"automationCoverage":metric,"stability":metric,"lineCoverage":metric,"branchCoverage":metric,"skipped":metric,"criticalRisks":metric},"blockingItems":[string]}.',
3247
3269
  "Each metric must contain numerator, denominator, ratio, threshold, status=pass|fail|unavailable, and reason (null only when passed).",
3248
3270
  "Use only explicit evidence. Missing or invalid required evidence is unavailable, never zero or an estimate.",
3249
3271
  "L-5 ready requires pass rate=100%, AC coverage=100%, automation coverage>=90%, stability>=95% with n>=5, line coverage>=80%, branch coverage>=70%, skipped=0, and zero blocking Critical risks.",
@@ -3268,75 +3290,115 @@ function buildBackendTestHybridDag(sources) {
3268
3290
  const globalConstraints = [
3269
3291
  ...taskConfig.hardConstraints,
3270
3292
  ...STANDARD_GLOBAL_CONSTRAINTS,
3271
- "backend-test-dag uses exactly 16 real top-level tasks and executes pytest exactly once.",
3272
- "Case and semantic request-revision verdicts fail at deterministic gates; no in-run revision or repair writer is authorized.",
3273
- "Analysis, execution, manifest, semantic review, single-run result, classification, canonical result, retrospective and outcome evidence remain run-owned and fail-closed.",
3293
+ "backend-test-dag uses exactly 12 real top-level tasks and executes pytest exactly once.",
3294
+ "Case review is advisory evidence consumed by canonical context, retrospective, and L-5; it does not authorize or block the pytest writer.",
3295
+ "Deterministic traceability is the only generated-asset hard gate before pytest.",
3296
+ "Analysis, execution, manifest, case review, traceability, single-run result, classification, canonical context, retrospective and L-5 evidence remain run-owned and fail-closed.",
3274
3297
  "Functional test case IDs must use BE-<MODULE>-<NNN> format.",
3275
3298
  "pytest writers may only create the initially declared testcase assets; production code, config, skip/xfail, swallowed failures and mock substitution are forbidden.",
3276
3299
  ];
3277
3300
  const analyze = buildAnalyzeInputsNode(sources);
3278
3301
  analyze.id = "analyze-and-discover-backend-test-pi";
3279
- analyze.outputContract = "Pure JSON envelope {analysis: Backend Test Analysis v2, execution: Backend Test Execution Contract v1}; no prose or writes.";
3302
+ analyze.outputContract =
3303
+ "Pure JSON envelope {analysis: Backend Test Analysis v2, execution: Backend Test Execution Contract v1}; no prose or writes.";
3280
3304
  analyze.subtask_prompt = `${analyze.subtask_prompt}\n\nAlso perform the read-only environment discovery described by Backend Test Execution Contract v1. Return exactly one JSON envelope with top-level keys analysis and execution; analysis must satisfy v2 and execution must satisfy v1.`;
3281
3305
  const contracts = {
3282
- id: "validate-backend-test-contracts-shell", depends_on: [analyze.id], role: "verifier", executor: "shell", complexity: "LOW",
3283
- writePolicy: "read-only", allowedPaths: commonReadOnlyPaths(sources), forbiddenPaths: commonForbiddenPaths(sources),
3306
+ id: "validate-backend-test-contracts-shell",
3307
+ depends_on: [analyze.id],
3308
+ role: "verifier",
3309
+ executor: "shell",
3310
+ complexity: "LOW",
3311
+ writePolicy: "read-only",
3312
+ allowedPaths: commonReadOnlyPaths(sources),
3313
+ forbiddenPaths: commonForbiddenPaths(sources),
3284
3314
  outputContract: "Materialize and validate contracts/backend-test-analysis.json and contracts/backend-test-execution.json.",
3285
- subtask_prompt: "Validate both backend-test intake contracts fail-closed.", shell: { commands: [], backendTestPipeline: "contracts", cwd: ".", timeoutMs: 60000 },
3315
+ subtask_prompt: "Validate both backend-test intake contracts fail-closed.",
3316
+ shell: {
3317
+ commands: [],
3318
+ backendTestPipeline: "contracts",
3319
+ cwd: ".",
3320
+ timeoutMs: 60000,
3321
+ },
3286
3322
  };
3287
3323
  const generateCases = buildGenerateBackendFunctionalCasesNode(sources);
3288
3324
  generateCases.id = "generate-backend-cases-and-manifest-pi";
3289
3325
  generateCases.depends_on = [contracts.id];
3290
- generateCases.outputContract = "Write testcase/md/** and end with one fenced json Backend Test Case Manifest v1 block matching the strict field contract.";
3326
+ generateCases.outputContract =
3327
+ "Write testcase/md/** and end with one fenced json Backend Test Case Manifest v1 block matching the strict field contract.";
3291
3328
  generateCases.subtask_prompt += `\n\nAfter writing Markdown, end assistant output with exactly one fenced json block containing Backend Test Case Manifest v1 derived from the written cases.\n\n${BACKEND_TEST_CASE_MANIFEST_OUTPUT_INSTRUCTIONS}`;
3292
- const manifest = buildBackendTestCaseManifestGateNode(sources, { dependsOn: [generateCases.id], fromNodeId: generateCases.id });
3293
- const reviewCases = buildReviewBackendCasesNode(sources, { dependsOn: [manifest.id, contracts.id] });
3294
- const caseGate = buildReviewBackendCasesGateNode(sources);
3295
- caseGate.depends_on = [reviewCases.id];
3296
- caseGate.dependsPolicy = undefined;
3297
- caseGate.outputContract = "Deterministic backend case review gate: exit 0 only when the first and only review emits VERDICT: pass.";
3298
- caseGate.subtask_prompt = "Block pytest generation when backend case review requests revision; do not authorize an in-run writer.";
3299
- caseGate.shell.verdictGate = { fromNodeId: reviewCases.id, accept: ["VERDICT: pass"], label: "backend case review", lineMode: "first-verdict-line" };
3329
+ const manifest = buildBackendTestCaseManifestGateNode(sources, {
3330
+ dependsOn: [generateCases.id],
3331
+ fromNodeId: generateCases.id,
3332
+ });
3333
+ const reviewCases = buildReviewBackendCasesNode(sources, {
3334
+ dependsOn: [manifest.id, contracts.id, "generate-backend-pytest-pi"],
3335
+ });
3300
3336
  const generatePytest = buildGenerateBackendPytestNode(sources);
3301
- generatePytest.depends_on = [caseGate.id, contracts.id];
3302
- const semanticReview = buildBackendTestSemanticReviewNode(sources, { dependsOn: [generatePytest.id, contracts.id, manifest.id] });
3303
- const semanticMaterialize = {
3304
- id: "validate-semantic-review-and-traceability-shell", depends_on: [semanticReview.id, manifest.id], role: "verifier", executor: "shell", complexity: "LOW",
3305
- writePolicy: "read-only", allowedPaths: commonReadOnlyPaths(sources), forbiddenPaths: commonForbiddenPaths(sources),
3306
- outputContract: "Materialize the only semantic review and validate pytest traceability.", subtask_prompt: "Materialize semantic facts and traceability; verdict authorization is handled by the next deterministic gate.",
3307
- shell: { commands: [], backendTestPipeline: "semantic-initial", cwd: ".", timeoutMs: 60000 },
3308
- };
3309
- const semanticGate = {
3310
- id: "backend-test-semantic-gate-shell", depends_on: [semanticMaterialize.id, semanticReview.id], role: "verifier", executor: "shell", complexity: "LOW",
3311
- writePolicy: "read-only", allowedPaths: commonReadOnlyPaths(sources), forbiddenPaths: commonForbiddenPaths(sources),
3312
- outputContract: "Pass-only authorization by reading contracts/backend-test-semantic-review.json; only verdict=pass proceeds to the single pytest execution.",
3313
- subtask_prompt: "Read the canonical semantic review artifact written by validate-semantic-review-and-traceability-shell. Authorize only when verdict is pass. Do not materialize, do not parse raw Pi Markdown or VERDICT lines, and do not authorize an in-run pytest writer.",
3337
+ generatePytest.depends_on = [manifest.id, contracts.id];
3338
+ const traceability = buildBackendTestTraceabilityGateNode(sources, {
3339
+ dependsOn: [generatePytest.id, manifest.id],
3340
+ });
3341
+ const execute = buildExecuteBackendPytestNode(sources, {
3342
+ id: "execute-and-parse-backend-pytest-shell",
3343
+ dependsOn: [traceability.id, contracts.id],
3344
+ reportStem: "backend-test-initial",
3345
+ });
3346
+ execute.shell.backendTestPipeline = "execute-parse-initial";
3347
+ const classify = buildClassifyBackendTestResultNode(sources);
3348
+ classify.depends_on = [execute.id];
3349
+ const context = {
3350
+ id: "materialize-classification-and-result-context-shell",
3351
+ depends_on: [classify.id, manifest.id, reviewCases.id, traceability.id],
3352
+ role: "verifier",
3353
+ executor: "shell",
3354
+ complexity: "LOW",
3355
+ writePolicy: "read-only",
3356
+ allowedPaths: commonReadOnlyPaths(sources),
3357
+ forbiddenPaths: commonForbiddenPaths(sources),
3358
+ outputContract: "Materialize Classification v1, copy the unique initial Result to canonical contracts/backend-test-result.json, and emit Result + Manifest + Classification + advisory case review + traceability context.",
3359
+ subtask_prompt: "Validate classification and materialize canonical single-run result context with auditable case review and traceability evidence, without repair eligibility or rerun.",
3314
3360
  shell: {
3315
3361
  commands: [],
3316
- backendTestPipeline: "semantic-initial",
3362
+ backendTestPipeline: "classification-result-context",
3317
3363
  cwd: ".",
3318
3364
  timeoutMs: 60000,
3319
3365
  },
3320
3366
  };
3321
- const execute = buildExecuteBackendPytestNode(sources, { id: "execute-and-parse-backend-pytest-shell", dependsOn: [semanticGate.id, contracts.id], reportStem: "backend-test-initial" });
3322
- execute.shell.backendTestPipeline = "execute-parse-initial";
3323
- const classify = buildClassifyBackendTestResultNode(sources);
3324
- classify.depends_on = [execute.id];
3325
- const context = {
3326
- id: "materialize-classification-and-result-context-shell", depends_on: [classify.id, manifest.id], role: "verifier", executor: "shell", complexity: "LOW",
3327
- writePolicy: "read-only", allowedPaths: commonReadOnlyPaths(sources), forbiddenPaths: commonForbiddenPaths(sources),
3328
- outputContract: "Materialize Classification v1, copy the unique initial Result to canonical contracts/backend-test-result.json, and emit Result + Manifest + Classification context.",
3329
- subtask_prompt: "Validate classification and materialize canonical single-run result context without repair eligibility or rerun.",
3330
- shell: { commands: [], backendTestPipeline: "classification-result-context", cwd: ".", timeoutMs: 60000 },
3331
- };
3332
3367
  const retrospect = buildTestRetrospectNode(sources);
3333
3368
  retrospect.depends_on = [context.id];
3334
3369
  retrospect.subtask_prompt = retrospect.subtask_prompt.replaceAll("select-effective-backend-test-result-shell", context.id);
3335
3370
  const l5Metrics = buildL5MetricsNode(sources);
3336
3371
  l5Metrics.depends_on = [retrospect.id];
3337
- const outcome = buildBackendTestOutcomeGateNode(sources);
3338
- const tasks = [analyze, contracts, generateCases, manifest, reviewCases, caseGate, generatePytest, semanticReview, semanticMaterialize, semanticGate, execute, classify, context, retrospect, l5Metrics, outcome];
3339
- const spec = { version: 3, title: `Backend test DAG: ${taskConfig.title}`, runtimeContract: GENERATED_DAG_RUNTIME_CONTRACT, outputLanguage: sources.outputLanguage ?? DEFAULT_DAG_OUTPUT_LANGUAGE, objective: extractObjective(sources.requirementMarkdown, taskConfig.title), successCriteria: extractSuccessCriteria(sources.requirementMarkdown, sources.taskId), globalConstraints, defaults: { ...BACKEND_TEST_DEFAULTS, contextProfile: taskConfig.contextProfile }, skillsByRole: BACKEND_TEST_SKILLS_BY_ROLE, executorModels: sources.executorModelMatrix ?? DEFAULT_DAG_EXECUTOR_MODELS, tasks };
3372
+ const tasks = [
3373
+ analyze,
3374
+ contracts,
3375
+ generateCases,
3376
+ manifest,
3377
+ reviewCases,
3378
+ generatePytest,
3379
+ traceability,
3380
+ execute,
3381
+ classify,
3382
+ context,
3383
+ retrospect,
3384
+ l5Metrics,
3385
+ ];
3386
+ const spec = {
3387
+ version: 3,
3388
+ title: `Backend test DAG: ${taskConfig.title}`,
3389
+ runtimeContract: GENERATED_DAG_RUNTIME_CONTRACT,
3390
+ outputLanguage: sources.outputLanguage ?? DEFAULT_DAG_OUTPUT_LANGUAGE,
3391
+ objective: extractObjective(sources.requirementMarkdown, taskConfig.title),
3392
+ successCriteria: extractSuccessCriteria(sources.requirementMarkdown, sources.taskId),
3393
+ globalConstraints,
3394
+ defaults: {
3395
+ ...BACKEND_TEST_DEFAULTS,
3396
+ contextProfile: taskConfig.contextProfile,
3397
+ },
3398
+ skillsByRole: BACKEND_TEST_SKILLS_BY_ROLE,
3399
+ executorModels: sources.executorModelMatrix ?? DEFAULT_DAG_EXECUTOR_MODELS,
3400
+ tasks,
3401
+ };
3340
3402
  applyDefaultReadOnlyRetryPolicy(spec);
3341
3403
  parseDagSpec(spec);
3342
3404
  assertValidDagSpec(spec);
@@ -3538,7 +3600,10 @@ function buildFrontendTestHybridDag(sources) {
3538
3600
  subtask_prompt: "Perform the final frontend case review after the sole permitted revision. Apply the same traceability, isolation, manifest, reset, session, snapshot, and evidence checks. First verdict line must be exact; any Important or Critical finding requires request-revision. Do not write files.",
3539
3601
  }, {
3540
3602
  id: "final-frontend-case-review-gate-shell",
3541
- depends_on: ["review-frontend-cases-pi", "review-frontend-cases-final-pi"],
3603
+ depends_on: [
3604
+ "review-frontend-cases-pi",
3605
+ "review-frontend-cases-final-pi",
3606
+ ],
3542
3607
  dependsPolicy: "all-or-condition-skip",
3543
3608
  role: "verifier",
3544
3609
  executor: "shell",
@@ -3687,7 +3752,11 @@ function buildFrontendTestHybridDag(sources) {
3687
3752
  forbiddenPaths: forbidden,
3688
3753
  outputContract: "Optional quality gate: pass only when frontend-test-result-v1 is outcome=passed and integrationMode=real with 0 failed/blocked and no missing AC. Does not gate retrospective closeout.",
3689
3754
  subtask_prompt: "Opt-in Delivery/Worker quality gate (frontendTest.strictOutcomeGate=true). Retrospective does not depend on this node.",
3690
- shell: { commands: [frontendTestOutcomeGate], cwd: ".", timeoutMs: 60000 },
3755
+ shell: {
3756
+ commands: [frontendTestOutcomeGate],
3757
+ cwd: ".",
3758
+ timeoutMs: 60000,
3759
+ },
3691
3760
  });
3692
3761
  }
3693
3762
  tasks.push({
@@ -4641,7 +4710,86 @@ function buildKnowledgeGraphBootstrapHybridDag(sources) {
4641
4710
  assertValidDagSpec(spec);
4642
4711
  return spec;
4643
4712
  }
4644
- function buildHybridDagForTemplate(sources, template) {
4713
+ export async function requireManagedTaskContractBinding(input) {
4714
+ const state = await observeTaskContract({
4715
+ repoRoot: input.repoRoot,
4716
+ taskId: input.taskId,
4717
+ });
4718
+ if (state.effectiveStatus !== "managed" || !state.ref) {
4719
+ const err = new Error(`dag generate requires managed Task Contract for task ${input.taskId} (effectiveStatus=${state.effectiveStatus}); run: loop-agent task contract adopt|apply --task ${input.taskId} ...`);
4720
+ err.code =
4721
+ state.effectiveStatus === "externally-modified"
4722
+ ? "EXTERNALLY_MODIFIED"
4723
+ : "NOT_MANAGED";
4724
+ throw err;
4725
+ }
4726
+ const ref = state.ref;
4727
+ return {
4728
+ schemaVersion: 1,
4729
+ taskId: ref.taskId,
4730
+ revision: ref.revision,
4731
+ projectionVersion: ref.projectionVersion,
4732
+ canonicalizerVersion: ref.canonicalizerVersion,
4733
+ taskConfigSchemaVersion: ref.taskConfigSchemaVersion,
4734
+ canonicalHash: ref.canonicalHash,
4735
+ taskConfigSha256: ref.taskConfigSha256,
4736
+ };
4737
+ }
4738
+ /** In-memory unit-test path only: produce a syntactically valid v4 binding. */
4739
+ function synthesizeInMemoryTaskContractBinding(sources) {
4740
+ const requirementSha = createHash("sha256")
4741
+ .update(sources.requirementMarkdown, "utf8")
4742
+ .digest("hex");
4743
+ const constraintsSha = createHash("sha256")
4744
+ .update(sources.constraintMarkdown ?? "", "utf8")
4745
+ .digest("hex");
4746
+ const taskConfigSha256 = createHash("sha256")
4747
+ .update(JSON.stringify(sources.taskConfig), "utf8")
4748
+ .digest("hex");
4749
+ const canonicalHash = createHash("sha256")
4750
+ .update(JSON.stringify({
4751
+ projectionVersion: 1,
4752
+ canonicalizerVersion: 1,
4753
+ taskConfigSchemaVersion: 1,
4754
+ sourceHashes: {
4755
+ requirementSha256: requirementSha,
4756
+ constraintsSha256: constraintsSha,
4757
+ },
4758
+ taskConfigSha256,
4759
+ }), "utf8")
4760
+ .digest("hex");
4761
+ return {
4762
+ schemaVersion: 1,
4763
+ taskId: sources.taskId,
4764
+ revision: 1,
4765
+ projectionVersion: 1,
4766
+ canonicalizerVersion: 1,
4767
+ taskConfigSchemaVersion: 1,
4768
+ canonicalHash,
4769
+ taskConfigSha256,
4770
+ };
4771
+ }
4772
+ async function resolveTaskContractBindingForGenerate(sources) {
4773
+ // Real CLI generation freezes the managed ref. Direct builder unit tests
4774
+ // may provide repoRoot-shaped fixture paths without a persisted task; keep
4775
+ // their deterministic synthetic binding path so topology/prompt tests remain
4776
+ // isolated from contract storage.
4777
+ if (sources.repoRoot) {
4778
+ const state = await observeTaskContract({
4779
+ repoRoot: sources.repoRoot,
4780
+ taskId: sources.taskId,
4781
+ });
4782
+ if (state.effectiveStatus === "managed" && state.ref) {
4783
+ return requireManagedTaskContractBinding({
4784
+ repoRoot: sources.repoRoot,
4785
+ taskId: sources.taskId,
4786
+ });
4787
+ }
4788
+ }
4789
+ return synthesizeInMemoryTaskContractBinding(sources);
4790
+ }
4791
+ async function buildHybridDagForTemplate(sources, template) {
4792
+ const taskContractBinding = await resolveTaskContractBindingForGenerate(sources);
4645
4793
  let spec;
4646
4794
  if (template === "frontend-implementation") {
4647
4795
  spec = buildFrontendHybridDagFromTask(sources);
@@ -4663,12 +4811,42 @@ function buildHybridDagForTemplate(sources, template) {
4663
4811
  else
4664
4812
  spec = buildSupervisedHybridDag(standard, sources);
4665
4813
  }
4814
+ applyProjectGovernanceReview(spec, template, sources);
4815
+ // New generate path always emits DagSpec v4 + bindings.
4816
+ spec.version = 4;
4817
+ if (!spec.runtimeContract) {
4818
+ spec.runtimeContract = GENERATED_DAG_RUNTIME_CONTRACT;
4819
+ }
4666
4820
  spec.sourceBinding = buildDagSourceBinding(sources);
4821
+ spec.taskContractBinding = taskContractBinding;
4822
+ assertNoGovernanceFlagOnDisallowedTemplate(spec, template);
4667
4823
  parseDagSpec(spec);
4668
4824
  assertValidDagSpec(spec);
4669
4825
  return spec;
4670
4826
  }
4671
- export function buildHybridDagFromTask(sources, options = {}) {
4827
+ const GOVERNANCE_DISALLOWED_TEMPLATES = new Set([
4828
+ "frontend-implementation",
4829
+ "frontend-test-dag",
4830
+ "backend-test-dag",
4831
+ "knowledge-sync-dag",
4832
+ "knowledge-graph-bootstrap-dag",
4833
+ ]);
4834
+ /**
4835
+ * Generate-time guard (constraint 2 / AC-007): the governance standard review
4836
+ * flag must never appear on knowledge-sync, knowledge-graph-bootstrap,
4837
+ * backend-test, or frontend tasks. Only standard verify-pi and the shared
4838
+ * review-pi (reviewed/supervised) opt in explicitly.
4839
+ */
4840
+ function assertNoGovernanceFlagOnDisallowedTemplate(spec, template) {
4841
+ if (!GOVERNANCE_DISALLOWED_TEMPLATES.has(template))
4842
+ return;
4843
+ for (const task of spec.tasks) {
4844
+ if (task.governanceStandardReview) {
4845
+ throw new Error(`governanceStandardReview must not be set on ${template} task ${task.id}`);
4846
+ }
4847
+ }
4848
+ }
4849
+ export async function buildHybridDagFromTask(sources, options = {}) {
4672
4850
  const selection = resolveTaskDagTemplateSelection({
4673
4851
  taskKind: sources.taskConfig.taskKind,
4674
4852
  title: sources.taskConfig.title,
@@ -4791,6 +4969,85 @@ function buildReviewGateNode(sources) {
4791
4969
  },
4792
4970
  };
4793
4971
  }
4972
+ function enableProjectGovernanceOnNode(task) {
4973
+ task.governanceStandardReview = true;
4974
+ }
4975
+ /**
4976
+ * Apply governance only to general implementation DAGs, and only when the
4977
+ * target repository actually contains AGENTS.md. Standard reuses verify-pi;
4978
+ * reviewed/supervised reuse review-pi and their existing review verdict gate.
4979
+ */
4980
+ function applyProjectGovernanceReview(spec, template, sources) {
4981
+ if (!sources.projectGovernancePresent)
4982
+ return;
4983
+ if (template === "standard-dag") {
4984
+ const verify = spec.tasks.find((task) => task.id === "verify-pi");
4985
+ if (!verify)
4986
+ return;
4987
+ enableProjectGovernanceOnNode(verify);
4988
+ insertGovernanceStandardGate(spec, sources);
4989
+ return;
4990
+ }
4991
+ if (template === "review-gated-dag" ||
4992
+ template === "supervised-implementation") {
4993
+ const review = spec.tasks.find((task) => task.id === "review-pi");
4994
+ if (review)
4995
+ enableProjectGovernanceOnNode(review);
4996
+ }
4997
+ }
4998
+ /**
4999
+ * Deterministic governance standard gate for standard-dag closeout. It reuses
5000
+ * the standard `verdictGate` preset (fromNodeId=verify-pi, accept
5001
+ * ["VERDICT: pass"], first-verdict-line). When no applicable AGENTS.md/standard
5002
+ * exists, projectGovernanceGate returns success without parsing a verdict.
5003
+ * When an applicable mandatory standard is violated, verify-pi must emit
5004
+ * VERDICT: request-revision and the gate blocks closeout. No new model node.
5005
+ */
5006
+ function buildGovernanceStandardGateNode(sources) {
5007
+ return {
5008
+ id: "governance-standard-gate-shell",
5009
+ depends_on: ["verify-pi"],
5010
+ role: "verifier",
5011
+ executor: "shell",
5012
+ complexity: "LOW",
5013
+ writePolicy: "read-only",
5014
+ allowedPaths: commonReadOnlyPaths(sources),
5015
+ forbiddenPaths: commonForbiddenPaths(sources),
5016
+ outputContract: "Deterministic governance verdict gate: exit 0 only when verify-pi first VERDICT line is pass (covers applicable mandatory standard compliance). No file writes.",
5017
+ subtask_prompt: "Deterministic gate: block closeout unless verify-pi emitted VERDICT: pass, which requires no unresolved applicable mandatory governance-standard violation. Read-only: do not modify files.",
5018
+ shell: {
5019
+ commands: [],
5020
+ projectGovernanceGate: {
5021
+ contextPath: ".runtime/project-governance-context.json",
5022
+ },
5023
+ verdictGate: {
5024
+ fromNodeId: "verify-pi",
5025
+ accept: ["VERDICT: pass"],
5026
+ label: "governance-standard",
5027
+ lineMode: "first-verdict-line",
5028
+ },
5029
+ cwd: ".",
5030
+ timeoutMs: 60000,
5031
+ },
5032
+ };
5033
+ }
5034
+ /**
5035
+ * Insert the governance-standard gate between verify-pi and closeout-pi in a
5036
+ * standard DAG. Idempotent and no-op if verify-pi/closeout-pi are absent.
5037
+ */
5038
+ function insertGovernanceStandardGate(spec, sources) {
5039
+ const closeout = spec.tasks.find((task) => task.id === "closeout-pi");
5040
+ const verify = spec.tasks.find((task) => task.id === "verify-pi");
5041
+ if (!closeout || !verify)
5042
+ return;
5043
+ if (spec.tasks.some((task) => task.id === "governance-standard-gate-shell")) {
5044
+ return;
5045
+ }
5046
+ const gate = buildGovernanceStandardGateNode(sources);
5047
+ const closeoutIndex = spec.tasks.findIndex((task) => task.id === "closeout-pi");
5048
+ spec.tasks.splice(closeoutIndex, 0, gate);
5049
+ closeout.depends_on = ["governance-standard-gate-shell"];
5050
+ }
4794
5051
  function buildReviewGatedHybridDag(standard, sources) {
4795
5052
  const spec = {
4796
5053
  ...standard,
@@ -5216,7 +5473,7 @@ export async function writeHybridDagDraft(sources, outputPath, options = {}) {
5216
5473
  const preparedSources = template === "frontend-implementation"
5217
5474
  ? await prepareFrontendMockSources(sources)
5218
5475
  : sources;
5219
- const spec = buildHybridDagForTemplate(preparedSources, template);
5476
+ const spec = await buildHybridDagForTemplate(preparedSources, template);
5220
5477
  await writeFile(outputPath, `${JSON.stringify(spec, null, 2)}\n`, "utf-8");
5221
5478
  return {
5222
5479
  taskId: sources.taskId,