@tea-agent/loop-agent 0.24.11-beta.0 → 0.25.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 +53 -4
- package/README.md +4 -4
- package/dist/application/dag/generate-task-dag.js +25 -8
- package/dist/commands/dag-init-hybrid.js +3 -2
- package/dist/commands/delegate.js +11 -11
- package/dist/commands/init.js +29 -14
- package/dist/commands/instructions.js +1 -2
- package/dist/executors/dag-pi-executor.js +74 -13
- package/dist/executors/pi-executor.js +25 -7
- package/dist/executors/pi-prompt-transport.js +198 -0
- package/dist/executors/pi-sdk-executor.js +1 -1
- package/dist/executors/process-tree.js +33 -0
- package/dist/executors/shell-executor.js +362 -24
- package/dist/executors/shell-verification.js +4 -2
- package/dist/infrastructure/harness/task-store.js +31 -0
- package/dist/shared/operator/capabilities.js +6 -0
- package/dist/task/read-model.js +3 -7
- package/dist/task/runtime.js +12 -3
- package/dist/verification/maven/cache.js +142 -0
- package/dist/verification/maven/index.js +120 -0
- package/dist/verification/maven/plan-commands.js +421 -0
- package/dist/verification/maven/pom-static.js +136 -0
- package/dist/verification/maven/scope-resolve.js +153 -0
- package/dist/verification/maven/stale.js +130 -0
- package/dist/verification/maven/types.js +23 -0
- package/dist/verification/maven/workspace-graph.js +322 -0
- package/dist/worker/cli.js +236 -21
- package/dist/worker/delivery/final-verification.js +12 -0
- package/dist/worker/delivery/git-transaction.js +32 -7
- package/dist/worker/delivery/package.js +9 -5
- package/dist/worker/delivery/verification-bundle.js +26 -5
- package/dist/worker/feature/advance.js +301 -0
- package/dist/worker/feature/doctor.js +223 -0
- package/dist/worker/feature/next-action.js +11 -3
- package/dist/worker/feature/scaffold.js +798 -0
- package/dist/worker/observe/dag-run-artifacts.js +90 -0
- package/dist/worker/observe/node-input.js +444 -0
- package/dist/worker/observe/routes.js +17 -0
- package/dist/worker/observe/static/api.js +9 -0
- package/dist/worker/observe/static/constants.js +9 -0
- package/dist/worker/observe/static/state.js +14 -0
- package/dist/worker/observe/static/styles.css +74 -0
- package/dist/worker/observe/static/views/dag-inspector.js +371 -15
- package/dist/worker/outcomes/projector.js +5 -1
- package/dist/worker/run-task/run-task.js +18 -2
- package/dist/worker/runner/run-ready.js +41 -1
- package/dist/workflows/dag/backend-test-markdown-workflow.js +300 -9
- package/dist/workflows/dag/backend-test-result-contract.js +103 -0
- package/dist/workflows/dag/failure-category.js +5 -1
- package/dist/workflows/dag/frontend-implementation-contract.js +4 -0
- package/dist/workflows/dag/frontend-prewrite-gate.js +0 -17
- package/dist/workflows/dag/frontend-test-result-contract.js +106 -41
- package/dist/workflows/dag/init-hybrid.js +224 -39
- package/dist/workflows/dag/node-execution.js +3 -2
- package/dist/workflows/dag/reconcile-run.js +24 -0
- package/dist/workflows/dag/types.js +57 -0
- package/dist/workflows/dag/validate.js +19 -2
- package/dist/workflows/loop/actions/dag-action.js +2 -1
- package/docs/architecture/dag-execution.md +5 -1
- package/docs/architecture/runtime-boundaries.md +11 -1
- package/docs/architecture/worker-and-feature.md +2 -0
- package/docs/init-surface.manifest.json +3 -0
- package/docs/templates/backend-test-dag.json +3 -3
- package/docs/templates/product-line/README.md +17 -1
- package/docs/templates/product-line/feature-scaffold-batch.example.yaml +31 -0
- package/docs/templates/product-line/scaffold-samples/README.md +36 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/acceptance.yaml +13 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/design.md +14 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/feature.yaml +3 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/requirement.md +6 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/tasks/BE-IMPL-001.yaml +83 -0
- package/docs/templates/product-line/scaffold-samples/backend-only/tasks/task-graph.yaml +14 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/acceptance.yaml +15 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/design.md +18 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/feature.yaml +3 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/requirement.md +6 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/BE-IMPL-001.yaml +84 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/CONTRACT-001.yaml +84 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/FE-IMPL-001.yaml +86 -0
- package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/task-graph.yaml +40 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/acceptance.yaml +13 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/design.md +14 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/feature.yaml +3 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/requirement.md +6 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/tasks/FE-IMPL-001.yaml +85 -0
- package/docs/templates/product-line/scaffold-samples/frontend-only/tasks/task-graph.yaml +14 -0
- package/examples/l5-report-coms-process-definition.html +322 -0
- package/package.json +1 -1
- package/skills/agent-worker/references/agent-worker-operator.md +84 -0
- package/skills/frontend-implementation/references/node-contracts.md +2 -2
- package/skills/loop-agent/SKILL.md +14 -16
- package/skills/loop-agent/references/command-reference.md +33 -12
- package/skills/loop-agent/references/harness-policy.md +28 -6
- package/skills/loop-agent/references/hybrid-dag.md +8 -7
- package/skills/loop-agent/references/post-implementation-and-patterns.md +19 -9
- package/skills/loop-agent/references/source-and-plan-practice.md +161 -0
- package/skills/loop-agent/references/task-workflow.md +13 -5
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
-
import { access, readdir, readFile, realpath,
|
|
2
|
+
import { access, readdir, readFile, realpath, } from "node:fs/promises";
|
|
3
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
-
import os from "node:os";
|
|
5
4
|
import path from "node:path";
|
|
5
|
+
import { writeJsonAtomic } from "../../infrastructure/harness/atomic-write.js";
|
|
6
6
|
import { assertValidDagSpec } from "./validate.js";
|
|
7
7
|
import { DAG_AGENT_RUNTIME_PI_ONLY, DAG_REPAIR_WRITER_PROTOCOL_EXPLICIT_NODE_V1, DAG_RUNTIME_CONTRACT_SCHEMA_VERSION, DEFAULT_DAG_OUTPUT_LANGUAGE, DEFAULT_DAG_EXECUTOR_MODELS, parseDagSpec, } from "./types.js";
|
|
8
|
+
import { planMavenVerification, } from "../../verification/maven/index.js";
|
|
8
9
|
import { pathMatchesPattern } from "../../shared/git-progress.js";
|
|
9
10
|
import { BASELINE_FORBIDDEN_PATHS } from "./governance-constants.js";
|
|
10
11
|
import { buildDecisionEnvelopePromptContract } from "./decision-envelope.js";
|
|
@@ -25,7 +26,7 @@ import { normalizeTaskRequirementText, resolveTaskDagTemplateSelection, } from "
|
|
|
25
26
|
import { BACKEND_TEST_EXECUTION_DEFAULT_TEST_ROOT, buildBackendTestExecutionPreflightShellSnippet, } from "./backend-test-execution-contract.js";
|
|
26
27
|
import { buildBackendTestOutcomeGateShellSnippet } from "./backend-test-result-contract.js";
|
|
27
28
|
import { buildBackendTestIntakeContext } from "./backend-test-intake-context.js";
|
|
28
|
-
import {
|
|
29
|
+
import { buildFrontendTestOutcomeGateShellSnippet } from "./frontend-test-result-contract.js";
|
|
29
30
|
import { classifyFrontendRisk, } from "./frontend-risk.js";
|
|
30
31
|
import { discoverFrontendProjectCapability, } from "./frontend-project-capability.js";
|
|
31
32
|
import { FRONTEND_IMPLEMENTATION_CONTRACT_SCHEMA_ID, loadFrontendImplementationContractJsonSchema, } from "./frontend-implementation-contract.js";
|
|
@@ -90,6 +91,7 @@ const FRONTEND_SKILLS_BY_ROLE = {
|
|
|
90
91
|
closeout: [],
|
|
91
92
|
};
|
|
92
93
|
const FRONTEND_IMPLEMENTATION_SKILLS = ["frontend-implementation"];
|
|
94
|
+
const FRONTEND_BOUNDED_IMPLEMENT_SKILLS = ["frontend-bounded-implement"];
|
|
93
95
|
const FRONTEND_DESIGN_REVIEW_SKILLS = ["frontend-design-review"];
|
|
94
96
|
const FRONTEND_REVIEW_SKILLS = ["frontend-review"];
|
|
95
97
|
const FRONTEND_VERIFICATION_SKILLS = ["frontend-verification"];
|
|
@@ -758,6 +760,56 @@ function buildVerifyShellCommands(input) {
|
|
|
758
760
|
const repoRoot = input.repoRoot;
|
|
759
761
|
return input.commands.map((command) => verifyCommandToShell(repoRoot, command));
|
|
760
762
|
}
|
|
763
|
+
function toDagMavenVerificationPlan(plan, shellCommands) {
|
|
764
|
+
const shellCommandDigests = shellCommands?.map((command) => createHash("sha256").update(command).digest("hex")) ?? plan.shellCommandDigests;
|
|
765
|
+
return {
|
|
766
|
+
schemaVersion: plan.schemaVersion,
|
|
767
|
+
plannerVersion: plan.plannerVersion,
|
|
768
|
+
fingerprint: plan.fingerprint,
|
|
769
|
+
inputDigest: plan.inputDigest,
|
|
770
|
+
targets: plan.targets,
|
|
771
|
+
commandDigests: plan.commandDigests,
|
|
772
|
+
plannedCommands: plan.plannedCommands,
|
|
773
|
+
...(plan.workspaceManifest
|
|
774
|
+
? { workspaceManifest: plan.workspaceManifest }
|
|
775
|
+
: {}),
|
|
776
|
+
...(shellCommandDigests && shellCommandDigests.length > 0
|
|
777
|
+
? { shellCommandDigests }
|
|
778
|
+
: {}),
|
|
779
|
+
};
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Apply task-scope Maven verification planning before shell serialization.
|
|
783
|
+
* Non-Maven command sets are returned unchanged (REQ-MVN-007).
|
|
784
|
+
*/
|
|
785
|
+
function applyMavenVerificationPlanning(input) {
|
|
786
|
+
if (!input.repoRoot || !input.commands || input.commands.length === 0) {
|
|
787
|
+
return { commands: input.commands };
|
|
788
|
+
}
|
|
789
|
+
const implement = resolveImplementPaths(input.taskConfig);
|
|
790
|
+
const planned = planMavenVerification({
|
|
791
|
+
repoRoot: input.repoRoot,
|
|
792
|
+
commands: input.commands,
|
|
793
|
+
scope: {
|
|
794
|
+
allowedPaths: input.taskConfig.allowedPaths ?? [],
|
|
795
|
+
writeSet: implement.writeSet,
|
|
796
|
+
sourcePaths: input.taskConfig.sourceFiles ?? [],
|
|
797
|
+
},
|
|
798
|
+
});
|
|
799
|
+
if (!planned.plan) {
|
|
800
|
+
return { commands: planned.commands };
|
|
801
|
+
}
|
|
802
|
+
// Freeze shell command digests against the serialized shell form used at runtime.
|
|
803
|
+
const shellCommands = buildVerifyShellCommands({
|
|
804
|
+
repoRoot: input.repoRoot,
|
|
805
|
+
commands: planned.commands,
|
|
806
|
+
fallbackCommands: [],
|
|
807
|
+
});
|
|
808
|
+
return {
|
|
809
|
+
commands: planned.commands,
|
|
810
|
+
mavenVerificationPlan: toDagMavenVerificationPlan(planned.plan, shellCommands),
|
|
811
|
+
};
|
|
812
|
+
}
|
|
761
813
|
function markdownVerifyCommand(repoRoot, command) {
|
|
762
814
|
const args = command.split(/\s+/).filter(Boolean);
|
|
763
815
|
if (args.length === 0 ||
|
|
@@ -871,7 +923,101 @@ function buildExplicitFrontendVerifyCommands(taskConfig, repoRoot) {
|
|
|
871
923
|
return { staticCommands, behaviorCommands };
|
|
872
924
|
}
|
|
873
925
|
function verifyCommandKey(command) {
|
|
874
|
-
|
|
926
|
+
const normalizedArgs = normalizeVerifyCommandArgs(command.args);
|
|
927
|
+
const normalizedCwd = path.resolve(command.cwd).replace(/\\/g, "/");
|
|
928
|
+
const cwdKey = process.platform === "win32"
|
|
929
|
+
? normalizedCwd.toLowerCase()
|
|
930
|
+
: normalizedCwd;
|
|
931
|
+
const envKey = Object.entries(command.env ?? {})
|
|
932
|
+
.filter((entry) => entry[1] !== undefined)
|
|
933
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
934
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
935
|
+
.join("\0");
|
|
936
|
+
return [cwdKey, normalizedArgs.join("\0"), envKey, command.timeoutMs ?? ""]
|
|
937
|
+
.join("\u0001");
|
|
938
|
+
}
|
|
939
|
+
function normalizeVerifyCommandArgs(args) {
|
|
940
|
+
if (args.length === 3 &&
|
|
941
|
+
/^(?:bash|sh)(?:\.exe)?$/i.test(path.basename(args[0])) &&
|
|
942
|
+
args[1] === "-lc") {
|
|
943
|
+
const parsed = tokenizeSimpleShellCommand(args[2]);
|
|
944
|
+
if (parsed)
|
|
945
|
+
return normalizeVerifyCommandArgs(parsed);
|
|
946
|
+
}
|
|
947
|
+
return args.map((arg, index) => {
|
|
948
|
+
const normalized = arg.replace(/\\/g, "/");
|
|
949
|
+
return index === 0 && process.platform === "win32"
|
|
950
|
+
? normalized.toLowerCase()
|
|
951
|
+
: normalized;
|
|
952
|
+
});
|
|
953
|
+
}
|
|
954
|
+
function tokenizeSimpleShellCommand(command) {
|
|
955
|
+
const tokens = [];
|
|
956
|
+
let token = "";
|
|
957
|
+
let quote = null;
|
|
958
|
+
let escaping = false;
|
|
959
|
+
let tokenStarted = false;
|
|
960
|
+
for (const char of command.trim()) {
|
|
961
|
+
if (escaping) {
|
|
962
|
+
token += char;
|
|
963
|
+
escaping = false;
|
|
964
|
+
tokenStarted = true;
|
|
965
|
+
continue;
|
|
966
|
+
}
|
|
967
|
+
if (char === "\\" && quote !== "'") {
|
|
968
|
+
escaping = true;
|
|
969
|
+
tokenStarted = true;
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
972
|
+
if (quote) {
|
|
973
|
+
if (quote === '"' && (char === "$" || char === "`"))
|
|
974
|
+
return undefined;
|
|
975
|
+
if (char === quote)
|
|
976
|
+
quote = null;
|
|
977
|
+
else
|
|
978
|
+
token += char;
|
|
979
|
+
tokenStarted = true;
|
|
980
|
+
continue;
|
|
981
|
+
}
|
|
982
|
+
if (char === "'" || char === '"') {
|
|
983
|
+
quote = char;
|
|
984
|
+
tokenStarted = true;
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
987
|
+
if (/\s/.test(char)) {
|
|
988
|
+
if (tokenStarted)
|
|
989
|
+
tokens.push(token);
|
|
990
|
+
token = "";
|
|
991
|
+
tokenStarted = false;
|
|
992
|
+
continue;
|
|
993
|
+
}
|
|
994
|
+
if (/[;&|<>`\r\n]/.test(char) || char === "$" || char === "(") {
|
|
995
|
+
return undefined;
|
|
996
|
+
}
|
|
997
|
+
token += char;
|
|
998
|
+
tokenStarted = true;
|
|
999
|
+
}
|
|
1000
|
+
if (escaping || quote)
|
|
1001
|
+
return undefined;
|
|
1002
|
+
if (tokenStarted)
|
|
1003
|
+
tokens.push(token);
|
|
1004
|
+
return tokens.length > 0 ? tokens : undefined;
|
|
1005
|
+
}
|
|
1006
|
+
function isFullSuiteVerifyCommand(command) {
|
|
1007
|
+
const args = normalizeVerifyCommandArgs(command.args);
|
|
1008
|
+
let index = 0;
|
|
1009
|
+
while (/^[A-Za-z_][A-Za-z0-9_]*=/.test(args[index] ?? ""))
|
|
1010
|
+
index += 1;
|
|
1011
|
+
const executable = path.basename(args[index] ?? "").toLowerCase().replace(/\.(?:exe|cmd)$/, "");
|
|
1012
|
+
const rest = args.slice(index + 1);
|
|
1013
|
+
if (["npm", "pnpm", "yarn", "bun"].includes(executable)) {
|
|
1014
|
+
return ((rest.length === 1 && rest[0] === "test") ||
|
|
1015
|
+
(rest.length === 2 && rest[0] === "run" && rest[1] === "test"));
|
|
1016
|
+
}
|
|
1017
|
+
if (["bash", "sh"].includes(executable) && rest.length === 1) {
|
|
1018
|
+
return /(?:^|\/)scripts\/ci\.sh$/i.test(rest[0].replace(/\\/g, "/"));
|
|
1019
|
+
}
|
|
1020
|
+
return rest.length === 0 && /(?:^|\/)scripts\/ci\.sh$/i.test((args[index] ?? "").replace(/\\/g, "/"));
|
|
875
1021
|
}
|
|
876
1022
|
function resolveDagVerifyStrategy(taskConfig, defaultIntermediateQuotaWhenFull = "full") {
|
|
877
1023
|
const explicitIntermediateQuota = taskConfig.dagVerifyStrategy?.intermediateQuota;
|
|
@@ -1339,7 +1485,7 @@ function mergeFinalVerifyCommands(repoRoot, taskConfig, adapterCommands) {
|
|
|
1339
1485
|
}));
|
|
1340
1486
|
const seen = new Set();
|
|
1341
1487
|
return [...taskCommands, ...adapterCommands].filter((command) => {
|
|
1342
|
-
const key =
|
|
1488
|
+
const key = verifyCommandKey(command);
|
|
1343
1489
|
if (seen.has(key))
|
|
1344
1490
|
return false;
|
|
1345
1491
|
seen.add(key);
|
|
@@ -1355,9 +1501,14 @@ export function buildStandardHybridDagFromTask(sources) {
|
|
|
1355
1501
|
const implementComplexity = resolveWriterComplexity(taskConfig);
|
|
1356
1502
|
const implementId = implementationNodeId();
|
|
1357
1503
|
const finalVerifyCommands = sources.verifyCommands?.final ?? [];
|
|
1358
|
-
const
|
|
1504
|
+
const plannedFinal = applyMavenVerificationPlanning({
|
|
1359
1505
|
repoRoot: sources.repoRoot,
|
|
1360
1506
|
commands: finalVerifyCommands,
|
|
1507
|
+
taskConfig,
|
|
1508
|
+
});
|
|
1509
|
+
const verifyShellCommands = buildVerifyShellCommands({
|
|
1510
|
+
repoRoot: sources.repoRoot,
|
|
1511
|
+
commands: plannedFinal.commands,
|
|
1361
1512
|
fallbackCommands: [],
|
|
1362
1513
|
});
|
|
1363
1514
|
const verifyShellTask = verifyShellCommands.length > 0
|
|
@@ -1381,13 +1532,16 @@ export function buildStandardHybridDagFromTask(sources) {
|
|
|
1381
1532
|
phase: "final",
|
|
1382
1533
|
quota: "full",
|
|
1383
1534
|
commandSource: "adapter",
|
|
1384
|
-
commands:
|
|
1535
|
+
commands: plannedFinal.commands,
|
|
1385
1536
|
fallbackCommands: [],
|
|
1386
1537
|
finalFullRequired: true,
|
|
1387
1538
|
commandTimeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
|
|
1388
1539
|
}),
|
|
1389
1540
|
cwd: ".",
|
|
1390
1541
|
timeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
|
|
1542
|
+
...(plannedFinal.mavenVerificationPlan
|
|
1543
|
+
? { mavenVerificationPlan: plannedFinal.mavenVerificationPlan }
|
|
1544
|
+
: {}),
|
|
1391
1545
|
},
|
|
1392
1546
|
},
|
|
1393
1547
|
]
|
|
@@ -1782,9 +1936,8 @@ function resolveFrontendMockContextBlock(sources) {
|
|
|
1782
1936
|
}
|
|
1783
1937
|
if (mode === "not-required") {
|
|
1784
1938
|
parts.push("Generation-time evidence does not require Mock. The assessment must still use contract/scout evidence: select not-needed when Mock is intentionally skipped, or select a safe Mock strategy if project evidence supports one.");
|
|
1785
|
-
if ((sources
|
|
1786
|
-
(capability.
|
|
1787
|
-
parts.push("Auto mode may skip Mock when no project Mock capability is confirmed. Do not block solely for missing Mock; keep the real request path as default and record any unproved backend behavior as Real Integration Gap.");
|
|
1939
|
+
if (frontendMockStrategyMustBeNotNeeded(sources)) {
|
|
1940
|
+
parts.push('Auto mode has no confirmed project Mock capability. The structured contract must set mockApi.strategy to "not-needed". Do not add Mock files or dependencies; keep the real request path as default and record any unproved backend behavior as Real Integration Gap.');
|
|
1788
1941
|
}
|
|
1789
1942
|
}
|
|
1790
1943
|
if (mode === "blocked") {
|
|
@@ -1792,6 +1945,12 @@ function resolveFrontendMockContextBlock(sources) {
|
|
|
1792
1945
|
}
|
|
1793
1946
|
return parts.join("\n");
|
|
1794
1947
|
}
|
|
1948
|
+
function frontendMockStrategyMustBeNotNeeded(sources) {
|
|
1949
|
+
const capabilityStatus = sources.frontendMockCapability?.status;
|
|
1950
|
+
return ((sources.taskConfig.frontendMock?.policy ?? "auto") === "auto" &&
|
|
1951
|
+
(sources.frontendMockMode ?? "not-required") === "not-required" &&
|
|
1952
|
+
(capabilityStatus === "absent" || capabilityStatus === "ambiguous"));
|
|
1953
|
+
}
|
|
1795
1954
|
function resolveFrontendCapabilityContextBlock(sources) {
|
|
1796
1955
|
const risk = sources.frontendRisk;
|
|
1797
1956
|
const capability = sources.frontendProjectCapability;
|
|
@@ -2146,6 +2305,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2146
2305
|
"Include ordered steps, target files, UI state handling, styling/component strategy, interaction notes, Mock/API strategy, dependency policy, deterministic verification entrypoints, and residual risks. Use only the fixed entrypoints below; implementation may add tests behind them but cannot replace them.",
|
|
2147
2306
|
"Every target file and verification target must be selected from the current target workspace and task scope. Do not reuse paths or symbols from examples, prior tasks, or loop-agent itself; if the project uses app/, packages/, spec/, __tests__, or another layout, preserve that layout.",
|
|
2148
2307
|
"End with exactly one fenced json object conforming to frontend-implementation-contract-v1 so small topology can materialize the contract without plan-revision.",
|
|
2308
|
+
"Each requirement must state its user-observable or logic-observable expectedOutcome. Each interaction must state its trigger and expectedBehavior. IDs plus file paths are not sufficient behavior semantics.",
|
|
2149
2309
|
requirementCoverageInstruction,
|
|
2150
2310
|
"Read-only: do not modify code, docs, artifacts, or repository files.",
|
|
2151
2311
|
fixedVerificationContext,
|
|
@@ -2200,6 +2360,7 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2200
2360
|
"Do not turn MOCK_STRATEGY: blocked into an implementable strategy without new repository or contract evidence that resolves every blocker.",
|
|
2201
2361
|
"Read-only: do not modify code, docs, artifacts, or repository files. This node revises the plan only.",
|
|
2202
2362
|
"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.",
|
|
2363
|
+
"Preserve each requirement expectedOutcome and each interaction trigger/expectedBehavior in the revised contract; do not reduce behavior semantics to IDs and paths.",
|
|
2203
2364
|
sourceContext,
|
|
2204
2365
|
frontendContractSchemaBlock,
|
|
2205
2366
|
].join("\n\n"),
|
|
@@ -2259,7 +2420,8 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2259
2420
|
reviewFromNodeId: "frontend-final-design-review-pi",
|
|
2260
2421
|
reviewFallbackFromNodeIds: ["frontend-design-review-pi"],
|
|
2261
2422
|
requiredRequirementIds: requirementIds,
|
|
2262
|
-
allowedMockStrategies: taskConfig.frontendMock?.policy === "disabled"
|
|
2423
|
+
allowedMockStrategies: taskConfig.frontendMock?.policy === "disabled" ||
|
|
2424
|
+
frontendMockStrategyMustBeNotNeeded(frontendSources)
|
|
2263
2425
|
? ["not-needed"]
|
|
2264
2426
|
: taskConfig.frontendMock?.policy === "required"
|
|
2265
2427
|
? ["native", "browser-intercept", "request-adapter"]
|
|
@@ -2316,13 +2478,17 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2316
2478
|
writeSet: implementPaths.writeSet,
|
|
2317
2479
|
allowedPaths: implementPaths.allowedPaths,
|
|
2318
2480
|
forbiddenPaths,
|
|
2319
|
-
skills:
|
|
2320
|
-
|
|
2481
|
+
skills: FRONTEND_BOUNDED_IMPLEMENT_SKILLS,
|
|
2482
|
+
writerOutcomePolicy: {
|
|
2483
|
+
type: "implementation-outcome-v1",
|
|
2484
|
+
},
|
|
2485
|
+
outputContract: "First non-empty line: IMPLEMENTATION_OUTCOME: changed | already-satisfied | blocked. Then a Markdown delivery summary with Contract Ref (path/schema/hash), Changed Files, Requirements Implemented, UI States, Tests Changed, Verification Attempts, Deviations, and Residual Risks. Follow fixed stages: contract confirm → tests → component/state → API/Mock → focused checks → diff cleanup.",
|
|
2321
2486
|
subtask_prompt: [
|
|
2322
2487
|
"Implement against the validated run-owned Frontend Implementation Contract from frontend-prewrite-gate-shell (path/schema/hash). Do not rebuild the contract from Markdown alone.",
|
|
2323
2488
|
"The canonical contract already contains the approved requirement, target-file, UI-state, verification, design, and Mock/API decisions. Do not re-open task sources, OpenSpec, AI workspace, plan/revision, or design-review prose, and do not repeat broad repository research. Inspect only contract target files and directly related local code needed to implement them.",
|
|
2324
2489
|
"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.",
|
|
2325
|
-
"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.",
|
|
2490
|
+
"Map every requirement id, expectedOutcome, interaction trigger/expectedBehavior, and applicable UI state from the contract to concrete files. Do not invent shell verification commands; only frozen static/behavior entrypoints will run.",
|
|
2491
|
+
"Begin implementation after the contract and its target files are confirmed. Do not spend the turn collecting optional context. If the canonical contract lacks behavior needed to edit safely, return IMPLEMENTATION_OUTCOME: blocked instead of reopening broad discovery.",
|
|
2326
2492
|
"Implement only the approved Mock strategy carried by the validated 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.",
|
|
2327
2493
|
"frontend-prewrite-gate-shell confirmed the effective plan/review, requirement coverage, Mock policy, and contract. Stay within writeSet and preserve unrelated files.",
|
|
2328
2494
|
"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.",
|
|
@@ -2377,8 +2543,11 @@ async function buildFrontendHybridDagFromTask(sources) {
|
|
|
2377
2543
|
writeSet: implementPaths.writeSet,
|
|
2378
2544
|
allowedPaths: implementPaths.allowedPaths,
|
|
2379
2545
|
forbiddenPaths,
|
|
2380
|
-
skills:
|
|
2381
|
-
|
|
2546
|
+
skills: FRONTEND_BOUNDED_IMPLEMENT_SKILLS,
|
|
2547
|
+
writerOutcomePolicy: {
|
|
2548
|
+
type: "implementation-outcome-v1",
|
|
2549
|
+
},
|
|
2550
|
+
outputContract: "First non-empty line: IMPLEMENTATION_OUTCOME: changed | already-satisfied | blocked. Then a repair summary for an eligible repairable assessment. Must not expand writeSet, re-interpret requirements, skip tests, or enable Mock by default.",
|
|
2382
2551
|
subtask_prompt: [
|
|
2383
2552
|
"Read contracts/frontend-repair-assessment.json and the validated frontend implementation contract.",
|
|
2384
2553
|
"This node runs only for eligible=true. Apply the smallest fix for the classified repairable failure inside the original implement writeSet only.",
|
|
@@ -3302,8 +3471,9 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3302
3471
|
"Write human-readable content in Simplified Chinese by default. Keep English only for machine-readable IDs and technical literals such as Case/AC/REQ/BR IDs, HTTP methods, paths, field names, enum values, commands, filenames, code symbols and exact source citations.",
|
|
3303
3472
|
"Create testcase/md/README.md as the concise entry page: test objective, target/environment, isolation/cleanup, module summary and a linked case index table with Case ID, Chinese case name, scenario type, endpoint and expected status/result. Avoid repeating every case body in README.",
|
|
3304
3473
|
"Write each module as readable case cards. Every case starts with `## BE-<MODULE>-<NNN>|<中文用例名称>`. The only sections required by the deterministic validator are `### 前置条件`, `### 操作步骤`, and `### 预期结果` (legacy English aliases remain accepted). Add `测试目的`, `验收标准`, `需求依据`, `测试数据`, and `自动化映射` when useful for human readability; every automatable case should explicitly name its target pytest script under `自动化映射` so traceability can scan only that script.",
|
|
3474
|
+
"Name each module file with a stable lowercase business stem such as `testcase/md/health.md` or `testcase/md/resource_notes.md`. Do not use Case-ID-like module filenames such as `BE-HEALTH.md` or `BE-NOTES.md`. For every automatable case, `自动化映射` must name exactly `testcase/test_<module>.py`, where <module> is that Markdown filename without `.md`, lowercased, with non-alphanumeric characters replaced by underscores. Example: `testcase/md/health.md` → `testcase/test_health.py`; `testcase/md/resource_notes.md` → `testcase/test_resource_notes.py`. Never invent a different pytest path in Markdown than the module stem implies.",
|
|
3305
3475
|
"Place steps and their expected results in a compact readable table when that improves clarity; otherwise keep numbered executable steps and numbered/bulleted independently assertable results. Every result must name the observable HTTP status, response field/value, state transition or membership condition, never vague wording such as ‘符合预期’.",
|
|
3306
|
-
"In `自动化映射`, record the planned script path and pytest function name when known. Put implementation-only restrictions in a concise `<details>` block rather than dominating the main case flow. Use only environment-supported fixtures/targets/isolation, record evidence gaps in Chinese, and do not emit JSON, pytest, or execute commands.",
|
|
3476
|
+
"In `自动化映射`, record the planned script path and pytest function name when known, and keep the script path identical to the module one-to-one path above. Put implementation-only restrictions in a concise `<details>` block rather than dominating the main case flow. Use only environment-supported fixtures/targets/isolation, record evidence gaps in Chinese, and do not emit JSON, pytest, or execute commands.",
|
|
3307
3477
|
intake.boundedSourceContext, "## Authoritative reference index", JSON.stringify(intake.referenceIndex, null, 2),
|
|
3308
3478
|
"For each index entry, use `readPath` for Pi read-tool calls and copy `path` exactly into Markdown Source References. Bound files under .harness/tasks/<taskId>/source/** are read-only inputs: reading them is allowed even though writing .harness/** is forbidden. Never resolve `path` relative to the repository root, search for substitutes, or fall back to docs/** when a bound read fails.",
|
|
3309
3479
|
"Read only precise indexed references needed for AC/API/field/rule evidence; references remain authoritative over derived text.",
|
|
@@ -3316,8 +3486,8 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3316
3486
|
outputContract: "Review source fidelity and directly revise only testcase/md/**; return concise Markdown, never JSON.",
|
|
3317
3487
|
subtask_prompt: [
|
|
3318
3488
|
"Independently review generated Markdown cases against the task requirements and environment evidence. Treat the files as human-facing test documentation: require clear preconditions, executable steps and assertable expected results; improve names, purpose, metadata and automation mapping where useful while preserving exact machine IDs and technical literals.",
|
|
3319
|
-
"Check AC completeness/meaning, endpoint, fields/shape, status/error codes, rules, states, documented boundaries/auth, positive/negative coverage, executable steps and assertable results. Reject avoidable English prose, duplicated bilingual wording, repeated boilerplate, oversized unstructured sections, vague results such as ‘符合预期’, and missing script/function mapping where it can be derived.",
|
|
3320
|
-
"Correct testcase/md/** directly: add documented omissions, remove unsupported cases, fix mappings/
|
|
3489
|
+
"Check AC completeness/meaning, endpoint, fields/shape, status/error codes, rules, states, documented boundaries/auth, positive/negative coverage, executable steps and assertable results. Reject avoidable English prose, duplicated bilingual wording, repeated boilerplate, oversized unstructured sections, vague results such as ‘符合预期’, Case-ID-like module filenames (for example `BE-HEALTH.md`), and missing or drifted script/function mapping where it can be derived.",
|
|
3490
|
+
"Correct testcase/md/** directly: add documented omissions, remove unsupported cases, rename module files to stable lowercase stems when needed, fix automation mappings so each case points at `testcase/test_<module>.py` derived from that module filename, merge duplicates, improve navigation/tables/Chinese wording, or record gaps in Chinese. Keep Case IDs, AC/REQ/BR IDs, HTTP methods, paths, fields, enum values, filenames, code symbols and source citations exact. The validator accepts Chinese and legacy English section aliases; retain or converge to the Chinese human-readable headings without losing structure.",
|
|
3321
3491
|
"Read only precise referenced source paths plus requirement sections needed for uncovered ACs. Do not scan the repository, modify source/**, generate pytest, execute tests, or emit JSON.",
|
|
3322
3492
|
intake.boundedSourceContext, "## Authoritative reference index", JSON.stringify(intake.referenceIndex, null, 2),
|
|
3323
3493
|
"For each index entry, use `readPath` for Pi read-tool calls and keep `path` as the exact Markdown Source References citation. Bound files under .harness/tasks/<taskId>/source/** are read-only inputs: reading them is allowed even though writing .harness/** is forbidden. Never resolve `path` relative to the repository root, search for substitutes, or fall back to docs/** when a bound read fails.",
|
|
@@ -3333,7 +3503,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3333
3503
|
subtask_prompt: [
|
|
3334
3504
|
"Convert testcase/md/** to pytest using upstream environment and advisory validation evidence plus only bounded pytest config/conftest. A FAIL advisory report does not authorize inventing missing behavior; use the final Markdown facts that are present.",
|
|
3335
3505
|
"Ensure every final Markdown Case ID appears in at least one real pytest test function or pytest test class method region, preferably as `test_BE_<MODULE>_<NNN>_<description>` and in that function/method docstring. Module-level functions and class-based pytest methods are both supported. Multiple test functions may cover one Case ID; assertions come only from 预期结果/Expected Results and setup comes only from 前置条件 plus any optional 测试数据/自动化映射 or their legacy English aliases.",
|
|
3336
|
-
"Name each generated pytest file so it corresponds one-to-one with its source Markdown module file: for each `testcase/md/<module>.md` (excluding README.md), emit exactly one `testcase/test_<module>.py`. The <module> stem is the Markdown filename without the `.md` extension, lowercased and with non-alphanumeric characters replaced by underscores. For example, `testcase/md/resource_notes.md` maps to `testcase/test_resource_notes.py`, and `testcase/md/order-api.md` maps to `testcase/test_order_api.py`. Never merge multiple Markdown modules into one pytest file, never split one module across several files, and never invent pytest filenames unrelated to the Markdown modules.",
|
|
3506
|
+
"Name each generated pytest file so it corresponds one-to-one with its source Markdown module file: for each `testcase/md/<module>.md` (excluding README.md), emit exactly one `testcase/test_<module>.py`. The <module> stem is the Markdown filename without the `.md` extension, lowercased and with non-alphanumeric characters replaced by underscores. For example, `testcase/md/resource_notes.md` maps to `testcase/test_resource_notes.py`, `testcase/md/health.md` maps to `testcase/test_health.py`, `testcase/md/BE-HEALTH.md` maps to `testcase/test_be_health.py`, and `testcase/md/order-api.md` maps to `testcase/test_order_api.py`. If Markdown automation mapping names a different path than this module stem path, still write the module stem path and do not invent prefixes such as `test_be_*` unless the module filename itself normalizes to that stem. Never merge multiple Markdown modules into one pytest file, never split one module across several files, and never invent pytest filenames unrelated to the Markdown modules.",
|
|
3337
3507
|
"Generate a reusable HTTP logging helper (or equivalent client wrapper) and call it for every interface request. The request log must include method, URL/path, and request parameters (query plus JSON/body/payload summary). The response log must include status code and response result (JSON/text/body summary), and both records must be visible in pytest stdout/stderr without changing assertions.",
|
|
3338
3508
|
"Compare timestamps and other semantically equivalent protocol values by parsed meaning, not byte-for-byte serialization. In particular, normalize valid ISO-8601 instants before equality/order assertions so differences such as omitted trailing fractional seconds do not create TestBug failures; preserve exact-string assertions only when the Markdown explicitly requires representation equality.",
|
|
3339
3509
|
"Before logging, recursively redact sensitive keys and header values including authorization, proxy-authorization, cookie, set-cookie, token, password, secret, api key and credentials. Never print full Authorization/Cookie values. Apply bounded truncation to serialized request and response bodies (with an explicit truncation marker) so large payloads cannot flood pytest or report artifacts.",
|
|
@@ -3345,7 +3515,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3345
3515
|
'mkdir -p "${HARNESS_DAG_RUN_DIR}/reports"',
|
|
3346
3516
|
'echo "pytest targets are resolved at runtime from final Markdown 自动化映射"',
|
|
3347
3517
|
].join("; ");
|
|
3348
|
-
const execute = shellNode("execute-backend-pytest-and-html-report-shell", [traceability.id], "markdown-execute-html", "Resolve the final Markdown Automation Notes/自动化映射 to a unique, safe set of testcase/**/test_*.py targets and execute only those scripts exactly once.
|
|
3518
|
+
const execute = shellNode("execute-backend-pytest-and-html-report-shell", [traceability.id], "markdown-execute-html", "Resolve the final Markdown Automation Notes/自动化映射 to a unique, safe set of testcase/**/test_*.py targets and execute only those scripts exactly once. Prefer the deterministic module one-to-one path when a mapped script is missing but the module stem file exists. Generate a native pytest-html self-contained report, then render the primary self-contained Chinese HTML report from the same pytest-html plus final Markdown case metadata without rerun. Keep 测试结论, quality status, failure overview, and a polished per-case result card with concise scenario, automation test name, result, duration, and redacted bounded HTTP request parameters/response results for both passed and failed cases. Do not render a technical/execution evidence section in HTML; retain auditable paths and hashes in facts.", "One scoped pytest execution over Markdown-mapped scripts producing a valid pytest-html report with per-case captured output, self-contained reports/backend-test.html, reports/backend-test.md, reports/backend-test-facts.md, a deterministic self-contained reports/backend-test-l5-dashboard.html (machine-computed L-5 metrics, no JSON), and an optional contracts/code-coverage-v1.json when jacocoCoverage is configured (JaCoCo TCP dump → jacoco.xml → parsed; failure-safe); exit 0/1 with valid evidence continues.", [pytestCommand], 300000);
|
|
3349
3519
|
if (execute.shell) {
|
|
3350
3520
|
execute.shell.envAllowlist = collectBackendTestShellEnvAllowlist(sources);
|
|
3351
3521
|
}
|
|
@@ -3356,13 +3526,14 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3356
3526
|
? { toolProfile: "write", writePolicy: "exclusive", writeSet: ["docs/test-reports/**"], allowedPaths: ["docs/test-reports/**"] }
|
|
3357
3527
|
: { writePolicy: "read-only", allowedPaths: ro }),
|
|
3358
3528
|
forbiddenPaths: forbidden,
|
|
3359
|
-
outputContract: canWriteReport ? "Final Markdown report and L-5 conclusion under docs/test-reports/**; no JSON." : "Final Markdown report and L-5 conclusion in assistant output; no JSON or writes.",
|
|
3529
|
+
outputContract: canWriteReport ? "Final Markdown report and L-5 conclusion under docs/test-reports/**; the deterministic L-5 dashboard at reports/backend-test-l5-dashboard.html is the authoritative visualization and must be linked, not re-rendered; no JSON." : "Final Markdown report and L-5 conclusion in assistant output; the deterministic L-5 dashboard at reports/backend-test-l5-dashboard.html is the authoritative visualization and must be linked; no JSON or writes.",
|
|
3360
3530
|
subtask_prompt: [
|
|
3361
|
-
"Generate the final Markdown report from upstream facts and run-owned environment, advisory case-validation, advisory traceability,
|
|
3531
|
+
"Generate the final Markdown report from upstream facts and run-owned environment, advisory case-validation, advisory traceability, pytest-html and HTML evidence. Do not emit JSON.",
|
|
3362
3532
|
"Use this exact human-facing section order: 测试结论 → 执行概览 → 质量校验 → 失败分析 → 风险与建议 → 证据与 L-5. Put the decision and key numbers first, use compact tables/bullets, and keep headings concise. Do not paste entire upstream reports, duplicate per-case tables already present in facts, or repeat the same evidence in multiple sections; link to paths/hashes and quote only the findings needed for the conclusion.",
|
|
3533
|
+
"The L-5 metrics and visualization are already produced deterministically by node 7 at reports/backend-test-l5-dashboard.html (rendered from computeL5ReportMetrics). Link to that dashboard as the authoritative L-5 view; do not recompute pass/AC/automation/coverage numbers or re-render an HTML dashboard yourself. Quote its L-5 decision verbatim.",
|
|
3363
3534
|
"Always state the exact PASS/FAIL status and findings from nodes 4 and 6. Their FAIL status does not block pytest, but it must remain visible as a quality/traceability risk and must never be rewritten as PASS.",
|
|
3364
3535
|
"Include environment, case quality/review, automation mapping, exact pytest facts, failure classification/analysis, risks, regression recommendations, evidence paths/hashes, coverage availability, and L-5 READY/NOT READY.",
|
|
3365
|
-
"Never override Shell/
|
|
3536
|
+
"Never override Shell/pytest-html facts. L-5 requires pass=100%, AC=100%, automation>=90%, line>=80%, branch>=70%, skipped=0 and no blocking Critical risk.",
|
|
3366
3537
|
canWriteReport ? "Write only under docs/test-reports/**." : "Keep the full report in assistant output.",
|
|
3367
3538
|
].join("\n\n"),
|
|
3368
3539
|
};
|
|
@@ -3376,7 +3547,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3376
3547
|
...taskConfig.hardConstraints, ...STANDARD_GLOBAL_CONSTRAINTS,
|
|
3377
3548
|
"backend-test-dag uses exactly 8 real top-level tasks and executes pytest exactly once over only the safe scripts explicitly mapped by final Markdown cases.",
|
|
3378
3549
|
"Model nodes produce Markdown and pytest assets, never backend-test business JSON envelopes.",
|
|
3379
|
-
"Environment, advisory Markdown validation, advisory traceability,
|
|
3550
|
+
"Environment, advisory Markdown validation, advisory traceability, pytest-html, HTML and execution facts are deterministic evidence. Nodes 4 and 6 record findings without blocking nodes 5, 7 or 8.",
|
|
3380
3551
|
"Only Markdown case generation/review may read source facts; pytest generation must not read source/**.",
|
|
3381
3552
|
"Functional case IDs use BE-<MODULE>-<NNN>; production code/config, skip/xfail, repair and rerun are forbidden.",
|
|
3382
3553
|
],
|
|
@@ -3503,8 +3674,6 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
3503
3674
|
"process.stdout.write(JSON.stringify({cases:manifest.cases}));",
|
|
3504
3675
|
].join("")),
|
|
3505
3676
|
].join(" ");
|
|
3506
|
-
// Heal malformed/missing case evidence to blocked; only unsafe evidenceDir hard-fails.
|
|
3507
|
-
const evidenceValidation = buildFrontendCaseEvidenceValidateShellSnippet();
|
|
3508
3677
|
const frontendTestOutcomeGate = buildFrontendTestOutcomeGateShellSnippet();
|
|
3509
3678
|
const frontendCaseQualityAdvisory = [
|
|
3510
3679
|
"node -e",
|
|
@@ -3752,9 +3921,9 @@ function buildFrontendTestHybridDag(sources) {
|
|
|
3752
3921
|
writeSet: [`${evidenceRoot}/**`],
|
|
3753
3922
|
allowedPaths: ["testcase/frontend/cases/**", `${evidenceRoot}/**`],
|
|
3754
3923
|
forbiddenPaths: forbidden,
|
|
3755
|
-
outputContract: "Deterministic evidence gate:
|
|
3756
|
-
subtask_prompt: "Validate frontend case evidence before result materialization.
|
|
3757
|
-
shell: { commands: [
|
|
3924
|
+
outputContract: "Deterministic evidence gate: missing/malformed evidence is advisory; only unsafe evidenceDir or evidence paths hard-fail. Does not block retrospect.",
|
|
3925
|
+
subtask_prompt: "Validate frontend case evidence before result materialization. Keep missing or malformed evidence as advisory findings; only path-escape failures abort the node.",
|
|
3926
|
+
shell: { commands: [], frontendTestEvidenceValidation: {}, cwd: ".", timeoutMs: 120000 },
|
|
3758
3927
|
}, {
|
|
3759
3928
|
id: "materialize-frontend-test-result-shell",
|
|
3760
3929
|
depends_on: ["validate-frontend-case-evidence-shell"],
|
|
@@ -5304,9 +5473,15 @@ function buildSoftVerifyNode(sources) {
|
|
|
5304
5473
|
const implementId = implementationNodeId();
|
|
5305
5474
|
const strategy = resolveDagVerifyStrategy(sources.taskConfig, "1");
|
|
5306
5475
|
const fallbackCommands = ["npm run typecheck"];
|
|
5476
|
+
const focusedIntermediate = sources.verifyCommands?.intermediate.filter((command) => !isFullSuiteVerifyCommand(command));
|
|
5477
|
+
const plannedIntermediate = applyMavenVerificationPlanning({
|
|
5478
|
+
repoRoot: sources.repoRoot,
|
|
5479
|
+
commands: focusedIntermediate,
|
|
5480
|
+
taskConfig: sources.taskConfig,
|
|
5481
|
+
});
|
|
5307
5482
|
const commands = buildVerifyShellCommands({
|
|
5308
5483
|
repoRoot: sources.repoRoot,
|
|
5309
|
-
commands:
|
|
5484
|
+
commands: plannedIntermediate.commands,
|
|
5310
5485
|
fallbackCommands,
|
|
5311
5486
|
});
|
|
5312
5487
|
return {
|
|
@@ -5326,12 +5501,16 @@ function buildSoftVerifyNode(sources) {
|
|
|
5326
5501
|
phase: "intermediate",
|
|
5327
5502
|
quota: strategy.intermediateQuota ?? "full",
|
|
5328
5503
|
commandSource: sources.verifyCommands ? "adapter" : "inline",
|
|
5329
|
-
commands:
|
|
5504
|
+
commands: plannedIntermediate.commands,
|
|
5330
5505
|
fallbackCommands,
|
|
5506
|
+
commandTexts: commands,
|
|
5331
5507
|
commandTimeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
|
|
5332
5508
|
}),
|
|
5333
5509
|
cwd: ".",
|
|
5334
5510
|
timeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
|
|
5511
|
+
...(plannedIntermediate.mavenVerificationPlan
|
|
5512
|
+
? { mavenVerificationPlan: plannedIntermediate.mavenVerificationPlan }
|
|
5513
|
+
: {}),
|
|
5335
5514
|
},
|
|
5336
5515
|
};
|
|
5337
5516
|
}
|
|
@@ -5423,9 +5602,14 @@ function buildHardVerifyNode(sources) {
|
|
|
5423
5602
|
const fallbackCommands = [
|
|
5424
5603
|
"HARNESS_ALLOW_ACTIVE_DAG_RUNS=1 bash scripts/check-repo.sh",
|
|
5425
5604
|
];
|
|
5426
|
-
const
|
|
5605
|
+
const plannedFinal = applyMavenVerificationPlanning({
|
|
5427
5606
|
repoRoot: sources.repoRoot,
|
|
5428
5607
|
commands: sources.verifyCommands?.final,
|
|
5608
|
+
taskConfig: sources.taskConfig,
|
|
5609
|
+
});
|
|
5610
|
+
const commands = buildVerifyShellCommands({
|
|
5611
|
+
repoRoot: sources.repoRoot,
|
|
5612
|
+
commands: plannedFinal.commands,
|
|
5429
5613
|
fallbackCommands,
|
|
5430
5614
|
});
|
|
5431
5615
|
return {
|
|
@@ -5445,13 +5629,17 @@ function buildHardVerifyNode(sources) {
|
|
|
5445
5629
|
phase: "final",
|
|
5446
5630
|
quota: "full",
|
|
5447
5631
|
commandSource: sources.verifyCommands ? "adapter" : "inline",
|
|
5448
|
-
commands:
|
|
5632
|
+
commands: plannedFinal.commands,
|
|
5449
5633
|
fallbackCommands,
|
|
5634
|
+
commandTexts: commands,
|
|
5450
5635
|
finalFullRequired: true,
|
|
5451
5636
|
commandTimeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
|
|
5452
5637
|
}),
|
|
5453
5638
|
cwd: ".",
|
|
5454
5639
|
timeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
|
|
5640
|
+
...(plannedFinal.mavenVerificationPlan
|
|
5641
|
+
? { mavenVerificationPlan: plannedFinal.mavenVerificationPlan }
|
|
5642
|
+
: {}),
|
|
5455
5643
|
},
|
|
5456
5644
|
};
|
|
5457
5645
|
}
|
|
@@ -5553,9 +5741,6 @@ function buildSupervisedHybridDag(standard, sources) {
|
|
|
5553
5741
|
assertValidDagSpec(spec);
|
|
5554
5742
|
return spec;
|
|
5555
5743
|
}
|
|
5556
|
-
export function defaultHybridDagOutputPath(taskId) {
|
|
5557
|
-
return path.join(os.tmpdir(), `${taskId}-hybrid-dag.json`);
|
|
5558
|
-
}
|
|
5559
5744
|
export async function writeHybridDagDraft(sources, outputPath, options = {}) {
|
|
5560
5745
|
const routingProjectCapability = sources.taskConfig.taskKind === "standard" &&
|
|
5561
5746
|
(options.template === undefined || options.template === "standard-dag")
|
|
@@ -5586,7 +5771,7 @@ export async function writeHybridDagDraft(sources, outputPath, options = {}) {
|
|
|
5586
5771
|
taskId: preparedSources.taskId,
|
|
5587
5772
|
});
|
|
5588
5773
|
}
|
|
5589
|
-
await
|
|
5774
|
+
await writeJsonAtomic(outputPath, spec, { repoRoot: sources.repoRoot });
|
|
5590
5775
|
return {
|
|
5591
5776
|
taskId: sources.taskId,
|
|
5592
5777
|
outputPath,
|
|
@@ -5599,7 +5784,7 @@ export async function writeHybridDagDraft(sources, outputPath, options = {}) {
|
|
|
5599
5784
|
export async function initHybridDagFromTask(repoRoot, taskId, options = {}) {
|
|
5600
5785
|
const sources = await loadTaskHybridSources(repoRoot, taskId);
|
|
5601
5786
|
assertTaskAllowedPathsPreflight(sources.taskConfig);
|
|
5602
|
-
const outputPath = options.outputPath ??
|
|
5787
|
+
const outputPath = options.outputPath ?? getTaskPaths(repoRoot, taskId).dagDraftPath;
|
|
5603
5788
|
return writeHybridDagDraft(sources, outputPath, {
|
|
5604
5789
|
template: options.template,
|
|
5605
5790
|
});
|
|
@@ -593,8 +593,9 @@ export async function executeDagNode(input) {
|
|
|
593
593
|
node.structuredArtifactSha256 = createHash("sha256").update(bytes).digest("hex");
|
|
594
594
|
node.structuredArtifactSchemaId = task.shell.jsonArtifactGate.schemaId;
|
|
595
595
|
}
|
|
596
|
-
else if (task.shell?.backendTestPipeline === "classification-result-context"
|
|
597
|
-
|
|
596
|
+
else if (task.shell?.backendTestPipeline === "classification-result-context" ||
|
|
597
|
+
task.shell?.backendTestPipeline === "markdown-execute-html") {
|
|
598
|
+
// Legacy 15-node and Markdown-first 8-node pipelines materialize
|
|
598
599
|
// contracts/backend-test-result.json without jsonArtifactGate. Bind it
|
|
599
600
|
// so Outcome adapters project kind=backend-test-result for Ready Planner.
|
|
600
601
|
const artifactPath = path.join(runDir, "contracts", "backend-test-result.json");
|
|
@@ -91,6 +91,21 @@ export async function executeDagReconcileRun(repoRoot, rawArgs) {
|
|
|
91
91
|
continue;
|
|
92
92
|
node.status = "ERROR";
|
|
93
93
|
node.finishedAt = reconciledAt;
|
|
94
|
+
const elapsedNodeMs = elapsedDurationMs(node.startedAt, reconciledAt);
|
|
95
|
+
if (elapsedNodeMs !== undefined) {
|
|
96
|
+
node.durationMs = Math.max(node.durationMs ?? 0, elapsedNodeMs);
|
|
97
|
+
}
|
|
98
|
+
const attemptsNewestFirst = node.attempts?.slice().reverse() ?? [];
|
|
99
|
+
const currentAttempt = attemptsNewestFirst.find((attempt) => attempt.attempt === node.currentAttempt &&
|
|
100
|
+
attempt.finishedAt === undefined) ?? attemptsNewestFirst.find((attempt) => attempt.finishedAt === undefined);
|
|
101
|
+
if (currentAttempt && currentAttempt.finishedAt === undefined) {
|
|
102
|
+
currentAttempt.finishedAt = reconciledAt;
|
|
103
|
+
const elapsedAttemptMs = elapsedDurationMs(currentAttempt.startedAt, reconciledAt);
|
|
104
|
+
if (elapsedAttemptMs !== undefined) {
|
|
105
|
+
currentAttempt.durationMs = Math.max(currentAttempt.durationMs ?? 0, elapsedAttemptMs);
|
|
106
|
+
}
|
|
107
|
+
currentAttempt.failureCategory = `operator-${parsed.action}`;
|
|
108
|
+
}
|
|
94
109
|
node.failureCategory = `operator-${parsed.action}`;
|
|
95
110
|
}
|
|
96
111
|
state.status = parsed.action === "supersede" ? "superseded" : "abandoned";
|
|
@@ -119,3 +134,12 @@ export async function executeDagReconcileRun(repoRoot, rawArgs) {
|
|
|
119
134
|
runDir,
|
|
120
135
|
};
|
|
121
136
|
}
|
|
137
|
+
function elapsedDurationMs(startedAt, finishedAt) {
|
|
138
|
+
if (!startedAt)
|
|
139
|
+
return undefined;
|
|
140
|
+
const started = Date.parse(startedAt);
|
|
141
|
+
const finished = Date.parse(finishedAt);
|
|
142
|
+
if (!Number.isFinite(started) || !Number.isFinite(finished))
|
|
143
|
+
return undefined;
|
|
144
|
+
return Math.max(0, finished - started);
|
|
145
|
+
}
|