@tea-agent/loop-agent 0.16.26 → 0.17.1
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 +45 -1
- 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 +55 -7
- package/dist/executors/pi-executor.js +206 -13
- package/dist/executors/pi-sdk-executor.js +21 -6
- package/dist/executors/shell-executor.js +55 -8
- package/dist/executors/shell-presets.js +16 -3
- 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 +266 -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-BEIdBogJ.js +9 -0
- package/dist/worker/console/static/assets/index-Rt0TqimP.css +1 -0
- package/dist/worker/console/static/index.html +13 -0
- package/dist/worker/console/vite.config.js +27 -0
- package/dist/worker/materialize/harness-task-materializer.js +34 -0
- package/dist/worker/observe/health.js +57 -0
- package/dist/worker/observe/routes.js +16 -9
- 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 +269 -116
- package/dist/workflows/dag/node-execution.js +25 -1
- package/dist/workflows/dag/retry-policy.js +16 -1
- package/dist/workflows/dag/runner.js +9 -0
- package/dist/workflows/dag/task-contract-binding.js +138 -0
- package/dist/workflows/dag/types.js +74 -10
- package/dist/workflows/dag/validate.js +25 -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 +49 -2
- 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,7 +7,7 @@ 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";
|
|
@@ -15,6 +15,7 @@ import { applySddEmbeddedEnhancements, probeRepoLocalSddSkills, } from "./sdd-em
|
|
|
15
15
|
import { discoverProjectGovernancePresence } from "./project-governance-context.js";
|
|
16
16
|
import { getTaskPaths, loadTaskConfig } from "../../task/runtime.js";
|
|
17
17
|
import { materializeTaskReferenceDocs } from "../../task/source-references.js";
|
|
18
|
+
import { observeTaskContract } from "../../task/contract/observe.js";
|
|
18
19
|
import { resolveVerifyPreset } from "../../executors/shell-verification.js";
|
|
19
20
|
import { resolveExecutorModelMatrices } from "../../executors/model-routing.js";
|
|
20
21
|
import { normalizeTaskRequirementText, resolveTaskDagTemplateSelection, } from "./task-demand-routing.js";
|
|
@@ -1379,6 +1380,7 @@ function buildFrontendMockAssessNode(sources, sourceContext, mockContextBlock, f
|
|
|
1379
1380
|
subtask_prompt: [
|
|
1380
1381
|
"Perform read-only Mock assessment and select one safe frontend data strategy.",
|
|
1381
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.",
|
|
1382
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.",
|
|
1383
1385
|
autoMaySkipMissingMock
|
|
1384
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."
|
|
@@ -1402,7 +1404,9 @@ function buildFrontendMockAssessNode(sources, sourceContext, mockContextBlock, f
|
|
|
1402
1404
|
"- Do not infer Mock service from lockfile-only or transitive dependency evidence.",
|
|
1403
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.",
|
|
1404
1406
|
...(autoMaySkipMissingMock
|
|
1405
|
-
? [
|
|
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
|
+
]
|
|
1406
1410
|
: []),
|
|
1407
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.",
|
|
1408
1412
|
"- Mock-backed behavior evidence proves the documented frontend contract only; it never proves real API integration.",
|
|
@@ -1650,11 +1654,10 @@ function pruneFrontendTasksForRisk(tasks, risk) {
|
|
|
1650
1654
|
if (risk.forceFullGates || risk.selectedRisk !== "small") {
|
|
1651
1655
|
return tasks;
|
|
1652
1656
|
}
|
|
1653
|
-
// small topology: drop
|
|
1657
|
+
// small topology: drop the initial design review + conditional revision cycle;
|
|
1654
1658
|
// keep mock assess/gate, single design review (final), contract, implement, verify, repair chain, review.
|
|
1655
1659
|
const drop = new Set([
|
|
1656
1660
|
"frontend-design-gate-pi",
|
|
1657
|
-
"frontend-first-design-gate-shell",
|
|
1658
1661
|
"frontend-plan-revision-pi",
|
|
1659
1662
|
]);
|
|
1660
1663
|
// If we drop plan-revision, contract shell must depend on plan-pi instead; final design review depends on plan.
|
|
@@ -1670,8 +1673,7 @@ function pruneFrontendTasksForRisk(tasks, risk) {
|
|
|
1670
1673
|
next.push("frontend-plan-pi");
|
|
1671
1674
|
continue;
|
|
1672
1675
|
}
|
|
1673
|
-
if (dep === "frontend-
|
|
1674
|
-
dep === "frontend-design-gate-pi") {
|
|
1676
|
+
if (dep === "frontend-design-gate-pi") {
|
|
1675
1677
|
// skip removed gates
|
|
1676
1678
|
continue;
|
|
1677
1679
|
}
|
|
@@ -1692,6 +1694,12 @@ function pruneFrontendTasksForRisk(tasks, risk) {
|
|
|
1692
1694
|
if (byId.has(need) && !depends_on.includes(need))
|
|
1693
1695
|
depends_on.push(need);
|
|
1694
1696
|
}
|
|
1697
|
+
return {
|
|
1698
|
+
...task,
|
|
1699
|
+
depends_on,
|
|
1700
|
+
dependsPolicy: "all",
|
|
1701
|
+
runIf: undefined,
|
|
1702
|
+
};
|
|
1695
1703
|
}
|
|
1696
1704
|
if (task.id === "frontend-requirement-coverage-shell") {
|
|
1697
1705
|
const shell = task.shell?.requirementCoverageGate
|
|
@@ -1702,6 +1710,7 @@ function pruneFrontendTasksForRisk(tasks, risk) {
|
|
|
1702
1710
|
fromNodeIds: task.shell.requirementCoverageGate.fromNodeIds.map((nodeId) => nodeId === "frontend-plan-revision-pi"
|
|
1703
1711
|
? "frontend-plan-pi"
|
|
1704
1712
|
: nodeId),
|
|
1713
|
+
fallbackFromNodeIds: undefined,
|
|
1705
1714
|
},
|
|
1706
1715
|
}
|
|
1707
1716
|
: task.shell;
|
|
@@ -1720,12 +1729,25 @@ function pruneFrontendTasksForRisk(tasks, risk) {
|
|
|
1720
1729
|
? {
|
|
1721
1730
|
...task.shell.jsonArtifactGate,
|
|
1722
1731
|
fromNodeId: "frontend-plan-pi",
|
|
1732
|
+
fallbackFromNodeIds: undefined,
|
|
1723
1733
|
}
|
|
1724
1734
|
: task.shell.jsonArtifactGate,
|
|
1725
1735
|
}
|
|
1726
1736
|
: task.shell;
|
|
1727
1737
|
return { ...task, depends_on: nextDeps, shell };
|
|
1728
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
|
+
}
|
|
1729
1751
|
if (task.id === "frontend-implement-pi") {
|
|
1730
1752
|
// still requires final design gate
|
|
1731
1753
|
for (const need of [
|
|
@@ -1830,7 +1852,7 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
1830
1852
|
? [`See 执行约束.md in task source (${sources.taskId})`]
|
|
1831
1853
|
: []),
|
|
1832
1854
|
...STANDARD_GLOBAL_CONSTRAINTS,
|
|
1833
|
-
"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.",
|
|
1834
1856
|
"Final design gate pass is the only authorization for frontend implementation writes.",
|
|
1835
1857
|
"Plan revision remains read-only and never edits business code.",
|
|
1836
1858
|
"Design revision failures route to replan-and-rerun, never dev-fix.",
|
|
@@ -1962,6 +1984,8 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
1962
1984
|
executor: "pi",
|
|
1963
1985
|
complexity: "MED",
|
|
1964
1986
|
writePolicy: "read-only",
|
|
1987
|
+
outputMode: "structured-required",
|
|
1988
|
+
retryPolicy: STRUCTURED_REQUIRED_PI_RETRY_POLICY,
|
|
1965
1989
|
allowedPaths: readOnlyPaths,
|
|
1966
1990
|
forbiddenPaths,
|
|
1967
1991
|
skills: FRONTEND_IMPLEMENTATION_SKILLS,
|
|
@@ -2000,53 +2024,27 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
2000
2024
|
sourceContext,
|
|
2001
2025
|
].join("\n\n"),
|
|
2002
2026
|
},
|
|
2003
|
-
{
|
|
2004
|
-
id: "frontend-first-design-gate-shell",
|
|
2005
|
-
depends_on: ["frontend-design-gate-pi"],
|
|
2006
|
-
role: "verifier",
|
|
2007
|
-
executor: "shell",
|
|
2008
|
-
complexity: "LOW",
|
|
2009
|
-
writePolicy: "read-only",
|
|
2010
|
-
allowedPaths: readOnlyPaths,
|
|
2011
|
-
forbiddenPaths,
|
|
2012
|
-
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.",
|
|
2013
|
-
subtask_prompt: "Deterministic gate: validate frontend-design-gate-pi first-line VERDICT is pass or request-revision; block downstream only on malformed/unexpected verdict.",
|
|
2014
|
-
shell: {
|
|
2015
|
-
commands: [],
|
|
2016
|
-
verdictGate: {
|
|
2017
|
-
fromNodeId: "frontend-design-gate-pi",
|
|
2018
|
-
accept: ["VERDICT: pass", "VERDICT: request-revision"],
|
|
2019
|
-
label: "frontend first design gate",
|
|
2020
|
-
lineMode: "first-verdict-line",
|
|
2021
|
-
},
|
|
2022
|
-
cwd: ".",
|
|
2023
|
-
timeoutMs: 60000,
|
|
2024
|
-
},
|
|
2025
|
-
},
|
|
2026
2027
|
{
|
|
2027
2028
|
id: "frontend-plan-revision-pi",
|
|
2028
2029
|
depends_on: [
|
|
2029
|
-
"frontend-first-design-gate-shell",
|
|
2030
2030
|
"frontend-plan-pi",
|
|
2031
2031
|
"frontend-design-gate-pi",
|
|
2032
2032
|
"frontend-mock-assess-pi",
|
|
2033
2033
|
],
|
|
2034
|
+
runIf: "$.nodes['frontend-design-gate-pi'].firstVerdictLine == 'VERDICT: request-revision'",
|
|
2034
2035
|
role: "planner",
|
|
2035
2036
|
executor: "pi",
|
|
2036
2037
|
complexity: "MED",
|
|
2037
2038
|
writePolicy: "read-only",
|
|
2039
|
+
outputMode: "structured-required",
|
|
2040
|
+
retryPolicy: STRUCTURED_REQUIRED_PI_RETRY_POLICY,
|
|
2038
2041
|
allowedPaths: readOnlyPaths,
|
|
2039
2042
|
forbiddenPaths,
|
|
2040
2043
|
skills: FRONTEND_IMPLEMENTATION_SKILLS,
|
|
2041
|
-
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.",
|
|
2042
2045
|
subtask_prompt: [
|
|
2043
2046
|
"Consume frontend-plan-pi (original plan) and frontend-design-gate-pi (first design review findings).",
|
|
2044
|
-
"
|
|
2045
|
-
"PASS_NO_REVISION_NEEDED",
|
|
2046
|
-
"The original plan from frontend-plan-pi is confirmed and does not require changes.",
|
|
2047
|
-
"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.",
|
|
2048
|
-
"",
|
|
2049
|
-
"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.",
|
|
2050
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.",
|
|
2051
2049
|
requirementCoverageInstruction,
|
|
2052
2050
|
"Do not turn MOCK_STRATEGY: blocked into an implementable strategy without new repository or contract evidence that resolves every blocker.",
|
|
@@ -2060,7 +2058,8 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
2060
2058
|
? [
|
|
2061
2059
|
{
|
|
2062
2060
|
id: "frontend-requirement-coverage-shell",
|
|
2063
|
-
depends_on: ["frontend-plan-revision-pi"],
|
|
2061
|
+
depends_on: ["frontend-plan-revision-pi", "frontend-plan-pi"],
|
|
2062
|
+
dependsPolicy: "all-or-condition-skip",
|
|
2064
2063
|
role: "verifier",
|
|
2065
2064
|
executor: "shell",
|
|
2066
2065
|
complexity: "LOW",
|
|
@@ -2073,6 +2072,7 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
2073
2072
|
commands: [],
|
|
2074
2073
|
requirementCoverageGate: {
|
|
2075
2074
|
fromNodeIds: ["frontend-plan-revision-pi"],
|
|
2075
|
+
fallbackFromNodeIds: ["frontend-plan-pi"],
|
|
2076
2076
|
requiredIds: requirementIds,
|
|
2077
2077
|
label: "frontend requirement coverage",
|
|
2078
2078
|
},
|
|
@@ -2084,7 +2084,8 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
2084
2084
|
: []),
|
|
2085
2085
|
{
|
|
2086
2086
|
id: "frontend-implementation-contract-shell",
|
|
2087
|
-
depends_on: ["frontend-plan-revision-pi"],
|
|
2087
|
+
depends_on: ["frontend-plan-revision-pi", "frontend-plan-pi"],
|
|
2088
|
+
dependsPolicy: "all-or-condition-skip",
|
|
2088
2089
|
role: "verifier",
|
|
2089
2090
|
executor: "shell",
|
|
2090
2091
|
complexity: "LOW",
|
|
@@ -2097,6 +2098,7 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
2097
2098
|
commands: [],
|
|
2098
2099
|
jsonArtifactGate: {
|
|
2099
2100
|
fromNodeId: "frontend-plan-revision-pi",
|
|
2101
|
+
fallbackFromNodeIds: ["frontend-plan-pi"],
|
|
2100
2102
|
schemaId: "frontend-implementation-contract-v1",
|
|
2101
2103
|
artifactName: "frontend-implementation-contract.json",
|
|
2102
2104
|
outputDir: "contracts",
|
|
@@ -2117,6 +2119,8 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
2117
2119
|
? ["frontend-requirement-coverage-shell"]
|
|
2118
2120
|
: []),
|
|
2119
2121
|
],
|
|
2122
|
+
dependsPolicy: "all-or-condition-skip",
|
|
2123
|
+
runIf: "$.nodes['frontend-design-gate-pi'].firstVerdictLine == 'VERDICT: request-revision'",
|
|
2120
2124
|
role: "reviewer",
|
|
2121
2125
|
executor: "pi",
|
|
2122
2126
|
complexity: "MED",
|
|
@@ -2124,12 +2128,11 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
2124
2128
|
allowedPaths: readOnlyPaths,
|
|
2125
2129
|
forbiddenPaths,
|
|
2126
2130
|
skills: FRONTEND_DESIGN_REVIEW_SKILLS,
|
|
2127
|
-
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.",
|
|
2128
2132
|
subtask_prompt: [
|
|
2129
|
-
"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.",
|
|
2130
2134
|
"First non-empty line must be exactly VERDICT: pass or VERDICT: request-revision.",
|
|
2131
|
-
"
|
|
2132
|
-
"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.",
|
|
2133
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.",
|
|
2134
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.",
|
|
2135
2138
|
"Request revision if any design gap remains, if corrections are incomplete, or if the revised plan introduces new unaddressed issues.",
|
|
@@ -2140,19 +2143,24 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
2140
2143
|
},
|
|
2141
2144
|
{
|
|
2142
2145
|
id: "frontend-final-design-gate-shell",
|
|
2143
|
-
depends_on: [
|
|
2146
|
+
depends_on: [
|
|
2147
|
+
"frontend-final-design-review-pi",
|
|
2148
|
+
"frontend-design-gate-pi",
|
|
2149
|
+
],
|
|
2150
|
+
dependsPolicy: "all-or-condition-skip",
|
|
2144
2151
|
role: "verifier",
|
|
2145
2152
|
executor: "shell",
|
|
2146
2153
|
complexity: "LOW",
|
|
2147
2154
|
writePolicy: "read-only",
|
|
2148
2155
|
allowedPaths: readOnlyPaths,
|
|
2149
2156
|
forbiddenPaths,
|
|
2150
|
-
outputContract: "Deterministic
|
|
2151
|
-
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.",
|
|
2152
2159
|
shell: {
|
|
2153
2160
|
commands: [],
|
|
2154
2161
|
verdictGate: {
|
|
2155
2162
|
fromNodeId: "frontend-final-design-review-pi",
|
|
2163
|
+
fallbackFromNodeIds: ["frontend-design-gate-pi"],
|
|
2156
2164
|
accept: ["VERDICT: pass"],
|
|
2157
2165
|
label: "frontend final design gate",
|
|
2158
2166
|
lineMode: "first-verdict-line",
|
|
@@ -2171,6 +2179,7 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
2171
2179
|
"frontend-plan-pi",
|
|
2172
2180
|
"frontend-mock-assess-pi",
|
|
2173
2181
|
],
|
|
2182
|
+
dependsPolicy: "all-or-condition-skip",
|
|
2174
2183
|
role: "implementer",
|
|
2175
2184
|
executor: "pi",
|
|
2176
2185
|
toolProfile: "write",
|
|
@@ -2186,7 +2195,7 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
2186
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.",
|
|
2187
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.",
|
|
2188
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.",
|
|
2189
|
-
"
|
|
2198
|
+
"frontend-final-design-gate-shell confirmed the effective initial-or-final design verdict is pass. Stay within writeSet and preserve unrelated files.",
|
|
2190
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.",
|
|
2191
2200
|
"Do not write root artifacts/** unless explicitly included in writeSet. Do not claim Browser/visual verification.",
|
|
2192
2201
|
writerDeliveryContract(taskConfig),
|
|
@@ -2311,6 +2320,7 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
2311
2320
|
"frontend-failure-assess-shell",
|
|
2312
2321
|
implementId,
|
|
2313
2322
|
],
|
|
2323
|
+
runIf: "$.nodes['frontend-failure-assess-shell'].json.eligible == true",
|
|
2314
2324
|
role: "implementer",
|
|
2315
2325
|
executor: "pi",
|
|
2316
2326
|
toolProfile: "write",
|
|
@@ -2320,10 +2330,10 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
2320
2330
|
allowedPaths: implementPaths.allowedPaths,
|
|
2321
2331
|
forbiddenPaths,
|
|
2322
2332
|
skills: FRONTEND_IMPLEMENTATION_SKILLS,
|
|
2323
|
-
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.",
|
|
2324
2334
|
subtask_prompt: [
|
|
2325
2335
|
"Read contracts/frontend-repair-assessment.json and the validated frontend implementation contract.",
|
|
2326
|
-
"
|
|
2336
|
+
"This node runs only for eligible=true. Apply the smallest fix for the classified repairable failure inside the original implement writeSet only.",
|
|
2327
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.",
|
|
2328
2338
|
"Do not re-plan requirements or expand allowed paths. Browser/visual remain not-run.",
|
|
2329
2339
|
writerDeliveryContract(taskConfig),
|
|
@@ -2399,6 +2409,7 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
2399
2409
|
"frontend-repair-pi",
|
|
2400
2410
|
implementId,
|
|
2401
2411
|
],
|
|
2412
|
+
dependsPolicy: "all-or-condition-skip",
|
|
2402
2413
|
role: "verifier",
|
|
2403
2414
|
executor: "shell",
|
|
2404
2415
|
complexity: "LOW",
|
|
@@ -2406,7 +2417,7 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
2406
2417
|
allowedPaths: readOnlyPaths,
|
|
2407
2418
|
forbiddenPaths,
|
|
2408
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.",
|
|
2409
|
-
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.",
|
|
2410
2421
|
shell: {
|
|
2411
2422
|
commands: ["frontend-worktree-diff-gate"],
|
|
2412
2423
|
cwd: ".",
|
|
@@ -2421,11 +2432,15 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
2421
2432
|
"frontend-static-reverify-shell",
|
|
2422
2433
|
"frontend-behavior-reverify-shell",
|
|
2423
2434
|
"frontend-repair-pi",
|
|
2435
|
+
"frontend-verification-trace-shell",
|
|
2436
|
+
"frontend-static-verify-shell",
|
|
2437
|
+
"frontend-behavior-verify-shell",
|
|
2424
2438
|
"frontend-failure-assess-shell",
|
|
2425
2439
|
implementId,
|
|
2426
2440
|
"frontend-implementation-contract-shell",
|
|
2427
2441
|
"frontend-contract-pi",
|
|
2428
2442
|
"frontend-plan-pi",
|
|
2443
|
+
"frontend-design-gate-pi",
|
|
2429
2444
|
"frontend-plan-revision-pi",
|
|
2430
2445
|
"frontend-final-design-review-pi",
|
|
2431
2446
|
"frontend-mock-assess-pi",
|
|
@@ -2433,6 +2448,7 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
2433
2448
|
? ["frontend-mock-verify-shell"]
|
|
2434
2449
|
: []),
|
|
2435
2450
|
],
|
|
2451
|
+
dependsPolicy: "all-or-condition-skip",
|
|
2436
2452
|
role: "reviewer",
|
|
2437
2453
|
executor: "pi",
|
|
2438
2454
|
complexity: "HIGH",
|
|
@@ -2445,9 +2461,9 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
2445
2461
|
"Review the frontend implementation and verification evidence.",
|
|
2446
2462
|
"First non-empty line must be exactly VERDICT: pass or VERDICT: request-revision.",
|
|
2447
2463
|
"Any Critical or Important finding must force VERDICT: request-revision.",
|
|
2448
|
-
"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.",
|
|
2449
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).",
|
|
2450
|
-
"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.",
|
|
2451
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.",
|
|
2452
2468
|
"Inspect the production/default-real-path static evidence directly and require Mock activation to be off for that check.",
|
|
2453
2469
|
"Distinguish Mock-backed evidence from real API integration evidence and preserve the Real Integration Gap when the backend was not exercised.",
|
|
@@ -2488,6 +2504,9 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
2488
2504
|
"frontend-static-reverify-shell",
|
|
2489
2505
|
"frontend-behavior-reverify-shell",
|
|
2490
2506
|
"frontend-repair-pi",
|
|
2507
|
+
"frontend-verification-trace-shell",
|
|
2508
|
+
"frontend-static-verify-shell",
|
|
2509
|
+
"frontend-behavior-verify-shell",
|
|
2491
2510
|
"frontend-failure-assess-shell",
|
|
2492
2511
|
"frontend-implementation-contract-shell",
|
|
2493
2512
|
"frontend-mock-assess-pi",
|
|
@@ -2495,6 +2514,7 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
2495
2514
|
? ["frontend-mock-verify-shell"]
|
|
2496
2515
|
: []),
|
|
2497
2516
|
],
|
|
2517
|
+
dependsPolicy: "all-or-condition-skip",
|
|
2498
2518
|
role: "closeout",
|
|
2499
2519
|
executor: "pi",
|
|
2500
2520
|
complexity: "MED",
|
|
@@ -2507,7 +2527,7 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
2507
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.",
|
|
2508
2528
|
subtask_prompt: [
|
|
2509
2529
|
"Return a frontend closeout summary covering Mock decision/strategy/files/verification/production boundary, changes, verification evidence, review result, known risks, and follow-up.",
|
|
2510
|
-
"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.",
|
|
2511
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.`,
|
|
2512
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.`,
|
|
2513
2533
|
"Read-only: do not modify code, docs, artifacts, or .harness/dag-runs/.",
|
|
@@ -2709,7 +2729,7 @@ const BACKEND_TEST_CASE_MANIFEST_OUTPUT_INSTRUCTIONS = [
|
|
|
2709
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.",
|
|
2710
2730
|
"acIds MUST exactly match the explicit AC-* values in the written case body; do not infer ACs from Business Rules or summary matrices.",
|
|
2711
2731
|
"Use full BE-<MODULE>-<NNN> caseId strings. Do not invent coverage percentages and do not emit coverageSummary; shell always writes the canonical summary.",
|
|
2712
|
-
|
|
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":[]}',
|
|
2713
2733
|
].join("\n\n");
|
|
2714
2734
|
function buildEmitBackendCaseManifestNode(sources, options) {
|
|
2715
2735
|
return {
|
|
@@ -2799,7 +2819,7 @@ function buildReviewBackendCasesNode(sources, options) {
|
|
|
2799
2819
|
writePolicy: "read-only",
|
|
2800
2820
|
allowedPaths: commonReadOnlyPaths(sources),
|
|
2801
2821
|
forbiddenPaths: commonForbiddenPaths(sources),
|
|
2802
|
-
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.",
|
|
2803
2823
|
subtask_prompt: [
|
|
2804
2824
|
"Review the generated backend functional test cases under testcase/md/ and the validated Case Manifest v1.",
|
|
2805
2825
|
"",
|
|
@@ -2830,7 +2850,7 @@ function buildReviewBackendCasesNode(sources, options) {
|
|
|
2830
2850
|
"## Verdict Rules:",
|
|
2831
2851
|
"- All Critical checks pass + Important findings ≤ 2 → VERDICT: pass",
|
|
2832
2852
|
"- Any Critical fails OR Important > 2 → VERDICT: request-revision",
|
|
2833
|
-
"- 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.",
|
|
2834
2854
|
"",
|
|
2835
2855
|
"## Output After Verdict:",
|
|
2836
2856
|
"1. Coverage Assessment table (AC → full BE-* case IDs) using manifest + MD",
|
|
@@ -2886,8 +2906,8 @@ function buildGenerateBackendPytestNode(sources) {
|
|
|
2886
2906
|
return {
|
|
2887
2907
|
id: "generate-backend-pytest-pi",
|
|
2888
2908
|
depends_on: [
|
|
2889
|
-
"
|
|
2890
|
-
"backend-test-
|
|
2909
|
+
"backend-test-case-manifest-shell",
|
|
2910
|
+
"validate-backend-test-contracts-shell",
|
|
2891
2911
|
],
|
|
2892
2912
|
role: "implementer",
|
|
2893
2913
|
executor: "pi",
|
|
@@ -2907,11 +2927,11 @@ function buildGenerateBackendPytestNode(sources) {
|
|
|
2907
2927
|
// 注意:Pi 节点超时由 executor 层控制(默认 30 分钟)
|
|
2908
2928
|
// 如需调整,在 harness.json 的 executors.pi 中配置 modelConfig.timeoutMs
|
|
2909
2929
|
subtask_prompt: [
|
|
2910
|
-
"Convert the
|
|
2930
|
+
"Convert the validated test cases under testcase/md/ into pytest automation code.",
|
|
2911
2931
|
"",
|
|
2912
2932
|
"## Inputs (MUST use validated contracts):",
|
|
2913
|
-
"-
|
|
2914
|
-
"- 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.",
|
|
2915
2935
|
"- Validated Backend Test Execution Contract v1 under contracts/backend-test-execution.json (execution gate).",
|
|
2916
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.",
|
|
2917
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).",
|
|
@@ -3027,7 +3047,7 @@ function buildBackendTestSemanticReviewNode(sources, options = {}) {
|
|
|
3027
3047
|
"Return exactly one pure JSON object with only verdict, findings, summary; no Markdown fence or surrounding prose.",
|
|
3028
3048
|
"verdict must be pass or request-revision. Each findings[] item must contain exactly severity, caseId, testFile, testSymbol, contractRefs, issue, requiredChange.",
|
|
3029
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.",
|
|
3030
|
-
|
|
3050
|
+
'Minimal shape: {"verdict":"pass","findings":[],"summary":"No contract-backed semantic contradiction found."}',
|
|
3031
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.",
|
|
3032
3052
|
"Do not use aliases such as file, symbol, refs, finding, or requiredFix; the strict contract requires testFile, testSymbol, contractRefs, issue, requiredChange.",
|
|
3033
3053
|
"request-revision only for concrete semantic contradiction with reviewed cases/formal analysis evidence. No style findings.",
|
|
@@ -3125,7 +3145,7 @@ function buildClassifyBackendTestResultNode(sources) {
|
|
|
3125
3145
|
"Read-only classifier for Backend Test Result v1.",
|
|
3126
3146
|
"Return exactly one JSON object (prefer pure JSON; single fenced json block tolerated; no trailing prose).",
|
|
3127
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.",
|
|
3128
|
-
|
|
3148
|
+
'Minimal shape: {"category":"Unknown","evidence":["outcome=completed-with-failures"],"confidence":0.5,"notes":"Single-run evidence is insufficient for a stronger classification."}',
|
|
3129
3149
|
"Read contracts/backend-test-result-initial.json (run-owned initial Result v1). Do NOT invent pass rates from raw logs.",
|
|
3130
3150
|
"category must be one of: ProductBug, TestBug, EnvFailure, ContractMismatch, FlakyTest, Unknown.",
|
|
3131
3151
|
"Hard constraints:",
|
|
@@ -3178,7 +3198,7 @@ function buildTestRetrospectNode(sources) {
|
|
|
3178
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.",
|
|
3179
3199
|
"- Stability MUST come from independent Stability Evidence: use successfulRuns / recordedRuns, same suite/version, and require n≥5; a single run is unavailable.",
|
|
3180
3200
|
"- Use classify-backend-test-result-pi JSON as interpretive evidence only.",
|
|
3181
|
-
"- 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.",
|
|
3182
3202
|
"",
|
|
3183
3203
|
"## Report Structure:",
|
|
3184
3204
|
"1. Maturity Rating with rationale",
|
|
@@ -3245,7 +3265,7 @@ function buildL5MetricsNode(sources) {
|
|
|
3245
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.",
|
|
3246
3266
|
"Do not create a new DAG, rewrite the retrospective report, change test outcome, or modify any repository file.",
|
|
3247
3267
|
"Return exactly one JSON object and no surrounding prose.",
|
|
3248
|
-
|
|
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]}.',
|
|
3249
3269
|
"Each metric must contain numerator, denominator, ratio, threshold, status=pass|fail|unavailable, and reason (null only when passed).",
|
|
3250
3270
|
"Use only explicit evidence. Missing or invalid required evidence is unavailable, never zero or an estimate.",
|
|
3251
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.",
|
|
@@ -3270,75 +3290,115 @@ function buildBackendTestHybridDag(sources) {
|
|
|
3270
3290
|
const globalConstraints = [
|
|
3271
3291
|
...taskConfig.hardConstraints,
|
|
3272
3292
|
...STANDARD_GLOBAL_CONSTRAINTS,
|
|
3273
|
-
"backend-test-dag uses exactly
|
|
3274
|
-
"Case
|
|
3275
|
-
"
|
|
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.",
|
|
3276
3297
|
"Functional test case IDs must use BE-<MODULE>-<NNN> format.",
|
|
3277
3298
|
"pytest writers may only create the initially declared testcase assets; production code, config, skip/xfail, swallowed failures and mock substitution are forbidden.",
|
|
3278
3299
|
];
|
|
3279
3300
|
const analyze = buildAnalyzeInputsNode(sources);
|
|
3280
3301
|
analyze.id = "analyze-and-discover-backend-test-pi";
|
|
3281
|
-
analyze.outputContract =
|
|
3302
|
+
analyze.outputContract =
|
|
3303
|
+
"Pure JSON envelope {analysis: Backend Test Analysis v2, execution: Backend Test Execution Contract v1}; no prose or writes.";
|
|
3282
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.`;
|
|
3283
3305
|
const contracts = {
|
|
3284
|
-
id: "validate-backend-test-contracts-shell",
|
|
3285
|
-
|
|
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),
|
|
3286
3314
|
outputContract: "Materialize and validate contracts/backend-test-analysis.json and contracts/backend-test-execution.json.",
|
|
3287
|
-
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
|
+
},
|
|
3288
3322
|
};
|
|
3289
3323
|
const generateCases = buildGenerateBackendFunctionalCasesNode(sources);
|
|
3290
3324
|
generateCases.id = "generate-backend-cases-and-manifest-pi";
|
|
3291
3325
|
generateCases.depends_on = [contracts.id];
|
|
3292
|
-
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.";
|
|
3293
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}`;
|
|
3294
|
-
const manifest = buildBackendTestCaseManifestGateNode(sources, {
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
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
|
+
});
|
|
3302
3336
|
const generatePytest = buildGenerateBackendPytestNode(sources);
|
|
3303
|
-
generatePytest.depends_on = [
|
|
3304
|
-
const
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
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.",
|
|
3316
3360
|
shell: {
|
|
3317
3361
|
commands: [],
|
|
3318
|
-
backendTestPipeline: "
|
|
3362
|
+
backendTestPipeline: "classification-result-context",
|
|
3319
3363
|
cwd: ".",
|
|
3320
3364
|
timeoutMs: 60000,
|
|
3321
3365
|
},
|
|
3322
3366
|
};
|
|
3323
|
-
const execute = buildExecuteBackendPytestNode(sources, { id: "execute-and-parse-backend-pytest-shell", dependsOn: [semanticGate.id, contracts.id], reportStem: "backend-test-initial" });
|
|
3324
|
-
execute.shell.backendTestPipeline = "execute-parse-initial";
|
|
3325
|
-
const classify = buildClassifyBackendTestResultNode(sources);
|
|
3326
|
-
classify.depends_on = [execute.id];
|
|
3327
|
-
const context = {
|
|
3328
|
-
id: "materialize-classification-and-result-context-shell", depends_on: [classify.id, manifest.id], role: "verifier", executor: "shell", complexity: "LOW",
|
|
3329
|
-
writePolicy: "read-only", allowedPaths: commonReadOnlyPaths(sources), forbiddenPaths: commonForbiddenPaths(sources),
|
|
3330
|
-
outputContract: "Materialize Classification v1, copy the unique initial Result to canonical contracts/backend-test-result.json, and emit Result + Manifest + Classification context.",
|
|
3331
|
-
subtask_prompt: "Validate classification and materialize canonical single-run result context without repair eligibility or rerun.",
|
|
3332
|
-
shell: { commands: [], backendTestPipeline: "classification-result-context", cwd: ".", timeoutMs: 60000 },
|
|
3333
|
-
};
|
|
3334
3367
|
const retrospect = buildTestRetrospectNode(sources);
|
|
3335
3368
|
retrospect.depends_on = [context.id];
|
|
3336
3369
|
retrospect.subtask_prompt = retrospect.subtask_prompt.replaceAll("select-effective-backend-test-result-shell", context.id);
|
|
3337
3370
|
const l5Metrics = buildL5MetricsNode(sources);
|
|
3338
3371
|
l5Metrics.depends_on = [retrospect.id];
|
|
3339
|
-
const
|
|
3340
|
-
|
|
3341
|
-
|
|
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
|
+
};
|
|
3342
3402
|
applyDefaultReadOnlyRetryPolicy(spec);
|
|
3343
3403
|
parseDagSpec(spec);
|
|
3344
3404
|
assertValidDagSpec(spec);
|
|
@@ -3540,7 +3600,10 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
3540
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.",
|
|
3541
3601
|
}, {
|
|
3542
3602
|
id: "final-frontend-case-review-gate-shell",
|
|
3543
|
-
depends_on: [
|
|
3603
|
+
depends_on: [
|
|
3604
|
+
"review-frontend-cases-pi",
|
|
3605
|
+
"review-frontend-cases-final-pi",
|
|
3606
|
+
],
|
|
3544
3607
|
dependsPolicy: "all-or-condition-skip",
|
|
3545
3608
|
role: "verifier",
|
|
3546
3609
|
executor: "shell",
|
|
@@ -3689,7 +3752,11 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
3689
3752
|
forbiddenPaths: forbidden,
|
|
3690
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.",
|
|
3691
3754
|
subtask_prompt: "Opt-in Delivery/Worker quality gate (frontendTest.strictOutcomeGate=true). Retrospective does not depend on this node.",
|
|
3692
|
-
shell: {
|
|
3755
|
+
shell: {
|
|
3756
|
+
commands: [frontendTestOutcomeGate],
|
|
3757
|
+
cwd: ".",
|
|
3758
|
+
timeoutMs: 60000,
|
|
3759
|
+
},
|
|
3693
3760
|
});
|
|
3694
3761
|
}
|
|
3695
3762
|
tasks.push({
|
|
@@ -4643,7 +4710,86 @@ function buildKnowledgeGraphBootstrapHybridDag(sources) {
|
|
|
4643
4710
|
assertValidDagSpec(spec);
|
|
4644
4711
|
return spec;
|
|
4645
4712
|
}
|
|
4646
|
-
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);
|
|
4647
4793
|
let spec;
|
|
4648
4794
|
if (template === "frontend-implementation") {
|
|
4649
4795
|
spec = buildFrontendHybridDagFromTask(sources);
|
|
@@ -4666,7 +4812,13 @@ function buildHybridDagForTemplate(sources, template) {
|
|
|
4666
4812
|
spec = buildSupervisedHybridDag(standard, sources);
|
|
4667
4813
|
}
|
|
4668
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
|
+
}
|
|
4669
4820
|
spec.sourceBinding = buildDagSourceBinding(sources);
|
|
4821
|
+
spec.taskContractBinding = taskContractBinding;
|
|
4670
4822
|
assertNoGovernanceFlagOnDisallowedTemplate(spec, template);
|
|
4671
4823
|
parseDagSpec(spec);
|
|
4672
4824
|
assertValidDagSpec(spec);
|
|
@@ -4694,7 +4846,7 @@ function assertNoGovernanceFlagOnDisallowedTemplate(spec, template) {
|
|
|
4694
4846
|
}
|
|
4695
4847
|
}
|
|
4696
4848
|
}
|
|
4697
|
-
export function buildHybridDagFromTask(sources, options = {}) {
|
|
4849
|
+
export async function buildHybridDagFromTask(sources, options = {}) {
|
|
4698
4850
|
const selection = resolveTaskDagTemplateSelection({
|
|
4699
4851
|
taskKind: sources.taskConfig.taskKind,
|
|
4700
4852
|
title: sources.taskConfig.title,
|
|
@@ -4836,7 +4988,8 @@ function applyProjectGovernanceReview(spec, template, sources) {
|
|
|
4836
4988
|
insertGovernanceStandardGate(spec, sources);
|
|
4837
4989
|
return;
|
|
4838
4990
|
}
|
|
4839
|
-
if (template === "review-gated-dag" ||
|
|
4991
|
+
if (template === "review-gated-dag" ||
|
|
4992
|
+
template === "supervised-implementation") {
|
|
4840
4993
|
const review = spec.tasks.find((task) => task.id === "review-pi");
|
|
4841
4994
|
if (review)
|
|
4842
4995
|
enableProjectGovernanceOnNode(review);
|
|
@@ -5320,7 +5473,7 @@ export async function writeHybridDagDraft(sources, outputPath, options = {}) {
|
|
|
5320
5473
|
const preparedSources = template === "frontend-implementation"
|
|
5321
5474
|
? await prepareFrontendMockSources(sources)
|
|
5322
5475
|
: sources;
|
|
5323
|
-
const spec = buildHybridDagForTemplate(preparedSources, template);
|
|
5476
|
+
const spec = await buildHybridDagForTemplate(preparedSources, template);
|
|
5324
5477
|
await writeFile(outputPath, `${JSON.stringify(spec, null, 2)}\n`, "utf-8");
|
|
5325
5478
|
return {
|
|
5326
5479
|
taskId: sources.taskId,
|