@tea-agent/loop-agent 0.39.2 → 0.39.3
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/dist/build-stamp.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"version": "0.39.
|
|
4
|
-
"gitSha": "
|
|
5
|
-
"builtAt": "2026-08-
|
|
3
|
+
"version": "0.39.3",
|
|
4
|
+
"gitSha": "048826da6ab8dba7f01daa635ab582f68c539d06",
|
|
5
|
+
"builtAt": "2026-08-28T09:06:21.116Z"
|
|
6
6
|
}
|
|
@@ -92,6 +92,18 @@ function isAtxHeading(line) {
|
|
|
92
92
|
return null;
|
|
93
93
|
return { level: match[1].length, text: match[2].trim() };
|
|
94
94
|
}
|
|
95
|
+
/** Find the nearest explicit AC heading in a nested Markdown heading path.
|
|
96
|
+
* A subsection such as "异常场景" belongs to its enclosing `#### AC-*` block,
|
|
97
|
+
* even though the subsection heading itself has no ID. */
|
|
98
|
+
function nearestAncestorExplicitAc(headingPath) {
|
|
99
|
+
const ancestors = headingPath.split(" > ").slice(0, -1).reverse();
|
|
100
|
+
for (const heading of ancestors) {
|
|
101
|
+
const matched = matchExplicitAcHeading(heading);
|
|
102
|
+
if (matched)
|
|
103
|
+
return matched;
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
95
107
|
function isListItem(line) {
|
|
96
108
|
return /^\s*(?:[-*+]|\d+[.)])\s+(?:\[(?: |x|X)\]\s+)?/.test(line);
|
|
97
109
|
}
|
|
@@ -173,6 +185,7 @@ export function splitDocumentIntoFragments(doc) {
|
|
|
173
185
|
// V4 显式 AC 标题(`#### AC-*`)本身即验收段:标题行的 ID 原样保留。
|
|
174
186
|
const lastHeadingSegment = section.headingPath.split(" > ").pop() ?? section.headingPath;
|
|
175
187
|
const explicitSectionAc = matchExplicitAcHeading(lastHeadingSegment);
|
|
188
|
+
const ancestorExplicitAc = nearestAncestorExplicitAc(section.headingPath);
|
|
176
189
|
const sectionKind = classifySectionHeading(section.headingPath);
|
|
177
190
|
const effectiveKind = explicitSectionAc ? "acceptance" : sectionKind;
|
|
178
191
|
const bodyStart = section.headingPath
|
|
@@ -181,7 +194,7 @@ export function splitDocumentIntoFragments(doc) {
|
|
|
181
194
|
const bodyEnd = section.endLine;
|
|
182
195
|
const bodyLines = lines.slice(Math.max(0, bodyStart - 1), Math.min(bodyEnd, lines.length));
|
|
183
196
|
// 当前验收章节内最近的显式 AC ID(V4 标题或 AC 条目),正文片段继承它。
|
|
184
|
-
let pendingAcId = null;
|
|
197
|
+
let pendingAcId = explicitSectionAc?.id ?? ancestorExplicitAc?.id ?? null;
|
|
185
198
|
const emit = (fragment) => {
|
|
186
199
|
const explicit = fragment.explicitId ?? null;
|
|
187
200
|
let id = explicit;
|
|
@@ -8,6 +8,7 @@ import { writeTextArtifactFile } from "../../infrastructure/harness/artifact-sto
|
|
|
8
8
|
import { findPackageRoot } from "../../shared/package-metadata.js";
|
|
9
9
|
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
10
10
|
import { resolveDagTaskSourcePath } from "../../task/dag-source-paths.js";
|
|
11
|
+
import { parseLedgerJson } from "../../task/source-prepare/ledger.js";
|
|
11
12
|
import { isDagSourceBindingV2 } from "./types.js";
|
|
12
13
|
import { canonicalContractRel, classifyStructuredContractFailure, formatStructuredArtifactPointer, openspecCitationsRel, parseOpenspecCitationsBlock, persistRuntimeSkeleton, } from "./structured-output-repair.js";
|
|
13
14
|
export const frontendNormalizationActionSchema = z.enum([
|
|
@@ -17,6 +18,7 @@ export const frontendNormalizationActionSchema = z.enum([
|
|
|
17
18
|
"canonicalize-verification-alias",
|
|
18
19
|
"drop-unresolved-verification-symbols",
|
|
19
20
|
"inject-source-binding",
|
|
21
|
+
"inject-source-fidelity-provenance",
|
|
20
22
|
]);
|
|
21
23
|
export function sha256Hex(input) {
|
|
22
24
|
return createHash("sha256").update(input, "utf8").digest("hex");
|
|
@@ -1155,6 +1157,74 @@ function withDerivedRequirementIdsWhenUnscoped(binding, parsed) {
|
|
|
1155
1157
|
? { ...binding, requirementIds: derivedIds }
|
|
1156
1158
|
: binding;
|
|
1157
1159
|
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Restore source-fidelity evidence after an editable planner/revision patch
|
|
1162
|
+
* has replaced the requirements array. Provenance is runtime-owned: the
|
|
1163
|
+
* ledger, pinned by the DAG source binding, is the sole authority for a
|
|
1164
|
+
* requirement's sourceFragmentIds and sourceRefs. This deliberately
|
|
1165
|
+
* overwrites values emitted by a model instead of treating them as an
|
|
1166
|
+
* override, so a planner cannot omit, fabricate, or drift source evidence.
|
|
1167
|
+
*/
|
|
1168
|
+
async function injectRuntimeOwnedSourceProvenance(input) {
|
|
1169
|
+
if (!isDagSourceBindingV2(input.sourceBinding)) {
|
|
1170
|
+
return { value: input.value, injected: false };
|
|
1171
|
+
}
|
|
1172
|
+
const workspaceRoot = workspaceRootFromDagRunDir(input.runDir);
|
|
1173
|
+
if (!workspaceRoot) {
|
|
1174
|
+
throw new Error("frontend source fidelity provenance injection requires a DAG run directory under .harness/dag-runs");
|
|
1175
|
+
}
|
|
1176
|
+
const ledgerPath = resolveDagTaskSourcePath({
|
|
1177
|
+
workspaceRoot,
|
|
1178
|
+
taskId: input.sourceBinding.taskId,
|
|
1179
|
+
sourcePath: input.sourceBinding.ledgerPath,
|
|
1180
|
+
});
|
|
1181
|
+
let rawLedger;
|
|
1182
|
+
try {
|
|
1183
|
+
rawLedger = await readFile(ledgerPath, "utf8");
|
|
1184
|
+
}
|
|
1185
|
+
catch (error) {
|
|
1186
|
+
throw new Error(`frontend source fidelity ledger unavailable for provenance injection: ${input.sourceBinding.ledgerPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1187
|
+
}
|
|
1188
|
+
const observedSha256 = sha256Hex(rawLedger);
|
|
1189
|
+
if (observedSha256 !== input.sourceBinding.ledgerSha256) {
|
|
1190
|
+
throw new Error(`frontend source fidelity ledger is stale or tampered for provenance injection: ${input.sourceBinding.ledgerPath} (bound ${input.sourceBinding.ledgerSha256}, observed ${observedSha256})`);
|
|
1191
|
+
}
|
|
1192
|
+
const ledger = parseLedgerJson(rawLedger);
|
|
1193
|
+
if (ledger.taskId !== input.sourceBinding.taskId) {
|
|
1194
|
+
throw new Error(`frontend source fidelity ledger taskId mismatch for provenance injection: bound ${input.sourceBinding.taskId}, ledger ${ledger.taskId}`);
|
|
1195
|
+
}
|
|
1196
|
+
if (ledger.inputDigest !== input.sourceBinding.inputDigest) {
|
|
1197
|
+
throw new Error(`frontend source fidelity ledger inputDigest mismatch for provenance injection: bound ${input.sourceBinding.inputDigest}, ledger ${ledger.inputDigest}`);
|
|
1198
|
+
}
|
|
1199
|
+
const record = asRecord(input.value);
|
|
1200
|
+
if (!record || !Array.isArray(record.requirements)) {
|
|
1201
|
+
return { value: input.value, injected: false };
|
|
1202
|
+
}
|
|
1203
|
+
const provenanceByRequirementId = new Map(ledger.canonicalRequirements.map((requirement) => [
|
|
1204
|
+
requirement.id,
|
|
1205
|
+
{
|
|
1206
|
+
sourceFragmentIds: [...requirement.sourceFragmentIds],
|
|
1207
|
+
sourceRefs: [...requirement.sourceRefs],
|
|
1208
|
+
},
|
|
1209
|
+
]));
|
|
1210
|
+
let injected = false;
|
|
1211
|
+
const requirements = record.requirements.map((item) => {
|
|
1212
|
+
const requirement = asRecord(item);
|
|
1213
|
+
if (!requirement)
|
|
1214
|
+
return item;
|
|
1215
|
+
const requirementId = canonicalizeRequirementId(asString(requirement.id));
|
|
1216
|
+
const provenance = provenanceByRequirementId.get(requirementId);
|
|
1217
|
+
if (!provenance)
|
|
1218
|
+
return item;
|
|
1219
|
+
injected = true;
|
|
1220
|
+
return {
|
|
1221
|
+
...requirement,
|
|
1222
|
+
sourceFragmentIds: provenance.sourceFragmentIds,
|
|
1223
|
+
sourceRefs: provenance.sourceRefs,
|
|
1224
|
+
};
|
|
1225
|
+
});
|
|
1226
|
+
return { value: { ...record, requirements }, injected };
|
|
1227
|
+
}
|
|
1158
1228
|
function looksLikeStrictFrontendContract(value) {
|
|
1159
1229
|
const record = asRecord(value);
|
|
1160
1230
|
if (!record)
|
|
@@ -1686,8 +1756,24 @@ export async function analyzeFrontendImplementationContract(input) {
|
|
|
1686
1756
|
// There is exactly one post-security candidate. A fallback candidate would
|
|
1687
1757
|
// allow malformed raw fields to bypass the boundary checks above.
|
|
1688
1758
|
const normalizedContract = coerceFrontendImplementationContractInput(parsed, canonicalBinding);
|
|
1759
|
+
let runtimeOwnedContract = normalizedContract;
|
|
1760
|
+
if (input.injectRuntimeOwnedSourceProvenance) {
|
|
1761
|
+
try {
|
|
1762
|
+
const injected = await injectRuntimeOwnedSourceProvenance({
|
|
1763
|
+
runDir: input.runDir,
|
|
1764
|
+
value: normalizedContract,
|
|
1765
|
+
sourceBinding: input.sourceBinding,
|
|
1766
|
+
});
|
|
1767
|
+
runtimeOwnedContract = injected.value;
|
|
1768
|
+
if (injected.injected)
|
|
1769
|
+
pushAction("inject-source-fidelity-provenance");
|
|
1770
|
+
}
|
|
1771
|
+
catch (error) {
|
|
1772
|
+
fail("blocked", error instanceof Error ? error.message : String(error), candidateJsonSha256);
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1689
1775
|
const candidate = deriveFrontendVerificationCoverage({
|
|
1690
|
-
...(asRecord(
|
|
1776
|
+
...(asRecord(runtimeOwnedContract) ?? parsed),
|
|
1691
1777
|
sourceBinding: canonicalBinding,
|
|
1692
1778
|
}, canonicalBinding);
|
|
1693
1779
|
const result = frontendImplementationContractSchema.safeParse(candidate);
|
|
@@ -1886,6 +1972,7 @@ export async function validateFrontendPlanPatchNodeOutput(input) {
|
|
|
1886
1972
|
runDir: input.runDir,
|
|
1887
1973
|
rawContractText: serializeDeterministicJson(merged),
|
|
1888
1974
|
sourceBinding: input.sourceBinding,
|
|
1975
|
+
injectRuntimeOwnedSourceProvenance: true,
|
|
1889
1976
|
});
|
|
1890
1977
|
const normalizedArtifact = await writeDeterministicJsonArtifact(input.runDir, path.posix.join(candidateDir, `attempt-${attempt}.normalized.json`), analysis.canonical);
|
|
1891
1978
|
await writeReport({
|
|
@@ -2053,6 +2140,7 @@ export async function validateFrontendRevisionPatchNodeOutput(input) {
|
|
|
2053
2140
|
runDir: input.runDir,
|
|
2054
2141
|
rawContractText: serializeDeterministicJson(merged),
|
|
2055
2142
|
sourceBinding: input.sourceBinding,
|
|
2143
|
+
injectRuntimeOwnedSourceProvenance: true,
|
|
2056
2144
|
});
|
|
2057
2145
|
const candidateDir = path.posix.join("contracts", "candidates", nodeId);
|
|
2058
2146
|
await writeDeterministicJsonArtifact(input.runDir, path.posix.join(candidateDir, `attempt-${attempt}.extracted.json`), patch);
|