@tea-agent/loop-agent 0.39.0-beta.2 → 0.39.0-beta.4

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 (202) hide show
  1. package/AGENTS.md +4 -1
  2. package/CHANGELOG.md +187 -92
  3. package/README.md +9 -5
  4. package/bin/loop-agent.js +7 -3
  5. package/dist/application/task-lifecycle/recommendations.js +13 -2
  6. package/dist/build-stamp.json +3 -3
  7. package/dist/cli/command-definitions.js +16 -5
  8. package/dist/cli/program.js +16 -2
  9. package/dist/cli/update/notifier.js +2 -2
  10. package/dist/cli.js +2 -1
  11. package/dist/commands/dag-rerun.js +55 -1
  12. package/dist/commands/init-model-catalog.js +464 -0
  13. package/dist/commands/init-upgrade.js +265 -97
  14. package/dist/commands/init.js +429 -28
  15. package/dist/commands/inspect-next.js +8 -0
  16. package/dist/executors/dag-pi-executor.js +1691 -29
  17. package/dist/executors/pi-extension-resolver.js +233 -0
  18. package/dist/executors/pi-playwright-cli-tool.js +51 -12
  19. package/dist/executors/pi-sdk-executor.js +180 -33
  20. package/dist/executors/shell-executor.js +1152 -171
  21. package/dist/executors/shell-write-guard.js +14 -0
  22. package/dist/shared/dag-prompt-override.js +28 -0
  23. package/dist/shared/openspec-spec.js +70 -4
  24. package/dist/shared/operator/capabilities.js +32 -0
  25. package/dist/shared/playwright-cli-command-policy.js +15 -0
  26. package/dist/shared/update/console-notifier.js +100 -0
  27. package/dist/{cli → shared}/update/npm-client.js +41 -8
  28. package/dist/{cli → shared}/update/state.js +41 -13
  29. package/dist/task/config-types.js +19 -5
  30. package/dist/task/contract/project.js +3 -0
  31. package/dist/task/contract/schema.js +2 -1
  32. package/dist/task/frontend-project-capability.js +60 -17
  33. package/dist/task/source-prepare/parse-intent.js +24 -4
  34. package/dist/task/source-prepare/prepare.js +36 -0
  35. package/dist/worker/cli.js +31 -1
  36. package/dist/worker/console/chat/pi-console-config.js +81 -10
  37. package/dist/worker/console/chat/routes.js +1 -0
  38. package/dist/worker/console/dag-execution-receipt.js +33 -0
  39. package/dist/worker/console/frontend-human-decision-adapter.js +19 -0
  40. package/dist/worker/console/frontend-split-operation-adapter.js +20 -0
  41. package/dist/worker/console/index.js +3 -0
  42. package/dist/worker/console/operation-store.js +226 -6
  43. package/dist/worker/console/operator-actions.js +263 -0
  44. package/dist/worker/console/operator-user-error.js +2 -2
  45. package/dist/worker/console/routes.js +43 -2
  46. package/dist/worker/console/security.js +63 -9
  47. package/dist/worker/console/server.js +151 -6
  48. package/dist/worker/console/static/assets/{abnfDiagram-N423BO3Z-C4kVYweA.js → abnfDiagram-N423BO3Z-zsNyvGaH.js} +1 -1
  49. package/dist/worker/console/static/assets/{arc-Bj2M8iO5.js → arc-BDjZ5kE1.js} +1 -1
  50. package/dist/worker/console/static/assets/{architectureDiagram-T3A2C74G-CLlXe4-6.js → architectureDiagram-T3A2C74G-BkgaujnN.js} +1 -1
  51. package/dist/worker/console/static/assets/{blockDiagram-VBNYF7ZC-psEp5xH0.js → blockDiagram-VBNYF7ZC-CqxOJi0j.js} +1 -1
  52. package/dist/worker/console/static/assets/{c4Diagram-5PPSVZJV-DeKcOCGJ.js → c4Diagram-5PPSVZJV-C2JHLdRi.js} +1 -1
  53. package/dist/worker/console/static/assets/channel-BuqaGKAT.js +1 -0
  54. package/dist/worker/console/static/assets/{chunk-2GRJ4B5K-K0C744p1.js → chunk-2GRJ4B5K-DF_YSvOS.js} +1 -1
  55. package/dist/worker/console/static/assets/{chunk-2Q5K7J3B-xbNw4J9-.js → chunk-2Q5K7J3B-D3Ort5Vo.js} +1 -1
  56. package/dist/worker/console/static/assets/{chunk-5RXB4S5H-DjLaSDUO.js → chunk-5RXB4S5H-XfAfZeUC.js} +1 -1
  57. package/dist/worker/console/static/assets/{chunk-5VM5RSS4-CtiPlKel.js → chunk-5VM5RSS4-SLBibhf5.js} +1 -1
  58. package/dist/worker/console/static/assets/{chunk-6Q2QTUOP-DZtolir7.js → chunk-6Q2QTUOP-Dz29Ye_f.js} +1 -1
  59. package/dist/worker/console/static/assets/{chunk-GF5L2VYU-CPcdNeew.js → chunk-GF5L2VYU-DB2G4H8V.js} +1 -1
  60. package/dist/worker/console/static/assets/{chunk-JWPE2WC7-NgHc6V37.js → chunk-JWPE2WC7-CIoGkxdu.js} +1 -1
  61. package/dist/worker/console/static/assets/{chunk-KBJHAD2P-DQ_T-hg8.js → chunk-KBJHAD2P-CG78FGkn.js} +1 -1
  62. package/dist/worker/console/static/assets/{chunk-RYQCIY6F-DgLsxcVP.js → chunk-RYQCIY6F-ZDHAvXPd.js} +1 -1
  63. package/dist/worker/console/static/assets/{chunk-XXDRQBXY-UrVoM9z1.js → chunk-XXDRQBXY-BKkkC4VA.js} +1 -1
  64. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-DNdSHjH9.js +1 -0
  65. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-DNdSHjH9.js +1 -0
  66. package/dist/worker/console/static/assets/{cose-bilkent-JH36ORCC-CLkYvTb3.js → cose-bilkent-JH36ORCC-SposUZsO.js} +1 -1
  67. package/dist/worker/console/static/assets/{cynefin-VYW2F7L2-BwT_xKrE.js → cynefin-VYW2F7L2-BSCtQpD7.js} +1 -1
  68. package/dist/worker/console/static/assets/{cynefinDiagram-MW4NZA55-Bi9tuzif.js → cynefinDiagram-MW4NZA55-DgFHrw9l.js} +1 -1
  69. package/dist/worker/console/static/assets/{dagre-VZM6K2ZE-BCIqkWBV.js → dagre-VZM6K2ZE-BVBasYbj.js} +1 -1
  70. package/dist/worker/console/static/assets/{diagram-7IWD3JNH-Dip6l9_Z.js → diagram-7IWD3JNH-6r_4rN_c.js} +1 -1
  71. package/dist/worker/console/static/assets/{diagram-B4RE2ZJO-B-xkh_wK.js → diagram-B4RE2ZJO-JmuaB41P.js} +1 -1
  72. package/dist/worker/console/static/assets/{diagram-LBJQPF4R-DZO0kTt_.js → diagram-LBJQPF4R-xXvAY3sk.js} +1 -1
  73. package/dist/worker/console/static/assets/{diagram-Q27KOJAE-D6ZplNbZ.js → diagram-Q27KOJAE-D2B3lY2o.js} +1 -1
  74. package/dist/worker/console/static/assets/{diagram-UB23O5K3-BPekbhoS.js → diagram-UB23O5K3-tK54tMEX.js} +1 -1
  75. package/dist/worker/console/static/assets/{ebnfDiagram-BXEA7PRR-BtmaJpK5.js → ebnfDiagram-BXEA7PRR-CNtELzV-.js} +1 -1
  76. package/dist/worker/console/static/assets/{erDiagram-JOGREHBK-6DS0Xc44.js → erDiagram-JOGREHBK-C1xKMKcW.js} +1 -1
  77. package/dist/worker/console/static/assets/{flowDiagram-UKHOOZJN-CihnROSm.js → flowDiagram-UKHOOZJN-CZ7dlPMz.js} +1 -1
  78. package/dist/worker/console/static/assets/{ganttDiagram-PKOTCBZU-CZC4zOE2.js → ganttDiagram-PKOTCBZU-BqGb42Xe.js} +1 -1
  79. package/dist/worker/console/static/assets/{gitGraphDiagram-DS77QQ5N-Dq1fhzGN.js → gitGraphDiagram-DS77QQ5N-D_BlXEtt.js} +1 -1
  80. package/dist/worker/console/static/assets/{index-DTZOKgAn.js → index-D5zGX6fG.js} +68 -68
  81. package/dist/worker/console/static/assets/index-DBbhESQ_.css +1 -0
  82. package/dist/worker/console/static/assets/{infoDiagram-6WML65LV-DlSl4BGx.js → infoDiagram-6WML65LV-Vce2vmIe.js} +1 -1
  83. package/dist/worker/console/static/assets/{ishikawaDiagram-WSZJBQD7-snnzFl_U.js → ishikawaDiagram-WSZJBQD7-DDwVBAvI.js} +1 -1
  84. package/dist/worker/console/static/assets/{journeyDiagram-NVQOT4AX-BDE7qpMx.js → journeyDiagram-NVQOT4AX-y_Ew-wv4.js} +1 -1
  85. package/dist/worker/console/static/assets/{kanban-definition-27J2QSJJ-CD2Ci04G.js → kanban-definition-27J2QSJJ-CjXJFp1K.js} +1 -1
  86. package/dist/worker/console/static/assets/{linear-DdnapKIH.js → linear-1itMst1Q.js} +1 -1
  87. package/dist/worker/console/static/assets/{mermaid.core-BVjAT9b8.js → mermaid.core-C-D3ZRUa.js} +5 -5
  88. package/dist/worker/console/static/assets/{mindmap-definition-FAOFIHXS-D0yJk-zA.js → mindmap-definition-FAOFIHXS-BiYywmZc.js} +1 -1
  89. package/dist/worker/console/static/assets/{pegDiagram-VL7TDLO6-BVo9tFP-.js → pegDiagram-VL7TDLO6-l2fydNWA.js} +1 -1
  90. package/dist/worker/console/static/assets/{pieDiagram-7S7Q4E2Y-DnOV-mZ_.js → pieDiagram-7S7Q4E2Y-Dz0hNbYW.js} +1 -1
  91. package/dist/worker/console/static/assets/{quadrantDiagram-CIZ2JOQS-CzUJo58i.js → quadrantDiagram-CIZ2JOQS-BujI_mLZ.js} +1 -1
  92. package/dist/worker/console/static/assets/{railroadDiagram-AXF67PYL-Bvikrolh.js → railroadDiagram-AXF67PYL-pWe84beb.js} +1 -1
  93. package/dist/worker/console/static/assets/{requirementDiagram-LRYGKXZP-DtaSVCap.js → requirementDiagram-LRYGKXZP-ByYu2frg.js} +1 -1
  94. package/dist/worker/console/static/assets/{sankeyDiagram-W5VNT64P-C2c9A0wW.js → sankeyDiagram-W5VNT64P-CoPnilFP.js} +1 -1
  95. package/dist/worker/console/static/assets/{sequenceDiagram-SI44F4Z6-DmXJcU7r.js → sequenceDiagram-SI44F4Z6-1Md2LnS0.js} +1 -1
  96. package/dist/worker/console/static/assets/{sizeCapture-X5ZJPWSS-B4GUFW92.js → sizeCapture-X5ZJPWSS-DqqqAoSH.js} +1 -1
  97. package/dist/worker/console/static/assets/{stateDiagram-OKZ733FA-Dsad2MXf.js → stateDiagram-OKZ733FA-C4sGkf--.js} +1 -1
  98. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8rmop_9.js +1 -0
  99. package/dist/worker/console/static/assets/{swimlanes-SLNWSIFB-DGq48Fbi.js → swimlanes-SLNWSIFB-ByxJrUzW.js} +2 -2
  100. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-B0ky-kBM.js +8 -0
  101. package/dist/worker/console/static/assets/{timeline-definition-Z64GVDOM-CSSi6OFf.js → timeline-definition-Z64GVDOM-D8MLgM0O.js} +1 -1
  102. package/dist/worker/console/static/assets/{vennDiagram-T6HMQDX7-BqyzPrWv.js → vennDiagram-T6HMQDX7-DnBis-wZ.js} +1 -1
  103. package/dist/worker/console/static/assets/{wardleyDiagram-T6FBY63Y-DlznVb6S.js → wardleyDiagram-T6FBY63Y-uuyYT7UO.js} +1 -1
  104. package/dist/worker/console/static/assets/{xychartDiagram-ELKLHX3M-CfBcgJ_K.js → xychartDiagram-ELKLHX3M-BjgabhwK.js} +1 -1
  105. package/dist/worker/console/static/index.html +2 -2
  106. package/dist/worker/console/static-src/active-run-badge.js +17 -0
  107. package/dist/worker/console/static-src/operator-chat/mutation-gate.js +1 -1
  108. package/dist/worker/loop-agent/loop-agent-client.js +7 -3
  109. package/dist/worker/materialize/frontend-split-task-materializer.js +72 -0
  110. package/dist/worker/observe/node-input.js +74 -3
  111. package/dist/worker/observe/node-process.js +377 -0
  112. package/dist/worker/observe/routes.js +36 -3
  113. package/dist/worker/observe/shell-handler-keys.js +34 -0
  114. package/dist/worker/observe/static/api.js +66 -0
  115. package/dist/worker/observe/static/constants.js +8 -1
  116. package/dist/worker/observe/static/dag-history-labels.js +4 -0
  117. package/dist/worker/observe/static/format.js +23 -0
  118. package/dist/worker/observe/static/markdown-render.js +20 -1
  119. package/dist/worker/observe/static/operator-chrome.js +4 -0
  120. package/dist/worker/observe/static/state.js +13 -2
  121. package/dist/worker/observe/static/styles.css +73 -0
  122. package/dist/worker/observe/static/views/dag-inspector.js +612 -8
  123. package/dist/worker/observe/static/views/dag.js +8 -1
  124. package/dist/worker/observe/static/views/failures.js +5 -2
  125. package/dist/worker/observe/static/views/session-timeline.js +78 -9
  126. package/dist/workflows/dag/backend-test-case-coverage-analysis.js +20 -2
  127. package/dist/workflows/dag/contract-output-registry.js +15 -0
  128. package/dist/workflows/dag/contract-validator-registrations.js +1 -2
  129. package/dist/workflows/dag/failure-category.js +12 -0
  130. package/dist/workflows/dag/frontend-closeout.js +221 -0
  131. package/dist/workflows/dag/frontend-design-policy.js +366 -0
  132. package/dist/workflows/dag/frontend-human-decision.js +182 -0
  133. package/dist/workflows/dag/frontend-implementation-contract.js +378 -160
  134. package/dist/workflows/dag/frontend-prewrite-gate.js +343 -391
  135. package/dist/workflows/dag/frontend-provider-capability-matrix.js +159 -0
  136. package/dist/workflows/dag/frontend-recovery-capsule.js +455 -0
  137. package/dist/workflows/dag/frontend-recovery-controller.js +201 -0
  138. package/dist/workflows/dag/frontend-recovery-lineage.js +178 -0
  139. package/dist/workflows/dag/frontend-recovery-plan.js +17 -10
  140. package/dist/workflows/dag/frontend-recovery-run.js +33 -20
  141. package/dist/workflows/dag/frontend-repair.js +7 -424
  142. package/dist/workflows/dag/frontend-review-context.js +195 -8
  143. package/dist/workflows/dag/frontend-risk.js +15 -2
  144. package/dist/workflows/dag/frontend-shadow-dual-write.js +665 -0
  145. package/dist/workflows/dag/frontend-shape-capsule-store.js +191 -0
  146. package/dist/workflows/dag/frontend-shape-facts.js +409 -0
  147. package/dist/workflows/dag/frontend-shape.js +425 -0
  148. package/dist/workflows/dag/frontend-split-application-service.js +203 -0
  149. package/dist/workflows/dag/frontend-split-orchestrator.js +899 -0
  150. package/dist/workflows/dag/frontend-test-case-checklist.js +26 -2
  151. package/dist/workflows/dag/frontend-test-case-manifest.js +5 -0
  152. package/dist/workflows/dag/frontend-test-case-quality.js +5 -13
  153. package/dist/workflows/dag/frontend-test-environment-probe.js +227 -0
  154. package/dist/workflows/dag/frontend-test-markdown.js +61 -0
  155. package/dist/workflows/dag/frontend-test-result-contract.js +160 -28
  156. package/dist/workflows/dag/frontend-test-standard-scenarios.js +68 -0
  157. package/dist/workflows/dag/frontend-typed-event-store.js +439 -0
  158. package/dist/workflows/dag/frontend-typed-event-transaction.js +180 -0
  159. package/dist/workflows/dag/frontend-verification-trace.js +52 -17
  160. package/dist/workflows/dag/frontend-worktree-diff.js +97 -18
  161. package/dist/workflows/dag/frontend-writer-admission.js +260 -0
  162. package/dist/workflows/dag/frontend-writer-status.js +244 -0
  163. package/dist/workflows/dag/init-hybrid.js +572 -500
  164. package/dist/workflows/dag/node-execution.js +307 -70
  165. package/dist/workflows/dag/prompt.js +10 -18
  166. package/dist/workflows/dag/rerun-plan.js +144 -5
  167. package/dist/workflows/dag/rerun-run.js +58 -6
  168. package/dist/workflows/dag/retry-policy.js +228 -4
  169. package/dist/workflows/dag/runner.js +314 -119
  170. package/dist/workflows/dag/scheduler.js +114 -16
  171. package/dist/workflows/dag/structured-output-repair.js +712 -0
  172. package/dist/workflows/dag/types.js +235 -23
  173. package/dist/workflows/dag/upstream-artifacts.js +0 -4
  174. package/dist/workflows/dag/validate.js +60 -10
  175. package/docs/init-surface.manifest.json +5 -0
  176. package/docs/operations/README.md +1 -1
  177. package/docs/templates/agent-dag.schema.json +44 -0
  178. package/docs/templates/frontend-test-dag.json +8 -10
  179. package/docs/templates/frontend-test-dag.retrieve-context.prompt.md +1 -1
  180. package/docs/templates/init-managed-agents.md +13 -3
  181. package/harness.json +3 -3
  182. package/package.json +5 -1
  183. package/scripts/next-info.mjs +356 -0
  184. package/scripts/next-publish-gate.mjs +238 -0
  185. package/scripts/release-source-binding.mjs +251 -0
  186. package/skills/codebase-scout/SKILL.md +1 -1
  187. package/skills/fe-test-ui-scout/SKILL.md +65 -0
  188. package/skills/fe-test-ui-scout/references/ledger-schema.md +62 -0
  189. package/skills/fe-test-ui-scout/references/recon-protocol.md +54 -0
  190. package/skills/frontend-implementation/SKILL.md +4 -4
  191. package/skills/frontend-implementation/references/design-spec.md +11 -7
  192. package/skills/frontend-implementation/references/node-contracts.md +20 -11
  193. package/skills/loop-agent/references/command-reference.md +11 -4
  194. package/skills/loop-agent/references/hybrid-dag.md +1 -1
  195. package/skills/playwright-cli/SKILL.md +22 -0
  196. package/skills/playwright-cli-case-generator/SKILL.md +20 -1
  197. package/dist/worker/console/static/assets/channel-CPF4N7pf.js +0 -1
  198. package/dist/worker/console/static/assets/classDiagram-JCYQIIEL-C2DwA_Y1.js +0 -1
  199. package/dist/worker/console/static/assets/classDiagram-v2-OCEON4UE-C2DwA_Y1.js +0 -1
  200. package/dist/worker/console/static/assets/index-Ya5FE7cD.css +0 -1
  201. package/dist/worker/console/static/assets/stateDiagram-v2-UEYNNEHI-B8FB_cNk.js +0 -1
  202. package/dist/worker/console/static/assets/swimlanesDiagram-ULZ7WXOC-CrZYaWfx.js +0 -8
@@ -1,8 +1,8 @@
1
1
  import { z } from "zod";
2
- import { isOpenspecSpecFilePath } from "../../shared/openspec-spec.js";
2
+ import { DEFAULT_FRONTEND_SPEC_ROOTS, isFrontendSpecFilePath, isValidFrontendSpecRoot, OPENSPEC_SPEC_EXT_RE, } from "../../shared/openspec-spec.js";
3
3
  import { campaignBudgetSchema, } from "../../application/evaluation/budget.js";
4
4
  import { assertDagPromptSourceRule } from "./prompt-source.js";
5
- import { dagRetryPolicySchema } from "./retry-policy.js";
5
+ import { dagRetryPolicySchema, TARGET_TEMPLATE_TRANSIENT_RETRY_PROFILE, } from "./retry-policy.js";
6
6
  import { dagLivenessPolicySchema, } from "./liveness-policy.js";
7
7
  import { dagOutputProtocolSchema } from "./output-protocol.js";
8
8
  export const dagComplexitySchema = z.enum(["HIGH", "MED", "LOW"]);
@@ -10,6 +10,18 @@ export const dagNodeExecutorSchema = z.enum(["pi", "shell", "static"]);
10
10
  export const CURSOR_DAG_EXECUTOR_REMOVED_ERROR = 'executor "cursor" is no longer supported; regenerate the DAG with Pi-only writers (implement-pi / repair-pi)';
11
11
  export const CURSOR_EXECUTOR_MODELS_REMOVED_ERROR = "executorModels.cursor is no longer supported; use executorModels.pi only";
12
12
  export const dagToolProfileSchema = z.enum(["read-only", "write"]);
13
+ /**
14
+ * Frozen Pi extension short ids for backend implementation DAGs (plan
15
+ * 2026-08-21 D2): the DAG carries only these ids; the executor resolves them
16
+ * against the Pi settings package inventory at run time. Adding a package
17
+ * requires changing this enum and the plan.
18
+ */
19
+ export const dagPiExtensionIdSchema = z.enum(["pi-codegraph", "pi-lens"]);
20
+ /** Node-level explicit Pi extension allowlist (omitted = all off, R8.1). */
21
+ export const dagPiExtensionsSchema = z
22
+ .array(dagPiExtensionIdSchema)
23
+ .min(1)
24
+ .transform((ids) => Array.from(new Set(ids)));
13
25
  /** Static command capabilities only; tasks cannot inject executables or shell prefixes. */
14
26
  export const dagCommandCapabilitySchema = z.enum(["playwright-cli"]);
15
27
  export const dagCommandPolicySchema = z.discriminatedUnion("mode", [
@@ -173,7 +185,16 @@ const dagFrontendNodeIdSchema = z
173
185
  .regex(/^[a-z][a-z0-9-]*$/, "frontend node id must be kebab-case");
174
186
  const openspecCandidatePathSchema = z
175
187
  .string()
176
- .refine((candidate) => !candidate.includes("\\") && isOpenspecSpecFilePath(candidate), "openspec candidate must be a repo-relative supported file under openspec/schemas/, openspec/project-specs/, or ai_workspace/");
188
+ .refine((candidate) => !candidate.includes("\\") &&
189
+ !candidate.startsWith("/") &&
190
+ !candidate.split("/").includes("..") &&
191
+ OPENSPEC_SPEC_EXT_RE.test(candidate), "openspec candidate must be a repo-relative supported specification file");
192
+ const openspecCandidateSnapshotSchema = z
193
+ .object({
194
+ path: openspecCandidatePathSchema,
195
+ sha256: z.string().regex(/^[a-f0-9]{64}$/),
196
+ })
197
+ .strict();
177
198
  export const dagFrontendPrewriteGateSchema = z.object({
178
199
  schemaVersion: z.literal(1),
179
200
  planFromNodeId: dagFrontendNodeIdSchema,
@@ -192,7 +213,24 @@ export const dagFrontendPrewriteGateSchema = z.object({
192
213
  outputDir: z.string().regex(/^[a-z0-9][a-z0-9._-]*$/),
193
214
  requireSourceFreshness: z.literal(true),
194
215
  implementationWriteSet: z.array(z.string().min(1)).min(1).optional(),
216
+ /**
217
+ * Generation-frozen roots that bound custom normative directories. Omitted
218
+ * legacy DAGs retain the canonical OpenSpec + ai_workspace roots.
219
+ */
220
+ openspecSpecRoots: z
221
+ .array(z.string().refine((root) => isValidFrontendSpecRoot(root), "openspec spec root must be a normalized repo-relative directory"))
222
+ .optional(),
195
223
  openspecCandidatePaths: z.array(openspecCandidatePathSchema).default([]),
224
+ /** Read-only selector output; omitted for scan-strict and legacy DAGs. */
225
+ openspecSelectionNodeId: dagFrontendNodeIdSchema.optional(),
226
+ /** Contract-declared and source-explicit paths that cannot be downgraded. */
227
+ openspecMandatoryPaths: z.array(openspecCandidatePathSchema).optional(),
228
+ /**
229
+ * Content fingerprints frozen with the generation-time OpenSpec scope. New
230
+ * DAGs use these to reject a plan whose cited specification changed after it
231
+ * was read; legacy DAGs without the field retain existence-only drift checks.
232
+ */
233
+ openspecCandidateSnapshots: z.array(openspecCandidateSnapshotSchema).optional(),
196
234
  /**
197
235
  * Generation-frozen component/theme specification bucket. Derived from
198
236
  * `designEvidence.classified.component ∪ theme ∪ rule.components` at DAG
@@ -237,14 +275,28 @@ export const dagFrontendPrewriteGateSchema = z.object({
237
275
  scanStrict: z.array(openspecCandidatePathSchema).default([]),
238
276
  })
239
277
  .optional(),
278
+ }).superRefine((gate, context) => {
279
+ const roots = gate.openspecSpecRoots ?? DEFAULT_FRONTEND_SPEC_ROOTS;
280
+ const candidateGroups = [
281
+ ["openspecCandidatePaths", gate.openspecCandidatePaths],
282
+ ["openspecMandatoryPaths", gate.openspecMandatoryPaths],
283
+ ["componentSpecCandidatePaths", gate.componentSpecCandidatePaths],
284
+ ["openspecCandidateSources.declared", gate.openspecCandidateSources?.declared],
285
+ ["openspecCandidateSources.taskSourceCited", gate.openspecCandidateSources?.taskSourceCited],
286
+ ["openspecCandidateSources.scanStrict", gate.openspecCandidateSources?.scanStrict],
287
+ ];
288
+ for (const [field, paths] of candidateGroups) {
289
+ for (const [index, candidate] of (paths ?? []).entries()) {
290
+ if (!isFrontendSpecFilePath(candidate, roots)) {
291
+ context.addIssue({
292
+ code: z.ZodIssueCode.custom,
293
+ path: [...field.split("."), index],
294
+ message: "openspec candidate must be a supported file under the generation-frozen openspec spec roots",
295
+ });
296
+ }
297
+ }
298
+ }
240
299
  });
241
- export const dagFrontendLintBaselineSchema = z
242
- .object({
243
- schemaVersion: z.literal(1),
244
- lintCommands: z.array(z.string().min(1)).min(1),
245
- lintEvidence: dagShellVerifyEvidenceSchema,
246
- })
247
- .strict();
248
300
  export const dagFrontendVerificationBundleSchema = z
249
301
  .object({
250
302
  schemaVersion: z.literal(1),
@@ -258,7 +310,9 @@ export const dagFrontendVerificationBundleSchema = z
258
310
  behaviorEvidence: dagShellVerifyEvidenceSchema,
259
311
  lintBaselineNodeId: dagFrontendNodeIdSchema.optional(),
260
312
  writerNodeIds: z.array(dagFrontendNodeIdSchema).optional(),
261
- mode: z.enum(["initial", "repair"]),
313
+ /** M8: verify-shell only ever runs in initial mode; a failure is terminal
314
+ * (routes to recovery) and never selects a same-run repair branch. */
315
+ mode: z.literal("initial"),
262
316
  })
263
317
  .superRefine((bundle, context) => {
264
318
  const groups = [
@@ -333,6 +387,135 @@ export const dagFrontendReviewContextSchema = z.object({
333
387
  schemaVersion: z.literal(1),
334
388
  requireBaseline: z.literal(true),
335
389
  });
390
+ /**
391
+ * Deterministic frontend contract-import shell (micro topology). Re-validates
392
+ * the generation-frozen managed Contract's source/binding/schema freshness and
393
+ * emits a digest. It never invokes a model and never rewrites requirements —
394
+ * micro requires a pre-validated managed Contract, not natural-language intake.
395
+ */
396
+ export const dagFrontendContractImportSchema = z.object({
397
+ schemaVersion: z.literal(1),
398
+ artifactName: z.string().regex(/^[a-z0-9][a-z0-9._-]*\.json$/),
399
+ outputDir: z.string().regex(/^[a-z0-9][a-z0-9._-]*$/),
400
+ requireSourceFreshness: z.literal(true),
401
+ });
402
+ /**
403
+ * Deterministic frontend design-policy shell (M8). Materializes the canonical
404
+ * implementation contract from the plan node and evaluates the design policy
405
+ * (Mock strategy, source freshness, component spec candidates, writeSet
406
+ * patterns, allowed dependencies). Runs BEFORE frontend-design-review-pi, so it
407
+ * never consumes a design verdict; the verdict is enforced by the writer
408
+ * admission shell instead. Replaces the prewrite-gate authorization path.
409
+ */
410
+ export const dagFrontendDesignPolicySchema = z
411
+ .object({
412
+ schemaVersion: z.literal(1),
413
+ planFromNodeId: dagFrontendNodeIdSchema,
414
+ requiredRequirementIds: z
415
+ .array(z.string().regex(/^(?:REQ|BR|AC)-[A-Z0-9]+(?:-[A-Z0-9]+)*$/))
416
+ .default([]),
417
+ allowedMockStrategies: z
418
+ .array(z.enum(["native", "browser-intercept", "request-adapter", "not-needed"]))
419
+ .min(1),
420
+ /** Frozen Mock verification command labels the DAG will actually execute. */
421
+ mockCommandLabels: z.array(z.string()).default([]),
422
+ artifactName: z.string().regex(/^[a-z0-9][a-z0-9._-]*\.json$/),
423
+ outputDir: z.string().regex(/^[a-z0-9][a-z0-9._-]*$/),
424
+ requireSourceFreshness: z.literal(true),
425
+ implementationWriteSet: z.array(z.string().min(1)).min(1).optional(),
426
+ /** Generation-frozen roots that bound custom normative directories. */
427
+ openspecSpecRoots: z
428
+ .array(z.string().refine((root) => isValidFrontendSpecRoot(root), "openspec spec root must be a normalized repo-relative directory"))
429
+ .optional(),
430
+ openspecCandidatePaths: z.array(openspecCandidatePathSchema).default([]),
431
+ /** Read-only selector output; omitted for scan-strict and legacy DAGs. */
432
+ openspecSelectionNodeId: dagFrontendNodeIdSchema.optional(),
433
+ /** Contract-declared and source-explicit paths that cannot be downgraded. */
434
+ openspecMandatoryPaths: z.array(openspecCandidatePathSchema).optional(),
435
+ openspecCandidateSnapshots: z
436
+ .array(openspecCandidateSnapshotSchema)
437
+ .optional(),
438
+ /**
439
+ * Generation-frozen component/theme specification bucket. Derived from
440
+ * `designEvidence.classified.component ∪ theme ∪ rule.components` at DAG
441
+ * generation. When non-empty and the canonical contract has UI-visible
442
+ * work, the design policy requires a non-empty `uiComponentChoices` and
443
+ * cross-checks each `specReference.path` against this set + the effective
444
+ * plan openspec-citations block.
445
+ */
446
+ componentSpecCandidatePaths: z.array(openspecCandidatePathSchema).optional(),
447
+ /** OpenSpec gate policy (cited | scan-strict); generation always writes it. */
448
+ openspecPolicy: z.enum(["cited", "scan-strict"]).optional(),
449
+ /** Generation-frozen candidate source metadata (diagnostic). */
450
+ openspecCandidateSources: z
451
+ .object({
452
+ declared: z.array(openspecCandidatePathSchema).default([]),
453
+ taskSourceCited: z.array(openspecCandidatePathSchema).default([]),
454
+ scanStrict: z.array(openspecCandidatePathSchema).default([]),
455
+ })
456
+ .optional(),
457
+ /** Dependency identifiers authorized at generation time (empty = none). */
458
+ allowedDependencies: z.array(z.string()).default([]),
459
+ })
460
+ .superRefine((gate, context) => {
461
+ const roots = gate.openspecSpecRoots ?? DEFAULT_FRONTEND_SPEC_ROOTS;
462
+ const candidateGroups = [
463
+ ["openspecCandidatePaths", gate.openspecCandidatePaths],
464
+ ["openspecMandatoryPaths", gate.openspecMandatoryPaths],
465
+ ["componentSpecCandidatePaths", gate.componentSpecCandidatePaths],
466
+ ["openspecCandidateSources.declared", gate.openspecCandidateSources?.declared],
467
+ ["openspecCandidateSources.taskSourceCited", gate.openspecCandidateSources?.taskSourceCited],
468
+ ["openspecCandidateSources.scanStrict", gate.openspecCandidateSources?.scanStrict],
469
+ ];
470
+ for (const [field, paths] of candidateGroups) {
471
+ for (const [index, candidate] of (paths ?? []).entries()) {
472
+ if (!isFrontendSpecFilePath(candidate, roots)) {
473
+ context.addIssue({
474
+ code: z.ZodIssueCode.custom,
475
+ path: [...field.split("."), index],
476
+ message: "openspec candidate must be a supported file under the generation-frozen openspec spec roots",
477
+ });
478
+ }
479
+ }
480
+ }
481
+ });
482
+ /**
483
+ * Deterministic frontend writer-admission shell (M8). Runs after
484
+ * frontend-design-review-pi: freezes the writer baseline, derives the concrete
485
+ * writeSet, folds the design-policy result + design verdict into admission,
486
+ * and writes contracts/frontend-writer-admission-result.json
487
+ * (schemaId frontend-writer-admission-shell-v1).
488
+ */
489
+ export const dagFrontendWriterAdmissionShellSchema = z
490
+ .object({
491
+ schemaVersion: z.literal(1),
492
+ /** Design verdict source (frontend-design-review-pi); pass is enforced.
493
+ * Omitted in small topology, which has no design review node. */
494
+ designReviewFromNodeId: dagFrontendNodeIdSchema.optional(),
495
+ /** Frozen command labels the verification bundle will execute. */
496
+ frozenCommandLabels: z.array(z.string()).default([]),
497
+ allowedMockStrategies: z
498
+ .array(z.enum(["native", "browser-intercept", "request-adapter", "not-needed"]))
499
+ .min(1),
500
+ /** Writer-preceding lint commands; baseline is materialized here (R5). */
501
+ lintCommands: z.array(z.string().min(1)).optional(),
502
+ lintEvidence: dagShellVerifyEvidenceSchema.optional(),
503
+ })
504
+ .superRefine((admission, context) => {
505
+ if ((admission.lintCommands?.length ?? 0) > 0 && !admission.lintEvidence) {
506
+ context.addIssue({
507
+ code: z.ZodIssueCode.custom,
508
+ path: ["lintEvidence"],
509
+ message: "lintEvidence is required when lintCommands are present",
510
+ });
511
+ }
512
+ });
513
+ /** Deterministic frontend closeout shell (M8). Renders the closeout from
514
+ * committed facts only (review context + typed review terminal + trace). */
515
+ export const dagFrontendCloseoutSchema = z.object({
516
+ schemaVersion: z.literal(1),
517
+ reviewFromNodeId: dagFrontendNodeIdSchema,
518
+ });
336
519
  export const ENV_VAR_NAME_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
337
520
  export const dagVersionSchema = z
338
521
  .union([z.literal(1), z.literal(2), z.literal(3), z.literal(4)])
@@ -380,7 +563,14 @@ export const dagWriteGuardPolicySchema = z.enum(["full", "tools-only"]);
380
563
  */
381
564
  export const dagWriterOutcomePolicySchema = z
382
565
  .object({
383
- type: z.literal("implementation-outcome-v1"),
566
+ /**
567
+ * Protocol face: `implementation-outcome-v1` keeps the legacy
568
+ * IMPLEMENTATION_OUTCOME first-line protocol (backend/README/module
569
+ * writers + frontend-repair-pi); `frontend-facts-v1` derives the status
570
+ * from mechanical facts (write-tool events / run delta / write guard /
571
+ * requirement coverage / focused-check) and never reads the first line.
572
+ */
573
+ type: z.enum(["implementation-outcome-v1", "frontend-facts-v1"]),
384
574
  /** Generation writers may forbid already-satisfied/no-diff completion. */
385
575
  requireChangedFiles: z.boolean().optional(),
386
576
  })
@@ -427,6 +617,8 @@ export const dagBackendTestPipelineSchema = z.enum([
427
617
  "ingest-backend-test-gap",
428
618
  ]);
429
619
  export const dagFrontendBrowserToolPreflightSchema = z.object({}).strict();
620
+ export const dagFrontendTestStandardScenariosSchema = z.object({}).strict();
621
+ export const dagFrontendTestEnvironmentProbeSchema = z.object({}).strict();
430
622
  export const dagFrontendTestEvidenceValidationSchema = z.object({}).strict();
431
623
  export const dagFrontendTestCaseChecklistSchema = z.object({}).strict();
432
624
  export const dagFrontendTestHtmlReportSchema = z.object({}).strict();
@@ -511,12 +703,17 @@ export const dagShellConfigSchema = z.object({
511
703
  .optional(),
512
704
  requirementCoverageGate: dagRequirementCoverageGateSchema.optional(),
513
705
  jsonArtifactGate: dagJsonArtifactGateSchema.optional(),
706
+ frontendDesignPolicy: dagFrontendDesignPolicySchema.optional(),
707
+ frontendWriterAdmission: dagFrontendWriterAdmissionShellSchema.optional(),
514
708
  frontendPrewriteGate: dagFrontendPrewriteGateSchema.optional(),
515
- frontendLintBaseline: dagFrontendLintBaselineSchema.optional(),
516
709
  frontendVerificationBundle: dagFrontendVerificationBundleSchema.optional(),
517
710
  frontendReviewContext: dagFrontendReviewContextSchema.optional(),
711
+ frontendContractImport: dagFrontendContractImportSchema.optional(),
712
+ frontendCloseout: dagFrontendCloseoutSchema.optional(),
518
713
  frontendTestCaseChecklist: dagFrontendTestCaseChecklistSchema.optional(),
519
714
  frontendBrowserToolPreflight: dagFrontendBrowserToolPreflightSchema.optional(),
715
+ frontendTestStandardScenarios: dagFrontendTestStandardScenariosSchema.optional(),
716
+ frontendTestEnvironmentProbe: dagFrontendTestEnvironmentProbeSchema.optional(),
520
717
  frontendTestEvidenceValidation: dagFrontendTestEvidenceValidationSchema.optional(),
521
718
  finalWriteSetApprovalGate: dagFinalWriteSetApprovalGateSchema.optional(),
522
719
  frontendTestL5Report: z.object({}).strict().optional(),
@@ -749,6 +946,13 @@ export const dagTaskSchema = z.object({ id: z.string().regex(/^[a-z][a-z0-9-]*$/
749
946
  role: dagRoleSchema.optional(),
750
947
  skills: z.array(z.string()).optional(),
751
948
  toolProfile: dagToolProfileSchema.optional(),
949
+ /**
950
+ * Explicit Pi extension allowlist (backend implementation DAGs only):
951
+ * frozen short ids resolved at run time against the Pi settings package
952
+ * inventory. Omitted = extension discovery stays fully closed (R8.1).
953
+ * Only `executor: "pi"` nodes may declare it (validated fail-closed).
954
+ */
955
+ piExtensions: dagPiExtensionsSchema.optional(),
752
956
  /** Default deny: file write (toolProfile=write) does not grant command execution. */
753
957
  commandPolicy: dagCommandPolicySchema.optional(),
754
958
  writePolicy: dagWritePolicySchema.optional(),
@@ -782,10 +986,6 @@ export const dagTaskSchema = z.object({ id: z.string().regex(/^[a-z][a-z0-9-]*$/
782
986
  * this run-owned skeleton before strict contract validation.
783
987
  */
784
988
  skeleton: z.record(z.string(), z.unknown()).optional(),
785
- /** Frontend plan/revision citation fence is only needed for a
786
- * generation-frozen OpenSpec candidate set. Undefined preserves the
787
- * historical required-fence protocol. */
788
- citationsRequired: z.boolean().optional(),
789
989
  })
790
990
  .strict()
791
991
  .superRefine((value, ctx) => {
@@ -800,12 +1000,6 @@ export const dagTaskSchema = z.object({ id: z.string().regex(/^[a-z][a-z0-9-]*$/
800
1000
  }
801
1001
  })
802
1002
  .optional(),
803
- /**
804
- * Opt-in upstream context mode for nodes that must consume a validated,
805
- * hash-bound artifact rather than a potentially truncated text preview.
806
- * The default remains preview mode for every existing DAG.
807
- */
808
- artifactFirstUpstreamNodeIds: z.array(z.string()).optional(),
809
1003
  /**
810
1004
  * Fail-closed outcome/diff consistency contract for bounded Pi writers.
811
1005
  * The writer must begin with IMPLEMENTATION_OUTCOME: changed,
@@ -836,6 +1030,14 @@ export const dagTaskSchema = z.object({ id: z.string().regex(/^[a-z][a-z0-9-]*$/
836
1030
  forbiddenPaths: z.array(z.string()).optional().default([]),
837
1031
  decisionGate: dagDecisionGateSchema.optional(),
838
1032
  retryPolicy: dagRetryPolicySchema.optional(),
1033
+ /**
1034
+ * Generation-path stamp for implicit same-run transient retry. Only the
1035
+ * three target templates stamp canonical final verify shells and
1036
+ * exclusive `implement-pi`. Zod-declared so parseDagSpec does not strip it.
1037
+ */
1038
+ transientRetryProfile: z
1039
+ .literal(TARGET_TEMPLATE_TRANSIENT_RETRY_PROFILE)
1040
+ .optional(),
839
1041
  /** Optional per-node adaptive liveness override (merged over defaults). */
840
1042
  livenessPolicy: dagLivenessPolicySchema,
841
1043
  dynamicExpansion: dagDynamicExpansionSchema.optional(),
@@ -1056,6 +1258,9 @@ export const FRONTEND_RECOVERY_RESULT_SCHEMA_VERSION = 1;
1056
1258
  * Phase 3a parents converge first, so `rollback-pending` is not produced and
1057
1259
  * was removed by AC-1; the phase starts at `child-staging` and ends at
1058
1260
  * `settled`.
1261
+ *
1262
+ * @deprecated Read-only compatibility only; the M6 operation controller no
1263
+ * longer uses this phase as the authoritative recovery entry point.
1059
1264
  */
1060
1265
  export const frontendRecoveryPhaseSchema = z.enum([
1061
1266
  "child-staging",
@@ -1075,6 +1280,9 @@ export const frontendRecoveryPhaseSchema = z.enum([
1075
1280
  * runner's `(existing?.revision ?? 0) + 1` CAS write (the schema only bounds it
1076
1281
  * non-negative). `childRunId` may be absent through `child-activating` and must
1077
1282
  * be non-empty from `child-running` onward.
1283
+ *
1284
+ * @deprecated Read-only compatibility only; the runner no longer writes this
1285
+ * intent (the M6 operation controller supervises recovery instead).
1078
1286
  */
1079
1287
  export const frontendRecoveryStateSchema = z
1080
1288
  .object({
@@ -1146,6 +1354,10 @@ export const frontendRecoveryEvidenceRefSchema = z
1146
1354
  * stays permissive (failureClass optional, evidenceRefs may be empty) so it
1147
1355
  * accepts the runner's actual phase-3a products, which do not always carry a
1148
1356
  * failure class or evidence reference.
1357
+ *
1358
+ * @deprecated Read-only compatibility only; the M6 operation controller writes
1359
+ * `recovered`/`auto-recovery-blocked` results directly and never uses this
1360
+ * schema as the authoritative recovery settlement.
1149
1361
  */
1150
1362
  export const frontendRecoveryResultSchema = z
1151
1363
  .object({
@@ -38,10 +38,6 @@ export function relocateNodeArtifactPaths(node, oldRunDir, newRunDir) {
38
38
  node.stdoutArtifactPath = rewrite(node.stdoutArtifactPath);
39
39
  node.assistantArtifactPath = rewrite(node.assistantArtifactPath);
40
40
  node.structuredArtifactPath = rewrite(node.structuredArtifactPath);
41
- if (node.frontendPlanOutput) {
42
- node.frontendPlanOutput.citationsArtifactPath = rewrite(node.frontendPlanOutput.citationsArtifactPath);
43
- node.frontendPlanOutput.rawCandidatePath = rewrite(node.frontendPlanOutput.rawCandidatePath);
44
- }
45
41
  node.nodeRecordPath = rewrite(node.nodeRecordPath);
46
42
  }
47
43
  export function relocateConvergenceArtifactPaths(convergence, oldRunDir, newRunDir) {
@@ -1,4 +1,4 @@
1
- import { DEFAULT_DAG_EXECUTOR_MODELS, ENV_VAR_NAME_PATTERN, dagCommandPolicyAllows, resolveDagCommandPolicy, } from "./types.js";
1
+ import { DEFAULT_DAG_EXECUTOR_MODELS, ENV_VAR_NAME_PATTERN, dagCommandPolicyAllows, dagPiExtensionIdSchema, resolveDagCommandPolicy, } from "./types.js";
2
2
  import { resolveShellCommands } from "../../executors/shell-executor.js";
3
3
  import { pathMatchesPattern } from "../../shared/git-progress.js";
4
4
  import { resolveRepairTaskForGate } from "./repair-artifact.js";
@@ -463,8 +463,12 @@ function validateShellTaskConfig(task, spec, issues) {
463
463
  if (commands.length === 0 &&
464
464
  !shell.jsonArtifactGate &&
465
465
  !shell.backendTestPipeline &&
466
- !shell.frontendPrewriteGate &&
466
+ !shell.frontendDesignPolicy &&
467
+ !shell.frontendWriterAdmission &&
468
+ !shell.frontendCloseout &&
467
469
  !shell.frontendBrowserToolPreflight &&
470
+ !shell.frontendTestStandardScenarios &&
471
+ !shell.frontendTestEnvironmentProbe &&
468
472
  !shell.frontendVerificationBundle &&
469
473
  !shell.frontendReviewContext &&
470
474
  !shell.frontendTestCaseChecklist &&
@@ -534,18 +538,26 @@ function validateShellTaskConfig(task, spec, issues) {
534
538
  }
535
539
  }
536
540
  }
537
- if (shell.frontendPrewriteGate) {
538
- const sourceNodeIds = [
539
- shell.frontendPrewriteGate.planFromNodeId,
540
- ...shell.frontendPrewriteGate.planFallbackFromNodeIds,
541
- shell.frontendPrewriteGate.reviewFromNodeId,
542
- ...shell.frontendPrewriteGate.reviewFallbackFromNodeIds,
543
- ];
541
+ if (shell.frontendDesignPolicy) {
542
+ const sourceNodeIds = [shell.frontendDesignPolicy.planFromNodeId];
544
543
  for (const sourceNodeId of new Set(sourceNodeIds)) {
545
544
  if (!task.depends_on.includes(sourceNodeId)) {
546
545
  issues.push({
547
546
  type: "missing-dependency",
548
- message: `shell task ${task.id} frontendPrewriteGate source ${sourceNodeId} must be a direct dependency`,
547
+ message: `shell task ${task.id} frontendDesignPolicy source ${sourceNodeId} must be a direct dependency`,
548
+ });
549
+ }
550
+ }
551
+ }
552
+ if (shell.frontendWriterAdmission) {
553
+ const sourceNodeIds = shell.frontendWriterAdmission.designReviewFromNodeId
554
+ ? [shell.frontendWriterAdmission.designReviewFromNodeId]
555
+ : [];
556
+ for (const sourceNodeId of new Set(sourceNodeIds)) {
557
+ if (!task.depends_on.includes(sourceNodeId)) {
558
+ issues.push({
559
+ type: "missing-dependency",
560
+ message: `shell task ${task.id} frontendWriterAdmission source ${sourceNodeId} must be a direct dependency`,
549
561
  });
550
562
  }
551
563
  }
@@ -953,6 +965,7 @@ export function validateDagSpec(spec) {
953
965
  validateCommandPolicyTaskConfig(task, issues);
954
966
  validateDynamicChildCommandPolicy(task, issues);
955
967
  validateProjectGovernanceTaskConfig(task, spec, issues);
968
+ validatePiExtensionsTaskConfig(task, issues);
956
969
  validateFailureAwareDependsOn(task, spec, issues);
957
970
  }
958
971
  validateSameRankWriteSetConflicts(spec, ranks, issues);
@@ -963,6 +976,43 @@ export function validateDagSpec(spec) {
963
976
  }
964
977
  return issues;
965
978
  }
979
+ /**
980
+ * Fail-closed `piExtensions` validation (plan 2026-08-21 D1/D2):
981
+ * - only `executor: "pi"` nodes may declare it (shell/static reject);
982
+ * - unknown ids outside the frozen enum are rejected at parse time by zod,
983
+ * and here for task objects that bypass schema parsing;
984
+ * - closeout nodes must stay fully closed (extension-free) — the two-bucket
985
+ * generator never assigns it there, so any appearance is contract drift.
986
+ */
987
+ function validatePiExtensionsTaskConfig(task, issues) {
988
+ const ids = task.piExtensions;
989
+ if (ids === undefined)
990
+ return;
991
+ if (task.executor !== "pi") {
992
+ issues.push({
993
+ type: "invalid-pi-extensions-config",
994
+ message: `task ${task.id} piExtensions requires executor "pi" (found ${task.executor})`,
995
+ });
996
+ return;
997
+ }
998
+ const allowed = new Set(dagPiExtensionIdSchema.options);
999
+ for (const id of ids) {
1000
+ if (!allowed.has(id)) {
1001
+ issues.push({
1002
+ type: "invalid-pi-extensions-config",
1003
+ message: `task ${task.id} piExtensions has unknown id "${id}"; allowed: ${[
1004
+ ...allowed,
1005
+ ].join(", ")}`,
1006
+ });
1007
+ }
1008
+ }
1009
+ if (task.id === "closeout-pi" && ids.length > 0) {
1010
+ issues.push({
1011
+ type: "invalid-pi-extensions-config",
1012
+ message: "closeout-pi must stay extension-free (read buckets exclude closeout)",
1013
+ });
1014
+ }
1015
+ }
966
1016
  function validateProjectGovernanceTaskConfig(task, spec, issues) {
967
1017
  if (task.governanceStandardReview) {
968
1018
  if (task.executor !== "pi" ||
@@ -50,6 +50,9 @@
50
50
  "scripts/kb-graph-materialize.mjs",
51
51
  "scripts/kb-graph-promote.mjs",
52
52
  "scripts/kb-graph-incremental-prepare.mjs",
53
+ "scripts/next-info.mjs",
54
+ "scripts/next-publish-gate.mjs",
55
+ "scripts/release-source-binding.mjs",
53
56
  "docs/templates/agent-dag.schema.json",
54
57
  "examples/example-dag.json",
55
58
  "skills/agent-worker/SKILL.md",
@@ -277,6 +280,7 @@
277
280
  "harness.json",
278
281
  "package.json",
279
282
  "src/commands/init.ts",
283
+ "src/commands/init-model-catalog.ts",
280
284
  "src/commands/client-recovery.ts",
281
285
  "src/commands/init-upgrade.ts",
282
286
  "src/cli.ts",
@@ -302,6 +306,7 @@
302
306
  "description": "Changes that may alter target-project initialization behavior, default DAG role skills, skill resolution, or package/init contracts.",
303
307
  "patterns": [
304
308
  "src/commands/init.ts",
309
+ "src/commands/init-model-catalog.ts",
305
310
  "src/commands/client-recovery.ts",
306
311
  "src/commands/init-upgrade.ts",
307
312
  "src/cli.ts",
@@ -7,7 +7,7 @@
7
7
  - [`local-development-environment.md`](local-development-environment.md):本地环境、Windows 差异和常见排障。
8
8
  - [`branch-merge-guideline.md`](branch-merge-guideline.md):按风险选择合并模式并留下 source-SHA 证据。
9
9
  - [`github-collaboration.md`](github-collaboration.md):仓库内 GitHub 协作约定及发布通道。
10
- - [`version-and-release-management.md`](version-and-release-management.md):版本发布管理——next / release-x.y / latest 渠道、workflow 与 promote 手册。
10
+ - [`version-and-release-management.md`](version-and-release-management.md):版本发布管理——next / release-x.y / latest 渠道、移动式 `npm-next` ref、`inspect next` 查询入口、workflow 与 promote 手册。
11
11
  - [`production-readiness.md`](production-readiness.md):交付前的生产就绪判断。
12
12
  - [`agent-worker-production-readiness.md`](agent-worker-production-readiness.md):agent-worker Feature/Task Pool 外层编排的 Production Readiness v1 操作合同。
13
13
  - [`backend-test-jacoco-coverage.md`](backend-test-jacoco-coverage.md):Java 被测服务挂载 JaCoCo tcpserver agent 的 Maven、`java -jar` 与 Docker 操作手册。
@@ -130,6 +130,18 @@
130
130
  "toolProfile": {
131
131
  "enum": ["read-only", "write"]
132
132
  },
133
+ "piExtensionId": {
134
+ "type": "string",
135
+ "enum": ["pi-codegraph", "pi-lens"],
136
+ "description": "Frozen Pi extension short id resolved at run time against the Pi settings package inventory. Adding a package requires changing the plan and this enum."
137
+ },
138
+ "piExtensions": {
139
+ "type": "array",
140
+ "minItems": 1,
141
+ "uniqueItems": true,
142
+ "items": { "$ref": "#/$defs/piExtensionId" },
143
+ "description": "Node-level explicit Pi extension allowlist (backend implementation DAGs only). Omitted = extension discovery stays fully closed (R8.1). Only executor 'pi' nodes may declare it."
144
+ },
133
145
  "role": {
134
146
  "enum": ["planner", "scout", "implementer", "reviewer", "supervisor", "verifier", "closeout"]
135
147
  },
@@ -356,6 +368,31 @@
356
368
  "properties": { "schemaVersion": { "const": 1 }, "requireBaseline": { "const": true } }
357
369
  },
358
370
  "frontendTestCaseChecklist": { "type": "object", "additionalProperties": false },
371
+ "frontendBrowserToolPreflight": { "type": "object", "additionalProperties": false },
372
+ "frontendTestStandardScenarios": { "type": "object", "additionalProperties": false },
373
+ "frontendTestEnvironmentProbe": { "type": "object", "additionalProperties": false },
374
+ "frontendTestCaseManifest": {
375
+ "type": "object",
376
+ "additionalProperties": false,
377
+ "properties": {
378
+ "maxCases": { "type": "integer", "minimum": 1 },
379
+ "declaredAcIds": { "type": "array", "items": { "type": "string" } }
380
+ }
381
+ },
382
+ "frontendTestResultFinalize": {
383
+ "type": "object",
384
+ "additionalProperties": false,
385
+ "properties": {
386
+ "declaredAcIds": { "type": "array", "items": { "type": "string" } }
387
+ }
388
+ },
389
+ "frontendTestReports": {
390
+ "type": "object",
391
+ "additionalProperties": false,
392
+ "properties": {
393
+ "l5": { "type": "boolean" }
394
+ }
395
+ },
359
396
  "frontendTestEvidenceValidation": { "type": "object", "additionalProperties": false },
360
397
  "frontendTestHtmlReport": { "type": "object", "additionalProperties": false },
361
398
  "backendTestPipeline": {
@@ -383,6 +420,12 @@
383
420
  { "required": ["frontendVerificationBundle"] },
384
421
  { "required": ["frontendReviewContext"] },
385
422
  { "required": ["frontendTestCaseChecklist"] },
423
+ { "required": ["frontendBrowserToolPreflight"] },
424
+ { "required": ["frontendTestStandardScenarios"] },
425
+ { "required": ["frontendTestEnvironmentProbe"] },
426
+ { "required": ["frontendTestCaseManifest"] },
427
+ { "required": ["frontendTestResultFinalize"] },
428
+ { "required": ["frontendTestReports"] },
386
429
  { "required": ["frontendTestEvidenceValidation"] },
387
430
  { "required": ["frontendTestHtmlReport"] },
388
431
  { "required": ["backendTestPipeline"] }
@@ -485,6 +528,7 @@
485
528
  "items": { "type": "string", "minLength": 1 }
486
529
  },
487
530
  "toolProfile": { "$ref": "#/$defs/toolProfile" },
531
+ "piExtensions": { "$ref": "#/$defs/piExtensions" },
488
532
  "governanceStandardReview": {
489
533
  "type": "boolean",
490
534
  "description": "Explicitly opts this node into writer-change-scoped AGENTS.md and repository-local code-standard review. Never inferred from node id or role."
@@ -66,12 +66,11 @@
66
66
  ".harness/**",
67
67
  "artifacts/**"
68
68
  ],
69
- "outputContract": "Write testcase/frontend/rag/standard-scenarios.v1.json for generate-time standard scenario coverage.",
70
- "subtask_prompt": "Materialize frontend-test standard scenarios v1 into the RAG package.",
69
+ "outputContract": "Write testcase/frontend/rag/standard-scenarios.v1.json for generate-time standard scenario coverage. Copy docs/templates or harness.json governanceRoot templates (including ai_workspace/loop-agent/templates) when present; otherwise write the minimal STD-FE-SMOKE-ENTRY fallback.",
70
+ "subtask_prompt": "Prepare frontend-test package: materialize standard-scenarios.v1.json into the RAG package from docs/templates, governanceRoot/templates, or the init-projected ai_workspace/loop-agent/templates path.",
71
71
  "shell": {
72
- "commands": [
73
- "node -e \"const fs=require('fs'),path=require('path');const dest='testcase/frontend/rag/standard-scenarios.v1.json';const candidates=[path.join('docs','templates','frontend-test-standard-scenarios.v1.json')];let src=null;for(const c of candidates){if(fs.existsSync(c)){src=c;break;}}fs.mkdirSync(path.dirname(dest),{recursive:true});if(src){fs.copyFileSync(src,dest);process.stdout.write(JSON.stringify({status:'copied',from:src,to:dest}));}else{const minimal={schemaVersion:1,id:'frontend-test-standard-scenarios-v1',scenarios:[{id:'STD-FE-SMOKE-ENTRY',title:'入口可打开',category:'smoke',priority:'must',testPoints:['open'],minCases:1}]};fs.writeFileSync(dest,JSON.stringify(minimal,null,2)+'\\n');process.stdout.write(JSON.stringify({status:'fallback',to:dest}));}"
74
- ],
72
+ "commands": [],
73
+ "frontendTestStandardScenarios": {},
75
74
  "cwd": ".",
76
75
  "timeoutMs": 60000
77
76
  }
@@ -120,12 +119,11 @@
120
119
  ".harness/**",
121
120
  "artifacts/**"
122
121
  ],
123
- "outputContract": "Fail-closed environment preflight: absolute non-production baseUrl + curl HTTP reachability; writes environmentProbe facts; unreachable => blockedReason frontend-base-url-unreachable (node ERROR so generate/map do not run).",
124
- "subtask_prompt": "Parse the concrete baseUrl selected by retrieve-frontend-test-context-pi from testcase/frontend/rag/context.md. Reject production, non-http(s), credentials, query and fragment. Probe with curl (HEAD then GET fallback; connect/max-time; no auth/cookie). 2xx/3xx => reachable and continue. 4xx/5xx/DNS/timeout/connection refused/TLS => blockedReason frontend-base-url-unreachable. Missing curl => blockedReason curl-unavailable. Do not start the app. Runtime hybrid generator embeds the authoritative probe script.",
122
+ "outputContract": "Fail-closed environment preflight: absolute non-production baseUrl + curl HTTP reachability; writes environmentProbe facts; unreachable => blockedReason frontend-base-url-unreachable with errorClass (connection-refused / dns-unresolved / connect-timeout / http-N). Node ERROR so generate/map do not run. Does not start the app.",
123
+ "subtask_prompt": "Parse the concrete baseUrl selected by retrieve-frontend-test-context-pi from testcase/frontend/rag/context.md. Reject production, non-http(s), credentials, query and fragment. Probe with curl (HEAD then GET fallback; connect/max-time; no auth/cookie). 2xx/3xx => reachable and continue. Connection refused records errorClass=connection-refused and tells the operator to start the local app then rerun from this node. 4xx/5xx/DNS/timeout/TLS => blockedReason frontend-base-url-unreachable. Missing curl => blockedReason curl-unavailable. Do not start the app.",
125
124
  "shell": {
126
- "commands": [
127
- "node -e \"console.log('template placeholder: runtime hybrid DAG embeds curl preflight; do not use this static command as source of truth')\""
128
- ],
125
+ "commands": [],
126
+ "frontendTestEnvironmentProbe": {},
129
127
  "cwd": ".",
130
128
  "timeoutMs": 60000
131
129
  }
@@ -17,5 +17,5 @@ Do not claim the environment is reachable until preflight completes. Preflight d
17
17
 
18
18
  ## Standard scenario coverage
19
19
 
20
- - Copy or reference `docs/templates/frontend-test-standard-scenarios.v1.json` into `testcase/frontend/rag/standard-scenarios.v1.json` when available.
20
+ - Copy or reference `docs/templates/frontend-test-standard-scenarios.v1.json`, `harness.json` `governanceRoot`/templates, or the init-projected `ai_workspace/loop-agent/templates/frontend-test-standard-scenarios.v1.json` into `testcase/frontend/rag/standard-scenarios.v1.json` when available.
21
21
  - Add `## Standard scenario coverage` to coverage-map.md with planned/n/a for each must scenario.