@tea-agent/loop-agent 0.16.1-beta.0 → 0.16.1-beta.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
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# 更新日志
|
|
2
2
|
|
|
3
|
+
## [0.16.1-beta.1] - 2026-07-19
|
|
4
|
+
|
|
5
|
+
### 修复
|
|
6
|
+
|
|
7
|
+
- 修复前端实现契约 Schema 漂移:DAG 生成时从当前 loop-agent 包内置 `docs/templates/frontend-implementation-contract.schema.json` 加载完整 `frontend-implementation-contract-v1` JSON Schema,并注入到 `frontend-plan-pi` 与 `frontend-plan-revision-pi` 提示中,模型不再需要搜索或猜测契约字段
|
|
8
|
+
- Prompt 中显式禁止 `schemaId`、`targetFiles`、`requirementCoverage` 等不在 Schema 内的字段,防止模型(如 DeepSeek)生成错误结构
|
|
9
|
+
- small 拓扑下 `frontend-plan-pi` 与 reviewed/high-risk 拓扑下 `frontend-plan-revision-pi` 均获得相同的完整 Schema 与确定性上下文
|
|
10
|
+
|
|
3
11
|
## [0.15.0] - 2026-07-18
|
|
4
12
|
|
|
5
13
|
### 重点更新
|
|
@@ -1,9 +1,86 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
2
3
|
import { readFile } from "node:fs/promises";
|
|
3
4
|
import path from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
4
6
|
import { z } from "zod";
|
|
5
7
|
import { writeDagRunJsonArtifact } from "../../infrastructure/harness/artifact-store.js";
|
|
8
|
+
import { findPackageRoot } from "../../shared/package-metadata.js";
|
|
6
9
|
export const FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID = "frontend-implementation-contract-v1";
|
|
10
|
+
/**
|
|
11
|
+
* Load the canonical frontend-implementation-contract-v1 JSON Schema from the
|
|
12
|
+
* installed loop-agent package docs/templates/ path. Package-root discovery
|
|
13
|
+
* works from both the source module and the compiled dist module without
|
|
14
|
+
* relying on CommonJS globals in the ESM runtime.
|
|
15
|
+
*
|
|
16
|
+
* Validation is fail-closed: missing file, malformed JSON, mismatched $id,
|
|
17
|
+
* missing additionalProperties: false, or incomplete top-level required keys
|
|
18
|
+
* all throw before any DAG prompt is assembled.
|
|
19
|
+
*/
|
|
20
|
+
export function loadFrontendImplementationContractJsonSchema(startDir = path.dirname(fileURLToPath(import.meta.url))) {
|
|
21
|
+
const packageRoot = findPackageRoot(startDir);
|
|
22
|
+
if (!packageRoot) {
|
|
23
|
+
throw new Error(`cannot locate loop-agent package root from ${path.resolve(startDir)}`);
|
|
24
|
+
}
|
|
25
|
+
const schemaPath = path.join(packageRoot, "docs", "templates", "frontend-implementation-contract.schema.json");
|
|
26
|
+
let content;
|
|
27
|
+
try {
|
|
28
|
+
content = readFileSync(schemaPath, "utf-8");
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
throw new Error(`cannot load frontend-implementation-contract.schema.json from current loop-agent package at ${schemaPath}: ${error.code ?? String(error)}`);
|
|
32
|
+
}
|
|
33
|
+
let parsed;
|
|
34
|
+
try {
|
|
35
|
+
parsed = JSON.parse(content);
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
throw new Error(`frontend-implementation-contract.schema.json is not valid JSON: ${error.message}`);
|
|
39
|
+
}
|
|
40
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
41
|
+
throw new Error("frontend-implementation-contract.schema.json root is not a JSON object");
|
|
42
|
+
}
|
|
43
|
+
const schema = parsed;
|
|
44
|
+
if (schema.$id !== FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID) {
|
|
45
|
+
throw new Error(`schema $id mismatch: expected ${FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID}, got ${String(schema.$id)}`);
|
|
46
|
+
}
|
|
47
|
+
if (schema.additionalProperties !== false) {
|
|
48
|
+
throw new Error("schema must have additionalProperties: false at top level");
|
|
49
|
+
}
|
|
50
|
+
const expectedRequired = [
|
|
51
|
+
"schemaVersion",
|
|
52
|
+
"sourceBinding",
|
|
53
|
+
"riskLevel",
|
|
54
|
+
"targets",
|
|
55
|
+
"requirements",
|
|
56
|
+
"uiStates",
|
|
57
|
+
"interactions",
|
|
58
|
+
"mockApi",
|
|
59
|
+
"designEvidence",
|
|
60
|
+
"verificationTargets",
|
|
61
|
+
"evidenceGaps",
|
|
62
|
+
];
|
|
63
|
+
const actualRequired = Array.isArray(schema.required) ? schema.required : [];
|
|
64
|
+
const missing = expectedRequired.filter((key) => !actualRequired.includes(key));
|
|
65
|
+
if (missing.length > 0) {
|
|
66
|
+
throw new Error(`schema required fields missing: ${missing.join(", ")}`);
|
|
67
|
+
}
|
|
68
|
+
const properties = schema.properties && typeof schema.properties === "object"
|
|
69
|
+
? schema.properties
|
|
70
|
+
: {};
|
|
71
|
+
const missingProperties = expectedRequired.filter((key) => !Object.hasOwn(properties, key));
|
|
72
|
+
if (missingProperties.length > 0) {
|
|
73
|
+
throw new Error(`schema properties missing: ${missingProperties.join(", ")}`);
|
|
74
|
+
}
|
|
75
|
+
const schemaVersion = properties.schemaVersion;
|
|
76
|
+
const mockApi = properties.mockApi;
|
|
77
|
+
const mockApiProperties = mockApi?.properties;
|
|
78
|
+
const productionDefaultOff = mockApiProperties?.productionDefaultOff;
|
|
79
|
+
if (schemaVersion?.const !== 1 || productionDefaultOff?.const !== true) {
|
|
80
|
+
throw new Error("schema fixed values are incomplete: schemaVersion.const must be 1 and mockApi.productionDefaultOff.const must be true");
|
|
81
|
+
}
|
|
82
|
+
return JSON.stringify(parsed);
|
|
83
|
+
}
|
|
7
84
|
const id = z.string().regex(/^(?:REQ|BR|AC)-[A-Z0-9]+(?:-[A-Z0-9]+)*$/);
|
|
8
85
|
const safePath = z
|
|
9
86
|
.string()
|
|
@@ -21,6 +21,7 @@ import { BACKEND_TEST_EXECUTION_DEFAULT_TEST_ROOT, buildBackendTestExecutionPref
|
|
|
21
21
|
import { buildBackendTestOutcomeGateShellSnippet } from "./backend-test-result-contract.js";
|
|
22
22
|
import { classifyFrontendRisk, } from "./frontend-risk.js";
|
|
23
23
|
import { discoverFrontendProjectCapability, } from "./frontend-project-capability.js";
|
|
24
|
+
import { FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID, loadFrontendImplementationContractJsonSchema, } from "./frontend-implementation-contract.js";
|
|
24
25
|
const REQUIREMENT_FILE = "需求.md";
|
|
25
26
|
const CONSTRAINT_FILE = "执行约束.md";
|
|
26
27
|
const REFERENCE_DIRECTORY = "references";
|
|
@@ -1733,6 +1734,46 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
1733
1734
|
allowedPaths: taskConfig.allowedPaths,
|
|
1734
1735
|
complexity: taskConfig.complexity,
|
|
1735
1736
|
});
|
|
1737
|
+
const frontendSourceBinding = buildDagSourceBinding(sources);
|
|
1738
|
+
const frontendContractSchemaBlock = (() => {
|
|
1739
|
+
const schema = loadFrontendImplementationContractJsonSchema();
|
|
1740
|
+
const requirement = frontendSourceBinding.sources.find((source) => source.kind === "requirement");
|
|
1741
|
+
if (!requirement) {
|
|
1742
|
+
throw new Error("frontend implementation contract context requires a bound requirement source");
|
|
1743
|
+
}
|
|
1744
|
+
const referencePaths = frontendSourceBinding.sources
|
|
1745
|
+
.filter((s) => s.kind === "reference")
|
|
1746
|
+
.map((s) => s.path);
|
|
1747
|
+
const fixedFields = {
|
|
1748
|
+
schemaVersion: 1,
|
|
1749
|
+
sourceBinding: {
|
|
1750
|
+
taskId: frontendSourceBinding.taskId,
|
|
1751
|
+
requirementPath: requirement.path,
|
|
1752
|
+
requirementSha256: requirement.sha256,
|
|
1753
|
+
referencePaths,
|
|
1754
|
+
requirementIds: frontendSourceBinding.requirementIds,
|
|
1755
|
+
},
|
|
1756
|
+
riskLevel: frontendRisk.selectedRisk,
|
|
1757
|
+
targets: { files: implementPaths.writeSet },
|
|
1758
|
+
};
|
|
1759
|
+
return [
|
|
1760
|
+
`## ${FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID} JSON Schema (authoritative; do not guess fields)`,
|
|
1761
|
+
schema,
|
|
1762
|
+
"",
|
|
1763
|
+
"## Fixed contract fields (deterministic; copy exactly and do not modify)",
|
|
1764
|
+
JSON.stringify(fixedFields),
|
|
1765
|
+
"",
|
|
1766
|
+
"## Forbidden fields (these are NOT in the schema; do not emit)",
|
|
1767
|
+
"- schemaId",
|
|
1768
|
+
"- targetFiles",
|
|
1769
|
+
"- requirementCoverage",
|
|
1770
|
+
"",
|
|
1771
|
+
"## Critical rules",
|
|
1772
|
+
"- verificationTargets is a TOP-LEVEL required array",
|
|
1773
|
+
"- uiStates items use name/applicable/expectedBehavior/implementationTargets/verificationTargetIds/notApplicableReason",
|
|
1774
|
+
"- mockApi.productionDefaultOff must always be true (including strategy: not-needed)",
|
|
1775
|
+
].join("\n");
|
|
1776
|
+
})();
|
|
1736
1777
|
const sourceContext = [
|
|
1737
1778
|
buildSourceContextBlock(sources),
|
|
1738
1779
|
capabilityContextBlock,
|
|
@@ -1741,7 +1782,7 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
1741
1782
|
.join("\n\n");
|
|
1742
1783
|
const hasMockVerifyCommands = (taskConfig.frontendMock?.verifyCommands.length ?? 0) > 0 ||
|
|
1743
1784
|
mockCapability.verifyCommands.length > 0;
|
|
1744
|
-
const requirementIds =
|
|
1785
|
+
const requirementIds = frontendSourceBinding.requirementIds;
|
|
1745
1786
|
const requirementCoverageInstruction = requirementIds.length > 0
|
|
1746
1787
|
? `Include a Requirement Coverage section that lists every exact source identifier: ${requirementIds.join(", ")}. Preserve each identifier verbatim and map it to concrete implementation and verification steps.`
|
|
1747
1788
|
: "";
|
|
@@ -1900,6 +1941,7 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
1900
1941
|
fixedVerificationContext,
|
|
1901
1942
|
sourceContext,
|
|
1902
1943
|
mockContextBlock,
|
|
1944
|
+
frontendContractSchemaBlock,
|
|
1903
1945
|
].join("\n\n"),
|
|
1904
1946
|
},
|
|
1905
1947
|
{
|
|
@@ -1976,6 +2018,7 @@ function buildFrontendHybridDagFromTask(sources) {
|
|
|
1976
2018
|
"Read-only: do not modify code, docs, artifacts, or repository files. This node revises the plan only.",
|
|
1977
2019
|
"End the response with exactly one fenced json object conforming to frontend-implementation-contract-v1. Bind it to the supplied task sources; map every requirement and applicable UI state to concrete implementation and verification targets or an explicit blocking evidence gap. Do not include secrets or unsafe paths.",
|
|
1978
2020
|
sourceContext,
|
|
2021
|
+
frontendContractSchemaBlock,
|
|
1979
2022
|
].join("\n\n"),
|
|
1980
2023
|
},
|
|
1981
2024
|
...(requirementIds.length > 0
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@ Pre-write nodes are read-only. Preserve IDs, labels, commands, language, require
|
|
|
9
9
|
- **`frontend-mock-assess-pi` + gate**: declares `firstProtocolLine: "MOCK_STRATEGY:"`; canonical output first line
|
|
10
10
|
`MOCK_STRATEGY: native|browser-intercept|request-adapter|not-needed|blocked`
|
|
11
11
|
Pi output mapping promotes the first matching protocol line ahead of any preamble without inventing or replacing its value; missing, malformed, or blocked strategies still fail closed. Prefer native Mock; browser intercept only with existing e2e; request-adapter only for reversible local preview. Default `auto` may select `not-needed` when contract/scout evidence confirms no project Mock capability, without adding Mock files/deps, while keeping real requests default and recording the Real Integration Gap. Other `not-needed` cases need positive no-remote/stable-backend evidence; invalid when `frontendMock.policy=required`. `blocked` for missing/conflicting contracts, unsafe paths/deps, unread specs, production-default-on, unverifiable entrypoints. Output Mock Decision, API/spec/service evidence, backend readiness, selection evidence, endpoint/fixture matrix, activation, targets, production safety, verification plan, real-integration gap, blocking issues. Never invent fields, store secrets, comment real requests, import test mocks into production, or treat Mock as real integration. Gate uses `first-non-empty` only; never authorizes writes. Unsafe required contracts → no writer.
|
|
12
|
-
- **`frontend-plan-pi` + design loop**: AC → steps, in-bound files, UI states, reuse, deps, activation/rollback, frozen verify entrypoints, real-integration gap. First gate: `VERDICT: pass|request-revision`. Pass may emit `PASS_NO_REVISION_NEEDED`; else full corrected plan without invented evidence. Final review rechecks plan/findings/revision/assessment/Mock safety. Only final `VERDICT: pass` authorizes writes; failure → replan/rerun (not dev-fix).
|
|
12
|
+
- **`frontend-plan-pi` + design loop**: AC → steps, in-bound files, UI states, reuse, deps, activation/rollback, frozen verify entrypoints, real-integration gap. First gate: `VERDICT: pass|request-revision`. Pass may emit `PASS_NO_REVISION_NEEDED`; else full corrected plan without invented evidence. Final review rechecks plan/findings/revision/assessment/Mock safety. Only final `VERDICT: pass` authorizes writes; failure → replan/rerun (not dev-fix). The plan-pi prompt now includes the complete `frontend-implementation-contract-v1` JSON Schema loaded from the loop-agent package `docs/templates/` path, plus deterministic source binding, risk level, and allowed implementation targets. The model does not need to search or guess contract fields; `schemaId`, `targetFiles`, `requirementCoverage` are explicitly forbidden.
|
|
13
13
|
- **`frontend-implement-pi`**: sole exclusive writer. Stay in `writeSet`; real requests default-on; Mock reversible, dev/test-only, production-off. Atomic handler/intercept/adapter with consumer+tests. Stop on forbidden paths or guesses. Output changed files, behavior, UI states, styling notes, verification attempted, residual risks. Optional mock-verify when frozen; static+behavior always; behavior must prove page consumption. Skipped-Mock `not-needed` keeps real integration pending unless the real backend path has fresh evidence.
|
|
14
14
|
|
|
15
15
|
## Contract / trace / stages (M1–M2)
|