@tea-agent/loop-agent 0.36.4-beta.0 → 0.37.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 (120) hide show
  1. package/CHANGELOG.md +50 -68
  2. package/dist/build-stamp.json +3 -3
  3. package/dist/infrastructure/harness/atomic-write.js +1 -2
  4. package/dist/task/contract/project.js +4 -0
  5. package/dist/task/frontend-project-capability.js +12 -162
  6. package/dist/task/source-prepare/completeness.js +36 -0
  7. package/dist/task/source-prepare/parse-intent.js +67 -11
  8. package/dist/task/source-prepare/prepare.js +102 -5
  9. package/dist/worker/console/chat/chat-event-store.js +91 -0
  10. package/dist/worker/console/chat/pi-runtime.js +25 -0
  11. package/dist/worker/console/chat/routes.js +7 -2
  12. package/dist/worker/console/chat/semantic-activity.js +7 -1
  13. package/dist/worker/console/chat/session-store.js +6 -1
  14. package/dist/worker/console/chat/turn-process.js +119 -12
  15. package/dist/worker/console/operator-actions.js +18 -1
  16. package/dist/worker/console/static/assets/abnfDiagram-N423BO3Z-DSvF6RXQ.js +1 -0
  17. package/dist/worker/console/static/assets/arc-DuI4ogtJ.js +1 -0
  18. package/dist/worker/console/static/assets/architectureDiagram-T3A2C74G-DdBbgD4y.js +36 -0
  19. package/dist/worker/console/static/assets/blockDiagram-VBNYF7ZC-xQC2HELm.js +132 -0
  20. package/dist/worker/console/static/assets/c4Diagram-5PPSVZJV-DPMSgwIZ.js +10 -0
  21. package/dist/worker/console/static/assets/channel-DLVz9TYW.js +1 -0
  22. package/dist/worker/console/static/assets/chunk-2GRJ4B5K-WTEKTxin.js +1 -0
  23. package/dist/worker/console/static/assets/chunk-2Q5K7J3B-B6vZ0Pv_.js +1 -0
  24. package/dist/worker/console/static/assets/chunk-5RXB4S5H-BrerNAix.js +231 -0
  25. package/dist/worker/console/static/assets/chunk-5VM5RSS4-D_ch2aaI.js +15 -0
  26. package/dist/worker/console/static/assets/chunk-6Q2QTUOP-VvfacNrq.js +88 -0
  27. package/dist/worker/console/static/assets/chunk-GF5L2VYU-B-uGFIg2.js +206 -0
  28. package/dist/worker/console/static/assets/chunk-JWPE2WC7-BlysCtod.js +1 -0
  29. package/dist/worker/console/static/assets/chunk-KBJHAD2P-BjAUAfcT.js +1 -0
  30. package/dist/worker/console/static/assets/chunk-RYQCIY6F-BeZnBmb-.js +1 -0
  31. package/dist/worker/console/static/assets/chunk-XXDRQBXY-Bbfe3UJG.js +1 -0
  32. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-BeAuGcO9.js +1 -0
  33. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-BeAuGcO9.js +1 -0
  34. package/dist/worker/console/static/assets/cose-bilkent-JH36ORCC-BKJQnYVM.js +1 -0
  35. package/dist/worker/console/static/assets/cynefin-VYW2F7L2-B1wtI3hU.js +166 -0
  36. package/dist/worker/console/static/assets/cynefinDiagram-MW4NZA55-DCF6DBSu.js +62 -0
  37. package/dist/worker/console/static/assets/cytoscape.esm-yzknjiTM.js +321 -0
  38. package/dist/worker/console/static/assets/dagre-VZM6K2ZE-BJyNGRlP.js +4 -0
  39. package/dist/worker/console/static/assets/defaultLocale-DX6XiGOO.js +1 -0
  40. package/dist/worker/console/static/assets/diagram-7IWD3JNH-B7j1-VsZ.js +30 -0
  41. package/dist/worker/console/static/assets/diagram-B4RE2ZJO-BREJ140a.js +3 -0
  42. package/dist/worker/console/static/assets/diagram-LBJQPF4R-CDPHmHGH.js +24 -0
  43. package/dist/worker/console/static/assets/diagram-Q27KOJAE-BtZLrez1.js +24 -0
  44. package/dist/worker/console/static/assets/diagram-UB23O5K3-mzybu8oU.js +41 -0
  45. package/dist/worker/console/static/assets/ebnfDiagram-BXEA7PRR-Dk3I2uO6.js +1 -0
  46. package/dist/worker/console/static/assets/erDiagram-JOGREHBK-BT97u_tQ.js +85 -0
  47. package/dist/worker/console/static/assets/flowDiagram-UKHOOZJN-CMOtYzui.js +156 -0
  48. package/dist/worker/console/static/assets/ganttDiagram-PKOTCBZU-DiNydkuY.js +292 -0
  49. package/dist/worker/console/static/assets/gitGraphDiagram-DS77QQ5N-Cwt6fLkl.js +106 -0
  50. package/dist/worker/console/static/assets/graph-DOmOIIwC.js +1 -0
  51. package/dist/worker/console/static/assets/index-DQxiso1w.js +325 -0
  52. package/dist/worker/console/static/assets/index-_j0Hzo69.css +1 -0
  53. package/dist/worker/console/static/assets/infoDiagram-6WML65LV-AsGIGKKB.js +2 -0
  54. package/dist/worker/console/static/assets/init-Gi6I4Gst.js +1 -0
  55. package/dist/worker/console/static/assets/ishikawaDiagram-WSZJBQD7-BL8ITZh3.js +70 -0
  56. package/dist/worker/console/static/assets/journeyDiagram-NVQOT4AX-VPKAUX8M.js +139 -0
  57. package/dist/worker/console/static/assets/kanban-definition-27J2QSJJ-BL56hfGp.js +89 -0
  58. package/dist/worker/console/static/assets/layout-D-LzfAck.js +1 -0
  59. package/dist/worker/console/static/assets/linear-BmSG3nIZ.js +1 -0
  60. package/dist/worker/console/static/assets/map-DxJ2ADlA.js +1 -0
  61. package/dist/worker/console/static/assets/mermaid.core-DWGK9kwP.js +308 -0
  62. package/dist/worker/console/static/assets/mindmap-definition-FAOFIHXS--vvKLjo0.js +96 -0
  63. package/dist/worker/console/static/assets/ordinal-Cboi1Yqb.js +1 -0
  64. package/dist/worker/console/static/assets/pegDiagram-VL7TDLO6-B41mxb2-.js +1 -0
  65. package/dist/worker/console/static/assets/pieDiagram-7S7Q4E2Y-DTlu77sw.js +39 -0
  66. package/dist/worker/console/static/assets/quadrantDiagram-CIZ2JOQS-Bk5A7alv.js +7 -0
  67. package/dist/worker/console/static/assets/railroadDiagram-AXF67PYL-Csf9jzQy.js +1 -0
  68. package/dist/worker/console/static/assets/requirementDiagram-LRYGKXZP-CeptoSNx.js +84 -0
  69. package/dist/worker/console/static/assets/sankeyDiagram-W5VNT64P-A-DLCLer.js +40 -0
  70. package/dist/worker/console/static/assets/sequenceDiagram-SI44F4Z6-22VbPdOI.js +162 -0
  71. package/dist/worker/console/static/assets/sizeCapture-X5ZJPWSS-DWvAyBC9.js +1 -0
  72. package/dist/worker/console/static/assets/stateDiagram-OKZ733FA-qICZ75yT.js +1 -0
  73. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-DzKvfHTX.js +1 -0
  74. package/dist/worker/console/static/assets/swimlanes-SLNWSIFB-xi7lcMyp.js +2 -0
  75. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-Cxqr_qM1.js +8 -0
  76. package/dist/worker/console/static/assets/timeline-definition-Z64GVDOM-Bu6Nl6O2.js +120 -0
  77. package/dist/worker/console/static/assets/vennDiagram-T6HMQDX7-BzMz4Bn9.js +34 -0
  78. package/dist/worker/console/static/assets/wardleyDiagram-T6FBY63Y-CxaJnzc5.js +78 -0
  79. package/dist/worker/console/static/assets/xychartDiagram-ELKLHX3M-5JK-omQK.js +7 -0
  80. package/dist/worker/console/static/fonts/katex/KaTeX_AMS-Regular.woff2 +0 -0
  81. package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Bold.woff2 +0 -0
  82. package/dist/worker/console/static/fonts/katex/KaTeX_Caligraphic-Regular.woff2 +0 -0
  83. package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Bold.woff2 +0 -0
  84. package/dist/worker/console/static/fonts/katex/KaTeX_Fraktur-Regular.woff2 +0 -0
  85. package/dist/worker/console/static/fonts/katex/KaTeX_Main-Bold.woff2 +0 -0
  86. package/dist/worker/console/static/fonts/katex/KaTeX_Main-BoldItalic.woff2 +0 -0
  87. package/dist/worker/console/static/fonts/katex/KaTeX_Main-Italic.woff2 +0 -0
  88. package/dist/worker/console/static/fonts/katex/KaTeX_Main-Regular.woff2 +0 -0
  89. package/dist/worker/console/static/fonts/katex/KaTeX_Math-BoldItalic.woff2 +0 -0
  90. package/dist/worker/console/static/fonts/katex/KaTeX_Math-Italic.woff2 +0 -0
  91. package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Bold.woff2 +0 -0
  92. package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Italic.woff2 +0 -0
  93. package/dist/worker/console/static/fonts/katex/KaTeX_SansSerif-Regular.woff2 +0 -0
  94. package/dist/worker/console/static/fonts/katex/KaTeX_Script-Regular.woff2 +0 -0
  95. package/dist/worker/console/static/fonts/katex/KaTeX_Size1-Regular.woff2 +0 -0
  96. package/dist/worker/console/static/fonts/katex/KaTeX_Size2-Regular.woff2 +0 -0
  97. package/dist/worker/console/static/fonts/katex/KaTeX_Size3-Regular.woff2 +0 -0
  98. package/dist/worker/console/static/fonts/katex/KaTeX_Size4-Regular.woff2 +0 -0
  99. package/dist/worker/console/static/fonts/katex/KaTeX_Typewriter-Regular.woff2 +0 -0
  100. package/dist/worker/console/static/index.html +2 -2
  101. package/dist/worker/console/static-src/operator-chat/chat-sse-events.js +25 -0
  102. package/dist/worker/console/static-src/operator-chat/useChatSessions.js +37 -2
  103. package/dist/worker/observe/static/operator-chrome.js +4 -2
  104. package/dist/workflows/dag/failure-routing.js +4 -9
  105. package/dist/workflows/dag/frontend-prewrite-gate.js +8 -37
  106. package/dist/workflows/dag/init-hybrid.js +4 -73
  107. package/dist/workflows/dag/lifecycle.js +0 -4
  108. package/dist/workflows/dag/node-execution.js +6 -16
  109. package/dist/workflows/dag/report.js +0 -6
  110. package/dist/workflows/dag/retry-policy.js +0 -11
  111. package/dist/workflows/dag/runner.js +19 -3
  112. package/dist/workflows/dag/types.js +0 -2
  113. package/dist/workflows/dag/validate.js +2 -7
  114. package/docs/templates/frontend-design-contract.md +14 -34
  115. package/docs/templates/frontend-task-constraints.md +13 -33
  116. package/docs/templates/frontend-task-requirement.md +20 -54
  117. package/package.json +6 -1
  118. package/skills/loop-agent/references/command-reference.md +1 -0
  119. package/dist/worker/console/static/assets/index-D83DYAFG.css +0 -1
  120. package/dist/worker/console/static/assets/index-IXm7oYjL.js +0 -59
@@ -44,9 +44,7 @@ function routeToProductLine(input) {
44
44
  case "decision-envelope":
45
45
  return "NeedsHuman";
46
46
  case "skipped":
47
- return input.skippedReason === "frontend-prewrite-not-authorized"
48
- ? "ContractMismatch"
49
- : "DependencyFailure";
47
+ return "DependencyFailure";
50
48
  case "shell-command":
51
49
  if (/\b(ENOENT|PATH|command not found|No such file|not found in PATH|bash: .*: No such file)\b/i.test(raw)) {
52
50
  return "EnvFailure";
@@ -92,12 +90,9 @@ export function routeDagFailure(input) {
92
90
  if (!productLineFailureCategory)
93
91
  return {};
94
92
  const recommendedFollowUp = productLineFailureCategory === "ContractMismatch" &&
95
- input.skippedReason === "frontend-prewrite-not-authorized"
96
- ? "fix-frontend-task-contract-and-regenerate-dag"
97
- : productLineFailureCategory === "ContractMismatch" &&
98
- isFrontendDesignGateNode(input.nodeId ?? "")
99
- ? "frontend-plan-revision-and-rerun"
100
- : FOLLOW_UP_BY_PRODUCT_LINE[productLineFailureCategory];
93
+ isFrontendDesignGateNode(input.nodeId ?? "")
94
+ ? "frontend-plan-revision-and-rerun"
95
+ : FOLLOW_UP_BY_PRODUCT_LINE[productLineFailureCategory];
101
96
  return {
102
97
  productLineFailureCategory,
103
98
  recommendedFollowUp,
@@ -25,7 +25,6 @@ export const frontendPrewriteFailureCodeSchema = z.enum([
25
25
  "mock-strategy-no-verification-commands",
26
26
  "target-outside-write-set",
27
27
  "verification-target-outside-write-set",
28
- "openspec-not-read",
29
28
  ]);
30
29
  export const frontendPrewriteResultV1Schema = z
31
30
  .object({
@@ -499,9 +498,13 @@ export async function runFrontendPrewriteGate(input) {
499
498
  });
500
499
  }
501
500
  }
502
- // Effective plan/review must actually read the candidate openspec paths when
503
- // any exist. This fail-closes BEFORE the canonical contract is materialized
504
- // so a writer is never authorized on top of unread normative evidence.
501
+ // Materialize the canonical contract only after every governance check passed.
502
+ const artifact = await writeFrontendImplementationContractArtifact({
503
+ runDir: input.runDir,
504
+ outputDir: input.config.outputDir,
505
+ artifactName: input.config.artifactName,
506
+ canonical: analysis.canonical,
507
+ });
505
508
  const candidatePaths = input.config.openspecCandidatePaths ?? [];
506
509
  const openspecReadPaths = await checkOpenspecReadEvidence({
507
510
  runDir: input.runDir,
@@ -510,31 +513,6 @@ export async function runFrontendPrewriteGate(input) {
510
513
  reviewNodeId,
511
514
  repoRoot: workspaceRoot ?? process.cwd(),
512
515
  });
513
- if (candidatePaths.length > 0) {
514
- const readSet = new Set(openspecReadPaths);
515
- const unread = candidatePaths.filter((candidate) => !readSet.has(candidate));
516
- if (unread.length > 0) {
517
- return finalizePrewrite(input, {
518
- ...basePending,
519
- verdict,
520
- candidateJsonSha256: analysis.candidateJsonSha256,
521
- normalizationActions: [],
522
- mockStrategy,
523
- classification: "retryable-invalid",
524
- failureReason: `frontend prewrite gate: effective plan/review did not read these openspec candidate paths: ${unread.join(", ")}. Read each candidate and report applicable rules plus hit path/section/line number, conflicts, and missing specifications, then rerun.`,
525
- failureCode: "openspec-not-read",
526
- openspecReadPaths,
527
- openspecCandidatePaths: candidatePaths,
528
- });
529
- }
530
- }
531
- // Materialize the canonical contract only after every governance check passed.
532
- const artifact = await writeFrontendImplementationContractArtifact({
533
- runDir: input.runDir,
534
- outputDir: input.config.outputDir,
535
- artifactName: input.config.artifactName,
536
- canonical: analysis.canonical,
537
- });
538
516
  const classification = analysis.normalizationActions.length > 0 ? "accepted-normalized" : "accepted";
539
517
  return finalizePrewrite(input, {
540
518
  ...basePending,
@@ -578,14 +556,7 @@ export function formatFrontendPrewriteGateStdout(result) {
578
556
  if (result.openspecReadPaths.length > 0) {
579
557
  lines.push(`openspec read: ${result.openspecReadPaths.join(", ")}`);
580
558
  }
581
- if (result.openspecCandidatePaths.length > 0) {
582
- const readSet = new Set(result.openspecReadPaths);
583
- const unread = result.openspecCandidatePaths.filter((candidate) => !readSet.has(candidate));
584
- if (unread.length > 0) {
585
- lines.push(`openspec unread: ${unread.join(", ")}`);
586
- }
587
- }
588
- else {
559
+ else if (result.openspecCandidatePaths.length === 0) {
589
560
  lines.push("openspec: unavailable");
590
561
  }
591
562
  return lines.join("\n");
@@ -9,7 +9,7 @@ import { planMavenVerification, } from "../../verification/maven/index.js";
9
9
  import { pathMatchesPattern } from "../../shared/git-progress.js";
10
10
  import { BASELINE_FORBIDDEN_PATHS } from "./governance-constants.js";
11
11
  import { buildDecisionEnvelopePromptContract } from "./decision-envelope.js";
12
- import { DEFAULT_READ_ONLY_PI_RETRY_POLICY, PLANNER_OUTPUT_LIMIT_RETRY_POLICY, PROTOCOL_AWARE_PI_RETRY_POLICY, STRUCTURED_REQUIRED_PI_RETRY_POLICY, BACKEND_TEST_WRITER_COMPLETENESS_RETRY_POLICY, WRITER_TRANSPORT_RETRY_POLICY, isSafeReadOnlyPiRetryCandidate, isWriterTransportRetryCandidate, } from "./retry-policy.js";
12
+ import { DEFAULT_READ_ONLY_PI_RETRY_POLICY, PROTOCOL_AWARE_PI_RETRY_POLICY, STRUCTURED_REQUIRED_PI_RETRY_POLICY, BACKEND_TEST_WRITER_COMPLETENESS_RETRY_POLICY, WRITER_TRANSPORT_RETRY_POLICY, isSafeReadOnlyPiRetryCandidate, isWriterTransportRetryCandidate, } from "./retry-policy.js";
13
13
  import { REVIEW_JSON_VERDICT_OUTPUT_PROTOCOL, REVIEW_VERDICT_OUTPUT_PROTOCOL, } from "./output-protocol.js";
14
14
  import { resolveAdapter } from "../../adapters/index.js";
15
15
  import { loadHarnessManifest } from "../../governance/harness.js";
@@ -558,39 +558,6 @@ export function hasApiDependency(sources) {
558
558
  .some((clause) => !negationPatterns.some((pattern) => pattern.test(clause)) &&
559
559
  dependencyPatterns.some((pattern) => pattern.test(clause)));
560
560
  }
561
- /**
562
- * Generation-time heuristic: does the task source mention interface/API/Mock
563
- * needs anywhere (requirement or constraints)?
564
- *
565
- * Mirrors hasApiDependency's clause-level negation filtering so explicit
566
- * "not involved / not implementing" clauses and out-of-scope sections do not
567
- * become positive evidence. Used to emit a machine-visible advisory when auto
568
- * mode narrows Mock to not-needed despite the source mentioning such needs.
569
- */
570
- export function frontendSourceMentionsMock(sources) {
571
- const text = [sources.requirementMarkdown, sources.constraintMarkdown]
572
- .filter((entry) => typeof entry === "string" && entry.trim().length > 0)
573
- .map((entry) => normalizeTaskRequirementText(entry).replace(/`[^`\n]*`/g, " "))
574
- .join("\n");
575
- const signalPatterns = [
576
- /mock/i,
577
- /模拟服务/,
578
- /接口桩/,
579
- /\bmsw\b/i,
580
- /(?<![A-Za-z0-9_])API(?![A-Za-z0-9_])/i,
581
- /接口/,
582
- ];
583
- const negationPatterns = [
584
- /(?:不涉及|无需|不需要|不依赖|不调用|不请求|不实现|没有|禁止|不得).{0,16}(?:接口|后端|服务端|远程数据|异步数据|API|mock|模拟|桩)/i,
585
- /\b(?:no|without|does\s+not|do\s+not|must\s+not)\b.{0,24}\b(?:api|endpoint|request|backend|server|mock)\b/i,
586
- ];
587
- return text
588
- .split(/[。!?!?;;,,\r\n]+/)
589
- .map((clause) => clause.trim())
590
- .filter(Boolean)
591
- .some((clause) => !negationPatterns.some((pattern) => pattern.test(clause)) &&
592
- signalPatterns.some((pattern) => pattern.test(clause)));
593
- }
594
561
  /**
595
562
  * Resolve frontend Mock mode from capability seed, task config, and interface dependency analysis.
596
563
  *
@@ -2144,7 +2111,6 @@ function resolveFrontendMockContextBlock(sources) {
2144
2111
  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.");
2145
2112
  if (frontendMockStrategyMustBeNotNeeded(sources)) {
2146
2113
  parts.push('Auto mode has no confirmed project Mock capability or no deterministic Mock verification command. 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.');
2147
- parts.push('HARD CONSTRAINT (frozen at generation time): this DAG allows only mockApi.strategy "not-needed"; the prewrite gate rejects any other strategy. If project governance (openspec / ai_workspace / decision records, e.g. a DEC rule requiring native) demands Mock-backed verification, that is a generation-time contract gap, not a plan-revision defect: declare frontendMock.verifyCommands (or policy: "required") in task.json and regenerate the DAG. Do not emit any mockApi.strategy outside the allowlist and do not add Mock files or dependencies within this run.');
2148
2114
  }
2149
2115
  }
2150
2116
  if (mode === "blocked") {
@@ -2175,33 +2141,9 @@ function resolveFrontendCapabilityContextBlock(sources) {
2175
2141
  }
2176
2142
  if (capability) {
2177
2143
  parts.push("", capability.adapterGuidance);
2178
- const classified = capability.designEvidence.classified;
2179
- const bucketLines = [];
2180
- const pushBucket = (label, paths) => {
2181
- if (paths.length > 0)
2182
- bucketLines.push(`${label}: ${paths.join(", ")}`);
2183
- };
2184
- pushBucket("schemas", classified.schemas);
2185
- pushBucket("code-template", classified.codeTemplate);
2186
- pushBucket("rule.api", classified.rule.api);
2187
- pushBucket("rule.mock", classified.rule.mock);
2188
- pushBucket("rule.router", classified.rule.router);
2189
- pushBucket("rule.hooks", classified.rule.hooks);
2190
- pushBucket("rule.utils", classified.rule.utils);
2191
- pushBucket("rule.components", classified.rule.components);
2192
- pushBucket("rule.other", classified.rule.other);
2193
- pushBucket("theme", classified.theme);
2194
- pushBucket("component", classified.component);
2195
- pushBucket("ui-other", classified.uiOther);
2196
- pushBucket("advisory-other", classified.advisoryOther);
2197
- parts.push("## Classified openspec specification paths (role semantics)");
2198
- if (bucketLines.length > 0) {
2199
- parts.push(...bucketLines);
2200
- }
2201
- else {
2202
- parts.push("(no openspec specification paths discovered — greenfield)");
2144
+ if (capability.designEvidence.normativePaths.length > 0) {
2145
+ parts.push(`openspec normative candidates: ${capability.designEvidence.normativePaths.slice(0, 12).join(", ")}`);
2203
2146
  }
2204
- parts.push("Each consuming node MUST report in its output: applicable rules, the hit path/section/line number for every applied specification, and any conflicts or missing specifications. Missing or conflicting required specifications must fail closed rather than silently substituting nearby repository conventions.");
2205
2147
  parts.push(`A11y capability: ${capability.a11y.status}` +
2206
2148
  (capability.a11y.tools.length
2207
2149
  ? ` (${capability.a11y.tools.join(", ")})`
@@ -2567,11 +2509,6 @@ async function buildFrontendHybridDagFromTask(sources) {
2567
2509
  `- Behavior command source: ${behaviorVerifyEvidence.commandSource}`,
2568
2510
  ...behaviorVerifyEvidence.commandLabels.map((command) => ` - ${JSON.stringify(command)}`),
2569
2511
  ].join("\n");
2570
- const advisories = [];
2571
- if (frontendSourceMentionsMock(frontendSources) &&
2572
- frontendMockStrategyMustBeNotNeeded(frontendSources)) {
2573
- advisories.push("auto 模式已将 Mock 策略收窄为 not-needed:任务源提到接口/API/Mock 需求,但仓库无确认 Mock 能力或无确定性 Mock 验证命令。若项目规范要求 Mock,请声明 frontendMock.verifyCommands 或 policy:required 后重新生成 DAG。");
2574
- }
2575
2512
  const spec = {
2576
2513
  version: 3,
2577
2514
  title: `Frontend implementation DAG: ${taskConfig.title}`,
@@ -2586,7 +2523,6 @@ async function buildFrontendHybridDagFromTask(sources) {
2586
2523
  skillsByRole: FRONTEND_SKILLS_BY_ROLE,
2587
2524
  executorModels: sources.executorModelMatrix ?? DEFAULT_DAG_EXECUTOR_MODELS,
2588
2525
  verifyStrategy: resolveDagVerifyStrategy(taskConfig),
2589
- advisories: advisories.length > 0 ? advisories : undefined,
2590
2526
  tasks: [
2591
2527
  {
2592
2528
  id: "frontend-contract-pi",
@@ -2681,7 +2617,6 @@ async function buildFrontendHybridDagFromTask(sources) {
2681
2617
  "Read-only: do not modify repository files.",
2682
2618
  fixedVerificationContext,
2683
2619
  sourceContext,
2684
- mockContextBlock,
2685
2620
  ].join("\n\n"),
2686
2621
  },
2687
2622
  {
@@ -2716,7 +2651,6 @@ async function buildFrontendHybridDagFromTask(sources) {
2716
2651
  fixedVerificationContext,
2717
2652
  sourceContext,
2718
2653
  frontendContractSchemaBlock,
2719
- mockContextBlock,
2720
2654
  ].join("\n\n"),
2721
2655
  },
2722
2656
  {
@@ -2747,7 +2681,6 @@ async function buildFrontendHybridDagFromTask(sources) {
2747
2681
  "Read-only: do not modify repository files.",
2748
2682
  fixedVerificationContext,
2749
2683
  sourceContext,
2750
- mockContextBlock,
2751
2684
  ].join("\n\n"),
2752
2685
  },
2753
2686
  {
@@ -5850,9 +5783,7 @@ function applyDefaultReadOnlyRetryPolicy(spec) {
5850
5783
  if (isSafeReadOnlyPiRetryCandidate(task)) {
5851
5784
  task.retryPolicy = task.outputProtocol
5852
5785
  ? PROTOCOL_AWARE_PI_RETRY_POLICY
5853
- : task.role === "planner"
5854
- ? PLANNER_OUTPUT_LIMIT_RETRY_POLICY
5855
- : DEFAULT_READ_ONLY_PI_RETRY_POLICY;
5786
+ : DEFAULT_READ_ONLY_PI_RETRY_POLICY;
5856
5787
  continue;
5857
5788
  }
5858
5789
  if (isWriterTransportRetryCandidate(task)) {
@@ -743,7 +743,6 @@ function findDoctorFailureNode(state) {
743
743
  nodeId: state.pausedByNodeId,
744
744
  status: node?.status,
745
745
  rawFailureCategory: node?.failureCategory,
746
- skippedReason: node?.skippedReason,
747
746
  };
748
747
  }
749
748
  const errorEntry = Object.entries(state.nodes).find(([, node]) => node.status === "ERROR");
@@ -759,7 +758,6 @@ function findDoctorFailureNode(state) {
759
758
  nodeId: selected[0],
760
759
  status: selected[1].status,
761
760
  rawFailureCategory: selected[1].failureCategory,
762
- skippedReason: selected[1].skippedReason,
763
761
  };
764
762
  }
765
763
  async function readRunOwnedBackendTestClassification(runDir) {
@@ -788,7 +786,6 @@ async function resolveDoctorFailureRouting(input) {
788
786
  normalizedFailureCategory: "unknown",
789
787
  nodeId: input.nodeId,
790
788
  productLineFailureCategory: classifiedCategory,
791
- skippedReason: input.skippedReason,
792
789
  });
793
790
  }
794
791
  return routeDagFailure(input);
@@ -810,7 +807,6 @@ async function formatDagDoctorMarkdown(repoRoot, runId) {
810
807
  rawFailureCategory,
811
808
  normalizedFailureCategory: normalizedCategory,
812
809
  nodeId: failure.nodeId,
813
- skippedReason: failure.skippedReason,
814
810
  });
815
811
  const evidence = failure.nodeId
816
812
  ? path.join(located.runDir, failure.nodeId, "result.summary.md")
@@ -216,28 +216,18 @@ function buildAttemptPrompt(task, basePrompt, attemptNumber, previousFailureCate
216
216
  "</retry_instruction>",
217
217
  ].join("\n");
218
218
  }
219
- if (previousFailureCategory !== "output-too-large") {
219
+ if (task.outputMode !== "structured-required" ||
220
+ previousFailureCategory !== "output-too-large") {
220
221
  return basePrompt;
221
222
  }
222
- if (task.outputMode === "structured-required") {
223
- return [
224
- basePrompt,
225
- "",
226
- "<retry_instruction>",
227
- "Previous attempt exceeded the structured output size limit.",
228
- "Return only the compact structured artifact required by this node's output contract.",
229
- "Do not include explanatory prose, duplicated upstream context, long evidence excerpts, or additional markdown sections.",
230
- "If a fenced JSON object is required, output exactly one fenced json block and nothing else.",
231
- "</retry_instruction>",
232
- ].join("\n");
233
- }
234
223
  return [
235
224
  basePrompt,
236
225
  "",
237
226
  "<retry_instruction>",
238
- "Previous attempt exceeded the output size limit.",
239
- "Return a minimal plan: ordered steps, narrow writeSet boundaries, and verification commands.",
240
- "Drop verbatim upstream quotes, long evidence excerpts, and repeated context.",
227
+ "Previous attempt exceeded the structured output size limit.",
228
+ "Return only the compact structured artifact required by this node's output contract.",
229
+ "Do not include explanatory prose, duplicated upstream context, long evidence excerpts, or additional markdown sections.",
230
+ "If a fenced JSON object is required, output exactly one fenced json block and nothing else.",
241
231
  "</retry_instruction>",
242
232
  ].join("\n");
243
233
  }
@@ -441,7 +441,6 @@ export async function buildDagRunReportEntry(input) {
441
441
  normalizedFailureCategory,
442
442
  nodeId,
443
443
  executor: node.executor,
444
- skippedReason: node.skippedReason,
445
444
  });
446
445
  const followUp = node.status === "ERROR" || node.status === "SKIPPED"
447
446
  ? recommendFollowUpForFailureCategory(node.failureCategory)
@@ -506,7 +505,6 @@ export async function buildDagRunReportEntry(input) {
506
505
  normalizedFailureCategory: normalizeDagFailureCategory(pausedNode.failureCategory, pausedNode.status),
507
506
  failureCategory: pausedNode.failureCategory,
508
507
  nodeId: input.state.pausedByNodeId,
509
- skippedReason: pausedNode.skippedReason,
510
508
  }
511
509
  : firstActionableNode
512
510
  ? {
@@ -515,7 +513,6 @@ export async function buildDagRunReportEntry(input) {
515
513
  normalizeDagFailureCategory(firstActionableNode.failureCategory, firstActionableNode.status),
516
514
  failureCategory: firstActionableNode.failureCategory,
517
515
  nodeId: firstActionableNode.nodeId,
518
- skippedReason: input.state.nodes[firstActionableNode.nodeId]?.skippedReason,
519
516
  }
520
517
  : {
521
518
  status: input.state.status,
@@ -532,9 +529,6 @@ export async function buildDagRunReportEntry(input) {
532
529
  rawFailureCategory: runRecoverySource.failureCategory,
533
530
  normalizedFailureCategory: runRecoverySource.normalizedFailureCategory,
534
531
  nodeId: "nodeId" in runRecoverySource ? runRecoverySource.nodeId : undefined,
535
- skippedReason: "skippedReason" in runRecoverySource
536
- ? runRecoverySource.skippedReason
537
- : undefined,
538
532
  });
539
533
  const runFollowUp = input.state.status === "failed" ||
540
534
  input.state.status === "partial_failed"
@@ -118,17 +118,6 @@ export const STRUCTURED_REQUIRED_PI_RETRY_POLICY = {
118
118
  ...DEFAULT_READ_ONLY_PI_RETRY_POLICY,
119
119
  retryCategories: [...STRUCTURED_REQUIRED_DAG_RETRY_CATEGORIES],
120
120
  };
121
- /**
122
- * Planner read-only nodes (standard DAG contract-pi / plan-pi) may retry with
123
- * a compact-output instruction when the model produced an oversized assistant
124
- * response. Only `output-too-large` is added on top of the default set;
125
- * report/review and other read-only roles keep the default set so they do not
126
- * silently learn new output semantics.
127
- */
128
- export const PLANNER_OUTPUT_LIMIT_RETRY_POLICY = {
129
- ...DEFAULT_READ_ONLY_PI_RETRY_POLICY,
130
- retryCategories: [...DEFAULT_DAG_RETRY_CATEGORIES, STRUCTURED_OUTPUT_RETRY_CATEGORY],
131
- };
132
121
  /**
133
122
  * Default retry for reviewer / recovery nodes that declare outputProtocol.
134
123
  * Includes protocol-invalid so missing VERDICT lines are corrected in-node.
@@ -115,6 +115,22 @@ export async function buildDagRunId(spec, options) {
115
115
  const existing = await listExistingDagRunIds(options.cwd);
116
116
  return collisionSafeDagRunId(baseId, existing, now);
117
117
  }
118
+ /**
119
+ * Append a human-readable round label to a run title when the runId carries
120
+ * a `-rN` collision suffix (2026-08-16): the sidebar / Inspect list showed
121
+ * identical titles for reruns and only the id hinted at the round.
122
+ */
123
+ export function titleWithRoundLabel(title, runId) {
124
+ const match = /-r(\d{1,2})$/.exec(runId);
125
+ if (!match)
126
+ return title;
127
+ const round = Number.parseInt(match[1], 10);
128
+ if (!Number.isFinite(round) || round < 2)
129
+ return title;
130
+ // Never stack labels on reruns of reruns.
131
+ const base = title.replace(/\s*(第\s*\d+\s*轮)$/u, "");
132
+ return `${base}(第 ${round} 轮)`;
133
+ }
118
134
  export function createInitialRunState(spec, opts, ranks, runId = opts.runId ?? "dag") {
119
135
  const nodes = {};
120
136
  for (const task of spec.tasks) {
@@ -136,7 +152,7 @@ export function createInitialRunState(spec, opts, ranks, runId = opts.runId ?? "
136
152
  : "execute";
137
153
  const state = {
138
154
  version: 1,
139
- title: spec.title,
155
+ title: titleWithRoundLabel(spec.title, runId),
140
156
  runId,
141
157
  cwd: opts.cwd,
142
158
  startedAt: new Date().toISOString(),
@@ -263,7 +279,7 @@ export async function runDag(spec, opts) {
263
279
  }
264
280
  if (opts.dryRun || opts.initOnly) {
265
281
  return {
266
- title: spec.title,
282
+ title: state.title,
267
283
  runId: state.runId,
268
284
  status: state.status,
269
285
  ranks,
@@ -717,7 +733,7 @@ async function executeDagCheckpoint(input) {
717
733
  await writeBudgetLedgerArtifacts(runDir, state.budgetLedger);
718
734
  await persistState({ allowCompletedFactsWrite: true });
719
735
  return {
720
- title: spec.title,
736
+ title: state.title,
721
737
  runId: state.runId,
722
738
  status: state.status,
723
739
  ranks,
@@ -822,8 +822,6 @@ export const dagSpecSchema = z
822
822
  defaults: dagDefaultsSchema,
823
823
  skillsByRole: z.record(z.string(), z.array(z.string())).optional(),
824
824
  executorModels: dagExecutorModelsSchema.optional(),
825
- /** Generation-time advisories surfaced as machine-visible warnings. */
826
- advisories: z.array(z.string()).optional(),
827
825
  tasks: z.array(dagTaskSchema).min(1),
828
826
  })
829
827
  .superRefine((spec, ctx) => {
@@ -14,7 +14,6 @@ const GOVERNANCE_WARNING_TYPES = new Set([
14
14
  "shell-verdict-gate-multi-command-state",
15
15
  "shell-verdict-gate-handwritten-inline",
16
16
  "orphan-writer-source-binding",
17
- "advisory",
18
17
  ]);
19
18
  const ROOT_ARTIFACT_PATH_PROBES = [
20
19
  "artifacts/修改记录.md",
@@ -33,11 +32,10 @@ export function collectGovernanceWarnings(issues) {
33
32
  return issues.filter(isGovernanceWarning);
34
33
  }
35
34
  function collectBlockingIssues(issues, options = {}) {
36
- const nonAdvisory = issues.filter((issue) => issue.type !== "advisory");
37
35
  if (options.strictGovernance) {
38
- return nonAdvisory;
36
+ return [...issues];
39
37
  }
40
- return nonAdvisory.filter((issue) => !isGovernanceWarning(issue));
38
+ return issues.filter((issue) => !isGovernanceWarning(issue));
41
39
  }
42
40
  function normalizePath(value) {
43
41
  return value.replace(/\\/g, "/").replace(/^\.\//, "");
@@ -958,9 +956,6 @@ export function validateDagSpec(spec) {
958
956
  validateSameRankWriteSetConflicts(spec, ranks, issues);
959
957
  validateSameRankAgentAttributionRisks(spec, ranks, issues);
960
958
  validateWriterSourceBinding(spec, issues);
961
- for (const advisory of spec.advisories ?? []) {
962
- issues.push({ type: "advisory", message: advisory });
963
- }
964
959
  return issues;
965
960
  }
966
961
  function validateProjectGovernanceTaskConfig(task, spec, issues) {
@@ -1,62 +1,42 @@
1
1
  # 前端设计契约模板
2
2
 
3
- 本模板只承载**通用编写协议 + 三要素 + fail-closed 规则**,不包含任何具体业务组件名、项目专属路径或项目专属命令。设计契约在进入实现前由 `frontend-plan-pi` 落入结构化 implementation contract,`frontend-design-review-pi` 按本模板逐节审查;每一节都要给出「**要写什么** / **DAG 如何消费** / **缺失/冲突时 fail-closed**」三要素。
4
-
5
3
  ## 页面目标
6
4
 
7
- - **要写什么**:写清每个目标页面的职责、用户到达该页面的目的与成功结果,逐页列出,与需求.md 的用户目标/目标页面一一对应。
8
- - **DAG 如何消费**:`frontend-plan-pi` 据它确定 `targets.routes` 与每页的实现步骤,`frontend-design-review-pi` 据它核对范围与需求一致性。
9
- - **缺失/冲突时 fail-closed**:页面目标与需求不一致、缺页或目标含糊(无成功结果)时阻塞,不得自行补页或改目标。
5
+ TODO
10
6
 
11
7
  ## 信息结构
12
8
 
13
- - **要写什么**:写清每页展示的信息层级与数据来源(字段、分组、优先级、空/错误占位),接口数据与静态文案分开写。
14
- - **DAG 如何消费**:`frontend-plan-pi` 据它安排组件数据流与状态展示,`frontend-design-review-pi` 据它核对接口字段映射。
15
- - **缺失/冲突时 fail-closed**:信息层级缺失、数据来源不可追溯、与接口字段冲突时阻塞,不得臆造字段或层级。
9
+ TODO
16
10
 
17
11
  ## 组件拆分
18
12
 
19
- - **要写什么**:把页面拆成组件树,逐组件写职责、复用边界、props/状态归属与可替换点;复用现有设计系统组件时引用其来源路径。
20
- - **DAG 如何消费**:`frontend-plan-pi` 据它确定 `targets.files` 与实现顺序,`frontend-design-review-pi` 据它审查拆分是否越界/重复。
21
- - **缺失/冲突时 fail-closed**:组件边界不清、同一职责被拆到多处、或与设计规范冲突时阻塞,不得按邻近代码自行决定复用。
13
+ TODO
22
14
 
23
15
  ## 交互规则
24
16
 
25
- - **要写什么**:逐条写清每个交互的触发、预期行为、状态变化与可恢复性(对应需求.md 的交互要求)。
26
- - **DAG 如何消费**:`frontend-plan-pi` 据它填充 `interactions[]`(`trigger` + `expectedBehavior`),verify 用行为命令断言。
27
- - **缺失/冲突时 fail-closed**:交互无预期行为、与 UI 状态冲突、或不可自动化断言时阻塞。
17
+ TODO
28
18
 
29
19
  ## UI 状态
30
20
 
31
- - **要写什么**:逐状态(loading/empty/error/success/disabled)写清触发条件、展示内容与转移;不适用状态写 `N/A` + 非空理由。
32
- - **DAG 如何消费**:进入 implementation contract 的 `uiStates[]`,verify 逐状态断言,`frontend-review-pi` 据它核对实现是否遗漏状态。
33
- - **缺失/冲突时 fail-closed**:适用状态缺失预期、N/A 无理由、或状态展示与交互/接口状态冲突时阻塞。
21
+ TODO
34
22
 
35
23
  ## Mock / API 策略
36
24
 
37
- 策略枚举固定为 `native | browser-intercept | request-adapter | not-needed | blocked`,与 runtime Mock 语义一致:`blocked` 永远不通过(缺少/冲突接口契约、路径或依赖未授权、无法证明生产默认关闭、固定验证入口无法覆盖、或唯一方案是注释真实请求时必须选它);`not-needed` 必须有后端可用或任务不涉及远程接口的真实/无远程证据,且固定 behavior 入口能覆盖相应行为;显式 `policy=required` 不接受 `not-needed`。契约只接受当前 policy 允许的非 `blocked` 首行。
38
-
39
- - **接口文档或 schema — 要写什么**:引用接口文档/schema 路径与版本,作为 fixture 与字段映射的唯一来源。**DAG 如何消费**:`frontend-plan-pi` 据它冻结 endpoint/fixture 映射,prewrite gate 校验 fixture 可追溯。**缺失/冲突时 fail-closed**:无文档/schema、字段冲突时选 `blocked`,不得自行发明接口。
40
- - **策略**:只写枚举中的一个值:`native | browser-intercept | request-adapter | not-needed | blocked`。
41
- - **endpoint / fixture / UI 状态映射 — 要写什么**:逐 endpoint 列 method、path、fixture 路径与消费组件,并映射到 UI 状态。**DAG 如何消费**:写入 `mockApi.endpoints[]`(method/path/fixture/consumer),非 `not-needed` 策略要求每个 endpoint 都有 fixture 与 consumer。**缺失/冲突时 fail-closed**:非 `not-needed` 却缺 fixture/consumer、endpoint 与 UI 状态映射不一致时阻塞。
42
- - **显式启用方式与 production 默认关闭边界 — 要写什么**:写清真实请求为默认路径、Mock 仅通过显式开关(环境变量/构建开关)启用的具体边界。**DAG 如何消费**:`productionDefaultOff` 恒为 `true`,`frontend-review-pi` 检查是否注释真实请求或默认开启 Mock。**缺失/冲突时 fail-closed**:无法证明生产默认关闭、或 Mock 会进入生产入口时选 `blocked`。
43
- - **DAG 已固化的验证入口 — 要写什么**:引用 DAG 生成时已冻结的 static/behavior/Mock 命令 label,不发明新命令。**DAG 如何消费**:`verificationTarget.commandLabel` 必须逐字落在冻结命令集合内,否则 contract 物化 `invalid-output`。**缺失/冲突时 fail-closed**:策略需要 Mock 验证命令却没有冻结命令时生成期 fail-closed(`no authorized Mock verification commands`)。
44
- - **Real Integration Gap 与后端就绪后的复验 — 要写什么**:写清当前未联通的真实集成缺口,以及后端就绪后的复验路径。**DAG 如何消费**:进入 `evidenceGaps[]`,closeout 报告 `Real integration: pending`,复验任务 `<task-id>-real-api-integration-verify` 由操作者显式触发。**缺失/冲突时 fail-closed**:把 Mock 证据当真实联调证据时 review 拒绝。
25
+ - 接口文档或 schema:TODO
26
+ - 策略:`native | browser-intercept | request-adapter | not-needed | blocked`
27
+ - endpoint / fixture / UI 状态映射:TODO
28
+ - 显式启用方式与 production 默认关闭边界:TODO
29
+ - DAG 已固化的验证入口:TODO
30
+ - Real Integration Gap 与后端就绪后的复验:TODO
45
31
 
46
32
  ## 样式与设计系统映射
47
33
 
48
- - **要写什么**:写清页面用到的主题/Token、设计系统组件与样式来源(引用具体来源路径),自定义样式与复用样式的边界。
49
- - **DAG 如何消费**:`frontend-plan-pi` 据它安排样式实现,`frontend-design-review-pi` 据它核对是否与设计系统冲突。
50
- - **缺失/冲突时 fail-closed**:样式来源缺失、Token/组件冲突、或自定义样式会破坏设计系统时阻塞。
34
+ TODO
51
35
 
52
36
  ## 响应式范围
53
37
 
54
- - **要写什么**:写清支持的视口范围(desktop/mobile/tablet)与断点、每个断点下的布局差异。
55
- - **DAG 如何消费**:进入 contract 的 target runtime environment,`frontend-plan-pi` 据它决定响应式策略。
56
- - **缺失/冲突时 fail-closed**:视口范围与需求运行环境不一致、或断点未覆盖声明环境时阻塞。
38
+ TODO
57
39
 
58
40
  ## 风险与非目标
59
41
 
60
- - **要写什么**:写清设计上的已知风险、依赖缺口与明确不做/排除的设计范围。
61
- - **DAG 如何消费**:进入 `evidenceGaps[]` 与 Non-goals,`frontend-review-pi`/closeout 据它保留风险与后续项。
62
- - **缺失/冲突时 fail-closed**:范围与需求非目标冲突、或风险被隐藏时阻塞。
42
+ TODO
@@ -1,55 +1,35 @@
1
1
  # 前端任务执行约束模板
2
2
 
3
- 本模板只承载**通用探索协议 + 结果 schema + 缺失/冲突时 fail-closed 规则**,不包含任何具体业务组件名、项目专属路径或项目专属命令。项目事实由生成期能力探测(`openspec/schemas/**`、`openspec/project-specs/**`、`ai_workspace/**`)产出并经语义归类注入任务契约。
4
-
5
3
  ## 技术约束
6
4
 
7
- - **要探索什么**:项目 `package.json` 的直接依赖与 scripts、框架/路由/状态/数据获取/组件库/样式/测试/构建相关配置与引导文件,以及 `openspec/schemas/**` 与 `openspec/project-specs/rules/**` 中声明技术边界的规范。
8
- - **结果必须包含**:每一条适用技术规则的源路径、命中章节与行号;被排除依赖的明确理由;与既有技术栈一致的运行时要求(编译、构建、入口、环境变量)。
9
- - **缺失/冲突时如何 fail-closed**:无法找到技术规范来源时,显式标注 `source: unavailable` 并只采用任务源与现有代码的可验证事实;发现冲突规则时不臆造取舍,先记录冲突并返回 `request-revision`/blocked,而不是静默采用邻近代码惯例。
5
+ TODO
10
6
 
11
7
  ## 代码风格约束
12
8
 
13
- - **要探索什么**:`openspec/project-specs/templates/**`(代码模板)与 `openspec/project-specs/rules/**` 中组件/钩子/工具等代码风格规则,以及现有组件、主题/Token、stories 与测试作为较弱 repository fallback。
14
- - **结果必须包含**:命中模板/规则的源路径、章节与行号;命名、目录、导出、类型、样式组织等可执行约定;冲突字段逐条列出。
15
- - **缺失/冲突时如何 fail-closed**:没有模板/规则命中时明确说明并记录 `repository fallback` 证据;冲突未解决时不得进入实现,必须返回阻塞信息。
9
+ TODO
16
10
 
17
11
  ## 设计约束
18
12
 
19
- - **要探索什么**:`openspec/project-specs/ui/**`(主题/组件目录)与 `openspec/project-specs/rules/**` 中页面/组件拆分、交互、UI 状态与响应式相关规则。
20
- - **结果必须包含**:命中设计规范/组件目录的源路径、章节与行号;组件拆分与复用边界、样式与设计系统映射、UI 状态与响应式范围。
21
- - **缺失/冲突时如何 fail-closed**:设计规范缺失时明确记录来源为 `unavailable` 并保留为已知风险;组件/主题冲突时必须阻断,不能按邻近代码自行决定。
13
+ TODO
22
14
 
23
15
  ## 验证约束
24
16
 
25
- - **要探索什么**:项目 `package.json` scripts 中真实存在的 lint/typecheck/build/test 命令,以及任务源 `需求.md`/`执行约束.md` 中声明的验证命令。
26
- - **结果必须包含**:命令的 label、原始命令文本与来源(scripts/任务源);每条命令可自终止(启动→断言→退出)的说明。
27
- - **缺失/冲突时如何 fail-closed**:只在确定命令真实存在时写入冻结命令集;不发明 shell 命令。命令不可执行或漂移时 fail closed,不宣称验证通过。
17
+ TODO
28
18
 
29
19
  ## Mock 约束(数据型任务)
30
20
 
31
- 本段决定生成期 `allowedMockStrategies` 与冻结的 Mock 验证命令。只有当生成期**能证明任务确实包含 Mock 且有确定性验证命令**时,DAG 才会放行 `native` / `browser-intercept` / `request-adapter`;否则自动收敛为 `not-needed`,design review 若按项目规范改选 `native`,会在 prewrite 被 `mock-strategy-outside-allowed` 拦截、`implement` 被跳过。
32
-
33
- - **何时必须填**:项目规范(`openspec/project-specs/**`、`ai_workspace/**`、mock 规则或类似 DEC-* 决策)要求/建议 Mock;或需求涉及远端接口且后端未就绪。规范要求 Mock 时,优先 `task.json.frontendMock.policy: "required"`。
34
- - **命令要探索、不要硬编码**:到项目 `package.json` scripts 里找实际存在的 Mock 相关脚本(如 `mock`、`mock:*`、`dev:mock`,或名字含 mock 的脚本),结合既有 service root 的 handler/fixture/bootstrap 启动方式,确定一条**真实存在、确定、可自终止**(启动→断言→退出 0)的验证命令。常驻 dev server 必须包装成自终止脚本(start→assert→stop),否则 verify shell 会超时 fail-closed。
35
- - **写入 task.json**:把探索到的命令原样写进 `frontendMock.verifyCommands`(`label` 唯一、`command` 与项目脚本一致)。`label` 会进入冻结命令集,implementation contract 的 `verificationTarget.commandLabel` 必须逐字引用它。
36
- - **命令来源白名单**:只允许项目 `package.json` 已有脚本、Mock capability seed、或本段声明的 `verifyCommands`;plan/design 阶段不能发明 shell 命令。
37
- - **Mock/API/schema 规范路径**:到 `openspec/project-specs/**`(rules 中的 mock/api 规则、templates 中的接口模板)与 `ai_workspace/**` 检索;结果必须列出源路径、章节与行号。缺失或冲突时 fail closed,不自行发明接口契约。
38
- - **既有 Mock service root、handler/fixture/bootstrap**:检索项目现有 Mock 服务根目录、handler、fixture 与 bootstrap 启动方式;结果必须列出发现路径,未发现时明确记录 `unavailable`。
39
- - **既有 browser/e2e interception 或 request adapter/DI seam**:检索项目现有浏览器/e2e 拦截或请求适配层/依赖注入 seam;结果必须列出发现路径或明确记录缺失。
40
- - **production 禁用边界**:结果必须说明真实请求为默认路径、Mock 仅通过显式测试/开发开关启用的具体边界;无法证明生产默认关闭时返回 `blocked`。
41
- - **真实请求默认路径与 Mock 显式启用方式**:结果必须写出真实请求默认路径与 Mock 显式启用方式(如环境变量/构建开关),不得通过注释真实请求或在生产组件内硬编码假数据实现。
42
- - **`task.json.frontendMock.policy`**:`auto | required | disabled`(规范强制 Mock 用 `required`)
43
- - **被拦截时怎么修**:`mock-strategy-outside-allowed` / `no authorized Mock verification commands` 是**生成期契约问题,不是 plan 问题**——补 `frontendMock.verifyCommands`(或 `policy: "required"` + 命令)后**重新生成 DAG** 再跑,plan-revision 无法修复它。
21
+ - Mock/API/schema 规范路径:TODO
22
+ - 既有 Mock service root、handler/fixture/bootstrap:TODO
23
+ - 既有 browser/e2e interception request adapter/DI seam:TODO
24
+ - 启动、健康检查和专项验证命令:TODO
25
+ - production 禁用边界:TODO
26
+ - 真实请求默认路径与 Mock 显式启用方式:TODO
27
+ - `task.json.frontendMock.policy`:`auto | required | disabled`
44
28
 
45
29
  ## allowedPaths
46
30
 
47
- - **要探索什么**:任务源 `需求.md`/`执行约束.md`/`task.json` 中声明的写入边界,以及本次交付实际涉及的文件/目录。
48
- - **结果必须包含**:与 `task.json.allowedPaths` 一致且窄化的路径列表;任何越界路径都必须在 plan 中记为阻断性 scope conflict。
49
- - **缺失/冲突时如何 fail-closed**:缺失允许路径或发现计划目标在允许路径之外时,不得擅自扩大边界,返回 blocked。
31
+ TODO
50
32
 
51
33
  ## forbiddenPaths
52
34
 
53
- - **要探索什么**:任务源与 `task.json` 中声明的禁止写入路径(运行时产物、依赖目录、构建输出、私有路径等)。
54
- - **结果必须包含**:禁止写入的路径清单,以及实现/验证步骤不触碰这些路径的确认。
55
- - **缺失/冲突时如何 fail-closed**:任何写入落入禁止路径立即阻断;禁止路径语义不变,不得新增读取权限字段或扩大写入边界。
35
+ TODO