@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
@@ -0,0 +1,251 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * release-source-binding: resolve the exact source SHA behind a selected next
4
+ * build, and validate cut-release-line preconditions.
5
+ *
6
+ * The next tarball carries dist/build-stamp.json { version, gitSha }. A release
7
+ * line must be cut from THAT commit — never from a floating main HEAD at
8
+ * workflow runtime. This module provides:
9
+ *
10
+ * - resolveSourceBinding: given a next version, return its gitSha (from the
11
+ * registry tarball build stamp) and validated facts.
12
+ * - validateCutPreconditions: line monotonicity, branch absence, no parallel
13
+ * un-promoted train, source sha on main.
14
+ */
15
+ import { execFileSync } from "node:child_process";
16
+ import process from "node:process";
17
+
18
+ export const NEXT_TARGET_ENV = "LOOP_AGENT_NEXT_TARGET";
19
+
20
+ const FORMAL_LINE_RE = /^\d+\.\d+$/;
21
+
22
+ /**
23
+ * Read build stamp fields from a packed tarball without installing it.
24
+ * @param {string} tarballPath
25
+ * @param {(cmd: string, args: string[]) => string} [run]
26
+ */
27
+ export function readTarballBuildStamp(tarballPath, run = runCommand) {
28
+ const listing = run("tar", ["-tzf", tarballPath]);
29
+ const stampEntry = listing
30
+ .split("\n")
31
+ .map((line) => line.trim())
32
+ .find((line) => /\/dist\/build-stamp\.json$/.test(line));
33
+ if (!stampEntry) {
34
+ throw new Error(`tarball ${tarballPath} does not contain dist/build-stamp.json`);
35
+ }
36
+ const stampJson = run("tar", ["-xzf", tarballPath, "-O", stampEntry]);
37
+ let parsed;
38
+ try {
39
+ parsed = JSON.parse(stampJson);
40
+ } catch (error) {
41
+ throw new Error(
42
+ `build stamp is not valid JSON: ${error instanceof Error ? error.message : String(error)}`,
43
+ );
44
+ }
45
+ if (typeof parsed.gitSha !== "string" || !/^[0-9a-f]{7,40}$/i.test(parsed.gitSha)) {
46
+ throw new Error("build stamp gitSha missing or malformed");
47
+ }
48
+ return { gitSha: parsed.gitSha, version: parsed.version ?? null };
49
+ }
50
+
51
+ function runCommand(cmd, args) {
52
+ return execFileSync(cmd, args, {
53
+ encoding: "utf8",
54
+ stdio: ["ignore", "pipe", "pipe"],
55
+ });
56
+ }
57
+
58
+ /**
59
+ * Download an exact next version tarball (without install scripts) into a
60
+ * directory and return its path.
61
+ */
62
+ export function downloadNpmTarball(version, destDir, run = runCommand) {
63
+ run("npm", [
64
+ "pack",
65
+ `@tea-agent/loop-agent@${version}`,
66
+ "--pack-destination",
67
+ destDir,
68
+ ]);
69
+ const listing = run("ls", [destDir]);
70
+ const file = listing
71
+ .split("\n")
72
+ .map((line) => line.trim())
73
+ .find((line) => line.endsWith(".tgz"));
74
+ if (!file) throw new Error(`npm pack did not produce a tarball in ${destDir}`);
75
+ return `${destDir}/${file}`;
76
+ }
77
+
78
+ /**
79
+ * Pure checks for cutting a release line.
80
+ *
81
+ * @param {object} input
82
+ * @param {string} input.selectedNextVersion e.g. "0.37.0-next.42"
83
+ * @param {string} input.releaseLine e.g. "0.37"
84
+ * @param {string} input.sourceSha commit the next tarball was built from
85
+ * @param {string[]} input.remoteBranches remote release branch names (e.g. ["release/0.36"])
86
+ * @param {string|null} [input.latestFormalVersion] current npm latest (for reference in results)
87
+ * @param {(sha: string) => boolean} [input.shaOnMain] whether sha is an ancestor of origin/main
88
+ * @returns {{ok: true} | {ok: false, reason: string, detail?: string}}
89
+ */
90
+ export function validateCutPreconditions(input) {
91
+ const nextMatch = /^(\d+)\.(\d+)\.(0|[1-9]\d*)-next\.([0-9]\d*)$/.exec(
92
+ String(input.selectedNextVersion ?? ""),
93
+ );
94
+ if (!nextMatch) {
95
+ return {
96
+ ok: false,
97
+ reason: "invalid-selected-next",
98
+ detail: `selectedNextVersion=${input.selectedNextVersion}`,
99
+ };
100
+ }
101
+ const nextLine = `${nextMatch[1]}.${nextMatch[2]}`;
102
+ const patch = nextMatch[3];
103
+ if (patch !== "0") {
104
+ return {
105
+ ok: false,
106
+ reason: "selected-next-not-minor-zero",
107
+ detail: `next target patch must be 0, got ${patch}`,
108
+ };
109
+ }
110
+ if (!FORMAL_LINE_RE.test(String(input.releaseLine ?? ""))) {
111
+ return {
112
+ ok: false,
113
+ reason: "invalid-release-line",
114
+ detail: `releaseLine=${input.releaseLine}`,
115
+ };
116
+ }
117
+ if (input.releaseLine !== nextLine) {
118
+ return {
119
+ ok: false,
120
+ reason: "line-mismatch",
121
+ detail: `selected next ${nextLine} vs release line ${input.releaseLine}`,
122
+ };
123
+ }
124
+ if (!/^[0-9a-f]{7,40}$/i.test(String(input.sourceSha ?? ""))) {
125
+ return {
126
+ ok: false,
127
+ reason: "invalid-source-sha",
128
+ detail: `sourceSha=${input.sourceSha}`,
129
+ };
130
+ }
131
+ const branches = Array.isArray(input.remoteBranches)
132
+ ? input.remoteBranches
133
+ : [];
134
+ const existing = branches.find((b) => b === `release/${input.releaseLine}`);
135
+ if (existing) {
136
+ return {
137
+ ok: false,
138
+ reason: "release-branch-exists",
139
+ detail: existing,
140
+ };
141
+ }
142
+ const existingLines = branches
143
+ .map((b) => /^release\/(\d+)\.(\d+)$/.exec(b))
144
+ .filter(Boolean)
145
+ .map((m) => ({ major: Number(m[1]), minor: Number(m[2]), name: m[0] }));
146
+ const [cutMajor, cutMinor] = input.releaseLine.split(".").map(Number);
147
+ const higher = existingLines.find(
148
+ (l) => l.major > cutMajor || (l.major === cutMajor && l.minor > cutMinor),
149
+ );
150
+ if (higher) {
151
+ return {
152
+ ok: false,
153
+ reason: "line-not-monotonic",
154
+ detail: `higher release branch exists: ${higher.name}`,
155
+ };
156
+ }
157
+ if (input.shaOnMain !== undefined && input.shaOnMain(input.sourceSha) === false) {
158
+ return {
159
+ ok: false,
160
+ reason: "source-sha-not-on-main",
161
+ detail: input.sourceSha,
162
+ };
163
+ }
164
+ return { ok: true };
165
+ }
166
+
167
+ /**
168
+ * Resolve the next target line from env override (LOOP_AGENT_NEXT_TARGET).
169
+ * Returns null when unset/invalid — callers decide the fallback derivation.
170
+ */
171
+ export function nextTargetFromEnv(env = process.env) {
172
+ const raw = env[NEXT_TARGET_ENV];
173
+ if (!raw) return null;
174
+ return /^\d+\.\d+\.0$/.test(raw) ? raw : null;
175
+ }
176
+
177
+ // CLI 入口安全判断(argv[1] 非合法 file: URL 时 new URL 会抛异常)
178
+ function scriptEntryHref(argv1) {
179
+ try {
180
+ return argv1 ? new URL(`file://${argv1}`).href : "";
181
+ } catch {
182
+ return "";
183
+ }
184
+ }
185
+
186
+ const invokedAsScript =
187
+ Boolean(process.argv[1]) && import.meta.url === scriptEntryHref(process.argv[1]);
188
+
189
+ if (invokedAsScript) {
190
+ const args = Object.fromEntries(
191
+ process.argv
192
+ .filter((arg) => arg.startsWith("--"))
193
+ .map((arg) => {
194
+ const [key, ...value] = arg.replace(/^--/, "").split("=");
195
+ return [key, value.join("=") || "true"];
196
+ }),
197
+ );
198
+ if (args["self-test"]) {
199
+ const scenario = String(args["self-test"]);
200
+ const scenarios = {
201
+ "cut-ok": () =>
202
+ validateCutPreconditions({
203
+ selectedNextVersion: "0.37.0-next.42",
204
+ releaseLine: "0.37",
205
+ sourceSha: "abc1234567890abcdef",
206
+ remoteBranches: ["release/0.36"],
207
+ }),
208
+ "cut-exists": () =>
209
+ validateCutPreconditions({
210
+ selectedNextVersion: "0.37.0-next.42",
211
+ releaseLine: "0.37",
212
+ sourceSha: "abc1234567890abcdef",
213
+ remoteBranches: ["release/0.36", "release/0.37"],
214
+ }),
215
+ "cut-not-monotonic": () =>
216
+ validateCutPreconditions({
217
+ selectedNextVersion: "0.37.0-next.42",
218
+ releaseLine: "0.37",
219
+ sourceSha: "abc1234567890abcdef",
220
+ remoteBranches: ["release/0.38"],
221
+ }),
222
+ "cut-line-mismatch": () =>
223
+ validateCutPreconditions({
224
+ selectedNextVersion: "0.37.0-next.42",
225
+ releaseLine: "0.38",
226
+ sourceSha: "abc1234567890abcdef",
227
+ remoteBranches: [],
228
+ }),
229
+ "cut-sha-not-on-main": () =>
230
+ validateCutPreconditions({
231
+ selectedNextVersion: "0.37.0-next.42",
232
+ releaseLine: "0.37",
233
+ sourceSha: "abc1234567890abcdef",
234
+ remoteBranches: [],
235
+ shaOnMain: () => false,
236
+ }),
237
+ };
238
+ const run = scenarios[scenario];
239
+ if (!run) {
240
+ process.stderr.write(`unknown self-test scenario: ${scenario}\n`);
241
+ process.exitCode = 2;
242
+ } else {
243
+ process.stdout.write(`${JSON.stringify(run())}\n`);
244
+ }
245
+ } else {
246
+ process.stderr.write(
247
+ "usage: node scripts/release-source-binding.mjs --self-test=<scenario>\n",
248
+ );
249
+ process.exitCode = 2;
250
+ }
251
+ }
@@ -10,7 +10,7 @@ description: 用于只读 scout 节点,在实现前定位现有代码、测试
10
10
  ## 规则
11
11
 
12
12
  - 从 repo 指令、task source 与邻近测试入手。
13
- - 可用时优先 CodeGraph;否则用 `rg` 与聚焦文件阅读。
13
+ - 可用时优先 CodeGraph;否则 Shell 搜索优先 `rg`,按名找文件优先 `fd`,再聚焦阅读文件。
14
14
  - 在提议新抽象前,识别现有 helper 与 ownership 边界。
15
15
  - 只返回事实,不做编辑。
16
16
 
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: fe-test-ui-scout
3
+ description: 在写 frontend-test Wave PRD/回归目录之前,对真实运行的被测页面做只读锚点侦察,产出并维护 testcase/frontend/rag/ui-anchors.md 锚点台账。用 playwright-cli snapshot/find 逐字面验证命中数与唯一性,防止 PRD 验收标准引用不存在或不唯一的文案。触发词:frontend-test、锚点、台账、ui-anchors、侦察、写 PRD 前、snapshot/find 校准、Wave PRD。
4
+ references:
5
+ - path: references/ledger-schema.md
6
+ required: true
7
+ - path: references/recon-protocol.md
8
+ required: true
9
+ ---
10
+
11
+ # FE-Test UI Scout(锚点侦察)
12
+
13
+ ## 定位
14
+
15
+ 写 Wave PRD / 校准回归目录 AC **之前**的前置侦察。产出锚点台账
16
+ `testcase/frontend/rag/ui-anchors.md`:每个「页面 × 状态」下真实可见、
17
+ `find` 可命中的字面清单。PRD 的每条 `find "..."` 断言必须能在台账找到对应行;
18
+ 台账没有的字面禁止写进验收标准。
19
+
20
+ 本 skill **不生成用例、不执行用例、不点任何写控件**。生成由
21
+ `playwright-cli-case-generator`(DAG generate 节点)负责,执行由
22
+ `playwright-cli`(DAG execute 节点)负责;本 skill 只在 DAG 之外的主会话使用。
23
+
24
+ ## 输入
25
+
26
+ - 被测 baseUrl(来自 `testcase/frontend/rag/context.md` 的 `environmentProbe=reachable` URL)
27
+ - 目标 Wave 的 AC 清单(回归目录中本 Wave 范围的行)
28
+ - 既有台账(增量更新,不整表重写)
29
+
30
+ ## 流程
31
+
32
+ 1. **读取范围**:从回归目录取本 Wave AC 的「页面 + 测试点 + 验收标准」,
33
+ 提取所有候选字面(引号内文案、aria-label、控件名)。
34
+ 2. **逐状态侦察**:对每个涉及页面/状态组合:
35
+ `playwright-cli open --browser=chrome <baseUrl>/#/<route>` → `snapshot` →
36
+ 逐候选 `find "<字面>"`,记录命中数与命中位置。
37
+ 3. **记录陷阱**:拆节点(`<kbd>`+`<span>`、文本+`<code>`)、同名多控件、
38
+ aria-label 与可见文本不一致、状态前置(no-session / has-session / empty、
39
+ setup-required)都要写进备注列。
40
+ 4. **增量写台账**:按 [references/ledger-schema.md](references/ledger-schema.md)
41
+ 的表结构与分节规则追加/修订行,保留既有行历史(用备注标注失效原因,
42
+ 不删行)。
43
+ 5. **报告**:侦察完成后输出小结(新增/修订/失效行数、命中率、给 PRD 的
44
+ 可用锚点清单与禁用清单)。
45
+
46
+ ## 硬规则
47
+
48
+ - 只读:不点提交/保存/发送类控件、不创建会话以外的任何状态(「新建空白
49
+ 对话」CTA 允许点击一次以进入 has-session 状态,属于台账明确的状态夹具)。
50
+ - 命中 0 或命中数 >1 且无法用状态/作用域区分的字面,标记 `不可断言`,
51
+ 禁止进入 PRD `find` 断言。
52
+ - 台账每行必须带验证时间与当时 baseUrl;页面改版后旧行标 `失效` 而非删除。
53
+ - 不猜测:没跑过 `find` 的字面不进台账。
54
+ - PRD 正文(含背景/夹具段)不得出现会被需求 id 扫描捕获的形态:大写或小写的 REQ/BR/AC 前缀 + 连字符 + hash/序号(如夹具任务 id 里的 req 段、或自示范用的「req 加连字符 hash」字面)。夹具引用只写日期 + 短 hash(如 f03535aa),规则说明文字用中文描述或代码块包裹仍可能被扫——一律避免连写。
55
+ - PRD 验收标准只用**目录 AC id**(如 AC-FE-INS-005)作条目 id;不要写本地序号前缀(如 AC-003 加粗目录 id 的双 id 形态)——双 id 会被同时提取,生成器只引用其一,另一个落入 acceptanceCoverage.missing。
56
+
57
+ ## 输出
58
+
59
+ - `testcase/frontend/rag/ui-anchors.md`(增量更新)
60
+ - 侦察小结(对话内输出,不落盘;结论回写 PRD 草稿的锚点引用)
61
+
62
+ ## References
63
+
64
+ - [references/ledger-schema.md](references/ledger-schema.md) — 台账表结构与分节规则
65
+ - [references/recon-protocol.md](references/recon-protocol.md) — 逐状态侦察操作协议与陷阱清单
@@ -0,0 +1,62 @@
1
+ # 台账(ui-anchors.md)表结构与分节规则
2
+
3
+ ## 文件位置与命名
4
+
5
+ - 路径:`testcase/frontend/rag/ui-anchors.md`
6
+ - 编码:UTF-8,LF;表格用 GitHub Flavored Markdown。
7
+ - 台账是**增量账本**:修订不删除历史行;失效行改状态列并注明原因。
8
+
9
+ ## 顶层结构
10
+
11
+ ```markdown
12
+ # UI 锚点台账(frontend-test)
13
+
14
+ - baseUrl:<验证时的绝对 URL>
15
+ - 更新时间:<YYYY-MM-DD HH:mm(本地时区)>
16
+ - 维护:fe-test-ui-scout skill
17
+
18
+ ## <页面路由> · <状态>
19
+
20
+ | 字面(find 原文) | 命中 | 唯一 | 控件/位置 | 状态 | 备注 | 验证时间 |
21
+ | --- | --- | --- | --- | --- | --- | --- |
22
+ | 后端研发(默认 DAG) | 1 | 是 | workflow-kind-select option | 有效 | 括号必须带全 | 2026-08-22 15:00 |
23
+ ```
24
+
25
+ ## 分节规则
26
+
27
+ - 一节 = 一个「页面路由 × 状态」组合。路由用 hash 形式(`#/tasks`、
28
+ `#/chat`、`/inspect/#/dags`);状态至少区分:
29
+ - `no-session`(无会话 landing)
30
+ - `has-session`(已存在会话,composer 可见)
31
+ - `empty-session`(新建空会话,无消息)
32
+ - 页面特有态(如 `#/night · 无计划`、`#/recovery · 无候选`、
33
+ setup-required)随需追加,节名写清楚。
34
+ - 同一页面多个状态各自成节;不要把跨状态结果合并进一行。
35
+
36
+ ## 列语义
37
+
38
+ | 列 | 说明 |
39
+ | --- | --- |
40
+ | 字面(find 原文) | `playwright-cli find "<字面>"` 的唯一入参原文,逐字节照抄(含括号、空格、中点) |
41
+ | 命中 | 侦察时 `find` 实际返回的匹配数(0/1/N) |
42
+ | 唯一 | 是/否/不适用;命中=1 且作用于目标控件才算「是」 |
43
+ | 控件/位置 | 命中元素的角色与位置(如 `h2`、`oc-suggestion strong`、`aria-label`) |
44
+ | 状态 | `有效` / `失效` / `待复核`;失效须在备注注明原因与替代字面 |
45
+ | 备注 | 陷阱与前置:拆节点(kbd+span、text+code)、同名多控件、aria-label≠可见文本、状态前置、setup 门 |
46
+ | 验证时间 | YYYY-MM-DD HH:mm |
47
+
48
+ ## 有效 / 失效判定
49
+
50
+ - **有效**:命中=1、目标控件、字面在当前 build 下可复现。
51
+ - **不可断言**:命中 0(字面不存在或被拆节点)、命中 >1 且状态/作用域
52
+ 无法区分(如「发送」同时命中 tips 与按钮)。这类行保留在台账里,
53
+ 状态列写 `不可断言`,备注写原因——它们正是 PRD 的「禁用清单」。
54
+ - **失效**:页面改版后不再命中。不删行,改状态 + 备注(替代字面是什么)。
55
+
56
+ ## 与 PRD / 目录的关系
57
+
58
+ - PRD 每条 `find "..."` 断言的字面必须出自台账某行且该行状态为
59
+ `有效`(或备注给出拆节点断言替代方案,如 `find "taskKind="` 与
60
+ `find "frontend-test"` 二选一)。
61
+ - 目录 AC 的 live 校准注记引用台账行(日期 + 路由+状态),保持
62
+ 「AC ↔ 台账」双向可追溯。
@@ -0,0 +1,54 @@
1
+ # 逐状态侦察操作协议与陷阱清单
2
+
3
+ ## 前置检查
4
+
5
+ 1. 确认 SUT 健康:`curl -s -o /dev/null -w '%{http_code}' <baseUrl>/api/health`
6
+ 返回 200;非 200 先解决环境,不侦察。
7
+ 2. 确认 SUT 跑的是当前 build(改过 `src/**` 后必须 `npm run build` 并重启
8
+ SUT,否则台账记录的是旧页面)。
9
+ 3. 准备隔离 app-data(SUT 启动参数已带 `/tmp` 隔离目录时无需额外动作);
10
+ 侦察结束把 no-session 状态还原(关闭页面即可;若点了「新建空白对话」
11
+ 产生空会话,记录在案即可,不必清理 app-data)。
12
+
13
+ ## 每个页面 × 状态的标准动作序列
14
+
15
+ ```text
16
+ playwright-cli open --browser=chrome <baseUrl>/#/<route>
17
+ playwright-cli snapshot
18
+ playwright-cli find "<候选字面 1>"
19
+ playwright-cli find "<候选字面 2>"
20
+ ...
21
+ playwright-cli close
22
+ ```
23
+
24
+ - `snapshot` 先行:从 snapshot 里核对候选字面的真实渲染形态(是否拆节点、
25
+ 是否 aria-label、是否在折叠组内需先展开)。
26
+ - 每个候选字面必须真实 `find` 一次;snapshot 里「看起来有」不算数。
27
+ - 折叠组(如「工程边界(可选,人类确认)」):先记录折叠态的组标题字面,
28
+ 再展开后侦察组内字段;两种形态分两行记。
29
+ - 需要状态夹具的节(has-session):点一次「新建空白对话」CTA 进入;
30
+ 这是协议允许的唯一写动作。
31
+
32
+ ## 陷阱清单(出现即写进备注列)
33
+
34
+ | 陷阱 | 症状 | 台账记法 |
35
+ | --- | --- | --- |
36
+ | 拆节点 | `find "Enter 发送"` 0 命中;DOM 是 `<kbd>Enter</kbd><span>发送</span>` | 字面行记完整原文 + 命中 0 + 状态 `不可断言`;备注给出替代(`find "发送"` 或 `find "Enter"`)并说明拆法 |
37
+ | text+code 拆分 | `taskKind=frontend-test` 渲染为 `taskKind=` + `<code>frontend-test</code>` | 同上 |
38
+ | 同名多控件 | 「发送」同时命中 tips span 与发送按钮 aria-label | 命中 N>1,唯一=否;备注指明各自位置,PRD 需换更独特字面或限定状态 |
39
+ | 同名多行按钮(列表态) | 「重命名会话」在 3 行会话各自命中 1,全局命中 3;单会话态侦察时命中 1 | 备注标「会话数依赖」;断言法降级为 hover 目标行 → snapshot 解析该行真实 ref(存在即通过),禁止声明 find 命中数=1;或确保夹具为单行态(清 app-data) |
40
+ | aria-label ≠ 可见文本 | 控件可访问名是 aria-label,渲染文本不同 | 字面列记 aria-label 值,备注「aria-label;可见文本为 X」 |
41
+ | 无名 flex 子项 | flex 匿名文本无 name-from-content,find 不到 | 记录控件缺 aria-label → 这是**产品可修问题**,报告给修复决策树(页面象限),台账暂记 `不可断言` |
42
+ | 括号/全半角 | 「后端研发(默认 DAG)」少括号 0 命中 | 字面逐字节照抄;备注提醒 |
43
+ | 状态前置 | 控件只在 has-session / setup-required 等状态下存在 | 分节记录;PRD 引用时必须声明状态夹具 |
44
+ | 折叠组 | 字面在 `<details>` 内,未展开时 find 0 | 两种形态分行记录(折叠标题/展开后字段) |
45
+ | 瞬态文案 | 「正在加载…」类短暂存在 | 备注标「瞬态」,PRD 不引用 |
46
+
47
+ ## 输出纪律
48
+
49
+ - find 命中数以工具返回为准,不目测。
50
+ - 每节侦察完立即写台账(不攒到最后凭记忆写)。
51
+ - 侦察小结按 SKILL.md 第 5 步输出:新增/修订/失效行数、命中率、
52
+ PRD 可用锚点清单与禁用清单。
53
+ - 发现疑似产品缺陷(白屏、文案缺失、aria 错误):记入小结的「移交修复」
54
+ 段,不在台账内修,不因缺陷跳过其余候选字面的侦察。
@@ -5,12 +5,12 @@ description: >-
5
5
  references:
6
6
  - path: references/node-contracts.md
7
7
  required: true
8
- # Compact contract index must stay under skill total budget (~10k with body + other refs).
9
- # Headroom above current ~4k file; do not raise alone without checking totalMaxChars.
10
- maxChars: 6000
8
+ # Current file is ~6.3KB; cap aligned with actual size so the required
9
+ # reference injects untruncated.
10
+ maxChars: 7000
11
11
  - path: references/design-spec.md
12
12
  required: true
13
- maxChars: 3000
13
+ maxChars: 3500
14
14
  - path: references/code-standards.md
15
15
  required: true
16
16
  maxChars: 3000
@@ -6,9 +6,10 @@ Knowledge base, OpenSpec, and `ai_workspace/` are parallel specification sources
6
6
 
7
7
  1. Attempt the configured component/design knowledge-base query first when a
8
8
  connector is available.
9
- 2. Regardless of knowledge-base success/failure/timeout/no-match, also
10
- recursively search `openspec/schemas/`, `openspec/project-specs/`, and
11
- `ai_workspace/` for index files and relevant content.
9
+ 2. Regardless of knowledge-base success/failure/timeout/no-match, use the
10
+ generation-frozen local-spec capability context and cited candidate set. For
11
+ unresolved task terms, search the three local roots in a bounded way; do not
12
+ recursively read every supported file merely because it exists.
12
13
  3. Treat relevant matches from both sources as the current project's
13
14
  specification for this run.
14
15
  4. Only then use component source, tokens, stories, tests, and pages as
@@ -23,10 +24,13 @@ Connector format is TODO; attempt only an available connector, otherwise record
23
24
 
24
25
  ## openspec Fallback Procedure
25
26
 
26
- - Inspect `<repoRoot>/openspec/schemas/`, `<repoRoot>/openspec/project-specs/`,
27
- and `<repoRoot>/ai_workspace/` when present; enumerate supported files recursively.
28
- - Read indexes first, then search names/content using task, route, component,
29
- interaction, theme, token, and state terms.
27
+ - Start with the generation-frozen classified paths and `frontendOpenspec`
28
+ candidates. Read their indexes or relevant sections first, then use task,
29
+ route, component, interaction, theme, token, and state terms for bounded
30
+ local searches under `<repoRoot>/openspec/schemas/`,
31
+ `<repoRoot>/openspec/project-specs/`, and `<repoRoot>/ai_workspace/`.
32
+ - Do not turn local fallback into a full-directory read. If a searched path is
33
+ relevant, record why it was selected and read it in context.
30
34
  - Read matches in context; a filename-only hit is not a rule.
31
35
  - Record search terms, inspected/matched paths, headings or tight line ranges,
32
36
  applied rules, and conflicts.
@@ -5,27 +5,36 @@ Pre-write nodes are read-only. Preserve IDs, labels, commands, language, require
5
5
  ## Core nodes
6
6
 
7
7
  - **`frontend-contract-pi`**: `Scope`, `Non-goals`, `Acceptance Criteria`, `UI States`, `Target Runtime Environment`, `Risks`, `Verification Expectations`; no guessed requirements.
8
- - **`frontend-scout-pi`**: routes, components, tokens, data/API/Mock, scripts, tests, assets; fact vs inference vs gap. Search+read `openspec/schemas/`, `openspec/project-specs/`, `ai_workspace/` first. Output stack, styling, conventions, state/data, test entry points, reuse, risks.
9
- - **`frontend-plan-pi` + conditional design loop**: AC → observable `expectedOutcome`; interactions → `trigger`+`expectedBehavior`; include steps, in-bound files, UI states, reuse, deps, Mock/API, frozen verify entrypoints and integration gaps. DAG freezes a protected skeleton (`schemaVersion`, `sourceBinding`, `riskLevel`, `targets.files`, `mockApi.productionDefaultOff`). Model returns a **JSON-only** editable RFC 7386 patch (`frontend-implementation-contract-plan-patch-v1`) plus one `openspec-citations` block. Runtime compiles canonical `frontend-implementation-contract-v1` and archives raw/extracted/normalized/validation candidates; unresolved optional symbols are dropped and audited. No plan prose; gate renders plan.md. IDs+paths ≠ behavior semantics. Applicable UI states need behavior/implementation/verification; non-applicable states need reasons. Active Mock needs frozen commands; `auto` without confirmed capability/command → `not-needed`; `required` without command → generation-time blocked. Only `request-revision` runs revision+final review; small-risk gets one review.
8
+ - **`frontend-contract-pi`**(仅 cited 且候选非空):在既有 Markdown contract 的末尾输出一个 `frontend-openspec-selection-v1` JSON block,对 generation-frozen specRoots 候选逐一分类为 `required` / `relevant` / `irrelevant`;显式 `requiredReadPaths` 和任务源文件引用必须为 `required`。只读,不改 task.json,不新增节点。
9
+ - **`frontend-scout-pi`**: routes, components, tokens, data/API/Mock, scripts, tests, assets; fact vs inference vs gap. Start from the generation-frozen OpenSpec capability context and contract selection block; then perform bounded task-term searches only for unresolved relevant rules. Do not recursively read every candidate directory. Output stack, styling, conventions, state/data, test entry points, reuse, risks.
10
+ - **`frontend-plan-pi`**: AC → observable `expectedOutcome`; interactions → `trigger`+`expectedBehavior`; include steps, in-bound files, UI states, reuse, deps, Mock/API, frozen verify entrypoints and integration gaps. DAG freezes a protected skeleton (`schemaVersion`, `sourceBinding`, `riskLevel`, `targets.files`, `mockApi.productionDefaultOff`). The model commits the plan decision ledger incrementally through typed `record_*` tools (target-surface / component-choice / state-flow / data-flow / mock-api / design-deviation / dependency) and closes with exactly one `finalize_plan`; narrative prose stays free-form context and is never the compile authority. `frontend-design-policy-shell` assembles the canonical contract from committed facts (validated, fail-closed — no fenced-JSON text parsing) and materializes `frontend-implementation-contract-v1`; unresolved optional symbols are dropped and audited. `frontend-design-policy-shell` deterministically renders plan.md. IDs+paths ≠ behavior semantics. Applicable UI states need behavior/implementation/verification; non-applicable states need reasons. Active Mock needs frozen commands; `auto` without confirmed capability/command → `not-needed`; `required` without command → generation-time blocked.
10
11
  - `uiComponentChoices[]` (optional): `{purpose, component, decision, specReference, rationale}`; `specified` needs `specReference{path,section,line}`; `new` needs null ref+rationale.
11
- - **`frontend-prewrite-gate-shell`**: sole write authorization. Resolve effective plan (revised if revision ran, else original) + review; require exact pass; retain every REQ/BR/AC id; enforce Mock policy; validate schema/source binding+writeSet containment; materialize `contracts/frontend-implementation-contract.json` from the **single** fenced contract block (multiple candidates fail closed `invalid-output`). Fallback only when conditional primary absent; malformed primary fails closed. Generation-time blocked Mock yields one blocking shell node, no writer.
12
+ - **`frontend-design-policy-shell`**: read-only deterministic design check (UI coverage, evidence resolvable/fresh, Mock strategy bound to frozen commands, component choice `specReference`, targetPaths boundary, dependency authorization, UI state ownership, requirement coverage); passes by materializing the single authoritative `frontend-implementation-contract-v1`. Blocked ends the run without a model reformat step.
12
13
  - `verificationTarget.commandLabel` must be a **frozen command label** from `verifyEvidence.commandLabels`; others fail closed (`invalid-output`). Empty frozen set (no `run.json`) skips check.
13
14
  - `mockApi.strategy !== "not-needed"` fails closed when `mockCommandLabels` empty (`no authorized Mock verification commands`).
14
- - OpenSpec policy: `task.json.frontendOpenspec.policy` (default `cited`); candidates = `requiredReadPaths` task-source citations; effective plan must append one fenced `openspec-citations` block (one JSON `{"path","section","line"}` per line). Candidates non-empty but block missing/unparseable `openspec-citation-block-unparseable`; candidate not cited `openspec-not-cited`; cited path without read event `openspec-citation-not-read` (anti-fabrication). Empty candidates skip enforcement. `scan-strict` keeps full-read semantics (`openspec-not-read`); both modes keep `candidate-missing-drift`.
15
- - Component conformance: missing `uiComponentChoices` (frozen bucket non-empty + UI-visible work) → `component-choices-missing`; bad/uncited `specReference.path` → `component-spec-reference-invalid` / `component-spec-not-cited`.
16
- - revisionPatch mode: revision output is an RFC 7386 merge-patch delta applied on the original canonical contract node (`planFallbackFromNodeIds[0]`); protected skeleton paths remain immutable. Legacy DAGs keep full-contract semantics. Size guard: canonical serialization >64KB → `retryable-invalid` + `contract-too-large`.
15
+ - OpenSpec policy: `task.json.frontendOpenspec.policy` (default `cited`); `specRoots` defaults to OpenSpec + ai_workspace and may be fixed during task preparation from an explicitly named normative directory. In cited mode the selector determines the required subset; declared paths/task-source citations cannot be downgraded. Effective plan cites only its required/used specs in one fenced `openspec-citations` block. `scan-strict` keeps full-read semantics (`openspec-not-read`); both modes keep `candidate-missing-drift`.
16
+ - Component conformance: missing `uiComponentChoices` (frozen bucket non-empty + UI-visible work) → `component-choices-missing`; bad/uncited `specReference.path` → `component-spec-reference-invalid` / `component-spec-not-cited`; at least one `decision=specified` choice must bind to the frozen component/theme bucket → `component-spec-choice-unbound`.
17
+ - Canonical serialization (UTF-8) >64KB → `retryable-invalid` + `contract-too-large`.
18
+ - **`frontend-writer-admission-shell`**: sole write authorization for `frontend-implement-pi`. Derives and freezes the concrete writeSet (implementation targets ∪ non-static verification target files), pre-writer baseline, and admission digest (Mock strategy, allowed strategies digest, frozen command digest, `productionDefaultOff`, real-integration expectation); writes `contracts/frontend-writer-admission-result.json` (schemaId `frontend-writer-admission-shell-v1`). The scheduler checks before queueing and before executing the writer: only `admitted` releases it, otherwise SKIPPED + `frontend-prewrite-not-authorized`. Admission blocked (`candidate-contract-invalid` / `prewrite-blocked`) is forced `failed` at aggregation.
17
19
  - **`frontend-implement-pi`**: sole regular exclusive writer; uses `frontend-bounded-implement` (not this skill). Stay in `writeSet`; real requests default-on. Atomic handler/intercept/adapter with tests. Stop on forbidden paths/guesses. First line `IMPLEMENTATION_OUTCOME: changed|already-satisfied|blocked`; runtime checks against attributed diff. Optional mock-verify when frozen; static+behavior always; behavior must prove page consumption. `not-needed` keeps real integration pending unless real backend has fresh evidence.
18
20
 
21
+ ## Typed terminal protocol
22
+
23
+ - `frontend-plan-pi` commits incremental `record_*` facts plus one `finalize_plan` (plan).
24
+ - `frontend-design-review-pi` commits `approve_design` | `request_design_changes` (request carries typed issueCategory + evidenceRefs + findings).
25
+ - `frontend-review-pi` commits `approve_review` | `request_review_changes` (request carries typed issueCategory + evidenceRefs + findings).
26
+ - These committed typed tools replace `VERDICT:` first-line parsing; the verdict is consumed only as deterministic data input by downstream admission/closeout.
27
+
19
28
  ## Contract / trace / stages (M1–M2)
20
29
 
21
- - `frontend-verify-assess-shell` runs Mock/static/behavior groups, binds `verificationTargets` to command labels+evidence, writes `frontend-verification-trace.json`+`frontend-repair-assessment.json`; browser/visual stay `not-run`.
22
- - `frontend-review-context-shell` captures real diff; combines contract, effective trace, repair assessment, diff into `contracts/frontend-review-context.json`.
30
+ - `frontend-verify-shell` runs frozen lint/Mock/static/behavior groups, binds `verificationTargets` to command labels+evidence, writes `frontend-lint-assessment.json` + `frontend-verification-trace.json`; any verification failure is terminal and materializes `frontend-verify-failure-v1` (failureClass / failureOwner / restartPhase) for recovery routing — no same-run repair. Browser/visual stay `not-run`.
31
+ - `frontend-review-context-shell` captures the real diff after the pre-writer baseline; combines contract, lint assessment, effective trace, and diff into `contracts/frontend-review-context.json`.
23
32
  - Implement stages: (1) contract confirm (2) tests sync (3) component/UI (4) API/Mock (5) frozen checks (6) diff cleanup. Summary: Contract Ref, Changed Files, Requirements, UI States, Tests, Verification Attempts, Deviations, Residual Risks.
24
33
 
25
- ## Repair (M3)
34
+ ## Terminal failure & recovery (M3)
26
35
 
27
- Only `eligible=true` runs `frontend-repair-pi` (same writeSet; no re-spec; max 1 attempt) + `frontend-reverify-shell`. No-failure skips both. Non-repairable contract/path/dependency/credential/deploy/spec-unclear failures fail closed.
36
+ Verification, design-review, or final-review failure is terminal — no same-run repair. `frontend-verify-shell` materializes `frontend-verify-failure-v1`; `request_design_changes` / `request_review_changes` block downstream admission/closeout. Recovery routes through the recovery capsule / new-dag; there is no same-run repair branch.
28
37
 
29
38
  ## Risk & capability (M4–M6)
30
39
 
31
- Deterministic risk, no model; high-risk beats small; supervised never small. Standard/high-risk: 15 top-level nodes; small: 13 (omits revision + final review). Capability seed injects adapters; openspec specs and task sources outrank adapter guidance. A11y: static/component tools only when present; Browser a11y always not-run.
40
+ Deterministic risk, no model; high-risk beats small; supervised never small. Topology by shape: Micro 7 (`frontend-contract-import-shell` → `frontend-writer-admission-shell` → `frontend-implement-pi` → `frontend-verify-shell` → `frontend-review-context-shell` → `frontend-review-pi` → `frontend-closeout-shell`), Small 10 (main chain minus the independent `frontend-design-review-pi`), Standard/High 11 (`frontend-contract-pi` `frontend-scout-pi` `frontend-plan-pi` `frontend-design-policy-shell` `frontend-design-review-pi` `frontend-writer-admission-shell` → `frontend-implement-pi` → `frontend-verify-shell` → `frontend-review-context-shell` → `frontend-review-pi` → `frontend-closeout-shell`). Capability seed injects adapters; openspec specs and task sources outrank adapter guidance. A11y: static/component tools only when present; Browser a11y always not-run.
@@ -100,6 +100,8 @@ loop-agent --help
100
100
  ```bash
101
101
  loop-agent inspect # 当前 repo(自动检测)
102
102
  loop-agent --repo-root /path/to/target-repo inspect # 指定 repo
103
+ loop-agent inspect next # 当前 npm next 绑定(version / SHA / unpublished commits)
104
+ loop-agent inspect next --json
103
105
  ```
104
106
 
105
107
  ### 健康检查
@@ -132,13 +134,18 @@ SDK 回归或 SDK 可选依赖不可用时用 `cli-only` 诊断。CLI fallback
132
134
 
133
135
  ```bash
134
136
  loop-agent init instructions --repo-root <target-repo>
137
+ loop-agent init model-catalog --repo-root <target-repo> --json
135
138
  loop-agent init --repo-root <target-repo> --profile full --merge \
136
- --provider deepseek --model deepseek/deepseek-v4-flash
139
+ --low-model deepseek/deepseek-chat \
140
+ --med-model deepseek/deepseek-v4-flash \
141
+ --high-model deepseek/deepseek-v4-pro
137
142
  loop-agent init doctor --repo-root <target-repo>
138
143
  loop-agent init check-update --repo-root <target-repo> --json
139
144
  loop-agent init check-update --repo-root <target-repo> --markdown
140
145
  loop-agent init update --repo-root <target-repo> --bootstrap-surface
141
146
  loop-agent init update --repo-root <target-repo> --apply-safe
147
+ loop-agent init update --repo-root <target-repo> --apply-safe \
148
+ --low-model <current-or-selected> --med-model <current-or-selected> --high-model <current-or-selected>
142
149
  loop-agent init reconcile --repo-root <target-repo> [--json|--markdown] # compatibility low-level diagnostic
143
150
  loop-agent init upgrade --repo-root <target-repo> --json
144
151
  loop-agent init upgrade --repo-root <target-repo> --run-id <run-id> --status --json
@@ -146,13 +153,13 @@ loop-agent init upgrade --repo-root <target-repo> --run-id <run-id> --continue -
146
153
  loop-agent init upgrade --repo-root <target-repo> --run-id <run-id> --report --markdown
147
154
  ```
148
155
 
149
- fresh init 将精确引用写到 `executors.pi.LOW/MED/HIGH`。fresh `loop-agent init` 会将合格且完整的 `provider/model` 引用投影到 `executors.pi.LOW/MED/HIGH`,即 `executors.pi.LOW`、`executors.pi.MED` `executors.pi.HIGH`。三个 tier 齐全时不会写入 `defaultModel`,也不会恢复或建议旧顶层 routing 字段。
156
+ fresh init 将精确引用写到 `executors.pi.LOW/MED/HIGH`。模型先运行只读 `init model-catalog`:Pi SDK runtime 与 OpenCode 非 verbose 列表由代码确定性发现,只向模型暴露 provider/model、能力/成本/窗口元数据、Pi setup、兼容性与 credential-ready 布尔值,不暴露原始配置和凭据。已有档位仍存在于 Pi 时原样保留,只重选失效/缺失档位;代码在 update 时拒绝替换仍有效档位。Pi 无定义时先向用户补齐必要模型元数据并帮助字段级配置 `models.json`;只有认证缺失时保留 harness 并修复认证。三个 tier 齐全时不会写入 `defaultModel`,也不会恢复旧顶层 routing 字段。
150
157
 
151
158
  `init check-update` 是只读升级报告,用于发现目标项目是否落后于当前包内初始化 surface。输出会区分 deterministic actions、model merge tasks、human decisions 和 recommended next。`--markdown` 会渲染可直接交给模型执行的合并指引,包含 `allowedPaths`、`forbiddenPaths`、`mergeRules` 和 `verification`。
152
159
 
153
- `init update --bootstrap-surface` 为旧项目写入 `.harness/init-surface.json` 的 `inferred-baseline`,不伪装成历史 recorded baseline。`init update --apply-safe` 只执行确定性安全动作:补缺失文件、创建目录、刷新 managed block;已有但无法确认与当前包一致的文件会进入 model merge tasks,不会被覆盖。模型 merge 只会自动展开不含 tier 或其他旧字段的唯一、完整且可证明等价的旧 `defaultModel: "provider/model"`;顶层 `model`、`models`、`modelProfiles`、`modelRouting`、`verify`、`sequentialWorkflowRole`、部分/对象 tier、bare model、冲突 provider 或未知语义均 fail closed,并保留原始 `harness.json`。
160
+ `init update --bootstrap-surface` 为旧项目写入 `.harness/init-surface.json` 的 `inferred-baseline`,不伪装成历史 recorded baseline。`init update --apply-safe` 只执行确定性安全动作:补缺失文件、创建目录、刷新 managed block;`harness.json` 永不走整文件 `refresh-owned-file`,即使它仍与旧 recorded hash 一致,也只允许字段级确定性迁移或 bounded merge。带完整三档参数的 update 只替换 catalog 已判定失效的档位,仍有效档位的对象形态与 thinking 配置保持不变。模型 merge 只会自动展开不含 tier 或其他旧字段的唯一、完整且可证明等价的旧 `defaultModel: "provider/model"`;其他歧义均 fail closed 并保留原文件。
154
161
 
155
- `init upgrade` 是写入型升级入口:首次运行冻结 controller identity、检查 npm latest、扫描 surface、自动执行安全动作,并把 run facts 持久化到 `.harness/init-upgrades/<run-id>/`。`--status` `--report` 严格只读;`--continue` 会重新核验 identity、当前 hash 与不变量。版本选择、无法判定冲突、凭据、破坏性删除或 active writer 才进入 human decision。普通语义冲突返回具体单文件 `allowedPaths` merge task,主会话必须合并后继续,不能把 `needs-model-merge` 作为完成。默认管理项目级 OpenCode/Pi recovery 与 `.pi/settings.json` nested merge,保留显式 disabled,Pi trust 后才加载;默认不读写 `~/.pi/agent/settings.json`。旧 `init reconcile` 保留为兼容低层诊断。
162
+ `init upgrade` 是写入型升级入口:首次运行冻结 controller identity 与脱敏模型目录,检查 npm latest,并用一次 surface scan 形成完整计划。固定投影按 recorded bytes 确定性增删改;recorded `harness.json` 按属性树增删字段并保留项目值、同步 schema;项目自适应内容由模型决定。多个语义合并在同一计划中串行排队,每次仍只开放一个文件的 `allowedPaths`;接受后直接激活下一项,不重复版本发现/规划,队列清空后只跑一次最终验证。确定性收敛最多两轮。完成态的 verification/report 均有 controller hash anchor,`--status`、`--report` 和已完成 run 的幂等 `--continue` 只读持久化证据,不重新跑项目校验。默认管理项目级 OpenCode/Pi recovery 与 `.pi/settings.json` nested merge,默认不读写 `~/.pi/agent/settings.json`。旧 `init reconcile` 保留为兼容低层诊断。
156
163
 
157
164
  升级在 managed block 收敛后生成只读 gitignore 迁移评估(`.harness/init-upgrades/<run-id>/gitignore-migration.json`):inventory 已跟踪的 `.harness/**` 与 `.agents/**`,建议 index-only 命令 `git rm -r --cached --ignore-unmatch .harness` / `.agents`(只改 index、保留工作区文件;`scripts/**` 与 `ai_workspace/loop-agent/**` 永不出现)。`.agents` 已跟踪内容会触发 human-decision 暂停等待审查;非 Git 仓库、git 查询失败或存在 staged 条目时 `status: blocked` 且无命令建议。controller 不自动修改 Git index。
158
165
 
@@ -16,7 +16,7 @@
16
16
 
17
17
  对于默认 `standard` 任务,生成器先读取 `source/需求.md` 中的结构化任务类型,再结合 `allowedPaths` 与 React/Next/Vue 强工程证据做确定性分类。确认是前端项目且任务不是明确后端、前后端混合、排除前端或仅文档/测试范围时,默认选择 `frontend-implementation` DAG,不依赖需求关键词。分类不会把普通后端实现路由到 `backend-test`;显式 profile、`workflowPolicy` 或 supervised quality gate 只记录治理强度,不把已识别的前端业务 workflow 换回通用模板。
18
18
 
19
- 前端专用链保留独立 contract/scout;plan 同时选择 Mock/API 策略并输出结构化 implementation contract。design initial pass 直接使用原计划,只有 request-revision 才运行 revision/final review;small-risk 只执行一次 design review。`frontend-prewrite-gate-shell` 合并生效 verdictREQ/BR/AC 覆盖、Mock policy contract 物化,是唯一写入授权。实现后 `frontend-verify-assess-shell` 合并 Mock/static/behavior/trace/assessment;只有 `eligible=true` 才运行同 writeSet repair `frontend-reverify-shell`。`frontend-review-context-shell` 绑定真实 diff 与有效验证证据后再 review/closeout。standard/high-risk15 个顶层节点,small-risk 13;绿色路径执行 11 个节点、7 Pi。生成期 blocked Mock 只生成一个确定性阻塞节点且没有 writer。
19
+ 前端专用链保留独立 contract/scout;plan 通过增量 typed `record_*` 工具提交决策账本并以唯一 `finalize_plan` 收口(无 fenced JSON 文本协议)。写授权由 `frontend-design-policy-shell`(确定性设计检查 + 物化唯一 authoritative `frontend-implementation-contract-v1`)与 `frontend-writer-admission-shell`(唯一写入授权:派生并冻结 writeSet、pre-writer baselineadmission digest,`admitted` 才放行 writer)承接;design review final review 的权威终态是 committed typed tool(`approve_design|request_design_changes`、`approve_review|request_review_changes`)。实现后 `frontend-verify-shell` 合并 lint/Mock/static/behavior/trace;任何验证失败都是 terminal,物化 `frontend-verify-failure-v1` recovery 路由,无同 run repair。`frontend-review-context-shell` 绑定真实 diff 与有效验证证据后再 review/closeout。拓扑按 shape 路由:Micro 7 / Small 10 / Standard-High 11 个顶层节点;Standard/High 完整主链为 `frontend-contract-pi` → `frontend-scout-pi` → `frontend-plan-pi` → `frontend-design-policy-shell` → `frontend-design-review-pi` → `frontend-writer-admission-shell` → `frontend-implement-pi` → `frontend-verify-shell` → `frontend-review-context-shell` `frontend-review-pi` `frontend-closeout-shell`,Small 去掉独立 `frontend-design-review-pi`,Micro `frontend-contract-import-shell` → `frontend-writer-admission-shell` `frontend-implement-pi` `frontend-verify-shell` `frontend-review-context-shell` → `frontend-review-pi` → `frontend-closeout-shell`。生成期 blocked Mock 只生成一个确定性阻塞节点且没有 writer。
20
20
 
21
21
  > Backend-test Markdown-first:先由确定性环境 Shell 检查 clean env 中 Python/pytest、常见配置、conftest/fixture、test root、server entry 和 HTML renderer,失败时不消耗模型调用。随后 Pi 生成中文 README 索引与模块用例卡片并独立 Review `testcase/md/**`。第 4 节点只把前置条件、操作步骤、预期结果作为必选章节,并检查 Case ID、业务 AC、步骤/预期和占位措辞;不校验需求来源引用有效性或 Markdown sensitive-shaped 内容。pytest writer 为每次真实接口调用记录脱敏、有界的请求 method/URL/参数摘要和响应 status/body 摘要。第 6 节点只扫描每条 Case 明确映射的 pytest 脚本,同时支持模块级函数和 pytest class 方法,并把缺少请求/响应日志、递归脱敏或有界截断证据记录为 advisory。第 4/6 节点均写 PASS/FAIL findings 而不阻断后续;pytest 仍只运行一次,生成 JUnit,并把 Markdown 名称/场景/脚本映射与同一 JUnit 合成为按测试概览、质量校验、失败概览、用例执行明细和技术证据组织的中文 self-contained HTML 与 Markdown facts。最终 Pi 按固定简洁结构汇总 advisory 状态、执行事实和 L-5 结论。active 流程不要求模型生成 backend-test 业务 JSON。
22
22