@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.
- package/CHANGELOG.md +46 -0
- package/README.md +14 -3
- package/dist/cli/command-definitions.js +43 -0
- package/dist/cli/program.js +26 -0
- package/dist/commands/dag-approve.js +4 -0
- package/dist/commands/dag-resume.js +1 -0
- package/dist/commands/dag-validate.js +6 -0
- package/dist/commands/operator.js +44 -0
- package/dist/commands/task-contract.js +271 -0
- package/dist/executors/dag-pi-executor.js +118 -16
- package/dist/executors/pi-executor.js +206 -13
- package/dist/executors/pi-sdk-executor.js +21 -6
- package/dist/executors/shell-executor.js +85 -8
- package/dist/executors/shell-presets.js +16 -3
- package/dist/executors/shell-write-guard.js +64 -2
- package/dist/shared/operator/capabilities.js +255 -0
- package/dist/shared/operator/envelope.js +59 -0
- package/dist/shared/operator/index.js +4 -0
- package/dist/shared/operator/registry.js +38 -0
- package/dist/shared/operator/types.js +5 -0
- package/dist/task/contract/adopt.js +166 -0
- package/dist/task/contract/apply.js +326 -0
- package/dist/task/contract/canonicalize.js +60 -0
- package/dist/task/contract/constants.js +29 -0
- package/dist/task/contract/diff.js +177 -0
- package/dist/task/contract/hash.js +42 -0
- package/dist/task/contract/import-revision.js +96 -0
- package/dist/task/contract/index.js +17 -0
- package/dist/task/contract/journal.js +155 -0
- package/dist/task/contract/lock.js +153 -0
- package/dist/task/contract/observe.js +296 -0
- package/dist/task/contract/paths.js +19 -0
- package/dist/task/contract/project.js +170 -0
- package/dist/task/contract/recover.js +312 -0
- package/dist/task/contract/request-ledger.js +37 -0
- package/dist/task/contract/schema.js +151 -0
- package/dist/task/contract/transaction.js +160 -0
- package/dist/task/contract/types.js +1 -0
- package/dist/task/contract/validate-draft.js +106 -0
- package/dist/task/index.js +3 -0
- package/dist/task/operator/capabilities.js +6 -0
- package/dist/task/operator/envelope.js +2 -0
- package/dist/task/operator/index.js +5 -0
- package/dist/task/operator/registry.js +2 -0
- package/dist/task/operator/types.js +1 -0
- package/dist/task/runtime.js +5 -1
- package/dist/task/source-references.js +7 -0
- package/dist/worker/cli.js +150 -32
- package/dist/worker/console/app-data.js +185 -0
- package/dist/worker/console/dag-confirmation.js +313 -0
- package/dist/worker/console/doctor.js +169 -0
- package/dist/worker/console/draft-store.js +80 -0
- package/dist/worker/console/index.js +15 -0
- package/dist/worker/console/interview/assessment.js +67 -0
- package/dist/worker/console/interview/session.js +100 -0
- package/dist/worker/console/interview/tools.js +109 -0
- package/dist/worker/console/loopback.js +16 -0
- package/dist/worker/console/observe-health-match.js +174 -0
- package/dist/worker/console/observe-link.js +33 -0
- package/dist/worker/console/operation-runner.js +166 -0
- package/dist/worker/console/operation-sse.js +158 -0
- package/dist/worker/console/operation-store.js +147 -0
- package/dist/worker/console/operator-actions.js +769 -0
- package/dist/worker/console/pi-readiness.js +94 -0
- package/dist/worker/console/recovery-cta.js +133 -0
- package/dist/worker/console/repo-fingerprint.js +29 -0
- package/dist/worker/console/resource-loader.js +95 -0
- package/dist/worker/console/routes.js +368 -0
- package/dist/worker/console/security.js +126 -0
- package/dist/worker/console/server.js +149 -0
- package/dist/worker/console/sibling-controller.js +28 -0
- package/dist/worker/console/static/assets/index-CbnMgdWa.js +9 -0
- package/dist/worker/console/static/assets/index-Dnj0RVs8.css +1 -0
- package/dist/worker/console/static/index.html +13 -0
- package/dist/worker/console/vite.config.js +27 -0
- package/dist/worker/delivery/git-transaction.js +43 -8
- package/dist/worker/observe/health.js +57 -0
- package/dist/worker/observe/paths.js +81 -0
- package/dist/worker/observe/routes.js +142 -27
- package/dist/worker/observe/spec-evidence.js +84 -0
- package/dist/worker/observe/static/api.js +23 -0
- package/dist/worker/observe/static/state.js +26 -0
- package/dist/worker/observe/static/styles.css +10 -0
- package/dist/worker/observe/static/views/dag-inspector.js +173 -6
- package/dist/workflows/dag/backend-test-analysis-contract.js +34 -9
- package/dist/workflows/dag/dynamic-runtime/shared.js +1 -0
- package/dist/workflows/dag/frontend-repair.js +1 -10
- package/dist/workflows/dag/init-hybrid.js +372 -115
- package/dist/workflows/dag/node-execution.js +57 -6
- package/dist/workflows/dag/project-governance-context.js +508 -0
- package/dist/workflows/dag/prompt.js +46 -1
- package/dist/workflows/dag/retry-policy.js +16 -1
- package/dist/workflows/dag/runner.js +9 -0
- package/dist/workflows/dag/skill-snapshot.js +1 -0
- package/dist/workflows/dag/task-contract-binding.js +138 -0
- package/dist/workflows/dag/types.js +84 -10
- package/dist/workflows/dag/validate.js +53 -7
- package/docs/README.md +2 -0
- package/docs/architecture/evolution.md +2 -0
- package/docs/architecture/system-overview.md +6 -0
- package/docs/architecture/worker-and-feature.md +7 -0
- package/docs/templates/agent-dag.schema.json +64 -2
- package/docs/templates/agent-dag.supervised-implementation.json +1 -0
- package/docs/templates/backend-test-dag.classify.prompt.md +1 -1
- package/docs/templates/backend-test-dag.generate-pytest.prompt.md +5 -5
- package/docs/templates/backend-test-dag.json +26 -154
- package/docs/templates/backend-test-dag.retrospect.prompt.md +1 -1
- package/docs/templates/backend-test-dag.review-cases.prompt.md +2 -2
- package/package.json +8 -2
- package/skills/agent-worker/SKILL.md +1 -0
- package/skills/agent-worker/references/agent-worker-operator.md +3 -2
- package/skills/frontend-design-review/SKILL.md +25 -16
- package/skills/frontend-implementation/references/node-contracts.md +5 -5
- package/skills/loop-agent/references/command-reference.md +48 -1
- 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
|
-
? [
|
|
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
|
|
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-
|
|
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;
|
|
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
|
-
"
|
|
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: "
|
|
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
|
|
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
|
-
"
|
|
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: [
|
|
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
|
|
2149
|
-
subtask_prompt: "Deterministic gate: block frontend implementation unless
|
|
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
|
-
"
|
|
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
|
|
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
|
-
"
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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: "
|
|
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
|
|
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
|
-
"
|
|
2888
|
-
"backend-test-
|
|
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
|
|
2930
|
+
"Convert the validated test cases under testcase/md/ into pytest automation code.",
|
|
2909
2931
|
"",
|
|
2910
2932
|
"## Inputs (MUST use validated contracts):",
|
|
2911
|
-
"-
|
|
2912
|
-
"- Validated Backend Test Analysis
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
3272
|
-
"Case
|
|
3273
|
-
"
|
|
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 =
|
|
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",
|
|
3283
|
-
|
|
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.",
|
|
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 =
|
|
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, {
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
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 = [
|
|
3302
|
-
const
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
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: "
|
|
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
|
|
3338
|
-
|
|
3339
|
-
|
|
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: [
|
|
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: {
|
|
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
|
|
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
|
-
|
|
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,
|