@tea-agent/loop-agent 0.24.10 → 0.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- 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 +239 -47
- 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/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 +177 -7
- package/dist/worker/delivery/final-verification.js +12 -0
- 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/workflows/dag/backend-test-markdown-workflow.js +127 -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/init-hybrid.js +208 -19
- package/dist/workflows/dag/reconcile-run.js +24 -0
- package/dist/workflows/dag/types.js +44 -0
- package/dist/workflows/dag/validate.js +16 -0
- package/docs/architecture/dag-execution.md +5 -1
- package/docs/architecture/runtime-boundaries.md +11 -1
- package/docs/architecture/worker-and-feature.md +13 -0
- package/docs/init-surface.manifest.json +3 -0
- package/docs/templates/agent-dag.schema.json +9 -0
- package/docs/templates/backend-test-dag.json +3 -3
- package/docs/templates/frontend-implementation-contract.schema.json +2 -2
- 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-bounded-implement/SKILL.md +53 -0
- package/skills/frontend-implementation/SKILL.md +3 -7
- package/skills/frontend-implementation/references/node-contracts.md +3 -3
- package/skills/loop-agent/SKILL.md +8 -8
- package/skills/loop-agent/references/command-reference.md +14 -2
- package/skills/loop-agent/references/harness-policy.md +22 -0
- package/skills/loop-agent/references/task-workflow.md +5 -0
|
@@ -5,6 +5,7 @@ import os from "node:os";
|
|
|
5
5
|
import path from "node:path";
|
|
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";
|
|
@@ -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.",
|
|
@@ -3346,7 +3515,7 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3346
3515
|
'mkdir -p "${HARNESS_DAG_RUN_DIR}/reports"',
|
|
3347
3516
|
'echo "pytest targets are resolved at runtime from final Markdown 自动化映射"',
|
|
3348
3517
|
].join("; ");
|
|
3349
|
-
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
|
|
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, and a deterministic self-contained reports/backend-test-l5-dashboard.html (machine-computed L-5 metrics, no JSON); exit 0/1 with valid evidence continues.", [pytestCommand], 300000);
|
|
3350
3519
|
if (execute.shell) {
|
|
3351
3520
|
execute.shell.envAllowlist = collectBackendTestShellEnvAllowlist(sources);
|
|
3352
3521
|
}
|
|
@@ -3357,10 +3526,11 @@ async function buildBackendTestHybridDag(sources) {
|
|
|
3357
3526
|
? { toolProfile: "write", writePolicy: "exclusive", writeSet: ["docs/test-reports/**"], allowedPaths: ["docs/test-reports/**"] }
|
|
3358
3527
|
: { writePolicy: "read-only", allowedPaths: ro }),
|
|
3359
3528
|
forbiddenPaths: forbidden,
|
|
3360
|
-
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.",
|
|
3361
3530
|
subtask_prompt: [
|
|
3362
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.",
|
|
3363
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.",
|
|
3364
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.",
|
|
3365
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.",
|
|
3366
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.",
|
|
@@ -5303,9 +5473,15 @@ function buildSoftVerifyNode(sources) {
|
|
|
5303
5473
|
const implementId = implementationNodeId();
|
|
5304
5474
|
const strategy = resolveDagVerifyStrategy(sources.taskConfig, "1");
|
|
5305
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
|
+
});
|
|
5306
5482
|
const commands = buildVerifyShellCommands({
|
|
5307
5483
|
repoRoot: sources.repoRoot,
|
|
5308
|
-
commands:
|
|
5484
|
+
commands: plannedIntermediate.commands,
|
|
5309
5485
|
fallbackCommands,
|
|
5310
5486
|
});
|
|
5311
5487
|
return {
|
|
@@ -5325,12 +5501,16 @@ function buildSoftVerifyNode(sources) {
|
|
|
5325
5501
|
phase: "intermediate",
|
|
5326
5502
|
quota: strategy.intermediateQuota ?? "full",
|
|
5327
5503
|
commandSource: sources.verifyCommands ? "adapter" : "inline",
|
|
5328
|
-
commands:
|
|
5504
|
+
commands: plannedIntermediate.commands,
|
|
5329
5505
|
fallbackCommands,
|
|
5506
|
+
commandTexts: commands,
|
|
5330
5507
|
commandTimeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
|
|
5331
5508
|
}),
|
|
5332
5509
|
cwd: ".",
|
|
5333
5510
|
timeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
|
|
5511
|
+
...(plannedIntermediate.mavenVerificationPlan
|
|
5512
|
+
? { mavenVerificationPlan: plannedIntermediate.mavenVerificationPlan }
|
|
5513
|
+
: {}),
|
|
5334
5514
|
},
|
|
5335
5515
|
};
|
|
5336
5516
|
}
|
|
@@ -5422,9 +5602,14 @@ function buildHardVerifyNode(sources) {
|
|
|
5422
5602
|
const fallbackCommands = [
|
|
5423
5603
|
"HARNESS_ALLOW_ACTIVE_DAG_RUNS=1 bash scripts/check-repo.sh",
|
|
5424
5604
|
];
|
|
5425
|
-
const
|
|
5605
|
+
const plannedFinal = applyMavenVerificationPlanning({
|
|
5426
5606
|
repoRoot: sources.repoRoot,
|
|
5427
5607
|
commands: sources.verifyCommands?.final,
|
|
5608
|
+
taskConfig: sources.taskConfig,
|
|
5609
|
+
});
|
|
5610
|
+
const commands = buildVerifyShellCommands({
|
|
5611
|
+
repoRoot: sources.repoRoot,
|
|
5612
|
+
commands: plannedFinal.commands,
|
|
5428
5613
|
fallbackCommands,
|
|
5429
5614
|
});
|
|
5430
5615
|
return {
|
|
@@ -5444,13 +5629,17 @@ function buildHardVerifyNode(sources) {
|
|
|
5444
5629
|
phase: "final",
|
|
5445
5630
|
quota: "full",
|
|
5446
5631
|
commandSource: sources.verifyCommands ? "adapter" : "inline",
|
|
5447
|
-
commands:
|
|
5632
|
+
commands: plannedFinal.commands,
|
|
5448
5633
|
fallbackCommands,
|
|
5634
|
+
commandTexts: commands,
|
|
5449
5635
|
finalFullRequired: true,
|
|
5450
5636
|
commandTimeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
|
|
5451
5637
|
}),
|
|
5452
5638
|
cwd: ".",
|
|
5453
5639
|
timeoutMs: DEFAULT_VERIFY_TIMEOUT_MS,
|
|
5640
|
+
...(plannedFinal.mavenVerificationPlan
|
|
5641
|
+
? { mavenVerificationPlan: plannedFinal.mavenVerificationPlan }
|
|
5642
|
+
: {}),
|
|
5454
5643
|
},
|
|
5455
5644
|
};
|
|
5456
5645
|
}
|
|
@@ -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
|
+
}
|
|
@@ -281,6 +281,36 @@ export const dagBackendTestPipelineSchema = z.enum([
|
|
|
281
281
|
"markdown-execute-html",
|
|
282
282
|
]);
|
|
283
283
|
export const dagFrontendTestEvidenceValidationSchema = z.object({}).strict();
|
|
284
|
+
export const dagMavenWorkspaceManifestSchema = z.object({
|
|
285
|
+
files: z.array(z.object({
|
|
286
|
+
path: z.string(),
|
|
287
|
+
size: z.number(),
|
|
288
|
+
mtimeMs: z.number(),
|
|
289
|
+
})),
|
|
290
|
+
directories: z.array(z.object({
|
|
291
|
+
path: z.string(),
|
|
292
|
+
entryNames: z.array(z.string()),
|
|
293
|
+
})),
|
|
294
|
+
});
|
|
295
|
+
export const dagMavenVerificationPlanSchema = z.object({
|
|
296
|
+
schemaVersion: z.number().int().positive(),
|
|
297
|
+
plannerVersion: z.string().min(1),
|
|
298
|
+
fingerprint: z.string().min(1),
|
|
299
|
+
inputDigest: z.string().min(1),
|
|
300
|
+
targets: z.array(z.object({
|
|
301
|
+
pomPath: z.string().min(1),
|
|
302
|
+
artifactId: z.string().min(1),
|
|
303
|
+
reactorRootPomPath: z.string().min(1).optional(),
|
|
304
|
+
moduleCoordinate: z.string().min(1).optional(),
|
|
305
|
+
})),
|
|
306
|
+
commandDigests: z.array(z.string()),
|
|
307
|
+
plannedCommands: z.array(z.object({
|
|
308
|
+
label: z.string(),
|
|
309
|
+
args: z.array(z.string()),
|
|
310
|
+
})),
|
|
311
|
+
workspaceManifest: dagMavenWorkspaceManifestSchema.optional(),
|
|
312
|
+
shellCommandDigests: z.array(z.string()).optional(),
|
|
313
|
+
});
|
|
284
314
|
export const dagShellConfigSchema = z.object({
|
|
285
315
|
commands: z.array(z.string()).default([]),
|
|
286
316
|
preset: dagShellPresetSchema.optional(),
|
|
@@ -303,6 +333,8 @@ export const dagShellConfigSchema = z.object({
|
|
|
303
333
|
envAllowlist: z.array(z.string()).optional(),
|
|
304
334
|
timeoutMs: z.number().optional(),
|
|
305
335
|
cwd: z.string().optional(),
|
|
336
|
+
/** Frozen Maven verification plan; shell executor re-checks fingerprint before running. */
|
|
337
|
+
mavenVerificationPlan: dagMavenVerificationPlanSchema.optional(),
|
|
306
338
|
});
|
|
307
339
|
export const dagStaticConfigSchema = z.object({
|
|
308
340
|
resultMarkdown: z.string().min(1),
|
|
@@ -410,6 +442,11 @@ export const dagDecisionGateSchema = z.object({
|
|
|
410
442
|
schemaVersion: z.literal(1).optional(),
|
|
411
443
|
mode: dagDecisionGateModeSchema.optional().default("record-only"),
|
|
412
444
|
});
|
|
445
|
+
export const dagWriterOutcomePolicySchema = z
|
|
446
|
+
.object({
|
|
447
|
+
type: z.literal("implementation-outcome-v1"),
|
|
448
|
+
})
|
|
449
|
+
.strict();
|
|
413
450
|
export const dagConvergenceSpecSchema = z
|
|
414
451
|
.object({
|
|
415
452
|
enabled: z.boolean().optional().default(false),
|
|
@@ -467,6 +504,13 @@ export const dagTaskSchema = z.object({
|
|
|
467
504
|
* on safe read-only Pi nodes before the node becomes ERROR.
|
|
468
505
|
*/
|
|
469
506
|
outputProtocol: dagOutputProtocolSchema.optional(),
|
|
507
|
+
/**
|
|
508
|
+
* Fail-closed outcome/diff consistency contract for bounded Pi writers.
|
|
509
|
+
* The writer must begin with IMPLEMENTATION_OUTCOME: changed,
|
|
510
|
+
* already-satisfied, or blocked; the executor checks it against the
|
|
511
|
+
* run-attributed diff.
|
|
512
|
+
*/
|
|
513
|
+
writerOutcomePolicy: dagWriterOutcomePolicySchema.optional(),
|
|
470
514
|
/**
|
|
471
515
|
* Explicit opt-in for the deterministic project governance context resolver
|
|
472
516
|
* (AGENTS.md chain + referenced code standards). Only tasks that set this
|
|
@@ -604,6 +604,21 @@ function validateOutputProtocolTaskConfig(task, issues) {
|
|
|
604
604
|
});
|
|
605
605
|
}
|
|
606
606
|
}
|
|
607
|
+
function validateWriterOutcomePolicyTaskConfig(task, issues) {
|
|
608
|
+
if (task.writerOutcomePolicy === undefined)
|
|
609
|
+
return;
|
|
610
|
+
const isBoundedPiWriter = task.executor === "pi" &&
|
|
611
|
+
task.toolProfile === "write" &&
|
|
612
|
+
task.role === "implementer" &&
|
|
613
|
+
task.writePolicy === "exclusive" &&
|
|
614
|
+
(task.writeSet?.length ?? 0) > 0;
|
|
615
|
+
if (!isBoundedPiWriter) {
|
|
616
|
+
issues.push({
|
|
617
|
+
type: "invalid-writer-outcome-policy",
|
|
618
|
+
message: `task ${task.id} declares writerOutcomePolicy but is not a bounded exclusive Pi implementer writer`,
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
}
|
|
607
622
|
function validateDecisionGateTaskConfig(task, issues) {
|
|
608
623
|
if (!task.decisionGate?.enabled) {
|
|
609
624
|
return;
|
|
@@ -758,6 +773,7 @@ export function validateDagSpec(spec) {
|
|
|
758
773
|
validateDecisionGateTaskConfig(task, issues);
|
|
759
774
|
validateRetryPolicyTaskConfig(task, issues);
|
|
760
775
|
validateOutputProtocolTaskConfig(task, issues);
|
|
776
|
+
validateWriterOutcomePolicyTaskConfig(task, issues);
|
|
761
777
|
validateProjectGovernanceTaskConfig(task, spec, issues);
|
|
762
778
|
validateFailureAwareDependsOn(task, spec, issues);
|
|
763
779
|
}
|
|
@@ -128,7 +128,7 @@ decision envelope 中的 **model verdict**(`decision` / `riskLevel` 等解析
|
|
|
128
128
|
|
|
129
129
|
- **pause → approve → resume**:`dag approve` 在 paused run 写 `human-approval.json`,将状态改回 `running` 并把目录迁回 `active/`;随后 `resumeDagRun`(`runner.ts`)要求 active lifecycle + approval artifact,并用 `prepareSkillSnapshotForContinuation` 复用 run-owned snapshot。
|
|
130
130
|
- **completed 写入**:只有 runner 在终态 `persistState({ allowCompletedFactsWrite: true })`(`runner.ts`)才能写 completed 目录;该 flag 经 `completed-facts-guard.ts` 校验。
|
|
131
|
-
- **显式 recovery mutation**:`dag reconcile-run`(`src/commands/dag-reconcile-run.ts`,命令层)默认仅检查;只有给出 `--action supersede|abandon` + reason,且 liveness 证明 runner 已停止时,才在原 lifecycle 写 reconciliation/state 并迁移到 `completed
|
|
131
|
+
- **显式 recovery mutation**:`dag reconcile-run`(`src/commands/dag-reconcile-run.ts`,命令层)默认仅检查;只有给出 `--action supersede|abandon` + reason,且 liveness 证明 runner 已停止时,才在原 lifecycle 写 reconciliation/state 并迁移到 `completed/`。仍为 `RUNNING` 的节点保持失败语义,但以 `reconciledAt - startedAt` 补齐节点和当前 attempt 的耗时,原始状态继续冻结在 `reconciliation.json`。它不是修改既有 completed history 的通用入口。Observe / status / doctor 始终只读。
|
|
132
132
|
- **status 枚举**:`DagRunState.status`;`TERMINAL_RUN_STATUSES` 判终态;`isTerminalDagRunStatus` 工具函数。
|
|
133
133
|
|
|
134
134
|
## convergence(可选、supervised)
|
|
@@ -142,6 +142,10 @@ decision envelope 中的 **model verdict**(`decision` / `riskLevel` 等解析
|
|
|
142
142
|
|
|
143
143
|
完成声明的权威是 shell command 的新鲜 exit code 与归档输出。验证命令执行在 `src/executors/shell-executor.ts`,环境与 preset helper 在 `src/executors/shell-verification.ts`;DAG authoring 写入的 `task.shell.verifyEvidence` 元数据由 `src/workflows/dag/node-execution.ts` 复制到 `node.verifyEvidence`。model verdict、Observe 或报告都不能替代这些 shell facts。
|
|
144
144
|
|
|
145
|
+
supervised 的 `soft-verify-shell` 只运行聚焦命令,明确排除无参数的 `npm test`、`npm run test` 与 `scripts/ci.sh` 等 full-suite 入口;`hard-verify-shell` 保留一次完整 final 集合。task 与 adapter 命令合并时会解包可证明安全的单命令 `bash -lc` wrapper,并以规范化 cwd、argv、env、timeout 做语义去重;`verifyEvidence.commandCount/commandLabels/commandTexts` 必须对应去重后实际执行的命令。
|
|
146
|
+
|
|
147
|
+
Pi SDK 正常路径继续在内存中传递 system prompt;只有 CLI fallback / `cli-only` 才通过权限收紧的系统临时文件传给 `--append-system-prompt`,归档命令只保存占位符。transport 解析并记录真实 temp root,持有 prompt 文件句柄,并冻结 temp root、随机私有目录、固定文件名三层 identity;收口时先用句柄清空正文,三层 identity 均匹配才精确 `unlink` 文件并非递归 `rmdir` 空目录。未知 sibling、symlink/junction 或 identity 漂移都会拒绝扩大删除范围。shell 与 Pi 子进程以独立进程组启动;超时在 Windows 使用 tree termination、在 POSIX 对进程组发信号,防止 npm/Vitest/tsx 后代在节点结束后继续持有 IPC 或临时文件。
|
|
148
|
+
|
|
145
149
|
## Dynamic Workflow
|
|
146
150
|
|
|
147
151
|
Dynamic Workflow 是 DAG runtime 上方的逻辑编排/编译层,**不**重写 runner:
|
|
@@ -18,6 +18,9 @@ Application layer (src/application/,逐步引入)
|
|
|
18
18
|
Workflow runtime (src/workflows/)
|
|
19
19
|
└─ DAG / Dynamic / Loop 核心执行规则;不应依赖 commands
|
|
20
20
|
|
|
21
|
+
Verification planners (src/verification/)
|
|
22
|
+
└─ 生成期/执行期验证规划(如 Maven workspace 静态图与冻结计划);纯库模块,不跑 CLI
|
|
23
|
+
|
|
21
24
|
Executors (src/executors/)
|
|
22
25
|
└─ Pi / Shell / Static 等受治理外部工具适配;不应依赖 commands 或 CLI formatting
|
|
23
26
|
|
|
@@ -66,11 +69,18 @@ Governance (scripts/check-*.sh, src/governance/)
|
|
|
66
69
|
- **允许依赖**:`src/executors/**`、`src/task/**`、`src/records/**`、`src/shared/**`、application use-case(目标态)。
|
|
67
70
|
- **禁止**:`import` 来自 `src/commands/**`(见下方过渡例外)。
|
|
68
71
|
|
|
72
|
+
### Verification planners
|
|
73
|
+
|
|
74
|
+
- **位置**:`src/verification/**`(当前含 `src/verification/maven/**`)
|
|
75
|
+
- **职责**:在 DAG 生成阶段与 `verify-shell` 执行前,对任务作用域驱动的验证命令做静态规划与冻结计划复核(例如 Maven Workspace Graph、`-f/-pl/-am` 重写、`.harness/cache/` 图缓存、`verification-plan-stale`)。
|
|
76
|
+
- **允许依赖**:`src/adapters/types`(`VerifyCommand`)、Node 标准库;由 `src/workflows/dag/**` 与 `src/executors/shell-executor.ts` 调用。
|
|
77
|
+
- **禁止**:执行 Maven/npm 等外部 goal 做发现;依赖 `src/commands/**` 或 CLI;把独立 POM 拼成伪 reactor;按 first-match/深度/字典序/启动 cwd 选择目标。
|
|
78
|
+
|
|
69
79
|
### Executors
|
|
70
80
|
|
|
71
81
|
- **位置**:`src/executors/**`
|
|
72
82
|
- **职责**:封装 Pi SDK、shell 执行、static 输出与 write guard。受治理 Agent runtime 只有 Pi。
|
|
73
|
-
- **允许依赖**:`src/shared
|
|
83
|
+
- **允许依赖**:`src/shared/**`、`src/verification/**`(执行前计划复核)、外部 SDK(不含 `@cursor/sdk`)。
|
|
74
84
|
- **禁止**:依赖 `src/commands/**`、CLI 输出格式,或 import `src/sidecars/**` / `@cursor/sdk`。
|
|
75
85
|
|
|
76
86
|
### Sidecars
|
|
@@ -83,6 +83,19 @@ controller identity 与 DAG skill snapshot 是两个不同冻结层(前者跨
|
|
|
83
83
|
- Delivery / Closeout:clean Delivery HEAD 上生成 canonical QA/最终验证证据、Delivery Package、Acceptance Coverage、PR 草稿;Closeout 默认预览,显式 `--apply --owner` 才原子写回。
|
|
84
84
|
- 权威证据:`CHANGELOG.md [0.10.0]`、`docs/reports/feature/2026-07-12-m2-completion-audit.md`。
|
|
85
85
|
|
|
86
|
+
### Final Verification 权威(ADR 0007)
|
|
87
|
+
|
|
88
|
+
| 入口 | 职责 |
|
|
89
|
+
| --- | --- |
|
|
90
|
+
| `agent-worker feature verify-final` | **唯一** canonical writer:Feature Verification Bundle、`qa-pass.json`、`final-verification.json` |
|
|
91
|
+
| `agent-worker feature advance` | 编排 verify-final → delivery → closeout preview[/apply](ADR 0007 运维入口) |
|
|
92
|
+
| `agent-worker feature doctor` | 只读诊断证据/Pool/dirty worktree;建议下一步命令 |
|
|
93
|
+
| Feature Packet `FINAL-VERIFY-*`(若保留) | 可选 **local smoke recipe**(样本 `verify:final` / reports);Task Done ≠ Final Verification Record |
|
|
94
|
+
| `feature delivery` | 重验 verify-final 证据;省略路径时默认 `.harness/task-pool/evidence/<featureId>/{qa-pass,final-verification}.json` |
|
|
95
|
+
| `feature closeout` | 只认 Delivery manifest + Final Verification Record 链 |
|
|
96
|
+
|
|
97
|
+
禁止把 packet Task 的 smoke 报告或 agent-dag `verify-pi` 结论当作 Delivery 放行条件。
|
|
98
|
+
|
|
86
99
|
### Inspect(Observe 只读 read model)
|
|
87
100
|
|
|
88
101
|
- 模块:`src/worker/observe/`、`src/worker/observability/{read-model,event-store}.ts`。
|
|
@@ -74,6 +74,7 @@
|
|
|
74
74
|
"skills/frontend-implementation/references/node-contracts.md",
|
|
75
75
|
"skills/frontend-implementation/references/design-spec.md",
|
|
76
76
|
"skills/frontend-implementation/references/code-standards.md",
|
|
77
|
+
"skills/frontend-bounded-implement/SKILL.md",
|
|
77
78
|
"skills/frontend-design-review/SKILL.md",
|
|
78
79
|
"skills/frontend-design-review/references/review-checklist.md",
|
|
79
80
|
"skills/frontend-review/SKILL.md",
|
|
@@ -151,6 +152,7 @@
|
|
|
151
152
|
".agents/skills/frontend-implementation/references/node-contracts.md",
|
|
152
153
|
".agents/skills/frontend-implementation/references/design-spec.md",
|
|
153
154
|
".agents/skills/frontend-implementation/references/code-standards.md",
|
|
155
|
+
".agents/skills/frontend-bounded-implement/SKILL.md",
|
|
154
156
|
".agents/skills/frontend-design-review/SKILL.md",
|
|
155
157
|
".agents/skills/frontend-design-review/references/review-checklist.md",
|
|
156
158
|
".agents/skills/frontend-review/SKILL.md",
|
|
@@ -229,6 +231,7 @@
|
|
|
229
231
|
".agents/skills/frontend-implementation/references/node-contracts.md": "copied",
|
|
230
232
|
".agents/skills/frontend-implementation/references/design-spec.md": "copied",
|
|
231
233
|
".agents/skills/frontend-implementation/references/code-standards.md": "copied",
|
|
234
|
+
".agents/skills/frontend-bounded-implement/SKILL.md": "copied",
|
|
232
235
|
".agents/skills/frontend-design-review/SKILL.md": "copied",
|
|
233
236
|
".agents/skills/frontend-design-review/references/review-checklist.md": "copied",
|
|
234
237
|
".agents/skills/frontend-review/SKILL.md": "copied",
|