@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.
Files changed (72) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/executors/dag-pi-executor.js +74 -13
  3. package/dist/executors/pi-executor.js +25 -7
  4. package/dist/executors/pi-prompt-transport.js +198 -0
  5. package/dist/executors/pi-sdk-executor.js +1 -1
  6. package/dist/executors/process-tree.js +33 -0
  7. package/dist/executors/shell-executor.js +239 -47
  8. package/dist/executors/shell-verification.js +4 -2
  9. package/dist/infrastructure/harness/task-store.js +31 -0
  10. package/dist/shared/operator/capabilities.js +6 -0
  11. package/dist/verification/maven/cache.js +142 -0
  12. package/dist/verification/maven/index.js +120 -0
  13. package/dist/verification/maven/plan-commands.js +421 -0
  14. package/dist/verification/maven/pom-static.js +136 -0
  15. package/dist/verification/maven/scope-resolve.js +153 -0
  16. package/dist/verification/maven/stale.js +130 -0
  17. package/dist/verification/maven/types.js +23 -0
  18. package/dist/verification/maven/workspace-graph.js +322 -0
  19. package/dist/worker/cli.js +177 -7
  20. package/dist/worker/delivery/final-verification.js +12 -0
  21. package/dist/worker/feature/advance.js +301 -0
  22. package/dist/worker/feature/doctor.js +223 -0
  23. package/dist/worker/feature/next-action.js +11 -3
  24. package/dist/worker/feature/scaffold.js +798 -0
  25. package/dist/workflows/dag/backend-test-markdown-workflow.js +127 -0
  26. package/dist/workflows/dag/failure-category.js +5 -1
  27. package/dist/workflows/dag/frontend-implementation-contract.js +4 -0
  28. package/dist/workflows/dag/frontend-prewrite-gate.js +0 -17
  29. package/dist/workflows/dag/init-hybrid.js +208 -19
  30. package/dist/workflows/dag/reconcile-run.js +24 -0
  31. package/dist/workflows/dag/types.js +44 -0
  32. package/dist/workflows/dag/validate.js +16 -0
  33. package/docs/architecture/dag-execution.md +5 -1
  34. package/docs/architecture/runtime-boundaries.md +11 -1
  35. package/docs/architecture/worker-and-feature.md +13 -0
  36. package/docs/init-surface.manifest.json +3 -0
  37. package/docs/templates/agent-dag.schema.json +9 -0
  38. package/docs/templates/backend-test-dag.json +3 -3
  39. package/docs/templates/frontend-implementation-contract.schema.json +2 -2
  40. package/docs/templates/product-line/README.md +17 -1
  41. package/docs/templates/product-line/feature-scaffold-batch.example.yaml +31 -0
  42. package/docs/templates/product-line/scaffold-samples/README.md +36 -0
  43. package/docs/templates/product-line/scaffold-samples/backend-only/acceptance.yaml +13 -0
  44. package/docs/templates/product-line/scaffold-samples/backend-only/design.md +14 -0
  45. package/docs/templates/product-line/scaffold-samples/backend-only/feature.yaml +3 -0
  46. package/docs/templates/product-line/scaffold-samples/backend-only/requirement.md +6 -0
  47. package/docs/templates/product-line/scaffold-samples/backend-only/tasks/BE-IMPL-001.yaml +83 -0
  48. package/docs/templates/product-line/scaffold-samples/backend-only/tasks/task-graph.yaml +14 -0
  49. package/docs/templates/product-line/scaffold-samples/fe-with-api/acceptance.yaml +15 -0
  50. package/docs/templates/product-line/scaffold-samples/fe-with-api/design.md +18 -0
  51. package/docs/templates/product-line/scaffold-samples/fe-with-api/feature.yaml +3 -0
  52. package/docs/templates/product-line/scaffold-samples/fe-with-api/requirement.md +6 -0
  53. package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/BE-IMPL-001.yaml +84 -0
  54. package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/CONTRACT-001.yaml +84 -0
  55. package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/FE-IMPL-001.yaml +86 -0
  56. package/docs/templates/product-line/scaffold-samples/fe-with-api/tasks/task-graph.yaml +40 -0
  57. package/docs/templates/product-line/scaffold-samples/frontend-only/acceptance.yaml +13 -0
  58. package/docs/templates/product-line/scaffold-samples/frontend-only/design.md +14 -0
  59. package/docs/templates/product-line/scaffold-samples/frontend-only/feature.yaml +3 -0
  60. package/docs/templates/product-line/scaffold-samples/frontend-only/requirement.md +6 -0
  61. package/docs/templates/product-line/scaffold-samples/frontend-only/tasks/FE-IMPL-001.yaml +85 -0
  62. package/docs/templates/product-line/scaffold-samples/frontend-only/tasks/task-graph.yaml +14 -0
  63. package/examples/l5-report-coms-process-definition.html +322 -0
  64. package/package.json +1 -1
  65. package/skills/agent-worker/references/agent-worker-operator.md +84 -0
  66. package/skills/frontend-bounded-implement/SKILL.md +53 -0
  67. package/skills/frontend-implementation/SKILL.md +3 -7
  68. package/skills/frontend-implementation/references/node-contracts.md +3 -3
  69. package/skills/loop-agent/SKILL.md +8 -8
  70. package/skills/loop-agent/references/command-reference.md +14 -2
  71. package/skills/loop-agent/references/harness-policy.md +22 -0
  72. 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
- return `${command.cwd}\0${command.args.join("\0")}`;
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 = `${command.cwd}\0${command.args.join("\0")}`;
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 verifyShellCommands = buildVerifyShellCommands({
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: finalVerifyCommands,
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.taskConfig.frontendMock?.policy ?? "auto") === "auto" &&
1786
- (capability.status === "absent" || capability.status === "ambiguous")) {
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: FRONTEND_IMPLEMENTATION_SKILLS,
2320
- outputContract: "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.",
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: FRONTEND_IMPLEMENTATION_SKILLS,
2381
- outputContract: "Repair summary for an eligible repairable assessment. Must not expand writeSet, re-interpret requirements, skip tests, or enable Mock by default.",
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 and reports/backend-test.md, plus internal reports/backend-test-facts.md evidence; exit 0/1 with valid evidence continues.", [pytestCommand], 300000);
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: sources.verifyCommands?.intermediate,
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: sources.verifyCommands?.intermediate,
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 commands = buildVerifyShellCommands({
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: sources.verifyCommands?.final,
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/`。它不是修改既有 completed history 的通用入口。Observe / status / doctor 始终只读。
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/**`、外部 SDK(不含 `@cursor/sdk`)。
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",