agentic-flow-kit 1.8.7 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (936) hide show
  1. package/README.md +113 -148
  2. package/bin/flow-kit.mjs +5 -2
  3. package/locales/en/installer.json +25 -0
  4. package/locales/en/overrides/.agents/commands/deploy.md +52 -0
  5. package/locales/en/overrides/.agents/commands/maintain.md +76 -0
  6. package/locales/en/overrides/.agents/commands/new-task.md +89 -0
  7. package/locales/en/overrides/.agents/commands/pipeline-run.md +56 -0
  8. package/locales/en/overrides/.agents/commands/pr-review.md +30 -0
  9. package/locales/en/overrides/.agents/commands/review.md +93 -0
  10. package/locales/en/overrides/.agents/commands/stages.md +256 -0
  11. package/locales/en/overrides/.agents/notes/pr-review-cost-discipline.md +48 -0
  12. package/locales/en/overrides/.agents/notes/process-order-rule.md +21 -0
  13. package/locales/en/overrides/.agents/notes/runtime-env.local.example.md +21 -0
  14. package/locales/en/overrides/.agents/pipelines/_TEMPLATE.md +75 -0
  15. package/locales/en/overrides/.agents/pipelines/pipeline-closing.md +28 -0
  16. package/locales/en/overrides/.agents/roles/implementer.md +28 -0
  17. package/locales/en/overrides/.agents/roles/independent-reviewer.md +39 -0
  18. package/locales/en/overrides/.agents/roles/pr-adjudicator.md +27 -0
  19. package/locales/en/overrides/.agents/roles/pr-finder.md +51 -0
  20. package/locales/en/overrides/.agents/roles/pr-verifier.md +44 -0
  21. package/locales/en/overrides/.agents/roles/ui-verifier.md +22 -0
  22. package/locales/en/overrides/.agents/roles/wiki-compiler.md +47 -0
  23. package/locales/en/overrides/.agents/skills/arena/SKILL.md +50 -0
  24. package/locales/en/overrides/.agents/skills/autonomous-run/SKILL.md +45 -0
  25. package/locales/en/overrides/.agents/skills/blast-radius/SKILL.md +53 -0
  26. package/locales/en/overrides/.agents/skills/csharp-quality-gates/SKILL.md +109 -0
  27. package/locales/en/overrides/.agents/skills/dev-tour/SKILL.md +98 -0
  28. package/locales/en/overrides/.agents/skills/dream/SKILL.md +63 -0
  29. package/locales/en/overrides/.agents/skills/eval/SKILL.md +42 -0
  30. package/locales/en/overrides/.agents/skills/interrogate/SKILL.md +49 -0
  31. package/locales/en/overrides/.agents/skills/number-vetting/SKILL.md +35 -0
  32. package/locales/en/overrides/.agents/skills/page-refactor/SKILL.md +192 -0
  33. package/locales/en/overrides/.agents/skills/papercut-fix/SKILL.md +92 -0
  34. package/locales/en/overrides/.agents/skills/pause-safely/SKILL.md +31 -0
  35. package/locales/en/overrides/.agents/skills/perf/SKILL.md +42 -0
  36. package/locales/en/overrides/.agents/skills/review-method/SKILL.md +92 -0
  37. package/locales/en/overrides/.agents/skills/session-resume/SKILL.md +69 -0
  38. package/locales/en/overrides/.agents/skills/shipyard-workflow-closeout/SKILL.md +109 -0
  39. package/locales/en/overrides/.agents/skills/show-me-your-work/SKILL.md +57 -0
  40. package/locales/en/overrides/.agents/skills/tfs-pr/SKILL.md +95 -0
  41. package/locales/en/overrides/.agents/skills/verify-ui/SKILL.md +116 -0
  42. package/locales/en/overrides/.agents/skills/visual-parity/SKILL.md +35 -0
  43. package/locales/en/overrides/.agents/skills/why/SKILL.md +56 -0
  44. package/locales/en/overrides/.agents/skills/wiki/SKILL.md +53 -0
  45. package/locales/en/overrides/.claude/agents/implementer.md +9 -0
  46. package/locales/en/overrides/.claude/agents/independent-reviewer.md +10 -0
  47. package/locales/en/overrides/.claude/agents/pr-adjudicator.md +10 -0
  48. package/locales/en/overrides/.claude/agents/pr-finder.md +10 -0
  49. package/locales/en/overrides/.claude/agents/pr-verifier.md +10 -0
  50. package/locales/en/overrides/.claude/agents/ui-verifier.md +10 -0
  51. package/locales/en/overrides/.claude/agents/wiki-compiler.md +10 -0
  52. package/locales/en/overrides/.claude/commands/wf-deploy.md +8 -0
  53. package/locales/en/overrides/.claude/commands/wf-maintain.md +8 -0
  54. package/locales/en/overrides/.claude/commands/wf-new-task.md +8 -0
  55. package/locales/en/overrides/.claude/commands/wf-pipeline-run.md +8 -0
  56. package/locales/en/overrides/.claude/commands/wf-pr-review.md +8 -0
  57. package/locales/en/overrides/.claude/commands/wf-review.md +8 -0
  58. package/locales/en/overrides/.claude/commands/wf-stages.md +8 -0
  59. package/locales/en/overrides/.cline/agents/implementer.yml +13 -0
  60. package/locales/en/overrides/.cline/agents/independent-reviewer.yml +11 -0
  61. package/locales/en/overrides/.cline/agents/pr-adjudicator.yml +11 -0
  62. package/locales/en/overrides/.cline/agents/pr-finder.yml +11 -0
  63. package/locales/en/overrides/.cline/agents/pr-verifier.yml +11 -0
  64. package/locales/en/overrides/.cline/agents/ui-verifier.yml +8 -0
  65. package/locales/en/overrides/.cline/agents/wiki-compiler.yml +14 -0
  66. package/locales/en/overrides/.cline/workflows/wf-deploy.md +8 -0
  67. package/locales/en/overrides/.cline/workflows/wf-maintain.md +8 -0
  68. package/locales/en/overrides/.cline/workflows/wf-new-task.md +8 -0
  69. package/locales/en/overrides/.cline/workflows/wf-pipeline-run.md +8 -0
  70. package/locales/en/overrides/.cline/workflows/wf-pr-review.md +8 -0
  71. package/locales/en/overrides/.cline/workflows/wf-review.md +8 -0
  72. package/locales/en/overrides/.cline/workflows/wf-stages.md +8 -0
  73. package/locales/en/overrides/.codebuddy/CODEBUDDY.md +7 -0
  74. package/locales/en/overrides/.codebuddy/agents/implementer.md +9 -0
  75. package/locales/en/overrides/.codebuddy/agents/independent-reviewer.md +10 -0
  76. package/locales/en/overrides/.codebuddy/agents/pr-adjudicator.md +10 -0
  77. package/locales/en/overrides/.codebuddy/agents/pr-finder.md +10 -0
  78. package/locales/en/overrides/.codebuddy/agents/pr-verifier.md +10 -0
  79. package/locales/en/overrides/.codebuddy/agents/ui-verifier.md +11 -0
  80. package/locales/en/overrides/.codebuddy/agents/wiki-compiler.md +10 -0
  81. package/locales/en/overrides/.codebuddy/commands/wf-deploy.md +8 -0
  82. package/locales/en/overrides/.codebuddy/commands/wf-maintain.md +8 -0
  83. package/locales/en/overrides/.codebuddy/commands/wf-new-task.md +8 -0
  84. package/locales/en/overrides/.codebuddy/commands/wf-pipeline-run.md +8 -0
  85. package/locales/en/overrides/.codebuddy/commands/wf-pr-review.md +8 -0
  86. package/locales/en/overrides/.codebuddy/commands/wf-review.md +8 -0
  87. package/locales/en/overrides/.codebuddy/commands/wf-stages.md +8 -0
  88. package/locales/en/overrides/.codebuddy/rules/backend-scope.md +5 -0
  89. package/locales/en/overrides/.codebuddy/rules/frontend-scope.md +5 -0
  90. package/locales/en/overrides/.codebuddy/skills/tfs-pr/SKILL.md +6 -0
  91. package/locales/en/overrides/.commandcode/agents/implementer.md +10 -0
  92. package/locales/en/overrides/.commandcode/agents/independent-reviewer.md +9 -0
  93. package/locales/en/overrides/.commandcode/agents/pr-adjudicator.md +9 -0
  94. package/locales/en/overrides/.commandcode/agents/pr-finder.md +9 -0
  95. package/locales/en/overrides/.commandcode/agents/pr-verifier.md +9 -0
  96. package/locales/en/overrides/.commandcode/agents/ui-verifier.md +10 -0
  97. package/locales/en/overrides/.commandcode/agents/wiki-compiler.md +10 -0
  98. package/locales/en/overrides/.commandcode/commands/wf-deploy.md +7 -0
  99. package/locales/en/overrides/.commandcode/commands/wf-maintain.md +7 -0
  100. package/locales/en/overrides/.commandcode/commands/wf-new-task.md +7 -0
  101. package/locales/en/overrides/.commandcode/commands/wf-pipeline-run.md +7 -0
  102. package/locales/en/overrides/.commandcode/commands/wf-pr-review.md +7 -0
  103. package/locales/en/overrides/.commandcode/commands/wf-review.md +7 -0
  104. package/locales/en/overrides/.commandcode/commands/wf-stages.md +7 -0
  105. package/locales/en/overrides/.opencode/agents/implementer.md +51 -0
  106. package/locales/en/overrides/.opencode/agents/independent-reviewer.md +18 -0
  107. package/locales/en/overrides/.opencode/agents/pr-adjudicator.md +18 -0
  108. package/locales/en/overrides/.opencode/agents/pr-finder.md +18 -0
  109. package/locales/en/overrides/.opencode/agents/pr-verifier.md +18 -0
  110. package/locales/en/overrides/.opencode/agents/ui-verifier.md +45 -0
  111. package/locales/en/overrides/.opencode/agents/wiki-compiler.md +51 -0
  112. package/locales/en/overrides/.opencode/commands/wf-deploy.md +7 -0
  113. package/locales/en/overrides/.opencode/commands/wf-maintain.md +7 -0
  114. package/locales/en/overrides/.opencode/commands/wf-new-task.md +7 -0
  115. package/locales/en/overrides/.opencode/commands/wf-pipeline-run.md +7 -0
  116. package/locales/en/overrides/.opencode/commands/wf-pr-review.md +7 -0
  117. package/locales/en/overrides/.opencode/commands/wf-review.md +7 -0
  118. package/locales/en/overrides/.opencode/commands/wf-stages.md +7 -0
  119. package/locales/en/overrides/.pi/agents/implementer.md +11 -0
  120. package/locales/en/overrides/.pi/agents/independent-reviewer.md +12 -0
  121. package/locales/en/overrides/.pi/agents/pr-adjudicator.md +12 -0
  122. package/locales/en/overrides/.pi/agents/pr-finder.md +11 -0
  123. package/locales/en/overrides/.pi/agents/pr-verifier.md +11 -0
  124. package/locales/en/overrides/.pi/agents/ui-verifier.md +11 -0
  125. package/locales/en/overrides/.pi/agents/wiki-compiler.md +11 -0
  126. package/locales/en/overrides/.qoder/agents/implementer.md +9 -0
  127. package/locales/en/overrides/.qoder/agents/independent-reviewer.md +10 -0
  128. package/locales/en/overrides/.qoder/agents/pr-adjudicator.md +10 -0
  129. package/locales/en/overrides/.qoder/agents/pr-finder.md +10 -0
  130. package/locales/en/overrides/.qoder/agents/pr-verifier.md +10 -0
  131. package/locales/en/overrides/.qoder/agents/ui-verifier.md +10 -0
  132. package/locales/en/overrides/.qoder/agents/wiki-compiler.md +10 -0
  133. package/locales/en/overrides/.qoder/commands/wf-deploy.md +8 -0
  134. package/locales/en/overrides/.qoder/commands/wf-maintain.md +8 -0
  135. package/locales/en/overrides/.qoder/commands/wf-new-task.md +8 -0
  136. package/locales/en/overrides/.qoder/commands/wf-pipeline-run.md +8 -0
  137. package/locales/en/overrides/.qoder/commands/wf-pr-review.md +8 -0
  138. package/locales/en/overrides/.qoder/commands/wf-review.md +8 -0
  139. package/locales/en/overrides/.qoder/commands/wf-stages.md +8 -0
  140. package/locales/en/overrides/.qwen/agents/implementer.md +10 -0
  141. package/locales/en/overrides/.qwen/agents/independent-reviewer.md +10 -0
  142. package/locales/en/overrides/.qwen/agents/pr-adjudicator.md +10 -0
  143. package/locales/en/overrides/.qwen/agents/pr-finder.md +10 -0
  144. package/locales/en/overrides/.qwen/agents/pr-verifier.md +10 -0
  145. package/locales/en/overrides/.qwen/agents/ui-verifier.md +10 -0
  146. package/locales/en/overrides/.qwen/agents/wiki-compiler.md +13 -0
  147. package/locales/en/overrides/.qwen/commands/wf-deploy.md +7 -0
  148. package/locales/en/overrides/.qwen/commands/wf-maintain.md +7 -0
  149. package/locales/en/overrides/.qwen/commands/wf-new-task.md +7 -0
  150. package/locales/en/overrides/.qwen/commands/wf-pipeline-run.md +7 -0
  151. package/locales/en/overrides/.qwen/commands/wf-pr-review.md +7 -0
  152. package/locales/en/overrides/.qwen/commands/wf-review.md +7 -0
  153. package/locales/en/overrides/.qwen/commands/wf-stages.md +7 -0
  154. package/locales/en/overrides/.trae/agents/implementer.md +9 -0
  155. package/locales/en/overrides/.trae/agents/independent-reviewer.md +10 -0
  156. package/locales/en/overrides/.trae/agents/pr-adjudicator.md +10 -0
  157. package/locales/en/overrides/.trae/agents/pr-finder.md +10 -0
  158. package/locales/en/overrides/.trae/agents/pr-verifier.md +10 -0
  159. package/locales/en/overrides/.trae/agents/ui-verifier.md +10 -0
  160. package/locales/en/overrides/.trae/agents/wiki-compiler.md +10 -0
  161. package/locales/en/overrides/.trae/commands/wf-deploy.md +8 -0
  162. package/locales/en/overrides/.trae/commands/wf-maintain.md +8 -0
  163. package/locales/en/overrides/.trae/commands/wf-new-task.md +8 -0
  164. package/locales/en/overrides/.trae/commands/wf-pipeline-run.md +8 -0
  165. package/locales/en/overrides/.trae/commands/wf-pr-review.md +8 -0
  166. package/locales/en/overrides/.trae/commands/wf-review.md +8 -0
  167. package/locales/en/overrides/.trae/commands/wf-stages.md +8 -0
  168. package/locales/en/overrides/.trae/rules/backend-scope.md +5 -0
  169. package/locales/en/overrides/.trae/rules/frontend-scope.md +5 -0
  170. package/locales/en/overrides/.zcode/agents/implementer.md +8 -0
  171. package/locales/en/overrides/.zcode/agents/independent-reviewer.md +8 -0
  172. package/locales/en/overrides/.zcode/agents/ui-verifier.md +8 -0
  173. package/locales/en/overrides/.zcode/agents/wiki-compiler.md +8 -0
  174. package/locales/en/overrides/AGENTS.md +52 -0
  175. package/locales/en/overrides/CLAUDE.md +20 -0
  176. package/locales/en/overrides/wiki/INDEX.md +33 -0
  177. package/locales/en/overrides/wiki/drafts-archive/README.md +6 -0
  178. package/locales/en/overrides/wiki//346/225/260/346/215/256/347/273/264/346/212/244/README.md +22 -0
  179. package/locales/en/overrides/wiki//347/237/245/350/257/206/346/262/211/346/267/200/346/200/273/350/247/210.html +230 -0
  180. package/locales/en/overrides/workflow/INDEX.md +13 -0
  181. package/locales/en/overrides/workflow/README.md +67 -0
  182. package/locales/en/overrides/workflow/archive/README.md +3 -0
  183. package/locales/en/overrides/workflow/delegations.md +4 -0
  184. package/locales/en/overrides/workflow/effective-policy.md +4 -0
  185. package/locales/en/overrides/workflow/gate-cost.md +4 -0
  186. package/locales/en/overrides/workflow/incidents/_TEMPLATE.md +49 -0
  187. package/locales/en/overrides/workflow/intents/_TEMPLATE.md +43 -0
  188. package/locales/en/overrides/workflow/metrics.md +4 -0
  189. package/locales/en/overrides/workflow/papercuts.md +3 -0
  190. package/locales/en/overrides/workflow/plans/_TEMPLATE.md +47 -0
  191. package/locales/en/overrides/workflow/regression-checklist.md +3 -0
  192. package/locales/en/overrides/workflow/specs/_TEMPLATE.md +86 -0
  193. package/locales/en/overrides/workflow/verify-evidence.md +4 -0
  194. package/locales/zh-CN/installer.json +25 -0
  195. package/package.json +23 -36
  196. package/schemas/install-manifest.schema.json +24 -0
  197. package/schemas/project-profile.schema.json +28 -0
  198. package/src/cli/args.mjs +41 -0
  199. package/src/cli/main.mjs +72 -0
  200. package/src/commands/add-host.mjs +29 -0
  201. package/src/commands/doctor.mjs +80 -0
  202. package/src/commands/install.mjs +149 -0
  203. package/src/commands/upgrade.mjs +30 -0
  204. package/src/git/config.mjs +61 -0
  205. package/src/hosts/registry.mjs +26 -0
  206. package/src/i18n.mjs +32 -0
  207. package/src/install/apply.mjs +124 -0
  208. package/src/install/manifest.mjs +40 -0
  209. package/src/install/plan.mjs +83 -0
  210. package/src/render/tokens.mjs +58 -0
  211. package/templates/engine/.agents/board/dev-tour.html +1940 -0
  212. package/templates/engine/.agents/board/index.html +835 -0
  213. package/templates/engine/.agents/commands/deploy.md +52 -0
  214. package/templates/engine/.agents/commands/maintain.md +76 -0
  215. package/templates/engine/.agents/commands/new-task.md +89 -0
  216. package/templates/engine/.agents/commands/pipeline-run.md +56 -0
  217. package/templates/engine/.agents/commands/pr-review.md +30 -0
  218. package/templates/engine/.agents/commands/review.md +93 -0
  219. package/templates/engine/.agents/commands/stages.md +256 -0
  220. package/templates/engine/.agents/config/commit-msg-rules.json +7 -0
  221. package/templates/engine/.agents/config/engine.json +11 -0
  222. package/templates/engine/.agents/config/gate-budgets.json +5 -0
  223. package/templates/engine/.agents/config/mechanism-registry.md +62 -0
  224. package/templates/engine/.agents/config/model-roles.json +26 -0
  225. package/templates/engine/.agents/config/model-roles.local.example.json +7 -0
  226. package/templates/engine/.agents/config/pr-review.json +67 -0
  227. package/templates/engine/.agents/config/principles.md +32 -0
  228. package/templates/engine/.agents/config/workflow-enums.txt +25 -0
  229. package/templates/engine/.agents/hooks/check-architecture.mjs +168 -0
  230. package/templates/engine/.agents/hooks/check-audit-fields.mjs +56 -0
  231. package/templates/engine/.agents/hooks/check-commit-plan-ref.mjs +84 -0
  232. package/templates/engine/.agents/hooks/check-confirm-order.mjs +281 -0
  233. package/templates/engine/.agents/hooks/check-delete-guard.mjs +63 -0
  234. package/templates/engine/.agents/hooks/check-notify-guard.mjs +55 -0
  235. package/templates/engine/.agents/hooks/commit-check.cjs +208 -0
  236. package/templates/engine/.agents/hooks/gates.json +33 -0
  237. package/templates/engine/.agents/hooks/local-pre-commit.mjs +27 -0
  238. package/templates/engine/.agents/hooks/pre-shell-check.cjs +361 -0
  239. package/templates/engine/.agents/hooks/pre-write-check.cjs +220 -0
  240. package/templates/engine/.agents/notes/gate-diet.md +75 -0
  241. package/templates/engine/.agents/notes/pr-review-cost-discipline.md +48 -0
  242. package/templates/engine/.agents/notes/process-order-rule.md +21 -0
  243. package/templates/engine/.agents/notes/runtime-env.local.example.md +21 -0
  244. package/templates/engine/.agents/pipelines/_TEMPLATE.md +75 -0
  245. package/templates/engine/.agents/pipelines/pipeline-closing.md +28 -0
  246. package/templates/engine/.agents/pipelines//347/244/272/344/276/213-/345/271/266/350/241/214/345/256/236/347/216/260/350/257/204/345/256/241.md +16 -0
  247. package/templates/engine/.agents/roles/implementer.md +28 -0
  248. package/templates/engine/.agents/roles/independent-reviewer.md +39 -0
  249. package/templates/engine/.agents/roles/pr-adjudicator.md +27 -0
  250. package/templates/engine/.agents/roles/pr-finder.md +51 -0
  251. package/templates/engine/.agents/roles/pr-verifier.md +44 -0
  252. package/templates/engine/.agents/roles/ui-verifier.md +22 -0
  253. package/templates/engine/.agents/roles/wiki-compiler.md +47 -0
  254. package/templates/engine/.agents/scripts/agg-delegations.mjs +439 -0
  255. package/templates/engine/.agents/scripts/agg-judgments.mjs +301 -0
  256. package/templates/engine/.agents/scripts/agg-kb-usage.mjs +196 -0
  257. package/templates/engine/.agents/scripts/append-compensation.mjs +109 -0
  258. package/templates/engine/.agents/scripts/archive-sweep.mjs +229 -0
  259. package/templates/engine/.agents/scripts/auto-tour.mjs +77 -0
  260. package/templates/engine/.agents/scripts/board-port.mjs +17 -0
  261. package/templates/engine/.agents/scripts/check-doc-body-drift.mjs +102 -0
  262. package/templates/engine/.agents/scripts/check-engine-artifacts.mjs +54 -0
  263. package/templates/engine/.agents/scripts/check-hook-wiring.mjs +75 -0
  264. package/templates/engine/.agents/scripts/check-lane-surface.mjs +393 -0
  265. package/templates/engine/.agents/scripts/check-ledger-doc-presence.mjs +71 -0
  266. package/templates/engine/.agents/scripts/check-ledger-invariant.mjs +873 -0
  267. package/templates/engine/.agents/scripts/check-local-gate-config.mjs +95 -0
  268. package/templates/engine/.agents/scripts/check-local-gates.mjs +248 -0
  269. package/templates/engine/.agents/scripts/check-loop-kit.mjs +550 -0
  270. package/templates/engine/.agents/scripts/check-loop-rev-semantics.mjs +59 -0
  271. package/templates/engine/.agents/scripts/check-loop-sections/s01-pairing.mjs +116 -0
  272. package/templates/engine/.agents/scripts/check-loop-sections/s03x-regr-evidence.mjs +38 -0
  273. package/templates/engine/.agents/scripts/check-loop-sections/s08-acceptance.mjs +130 -0
  274. package/templates/engine/.agents/scripts/check-loop-sections/s15-fingerprint.mjs +152 -0
  275. package/templates/engine/.agents/scripts/check-loop-sections/s23-convergence-audit.mjs +182 -0
  276. package/templates/engine/.agents/scripts/check-loop-sections/s29-regr-enforcer.mjs +69 -0
  277. package/templates/engine/.agents/scripts/check-loop.mjs +131 -0
  278. package/templates/engine/.agents/scripts/check-regression-consistency.mjs +252 -0
  279. package/templates/engine/.agents/scripts/check-schema-residue.mjs +144 -0
  280. package/templates/engine/.agents/scripts/check-scope-range.mjs +156 -0
  281. package/templates/engine/.agents/scripts/check-scope.mjs +121 -0
  282. package/templates/engine/.agents/scripts/check-scripts-syntax.mjs +138 -0
  283. package/templates/engine/.agents/scripts/check-secrets-lite.mjs +239 -0
  284. package/templates/engine/.agents/scripts/check-secrets.mjs +104 -0
  285. package/templates/engine/.agents/scripts/check-test-impact-map.mjs +107 -0
  286. package/templates/engine/.agents/scripts/ci-assert-frontend-deps.mjs +42 -0
  287. package/templates/engine/.agents/scripts/ci-commit-hygiene.mjs +86 -0
  288. package/templates/engine/.agents/scripts/ci-install-gitleaks.mjs +75 -0
  289. package/templates/engine/.agents/scripts/ci-push-before.mjs +49 -0
  290. package/templates/engine/.agents/scripts/closeout.mjs +127 -0
  291. package/templates/engine/.agents/scripts/compact-ledgers.mjs +70 -0
  292. package/templates/engine/.agents/scripts/confirm-doc.mjs +737 -0
  293. package/templates/engine/.agents/scripts/delegation-presence.mjs +38 -0
  294. package/templates/engine/.agents/scripts/dev-tour-model.mjs +1398 -0
  295. package/templates/engine/.agents/scripts/dream-run.mjs +283 -0
  296. package/templates/engine/.agents/scripts/ensure-board.mjs +187 -0
  297. package/templates/engine/.agents/scripts/failure-attribution.mjs +11 -0
  298. package/templates/engine/.agents/scripts/fill-intent.mjs +60 -0
  299. package/templates/engine/.agents/scripts/fill-plan.mjs +105 -0
  300. package/templates/engine/.agents/scripts/fill-spec.mjs +72 -0
  301. package/templates/engine/.agents/scripts/gen-dev-tour.mjs +191 -0
  302. package/templates/engine/.agents/scripts/gen-effective-policy.mjs +113 -0
  303. package/templates/engine/.agents/scripts/gen-engine-map.mjs +292 -0
  304. package/templates/engine/.agents/scripts/gen-narrative-claims.mjs +275 -0
  305. package/templates/engine/.agents/scripts/gen-verify-evidence.mjs +115 -0
  306. package/templates/engine/.agents/scripts/gen-wiki-board.mjs +221 -0
  307. package/templates/engine/.agents/scripts/gen-workflow-index.mjs +201 -0
  308. package/templates/engine/.agents/scripts/gen-workflow-metrics.mjs +692 -0
  309. package/templates/engine/.agents/scripts/git-merge-jsonl.mjs +62 -0
  310. package/templates/engine/.agents/scripts/judgment-ledger.mjs +68 -0
  311. package/templates/engine/.agents/scripts/kb-cache-evict.mjs +50 -0
  312. package/templates/engine/.agents/scripts/kb-search.mjs +423 -0
  313. package/templates/engine/.agents/scripts/lane-surface-audit.mjs +145 -0
  314. package/templates/engine/.agents/scripts/ledger-loader.mjs +77 -0
  315. package/templates/engine/.agents/scripts/ledger-shards.mjs +346 -0
  316. package/templates/engine/.agents/scripts/metrics-core.mjs +131 -0
  317. package/templates/engine/.agents/scripts/pipeline-run.mjs +964 -0
  318. package/templates/engine/.agents/scripts/policy.mjs +250 -0
  319. package/templates/engine/.agents/scripts/pr-create.mjs +54 -0
  320. package/templates/engine/.agents/scripts/pr-review.mjs +1551 -0
  321. package/templates/engine/.agents/scripts/quote-grounding.mjs +233 -0
  322. package/templates/engine/.agents/scripts/regen-ledgers.mjs +59 -0
  323. package/templates/engine/.agents/scripts/rewrite-file.mjs +135 -0
  324. package/templates/engine/.agents/scripts/rule-budget.mjs +246 -0
  325. package/templates/engine/.agents/scripts/stage-gates.mjs +812 -0
  326. package/templates/engine/.agents/scripts/test-impact-lib.mjs +451 -0
  327. package/templates/engine/.agents/scripts/test-impact.mjs +105 -0
  328. package/templates/engine/.agents/scripts/trust-mode.mjs +130 -0
  329. package/templates/engine/.agents/scripts/verify-scope.mjs +163 -0
  330. package/templates/engine/.agents/scripts/verify-wiki-consistency.mjs +264 -0
  331. package/templates/engine/.agents/scripts/verify.mjs +847 -0
  332. package/templates/engine/.agents/scripts/warn-format.mjs +57 -0
  333. package/templates/engine/.agents/scripts/wf-journal.mjs +230 -0
  334. package/templates/engine/.agents/scripts/workflow-board-server.mjs +696 -0
  335. package/templates/engine/.agents/scripts/workflow-enums.mjs +70 -0
  336. package/templates/engine/.agents/scripts/workflows-check.mjs +260 -0
  337. package/templates/engine/.agents/settings.json +33 -0
  338. package/templates/engine/.agents/skills/arena/SKILL.md +50 -0
  339. package/templates/engine/.agents/skills/autonomous-run/SKILL.md +45 -0
  340. package/templates/engine/.agents/skills/blast-radius/SKILL.md +53 -0
  341. package/templates/engine/.agents/skills/csharp-quality-gates/SKILL.md +109 -0
  342. package/templates/engine/.agents/skills/csharp-quality-gates/scripts/quality-gates.mjs +430 -0
  343. package/templates/engine/.agents/skills/dev-tour/SKILL.md +98 -0
  344. package/templates/engine/.agents/skills/dream/SKILL.md +63 -0
  345. package/templates/engine/.agents/skills/eval/SKILL.md +42 -0
  346. package/templates/engine/.agents/skills/eval/scripts/eval-score.mjs +59 -0
  347. package/templates/engine/.agents/skills/interrogate/SKILL.md +49 -0
  348. package/templates/engine/.agents/skills/interrogate/scripts/interrogate-plan.mjs +72 -0
  349. package/templates/engine/.agents/skills/number-vetting/SKILL.md +35 -0
  350. package/templates/engine/.agents/skills/page-refactor/SKILL.md +192 -0
  351. package/templates/engine/.agents/skills/page-refactor/references/templates.md +989 -0
  352. package/templates/engine/.agents/skills/papercut-fix/SKILL.md +92 -0
  353. package/templates/engine/.agents/skills/papercut-fix/references/response-template.md +81 -0
  354. package/templates/engine/.agents/skills/pause-safely/SKILL.md +31 -0
  355. package/templates/engine/.agents/skills/perf/SKILL.md +42 -0
  356. package/templates/engine/.agents/skills/review-method/SKILL.md +92 -0
  357. package/templates/engine/.agents/skills/review-method/golden-set.md +61 -0
  358. package/templates/engine/.agents/skills/session-resume/SKILL.md +69 -0
  359. package/templates/engine/.agents/skills/session-resume/scripts/worktree-audit.mjs +110 -0
  360. package/templates/engine/.agents/skills/shipyard-workflow-closeout/SKILL.md +109 -0
  361. package/templates/engine/.agents/skills/show-me-your-work/SKILL.md +57 -0
  362. package/templates/engine/.agents/skills/show-me-your-work/references/decision-log-template.tsv +1 -0
  363. package/templates/engine/.agents/skills/show-me-your-work/scripts/decision-log.mjs +24 -0
  364. package/templates/engine/.agents/skills/tfs-pr/SKILL.md +95 -0
  365. package/templates/engine/.agents/skills/tfs-pr/scripts/tfs-pr.mjs +947 -0
  366. package/templates/engine/.agents/skills/verify-ui/SKILL.md +116 -0
  367. package/templates/engine/.agents/skills/verify-ui/features/.gitkeep +0 -0
  368. package/templates/engine/.agents/skills/verify-ui/scripts/gen-verify-feature.mjs +114 -0
  369. package/templates/engine/.agents/skills/visual-parity/SKILL.md +35 -0
  370. package/templates/engine/.agents/skills/why/SKILL.md +56 -0
  371. package/templates/engine/.agents/skills/why/scripts/why-evidence.mjs +74 -0
  372. package/templates/engine/.agents/skills/wiki/SKILL.md +53 -0
  373. package/templates/engine/.agents/tests/auto-tour.test.mjs +82 -0
  374. package/templates/engine/.agents/tests/dev-tour-model.test.mjs +452 -0
  375. package/templates/engine/.agents/tests/fill-plan-lite.test.mjs +46 -0
  376. package/templates/engine/.agents/tests/fixtures/dev-tour/plan-bare-mid-path.md +14 -0
  377. package/templates/engine/.agents/tests/fixtures/dev-tour/plan-bare-mid-path.model.json +179 -0
  378. package/templates/engine/.agents/tests/fixtures/dev-tour/plan-fake-path-guard.md +21 -0
  379. package/templates/engine/.agents/tests/fixtures/dev-tour/plan-fake-path-guard.model.json +194 -0
  380. package/templates/engine/.agents/tests/fixtures/dev-tour/plan-h3-batches.md +20 -0
  381. package/templates/engine/.agents/tests/fixtures/dev-tour/plan-h3-batches.model.json +207 -0
  382. package/templates/engine/.agents/tests/fixtures/dev-tour/plan-issues.md +24 -0
  383. package/templates/engine/.agents/tests/fixtures/dev-tour/plan-issues.model.json +225 -0
  384. package/templates/engine/.agents/tests/fixtures/dev-tour/plan-l1-single-file.md +15 -0
  385. package/templates/engine/.agents/tests/fixtures/dev-tour/plan-l1-single-file.model.json +160 -0
  386. package/templates/engine/.agents/tests/fixtures/dev-tour/plan-layer-word-unique.md +29 -0
  387. package/templates/engine/.agents/tests/fixtures/dev-tour/plan-layer-word-unique.model.json +266 -0
  388. package/templates/engine/.agents/tests/fixtures/dev-tour/plan-layers-fallback.md +17 -0
  389. package/templates/engine/.agents/tests/fixtures/dev-tour/plan-layers-fallback.model.json +207 -0
  390. package/templates/engine/.agents/tests/fixtures/dev-tour/plan-numbered-steps.md +39 -0
  391. package/templates/engine/.agents/tests/fixtures/dev-tour/plan-numbered-steps.model.json +359 -0
  392. package/templates/engine/.agents/tests/fixtures/dev-tour/plan-path-guess.md +16 -0
  393. package/templates/engine/.agents/tests/fixtures/dev-tour/plan-path-guess.model.json +131 -0
  394. package/templates/engine/.agents/tests/fixtures/dev-tour/plan-table-rows.md +29 -0
  395. package/templates/engine/.agents/tests/fixtures/dev-tour/plan-table-rows.model.json +190 -0
  396. package/templates/engine/.agents/tests/fixtures/dev-tour/plan-task-checkboxes.md +39 -0
  397. package/templates/engine/.agents/tests/fixtures/dev-tour/plan-task-checkboxes.model.json +362 -0
  398. package/templates/engine/.agents/tests/fixtures/dev-tour/plan-union-label.md +24 -0
  399. package/templates/engine/.agents/tests/fixtures/dev-tour/plan-union-label.model.json +224 -0
  400. package/templates/engine/.agents/tests/tools/gen-synthetic-plan.mjs +108 -0
  401. package/templates/git/.gitattributes +22 -0
  402. package/templates/git/.githooks/commit-msg +72 -0
  403. package/templates/git/.githooks/post-commit +62 -0
  404. package/templates/git/.githooks/pre-commit +197 -0
  405. package/templates/git/.githooks/pre-merge-commit +28 -0
  406. package/templates/git/.githooks/pre-push +201 -0
  407. package/templates/git/.gitignore.engine +11 -0
  408. package/templates/hosts/claude/CLAUDE.md +55 -0
  409. package/templates/hosts/claude/agents/implementer.md +7 -0
  410. package/templates/hosts/claude/agents/independent-reviewer.md +8 -0
  411. package/templates/hosts/claude/agents/pr-adjudicator.md +8 -0
  412. package/templates/hosts/claude/agents/pr-finder.md +8 -0
  413. package/templates/hosts/claude/agents/pr-verifier.md +8 -0
  414. package/templates/hosts/claude/agents/ui-verifier.md +8 -0
  415. package/templates/hosts/claude/agents/wiki-compiler.md +8 -0
  416. package/templates/hosts/claude/commands/wf-deploy.md +10 -0
  417. package/templates/hosts/claude/commands/wf-maintain.md +10 -0
  418. package/templates/hosts/claude/commands/wf-new-task.md +10 -0
  419. package/templates/hosts/claude/commands/wf-pipeline-run.md +10 -0
  420. package/templates/hosts/claude/commands/wf-pr-review.md +10 -0
  421. package/templates/hosts/claude/commands/wf-review.md +10 -0
  422. package/templates/hosts/claude/commands/wf-stages.md +10 -0
  423. package/templates/hosts/claude/settings.json +57 -0
  424. package/templates/hosts/cline/agents/implementer.yml +15 -0
  425. package/templates/hosts/cline/agents/independent-reviewer.yml +20 -0
  426. package/templates/hosts/cline/agents/pr-adjudicator.yml +18 -0
  427. package/templates/hosts/cline/agents/pr-finder.yml +19 -0
  428. package/templates/hosts/cline/agents/pr-verifier.yml +18 -0
  429. package/templates/hosts/cline/agents/ui-verifier.yml +17 -0
  430. package/templates/hosts/cline/agents/wiki-compiler.yml +20 -0
  431. package/templates/hosts/cline/hooks/PreToolUse.cjs +260 -0
  432. package/templates/hosts/cline/workflows/wf-deploy.md +8 -0
  433. package/templates/hosts/cline/workflows/wf-maintain.md +8 -0
  434. package/templates/hosts/cline/workflows/wf-new-task.md +8 -0
  435. package/templates/hosts/cline/workflows/wf-pipeline-run.md +8 -0
  436. package/templates/hosts/cline/workflows/wf-pr-review.md +8 -0
  437. package/templates/hosts/cline/workflows/wf-review.md +8 -0
  438. package/templates/hosts/cline/workflows/wf-stages.md +8 -0
  439. package/templates/hosts/codebuddy/CODEBUDDY.md +7 -0
  440. package/templates/hosts/codebuddy/agents/implementer.md +9 -0
  441. package/templates/hosts/codebuddy/agents/independent-reviewer.md +10 -0
  442. package/templates/hosts/codebuddy/agents/pr-adjudicator.md +10 -0
  443. package/templates/hosts/codebuddy/agents/pr-finder.md +10 -0
  444. package/templates/hosts/codebuddy/agents/pr-verifier.md +10 -0
  445. package/templates/hosts/codebuddy/agents/ui-verifier.md +11 -0
  446. package/templates/hosts/codebuddy/agents/wiki-compiler.md +10 -0
  447. package/templates/hosts/codebuddy/commands/wf-deploy.md +10 -0
  448. package/templates/hosts/codebuddy/commands/wf-maintain.md +10 -0
  449. package/templates/hosts/codebuddy/commands/wf-new-task.md +10 -0
  450. package/templates/hosts/codebuddy/commands/wf-pipeline-run.md +10 -0
  451. package/templates/hosts/codebuddy/commands/wf-pr-review.md +10 -0
  452. package/templates/hosts/codebuddy/commands/wf-review.md +10 -0
  453. package/templates/hosts/codebuddy/commands/wf-stages.md +10 -0
  454. package/templates/hosts/codebuddy/rules/backend-scope.md +5 -0
  455. package/templates/hosts/codebuddy/rules/frontend-scope.md +5 -0
  456. package/templates/hosts/codebuddy/settings.json +31 -0
  457. package/templates/hosts/codebuddy/skills/tfs-pr/SKILL.md +6 -0
  458. package/templates/hosts/commandcode/agents/implementer.md +10 -0
  459. package/templates/hosts/commandcode/agents/independent-reviewer.md +9 -0
  460. package/templates/hosts/commandcode/agents/pr-adjudicator.md +9 -0
  461. package/templates/hosts/commandcode/agents/pr-finder.md +9 -0
  462. package/templates/hosts/commandcode/agents/pr-verifier.md +9 -0
  463. package/templates/hosts/commandcode/agents/ui-verifier.md +11 -0
  464. package/templates/hosts/commandcode/agents/wiki-compiler.md +10 -0
  465. package/templates/hosts/commandcode/commands/wf-deploy.md +9 -0
  466. package/templates/hosts/commandcode/commands/wf-maintain.md +9 -0
  467. package/templates/hosts/commandcode/commands/wf-new-task.md +9 -0
  468. package/templates/hosts/commandcode/commands/wf-pipeline-run.md +9 -0
  469. package/templates/hosts/commandcode/commands/wf-pr-review.md +9 -0
  470. package/templates/hosts/commandcode/commands/wf-review.md +9 -0
  471. package/templates/hosts/commandcode/commands/wf-stages.md +9 -0
  472. package/templates/hosts/commandcode/settings.json +59 -0
  473. package/templates/hosts/opencode/agents/implementer.md +51 -0
  474. package/templates/hosts/opencode/agents/independent-reviewer.md +18 -0
  475. package/templates/hosts/opencode/agents/pr-adjudicator.md +18 -0
  476. package/templates/hosts/opencode/agents/pr-finder.md +18 -0
  477. package/templates/hosts/opencode/agents/pr-verifier.md +18 -0
  478. package/templates/hosts/opencode/agents/ui-verifier.md +45 -0
  479. package/templates/hosts/opencode/agents/wiki-compiler.md +51 -0
  480. package/templates/hosts/opencode/commands/wf-deploy.md +7 -0
  481. package/templates/hosts/opencode/commands/wf-maintain.md +7 -0
  482. package/templates/hosts/opencode/commands/wf-new-task.md +7 -0
  483. package/templates/hosts/opencode/commands/wf-pipeline-run.md +7 -0
  484. package/templates/hosts/opencode/commands/wf-pr-review.md +7 -0
  485. package/templates/hosts/opencode/commands/wf-review.md +7 -0
  486. package/templates/hosts/opencode/commands/wf-stages.md +7 -0
  487. package/templates/hosts/pi/agents/implementer.md +10 -0
  488. package/templates/hosts/pi/agents/independent-reviewer.md +11 -0
  489. package/templates/hosts/pi/agents/pr-adjudicator.md +11 -0
  490. package/templates/hosts/pi/agents/pr-finder.md +10 -0
  491. package/templates/hosts/pi/agents/pr-verifier.md +10 -0
  492. package/templates/hosts/pi/agents/ui-verifier.md +10 -0
  493. package/templates/hosts/pi/agents/wiki-compiler.md +10 -0
  494. package/templates/hosts/pi/extensions/lib/gates-core.cjs +196 -0
  495. package/templates/hosts/pi/extensions/shipyard-gates.ts +95 -0
  496. package/templates/hosts/pi/settings.json +5 -0
  497. package/templates/hosts/qoder/agents/implementer.md +9 -0
  498. package/templates/hosts/qoder/agents/independent-reviewer.md +10 -0
  499. package/templates/hosts/qoder/agents/pr-adjudicator.md +10 -0
  500. package/templates/hosts/qoder/agents/pr-finder.md +10 -0
  501. package/templates/hosts/qoder/agents/pr-verifier.md +10 -0
  502. package/templates/hosts/qoder/agents/ui-verifier.md +10 -0
  503. package/templates/hosts/qoder/agents/wiki-compiler.md +10 -0
  504. package/templates/hosts/qoder/commands/wf-deploy.md +8 -0
  505. package/templates/hosts/qoder/commands/wf-maintain.md +8 -0
  506. package/templates/hosts/qoder/commands/wf-new-task.md +8 -0
  507. package/templates/hosts/qoder/commands/wf-pipeline-run.md +8 -0
  508. package/templates/hosts/qoder/commands/wf-pr-review.md +8 -0
  509. package/templates/hosts/qoder/commands/wf-review.md +8 -0
  510. package/templates/hosts/qoder/commands/wf-stages.md +8 -0
  511. package/templates/hosts/qoder/settings.json +63 -0
  512. package/templates/hosts/qwen/agents/implementer.md +8 -0
  513. package/templates/hosts/qwen/agents/independent-reviewer.md +8 -0
  514. package/templates/hosts/qwen/agents/pr-adjudicator.md +8 -0
  515. package/templates/hosts/qwen/agents/pr-finder.md +8 -0
  516. package/templates/hosts/qwen/agents/pr-verifier.md +8 -0
  517. package/templates/hosts/qwen/agents/ui-verifier.md +8 -0
  518. package/templates/hosts/qwen/agents/wiki-compiler.md +11 -0
  519. package/templates/hosts/qwen/commands/wf-deploy.md +9 -0
  520. package/templates/hosts/qwen/commands/wf-maintain.md +9 -0
  521. package/templates/hosts/qwen/commands/wf-new-task.md +9 -0
  522. package/templates/hosts/qwen/commands/wf-pipeline-run.md +9 -0
  523. package/templates/hosts/qwen/commands/wf-pr-review.md +9 -0
  524. package/templates/hosts/qwen/commands/wf-review.md +9 -0
  525. package/templates/hosts/qwen/commands/wf-stages.md +9 -0
  526. package/templates/hosts/qwen/settings.json +59 -0
  527. package/templates/hosts/trae/agents/implementer.md +9 -0
  528. package/templates/hosts/trae/agents/independent-reviewer.md +10 -0
  529. package/templates/hosts/trae/agents/pr-adjudicator.md +10 -0
  530. package/templates/hosts/trae/agents/pr-finder.md +10 -0
  531. package/templates/hosts/trae/agents/pr-verifier.md +10 -0
  532. package/templates/hosts/trae/agents/ui-verifier.md +10 -0
  533. package/templates/hosts/trae/agents/wiki-compiler.md +10 -0
  534. package/templates/hosts/trae/commands/wf-deploy.md +8 -0
  535. package/templates/hosts/trae/commands/wf-maintain.md +8 -0
  536. package/templates/hosts/trae/commands/wf-new-task.md +8 -0
  537. package/templates/hosts/trae/commands/wf-pipeline-run.md +8 -0
  538. package/templates/hosts/trae/commands/wf-pr-review.md +8 -0
  539. package/templates/hosts/trae/commands/wf-review.md +8 -0
  540. package/templates/hosts/trae/commands/wf-stages.md +8 -0
  541. package/templates/hosts/trae/hooks.json +17 -0
  542. package/templates/hosts/trae/rules/backend-scope.md +5 -0
  543. package/templates/hosts/trae/rules/frontend-scope.md +5 -0
  544. package/templates/hosts/zcode/agents/implementer.md +8 -0
  545. package/templates/hosts/zcode/agents/independent-reviewer.md +8 -0
  546. package/templates/hosts/zcode/agents/ui-verifier.md +8 -0
  547. package/templates/hosts/zcode/agents/wiki-compiler.md +8 -0
  548. package/templates/hosts/zcode/config.json +31 -0
  549. package/templates/hosts/zcode/workflows/code-review.dwf.ts +428 -0
  550. package/templates/hosts/zcode/workflows/fullchain-flow-test.dwf.ts +898 -0
  551. package/templates/profiles/dotnet/.agents/config/lane-surfaces.txt +33 -0
  552. package/templates/profiles/dotnet/.agents/config/rule-budgets.txt +84 -0
  553. package/templates/profiles/dotnet/.agents/config/test-impact-map.json +590 -0
  554. package/templates/profiles/dotnet/.agents/config/workflow-modules.txt +18 -0
  555. package/templates/profiles/dotnet/.agents/hooks/commit-check.config.json +18 -0
  556. package/templates/profiles/dotnet/.agents/hooks/delete-guard-allowlist.txt +28 -0
  557. package/templates/profiles/dotnet/.agents/notes/runtime-env.md +178 -0
  558. package/templates/profiles/dotnet/AGENTS.md +52 -0
  559. package/templates/profiles/empty/.agents/config/lane-surfaces.txt +12 -0
  560. package/templates/profiles/empty/.agents/config/rule-budgets.txt +3 -0
  561. package/templates/profiles/empty/.agents/config/test-impact-map.json +8 -0
  562. package/templates/profiles/empty/.agents/config/workflow-modules.txt +2 -0
  563. package/templates/profiles/empty/.agents/hooks/commit-check.config.json +5 -0
  564. package/templates/profiles/empty/.agents/hooks/delete-guard-allowlist.txt +2 -0
  565. package/templates/profiles/empty/.agents/notes/runtime-env.md +44 -0
  566. package/templates/profiles/empty/AGENTS.md +52 -0
  567. package/templates/profiles/node/.agents/config/lane-surfaces.txt +12 -0
  568. package/templates/profiles/node/.agents/config/rule-budgets.txt +3 -0
  569. package/templates/profiles/node/.agents/config/test-impact-map.json +8 -0
  570. package/templates/profiles/node/.agents/config/workflow-modules.txt +2 -0
  571. package/templates/profiles/node/.agents/hooks/commit-check.config.json +5 -0
  572. package/templates/profiles/node/.agents/hooks/delete-guard-allowlist.txt +2 -0
  573. package/templates/profiles/node/.agents/notes/runtime-env.md +44 -0
  574. package/templates/profiles/node/AGENTS.md +52 -0
  575. package/templates/scaffold/wiki/INDEX.md +33 -0
  576. package/templates/scaffold/wiki/drafts-archive/README.md +7 -0
  577. package/templates/scaffold/wiki//344/272/247/345/223/201/351/234/200/346/261/202/.gitkeep +0 -0
  578. package/templates/scaffold/wiki//345/274/200/345/217/221/346/226/271/346/241/210/.gitkeep +0 -0
  579. package/templates/scaffold/wiki//346/225/260/346/215/256/347/273/264/346/212/244/README.md +22 -0
  580. package/templates/scaffold/wiki//346/265/213/350/257/225/346/212/245/345/221/212/.gitkeep +0 -0
  581. package/templates/scaffold/wiki//347/224/250/346/210/267/345/212/237/350/203/275/.gitkeep +0 -0
  582. package/templates/scaffold/wiki//347/237/245/350/257/206/346/262/211/346/267/200/346/200/273/350/247/210.html +230 -0
  583. package/templates/scaffold/wiki//351/241/271/347/233/256/350/247/204/350/214/203/.gitkeep +0 -0
  584. package/templates/scaffold/workflow/INDEX.md +13 -0
  585. package/templates/scaffold/workflow/README.md +67 -0
  586. package/templates/scaffold/workflow/archive/README.md +3 -0
  587. package/templates/scaffold/workflow/delegations.d/.gitkeep +0 -0
  588. package/templates/scaffold/workflow/delegations.md +4 -0
  589. package/templates/scaffold/workflow/effective-policy.md +4 -0
  590. package/templates/scaffold/workflow/evidence/.gitkeep +0 -0
  591. package/templates/scaffold/workflow/gate-cost.md +4 -0
  592. package/templates/scaffold/workflow/gate-metrics.d/.gitkeep +0 -0
  593. package/templates/scaffold/workflow/incidents/_TEMPLATE.md +49 -0
  594. package/templates/scaffold/workflow/intents/_TEMPLATE.md +43 -0
  595. package/templates/scaffold/workflow/metrics.md +4 -0
  596. package/templates/scaffold/workflow/papercuts.d/.gitkeep +0 -0
  597. package/templates/scaffold/workflow/papercuts.md +3 -0
  598. package/templates/scaffold/workflow/plans/_TEMPLATE.md +47 -0
  599. package/templates/scaffold/workflow/regression-checklist.d/.gitkeep +0 -0
  600. package/templates/scaffold/workflow/regression-checklist.md +3 -0
  601. package/templates/scaffold/workflow/research/.gitkeep +0 -0
  602. package/templates/scaffold/workflow/reviews.d/.gitkeep +0 -0
  603. package/templates/scaffold/workflow/specs/_TEMPLATE.md +87 -0
  604. package/templates/scaffold/workflow/verify-evidence/.gitkeep +0 -0
  605. package/templates/scaffold/workflow/verify-evidence.md +4 -0
  606. package/LICENSE +0 -21
  607. package/modules/gates/README.md +0 -25
  608. package/modules/gates/dotnet-ca/README.md +0 -24
  609. package/modules/gates/dotnet-ca/check-architecture.sh +0 -150
  610. package/modules/gates/dotnet-ca/local-pre-commit.line +0 -1
  611. package/modules/gates/generated-readonly/README.md +0 -13
  612. package/modules/gates/generated-readonly/check-generated.mjs +0 -61
  613. package/modules/gates/generated-readonly/local-pre-commit.line +0 -1
  614. package/modules/gates/node-layer/README.md +0 -9
  615. package/modules/gates/node-layer/check-node-layer.mjs +0 -29
  616. package/modules/gates/node-layer/local-pre-commit.line +0 -1
  617. package/modules/gates/py-import/README.md +0 -9
  618. package/modules/gates/py-import/check-py-import.mjs +0 -30
  619. package/modules/gates/py-import/local-pre-commit.line +0 -1
  620. package/modules/gates/test-protect/README.md +0 -23
  621. package/modules/gates/test-protect/check-test-protect.mjs +0 -64
  622. package/modules/gates/test-protect/local-pre-commit.line +0 -1
  623. package/modules/hosts/README.md +0 -57
  624. package/modules/hosts/claude/agents/implementer.md +0 -51
  625. package/modules/hosts/claude/agents/independent-reviewer.md +0 -49
  626. package/modules/hosts/claude/agents/ui-verifier.md +0 -48
  627. package/modules/hosts/claude/commands/wf-build.md +0 -69
  628. package/modules/hosts/claude/commands/wf-deploy.md +0 -58
  629. package/modules/hosts/claude/commands/wf-design.md +0 -49
  630. package/modules/hosts/claude/commands/wf-gate-checklist.md +0 -77
  631. package/modules/hosts/claude/commands/wf-maintain.md +0 -76
  632. package/modules/hosts/claude/commands/wf-new-task.md +0 -102
  633. package/modules/hosts/claude/commands/wf-pipeline-run.md +0 -35
  634. package/modules/hosts/claude/commands/wf-plan.md +0 -40
  635. package/modules/hosts/claude/commands/wf-review.md +0 -49
  636. package/modules/hosts/claude/commands/wf-sync-hosts.md +0 -64
  637. package/modules/hosts/claude/commands/wf-test.md +0 -68
  638. package/modules/hosts/codex/agents/implementer.md +0 -51
  639. package/modules/hosts/codex/agents/independent-reviewer.md +0 -49
  640. package/modules/hosts/codex/agents/ui-verifier.md +0 -48
  641. package/modules/hosts/codex/commands/wf-build.md +0 -69
  642. package/modules/hosts/codex/commands/wf-deploy.md +0 -58
  643. package/modules/hosts/codex/commands/wf-design.md +0 -49
  644. package/modules/hosts/codex/commands/wf-gate-checklist.md +0 -77
  645. package/modules/hosts/codex/commands/wf-maintain.md +0 -76
  646. package/modules/hosts/codex/commands/wf-new-task.md +0 -102
  647. package/modules/hosts/codex/commands/wf-pipeline-run.md +0 -35
  648. package/modules/hosts/codex/commands/wf-plan.md +0 -40
  649. package/modules/hosts/codex/commands/wf-review.md +0 -49
  650. package/modules/hosts/codex/commands/wf-sync-hosts.md +0 -64
  651. package/modules/hosts/codex/commands/wf-test.md +0 -68
  652. package/modules/hosts/codex/skills/flow-kit/SKILL.md +0 -14
  653. package/modules/hosts/cursor/agents/implementer.md +0 -51
  654. package/modules/hosts/cursor/agents/independent-reviewer.md +0 -49
  655. package/modules/hosts/cursor/agents/ui-verifier.md +0 -48
  656. package/modules/hosts/cursor/commands/wf-build.md +0 -69
  657. package/modules/hosts/cursor/commands/wf-deploy.md +0 -58
  658. package/modules/hosts/cursor/commands/wf-design.md +0 -49
  659. package/modules/hosts/cursor/commands/wf-gate-checklist.md +0 -77
  660. package/modules/hosts/cursor/commands/wf-maintain.md +0 -76
  661. package/modules/hosts/cursor/commands/wf-new-task.md +0 -102
  662. package/modules/hosts/cursor/commands/wf-pipeline-run.md +0 -35
  663. package/modules/hosts/cursor/commands/wf-plan.md +0 -40
  664. package/modules/hosts/cursor/commands/wf-review.md +0 -49
  665. package/modules/hosts/cursor/commands/wf-sync-hosts.md +0 -64
  666. package/modules/hosts/cursor/commands/wf-test.md +0 -68
  667. package/modules/hosts/omp/agents/implementer.md +0 -52
  668. package/modules/hosts/omp/agents/independent-reviewer.md +0 -50
  669. package/modules/hosts/omp/agents/ui-verifier.md +0 -49
  670. package/modules/hosts/opencode/agents/implementer.md +0 -59
  671. package/modules/hosts/opencode/agents/independent-reviewer.md +0 -53
  672. package/modules/hosts/opencode/agents/ui-verifier.md +0 -52
  673. package/modules/hosts/opencode/commands/wf-build.md +0 -69
  674. package/modules/hosts/opencode/commands/wf-deploy.md +0 -58
  675. package/modules/hosts/opencode/commands/wf-design.md +0 -49
  676. package/modules/hosts/opencode/commands/wf-gate-checklist.md +0 -77
  677. package/modules/hosts/opencode/commands/wf-maintain.md +0 -76
  678. package/modules/hosts/opencode/commands/wf-new-task.md +0 -102
  679. package/modules/hosts/opencode/commands/wf-pipeline-run.md +0 -35
  680. package/modules/hosts/opencode/commands/wf-plan.md +0 -40
  681. package/modules/hosts/opencode/commands/wf-review.md +0 -49
  682. package/modules/hosts/opencode/commands/wf-sync-hosts.md +0 -64
  683. package/modules/hosts/opencode/commands/wf-test.md +0 -68
  684. package/modules/hosts/trae/agents/implementer.md +0 -52
  685. package/modules/hosts/trae/agents/independent-reviewer.md +0 -51
  686. package/modules/hosts/trae/agents/ui-verifier.md +0 -50
  687. package/modules/hosts/trae/commands/wf-build.md +0 -70
  688. package/modules/hosts/trae/commands/wf-deploy.md +0 -59
  689. package/modules/hosts/trae/commands/wf-design.md +0 -50
  690. package/modules/hosts/trae/commands/wf-gate-checklist.md +0 -78
  691. package/modules/hosts/trae/commands/wf-maintain.md +0 -77
  692. package/modules/hosts/trae/commands/wf-new-task.md +0 -103
  693. package/modules/hosts/trae/commands/wf-pipeline-run.md +0 -36
  694. package/modules/hosts/trae/commands/wf-plan.md +0 -41
  695. package/modules/hosts/trae/commands/wf-review.md +0 -50
  696. package/modules/hosts/trae/commands/wf-sync-hosts.md +0 -65
  697. package/modules/hosts/trae/commands/wf-test.md +0 -69
  698. package/modules/hosts/trae/hooks/post-edit-check.cjs +0 -98
  699. package/modules/hosts/trae/hooks/pre-shell-check.cjs +0 -192
  700. package/modules/hosts/trae/hooks/resolve-shell.cjs +0 -30
  701. package/modules/hosts/trae/hooks.json +0 -29
  702. package/modules/hosts/trae/rules/beautify-input-chinese.md +0 -6
  703. package/modules/hosts/trae/rules/git-commit-message.md +0 -10
  704. package/modules/hosts/trae/rules/project-workflow.md +0 -14
  705. package/modules/hosts/zcode/agents/implementer.md +0 -51
  706. package/modules/hosts/zcode/agents/independent-reviewer.md +0 -49
  707. package/modules/hosts/zcode/agents/ui-verifier.md +0 -48
  708. package/src/add-gate.mjs +0 -116
  709. package/src/add-gate.test.mjs +0 -103
  710. package/src/add-host.mjs +0 -68
  711. package/src/cli.mjs +0 -169
  712. package/src/doctor.mjs +0 -629
  713. package/src/executor.mjs +0 -262
  714. package/src/executor.test.mjs +0 -295
  715. package/src/fresh-init.test.mjs +0 -64
  716. package/src/gate-dotnet-ca.test.mjs +0 -124
  717. package/src/gates.test.mjs +0 -143
  718. package/src/init.mjs +0 -382
  719. package/src/init.test.mjs +0 -163
  720. package/src/instance-ref.test.mjs +0 -62
  721. package/src/jscpd-drift.test.mjs +0 -145
  722. package/src/pack.test.mjs +0 -24
  723. package/src/profiles.mjs +0 -183
  724. package/src/render.mjs +0 -132
  725. package/src/rule-budget.test.mjs +0 -103
  726. package/src/run-tests.mjs +0 -56
  727. package/src/stack-profile.test.mjs +0 -95
  728. package/src/sync-hosts.mjs +0 -257
  729. package/src/sync-hosts.test.mjs +0 -291
  730. package/src/sync.mjs +0 -228
  731. package/src/sync.test.mjs +0 -452
  732. package/src/trae-hooks.test.mjs +0 -127
  733. package/src/validate.mjs +0 -77
  734. package/src/validate.test.mjs +0 -95
  735. package/templates/.gitattributes +0 -17
  736. package/templates/AGENTS.md +0 -58
  737. package/templates/_agents/board/index.html +0 -676
  738. package/templates/_agents/commands/build.md +0 -84
  739. package/templates/_agents/commands/deploy.md +0 -66
  740. package/templates/_agents/commands/design.md +0 -63
  741. package/templates/_agents/commands/gate-checklist.md +0 -91
  742. package/templates/_agents/commands/maintain.md +0 -90
  743. package/templates/_agents/commands/new-task.md +0 -110
  744. package/templates/_agents/commands/pipeline-run.md +0 -43
  745. package/templates/_agents/commands/plan.md +0 -48
  746. package/templates/_agents/commands/review.md +0 -62
  747. package/templates/_agents/commands/sync-hosts.md +0 -77
  748. package/templates/_agents/commands/test.md +0 -87
  749. package/templates/_agents/control-surfaces.txt +0 -53
  750. package/templates/_agents/engine-lock.json +0 -342
  751. package/templates/_agents/hooks/check-index-incremental.mjs +0 -48
  752. package/templates/_agents/hooks/check-pairing-incremental.mjs +0 -66
  753. package/templates/_agents/hooks/check-wiki-ledger.mjs +0 -30
  754. package/templates/_agents/hooks/commit-check.cjs +0 -193
  755. package/templates/_agents/hooks/local-pre-commit +0 -6
  756. package/templates/_agents/lane-surfaces.txt +0 -17
  757. package/templates/_agents/level-hints.txt +0 -30
  758. package/templates/_agents/loop/papercut-debt/DESIGN.md +0 -112
  759. package/templates/_agents/mechanism-registry.md +0 -35
  760. package/templates/_agents/metric-claims.txt +0 -81
  761. package/templates/_agents/notes/engine-internals.md +0 -17
  762. package/templates/_agents/notes/runtime-env.md +0 -18
  763. package/templates/_agents/roles/implementer.md +0 -47
  764. package/templates/_agents/roles/independent-reviewer.md +0 -45
  765. package/templates/_agents/roles/ui-verifier.md +0 -44
  766. package/templates/_agents/rule-budgets.txt +0 -20
  767. package/templates/_agents/scripts/agg-delegations.cjs +0 -302
  768. package/templates/_agents/scripts/agg-delegations.test.mjs +0 -282
  769. package/templates/_agents/scripts/agg-gate-stats.mjs +0 -113
  770. package/templates/_agents/scripts/agg-gate-stats.test.mjs +0 -95
  771. package/templates/_agents/scripts/agg-judgments.mjs +0 -156
  772. package/templates/_agents/scripts/agg-judgments.test.mjs +0 -93
  773. package/templates/_agents/scripts/archive-sweep.mjs +0 -235
  774. package/templates/_agents/scripts/archive-sweep.test.mjs +0 -129
  775. package/templates/_agents/scripts/check-attribution.mjs +0 -37
  776. package/templates/_agents/scripts/check-convergence.mjs +0 -124
  777. package/templates/_agents/scripts/check-engine-integrity.mjs +0 -220
  778. package/templates/_agents/scripts/check-engine-integrity.test.mjs +0 -215
  779. package/templates/_agents/scripts/check-funnel.mjs +0 -56
  780. package/templates/_agents/scripts/check-hygiene.mjs +0 -167
  781. package/templates/_agents/scripts/check-hygiene.test.mjs +0 -182
  782. package/templates/_agents/scripts/check-index-incremental.test.mjs +0 -73
  783. package/templates/_agents/scripts/check-lane-surface.mjs +0 -238
  784. package/templates/_agents/scripts/check-lane-surface.test.mjs +0 -302
  785. package/templates/_agents/scripts/check-ledger-invariant.mjs +0 -586
  786. package/templates/_agents/scripts/check-ledger-invariant.test.mjs +0 -639
  787. package/templates/_agents/scripts/check-loop-rev.test.mjs +0 -336
  788. package/templates/_agents/scripts/check-loop-unit.test.mjs +0 -103
  789. package/templates/_agents/scripts/check-loop.mjs +0 -1987
  790. package/templates/_agents/scripts/check-loop.sh +0 -6
  791. package/templates/_agents/scripts/check-loop.test.mjs +0 -3912
  792. package/templates/_agents/scripts/check-metric-claims.mjs +0 -277
  793. package/templates/_agents/scripts/check-prejudgment.mjs +0 -64
  794. package/templates/_agents/scripts/check-registry.mjs +0 -45
  795. package/templates/_agents/scripts/check-scope.mjs +0 -96
  796. package/templates/_agents/scripts/check-scope.test.mjs +0 -99
  797. package/templates/_agents/scripts/check-scripts-syntax.mjs +0 -84
  798. package/templates/_agents/scripts/check-scripts-syntax.test.mjs +0 -72
  799. package/templates/_agents/scripts/check-spec-verif.mjs +0 -47
  800. package/templates/_agents/scripts/check-stage-index.mjs +0 -40
  801. package/templates/_agents/scripts/check-stage-index.test.mjs +0 -77
  802. package/templates/_agents/scripts/closeout-l1.mjs +0 -217
  803. package/templates/_agents/scripts/closeout-l1.test.mjs +0 -105
  804. package/templates/_agents/scripts/commit-check-trigger.test.mjs +0 -205
  805. package/templates/_agents/scripts/compact-ledgers.mjs +0 -97
  806. package/templates/_agents/scripts/compact-ledgers.test.mjs +0 -162
  807. package/templates/_agents/scripts/confirm-doc.mjs +0 -610
  808. package/templates/_agents/scripts/confirm-doc.test.mjs +0 -1119
  809. package/templates/_agents/scripts/doctor.test.mjs +0 -770
  810. package/templates/_agents/scripts/draft-sediment.mjs +0 -178
  811. package/templates/_agents/scripts/draft-sediment.test.mjs +0 -272
  812. package/templates/_agents/scripts/edit-face-check.mjs +0 -109
  813. package/templates/_agents/scripts/edit-face-check.test.mjs +0 -99
  814. package/templates/_agents/scripts/ensure-board.mjs +0 -185
  815. package/templates/_agents/scripts/fill-intent.mjs +0 -91
  816. package/templates/_agents/scripts/fill-intent.test.mjs +0 -113
  817. package/templates/_agents/scripts/fill-plan.mjs +0 -79
  818. package/templates/_agents/scripts/fill-plan.test.mjs +0 -224
  819. package/templates/_agents/scripts/fill-spec.mjs +0 -70
  820. package/templates/_agents/scripts/fill-spec.test.mjs +0 -266
  821. package/templates/_agents/scripts/fixture-scripts.mjs +0 -153
  822. package/templates/_agents/scripts/fixture-scripts.test.mjs +0 -112
  823. package/templates/_agents/scripts/gate-checklist.mjs +0 -209
  824. package/templates/_agents/scripts/gate-checklist.test.mjs +0 -201
  825. package/templates/_agents/scripts/gate-seg.mjs +0 -54
  826. package/templates/_agents/scripts/gate-seg.test.mjs +0 -112
  827. package/templates/_agents/scripts/gen-effective-policy.mjs +0 -107
  828. package/templates/_agents/scripts/gen-effective-policy.test.mjs +0 -100
  829. package/templates/_agents/scripts/gen-verify-evidence.mjs +0 -107
  830. package/templates/_agents/scripts/gen-verify-evidence.test.mjs +0 -84
  831. package/templates/_agents/scripts/gen-wiki-board.mjs +0 -276
  832. package/templates/_agents/scripts/gen-wiki-board.test.mjs +0 -249
  833. package/templates/_agents/scripts/gen-workflow-dashboard.mjs +0 -251
  834. package/templates/_agents/scripts/gen-workflow-dashboard.test.mjs +0 -188
  835. package/templates/_agents/scripts/gen-workflow-index.mjs +0 -201
  836. package/templates/_agents/scripts/gen-workflow-index.test.mjs +0 -152
  837. package/templates/_agents/scripts/gen-workflow-metrics.mjs +0 -466
  838. package/templates/_agents/scripts/gen-workflow-metrics.test.mjs +0 -343
  839. package/templates/_agents/scripts/git-merge-jsonl.mjs +0 -63
  840. package/templates/_agents/scripts/git-merge-jsonl.test.mjs +0 -125
  841. package/templates/_agents/scripts/judgment-ledger.mjs +0 -65
  842. package/templates/_agents/scripts/judgment-ledger.test.mjs +0 -129
  843. package/templates/_agents/scripts/kb-cache-evict.mjs +0 -50
  844. package/templates/_agents/scripts/kb-cache-evict.test.mjs +0 -104
  845. package/templates/_agents/scripts/kb-search.mjs +0 -413
  846. package/templates/_agents/scripts/kb-search.test.mjs +0 -299
  847. package/templates/_agents/scripts/lane-surface-audit.mjs +0 -143
  848. package/templates/_agents/scripts/lane-surface-audit.test.mjs +0 -93
  849. package/templates/_agents/scripts/ledger-doc-chain.test.mjs +0 -166
  850. package/templates/_agents/scripts/ledger-loader.mjs +0 -108
  851. package/templates/_agents/scripts/ledger-loader.test.mjs +0 -131
  852. package/templates/_agents/scripts/ledger-prune.mjs +0 -96
  853. package/templates/_agents/scripts/ledger-prune.test.mjs +0 -120
  854. package/templates/_agents/scripts/ledger-shards.mjs +0 -344
  855. package/templates/_agents/scripts/ledger-shards.test.mjs +0 -356
  856. package/templates/_agents/scripts/ledger-ts-monotonic.test.mjs +0 -135
  857. package/templates/_agents/scripts/level-criteria.mjs +0 -231
  858. package/templates/_agents/scripts/level-criteria.test.mjs +0 -281
  859. package/templates/_agents/scripts/mechanism-retire-watch.mjs +0 -183
  860. package/templates/_agents/scripts/mechanism-retire-watch.test.mjs +0 -114
  861. package/templates/_agents/scripts/papercut-debt-controller.mjs +0 -141
  862. package/templates/_agents/scripts/papercut-debt-controller.test.mjs +0 -127
  863. package/templates/_agents/scripts/papercut-debt-sensor.mjs +0 -170
  864. package/templates/_agents/scripts/papercut-debt-sensor.test.mjs +0 -146
  865. package/templates/_agents/scripts/pipeline-run-docs.mjs +0 -76
  866. package/templates/_agents/scripts/pipeline-run-run.mjs +0 -63
  867. package/templates/_agents/scripts/pipeline-run.mjs +0 -867
  868. package/templates/_agents/scripts/pipeline-run.test.mjs +0 -517
  869. package/templates/_agents/scripts/policy.mjs +0 -281
  870. package/templates/_agents/scripts/pre-push.test.mjs +0 -105
  871. package/templates/_agents/scripts/resolve-merge-conflicts.mjs +0 -110
  872. package/templates/_agents/scripts/resolve-merge-conflicts.test.mjs +0 -87
  873. package/templates/_agents/scripts/rewrite-file.mjs +0 -133
  874. package/templates/_agents/scripts/rewrite-file.test.mjs +0 -148
  875. package/templates/_agents/scripts/rule-budget.mjs +0 -96
  876. package/templates/_agents/scripts/rule-budget.test.mjs +0 -118
  877. package/templates/_agents/scripts/run-tests.mjs +0 -99
  878. package/templates/_agents/scripts/run-tests.test.mjs +0 -105
  879. package/templates/_agents/scripts/solidify-task.mjs +0 -109
  880. package/templates/_agents/scripts/solidify-task.test.mjs +0 -129
  881. package/templates/_agents/scripts/stage-gates.mjs +0 -343
  882. package/templates/_agents/scripts/stage-gates.test.mjs +0 -105
  883. package/templates/_agents/scripts/trust-mode.mjs +0 -67
  884. package/templates/_agents/scripts/trust-mode.test.mjs +0 -110
  885. package/templates/_agents/scripts/verify-wiki-consistency.mjs +0 -253
  886. package/templates/_agents/scripts/verify-wiki-consistency.test.mjs +0 -123
  887. package/templates/_agents/scripts/verify.mjs +0 -345
  888. package/templates/_agents/scripts/verify.test.mjs +0 -373
  889. package/templates/_agents/scripts/wf-journal.mjs +0 -231
  890. package/templates/_agents/scripts/wf-journal.test.mjs +0 -216
  891. package/templates/_agents/scripts/wiki-search.mjs +0 -19
  892. package/templates/_agents/scripts/wiki-search.test.mjs +0 -57
  893. package/templates/_agents/scripts/workflow-board-server.mjs +0 -393
  894. package/templates/_agents/scripts/workflow-board-server.test.mjs +0 -201
  895. package/templates/_agents/scripts/workflow-enums.mjs +0 -70
  896. package/templates/_agents/scripts/workflow-enums.test.mjs +0 -118
  897. package/templates/_agents/scripts/workflows-check.mjs +0 -260
  898. package/templates/_agents/scripts/workflows-check.test.mjs +0 -278
  899. package/templates/_agents/scripts-test-exempt.txt +0 -16
  900. package/templates/_agents/skills/papercut-fix/SKILL.md +0 -60
  901. package/templates/_agents/skills/papercut-fix/references/response-template.md +0 -81
  902. package/templates/_agents/skills/review-method/SKILL.md +0 -58
  903. package/templates/_agents/skills/verify-ui/SKILL.md +0 -61
  904. package/templates/_agents/skills/wiki/SKILL.md +0 -106
  905. package/templates/_agents/workflow-enums.txt +0 -26
  906. package/templates/_agents/workflow-modules.txt +0 -11
  907. package/templates/_agents/workflows/_TEMPLATE.md +0 -75
  908. package/templates/_agents/workflows/pipeline-closing.md +0 -28
  909. package/templates/_agents/workflows//347/244/272/344/276/213-/345/271/266/350/241/214/345/256/236/347/216/260/350/257/204/345/256/241.md +0 -16
  910. package/templates/_githooks/commit-msg +0 -17
  911. package/templates/_githooks/post-commit +0 -10
  912. package/templates/_githooks/pre-commit +0 -126
  913. package/templates/_githooks/pre-merge-commit +0 -29
  914. package/templates/_githooks/pre-push +0 -136
  915. package/templates/_github/workflows/kit-ci.yml +0 -49
  916. package/templates/wiki/INDEX.md +0 -73
  917. package/templates/wiki/drafts-archive/README.md +0 -3
  918. package/templates/wiki//344/272/247/345/223/201/351/234/200/346/261/202/README.md +0 -3
  919. package/templates/wiki//345/274/200/345/217/221/346/226/271/346/241/210/README.md +0 -3
  920. package/templates/wiki//346/225/260/346/215/256/347/273/264/346/212/244/README.md +0 -3
  921. package/templates/wiki//346/265/213/350/257/225/346/212/245/345/221/212/README.md +0 -3
  922. package/templates/wiki//347/224/250/346/210/267/345/212/237/350/203/275/README.md +0 -3
  923. package/templates/wiki//347/237/245/350/257/206/346/262/211/346/267/200/346/200/273/350/247/210.html +0 -77
  924. package/templates/wiki//351/241/271/347/233/256/350/247/204/350/214/203/README.md +0 -3
  925. package/templates/wiki//351/241/271/347/233/256/350/256/241/345/210/222/README.md +0 -3
  926. package/templates/workflow/README.md +0 -91
  927. package/templates/workflow/delegations.md +0 -29
  928. package/templates/workflow/incidents/_TEMPLATE.md +0 -51
  929. package/templates/workflow/intents/_TEMPLATE.md +0 -51
  930. package/templates/workflow/papercuts.md +0 -9
  931. package/templates/workflow/plans/_TEMPLATE.md +0 -47
  932. package/templates/workflow/regression-checklist.md +0 -23
  933. package/templates/workflow/specs/_TEMPLATE.md +0 -80
  934. /package/templates/{_agents → engine/.agents}/board/marked.min.js +0 -0
  935. /package/templates/{_agents/workflows → engine/.agents/pipelines}/steps//347/244/272/344/276/213-/351/203/250/347/275/262/351/252/214/350/257/201.md" +0 -0
  936. /package/templates/{_agents → engine/.agents}/scripts/git-core.mjs +0 -0
@@ -0,0 +1,1940 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>开发全景图</title>
7
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>◈</text></svg>">
8
+ <style>
9
+ /* ============================================================
10
+ DEV TOUR — 开发全景图 dossier(模板单源,脚本三锚点由生成器 / 看板服务注入)
11
+ 方向:暖纸 · 发丝线 · 极简 —— 令牌层与看板 index.html 同源,两页可同步
12
+ 暖纸底 + 暖墨字;朱红是全页唯一强调色,只给选中左缘 / tab 指示线 /
13
+ 时序箭头等必须被看见的位置;状态色只上小徽标、圆点与细线,禁止大色块。
14
+ 等宽用本机栈(JetBrains Mono→Cascadia→Consolas),正文中文雅黑,零外部请求。
15
+ ============================================================ */
16
+
17
+ :root {
18
+ --bg: #f2f0e9; /* 暖纸 */
19
+ --surface: #f7f5ee; /* 顶栏 / 左右轨 */
20
+ --card: #fdfcf8; /* 卡片近白 */
21
+ --card-hover: #ffffff;
22
+ --border: #ddd7c8; /* 发丝线 */
23
+ --border-strong: #b5ad99;
24
+ --text: #26241d; /* 暖墨 */
25
+ --muted: #6c675a;
26
+ --faint: #99937f; /* 仅装饰:滚动条 / 占位符 */
27
+ --accent: #bc4327; /* 朱 — 唯一强调色 */
28
+ --intent: #3f6288; --incident: #a04b35; --spec: #6f5a88; --plan: #3e7157;
29
+ --ok: #3e7157; --warn: #7d6224; --bad: #a04b35;
30
+ --shadow-1: 0 1px 2px rgba(38,36,29,.05), 0 3px 10px rgba(38,36,29,.07);
31
+ --shadow-2: -16px 0 40px rgba(38,36,29,.16);
32
+ --ease: cubic-bezier(.16, 1, .3, 1);
33
+ --mono: "JetBrains Mono", "Cascadia Code", Consolas, "Courier New", monospace;
34
+ --sans: "Microsoft YaHei", "PingFang SC", "Segoe UI", sans-serif;
35
+ }
36
+ * { box-sizing: border-box; margin: 0; }
37
+ ::selection { background: rgba(188,67,39,.18); }
38
+ ::-webkit-scrollbar { width: 10px; height: 10px; }
39
+ ::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 5px; border: 2px solid var(--bg); }
40
+ ::-webkit-scrollbar-track { background: transparent; }
41
+ html, body { height: 100%; }
42
+ body { margin: 0; overflow: hidden; background: var(--bg); color: var(--text); font: 14px/1.7 "Microsoft YaHei", "PingFang SC", "Segoe UI", sans-serif; letter-spacing: .01em; }
43
+ /* 纸张颗粒:5% 噪点叠层让纸面有物理质感;pointer-events 关闭不挡交互 */
44
+ body::before { content: ""; position: fixed; inset: 0; z-index: 0; pointer-events: none; opacity: .05;
45
+ background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E"); }
46
+ #app { height: 100%; display: flex; flex-direction: column; position: relative; z-index: 1; }
47
+ button, input { font: inherit; color: inherit; }
48
+ button { cursor: pointer; }
49
+ a { color: var(--text); text-decoration: underline; text-decoration-color: var(--accent); text-underline-offset: 3px; }
50
+ :focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
51
+
52
+ /* ---- 顶栏:surface 纸条 + 墨字标题 + 朱方块记忆点 ---- */
53
+ #topbar {
54
+ flex: 0 0 58px; display: flex; align-items: center; gap: 14px; padding: 0 22px;
55
+ background: var(--surface); border-bottom: 1px solid var(--border);
56
+ position: relative; z-index: 5;
57
+ }
58
+ .brand { display: flex; flex-direction: column; justify-content: center; gap: 3px; min-width: 0; }
59
+ .brand h1 { font: 700 15px/1 var(--mono); letter-spacing: .02em; max-width: 560px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
60
+ .brand h1::after { content: ""; display: inline-block; width: 7px; height: 7px; background: var(--accent); margin-left: 9px; }
61
+ .brand .sub { max-width: 560px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font: 10.5px/1.3 var(--mono); letter-spacing: .02em; }
62
+ .stat { display: inline-flex; align-items: baseline; gap: 3px; font: 500 11px/1 var(--mono); color: var(--muted); background: transparent; border: 1px solid var(--border); border-radius: 3px; padding: 4px 8px; font-variant-numeric: tabular-nums; white-space: nowrap; }
63
+ .stat b { color: var(--text); font-weight: 700; font-size: 12px; }
64
+ .stat.add b { color: var(--ok); }
65
+ .stat.del b { color: var(--bad); }
66
+ .stats { display: flex; align-items: center; gap: 6px; margin-left: auto; }
67
+ .actions { display: flex; align-items: center; gap: 8px; }
68
+ .btn { background: var(--card); border: 1px solid var(--border); color: var(--muted); border-radius: 3px; padding: 6px 10px; font: 12px/1.4 var(--mono); white-space: nowrap; transition: border-color .15s ease-out, color .15s ease-out, background .15s ease-out; }
69
+ .btn:hover { border-color: var(--border-strong); color: var(--text); background: var(--card-hover); }
70
+ .btn.on { background: var(--text); color: var(--bg); border-color: var(--text); }
71
+ kbd { font: 9px/1 var(--mono); padding: 1px 3px; margin-left: 4px; border: 1px solid var(--border-strong); border-radius: 2px; opacity: .75; }
72
+ #live { display: inline-flex; align-items: center; gap: 6px; font: 11px/1 var(--mono); color: var(--muted); white-space: nowrap; }
73
+ #live i { width: 7px; height: 7px; border-radius: 50%; background: var(--ok); }
74
+ #live.off i { background: var(--warn); }
75
+
76
+ /* ---- 三栏 ---- */
77
+ #main { flex: 1; min-height: 0; display: grid; grid-template-columns: 300px minmax(0, 1fr) 420px; grid-template-rows: minmax(0, 1fr); }
78
+
79
+ /* ---- 左轨 ---- */
80
+ #sidebar { min-width: 0; min-height: 0; display: flex; flex-direction: column; background: var(--surface); border-right: 1px solid var(--border); }
81
+ .side-head { padding: 14px 16px 12px; border-bottom: 1px solid var(--border); }
82
+ .side-head .eyebrow { font: 600 10px/1 var(--mono); letter-spacing: .12em; color: var(--muted); }
83
+ .side-head .proj { display: flex; align-items: center; gap: 6px; margin-top: 5px; font-size: 14px; font-weight: 600; }
84
+ .side-head .proj span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
85
+ .side-tools { padding: 10px 12px; display: flex; gap: 6px; border-bottom: 1px solid var(--border); }
86
+ #search { flex: 1; min-width: 0; background: var(--card); border: 1px solid var(--border); border-radius: 3px; color: var(--text); padding: 5px 9px; font: 12px/1.4 var(--mono); outline: none; transition: border-color .15s ease-out; }
87
+ #search::placeholder { color: var(--faint); }
88
+ #search:focus { border-color: var(--border-strong); }
89
+ .mini { background: var(--card); border: 1px solid var(--border); border-radius: 3px; color: var(--muted); padding: 5px 9px; font: 11px/1.4 var(--mono); white-space: nowrap; transition: border-color .15s ease-out, color .15s ease-out; }
90
+ .mini:hover { border-color: var(--border-strong); color: var(--text); }
91
+ .side-sec { display: flex; align-items: center; justify-content: space-between; padding: 13px 16px 6px; }
92
+ .side-sec b { font: 600 10px/1 var(--mono); letter-spacing: .12em; color: var(--muted); }
93
+ .side-sec span { font: 10px/1 var(--mono); color: var(--faint); font-variant-numeric: tabular-nums; }
94
+ #step-nav { max-height: 30%; overflow: auto; padding: 0 8px 8px; border-bottom: 1px solid var(--border); }
95
+ .nav-step {
96
+ display: flex; align-items: center; gap: 8px; width: 100%; text-align: left;
97
+ border: 0; border-left: 2px solid transparent;
98
+ background: transparent; padding: 6px 7px 6px 8px; font-size: 12px; color: var(--muted);
99
+ transition: background .15s ease-out, border-color .15s ease-out, color .15s ease-out;
100
+ }
101
+ .nav-step:hover { background: var(--bg); }
102
+ .nav-step.on { background: rgba(188,67,39,.08); border-left-color: var(--accent); color: var(--text); }
103
+ .nav-step .n {
104
+ flex: 0 0 20px; width: 20px; height: 20px; display: grid; place-items: center;
105
+ border: 1px solid var(--border-strong); border-radius: 2px; background: transparent;
106
+ color: var(--muted); font: 700 10px/1 var(--mono); font-variant-numeric: tabular-nums;
107
+ }
108
+ .nav-step.on .n { background: var(--accent); border-color: var(--accent); color: var(--bg); }
109
+ .nav-step.appendix .n { background: transparent; border-color: var(--border-strong); color: var(--muted); }
110
+ .nav-step.appendix.on .n { background: var(--accent); border-color: var(--accent); color: var(--bg); }
111
+ .nav-step.appendix .t { color: var(--muted); }
112
+ .nav-step.appendix.on .t { color: var(--text); }
113
+ .nav-step .t { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
114
+ .nav-step .c { font: 10.5px var(--mono); color: var(--faint); font-variant-numeric: tabular-nums; }
115
+ #file-tree { flex: 1; overflow: auto; padding: 4px 8px 34px; }
116
+ .tree-dir { user-select: none; }
117
+ .tree-row {
118
+ display: flex; align-items: center; gap: 6px; padding: 5px; border-radius: 3px;
119
+ font: 11.5px/1.5 var(--mono); color: var(--muted); cursor: pointer; white-space: nowrap;
120
+ transition: background .15s ease-out, color .15s ease-out;
121
+ }
122
+ .tree-row:hover { background: var(--bg); color: var(--text); }
123
+ .tree-row .chev { width: 10px; flex: 0 0 10px; color: var(--faint); font-size: 9px; transition: transform .15s ease-out; }
124
+ .tree-dir.closed > .tree-row .chev { transform: rotate(-90deg); }
125
+ .tree-dir.closed > .tree-children { display: none; }
126
+ .tree-row .folder { color: var(--muted); }
127
+ .tree-file {
128
+ display: flex; align-items: center; gap: 6px; padding: 5px 5px 5px 20px;
129
+ border-left: 2px solid transparent; border-radius: 0 3px 3px 0;
130
+ font: 11.5px/1.5 var(--mono); cursor: pointer; white-space: nowrap;
131
+ transition: background .15s ease-out, border-color .15s ease-out;
132
+ }
133
+ .tree-file:hover { background: var(--bg); }
134
+ .tree-file.on { background: rgba(188,67,39,.08); border-left-color: var(--accent); }
135
+ .tree-file .dot { flex: 0 0 6px; width: 6px; height: 6px; border-radius: 50%; background: var(--faint); }
136
+ .tree-file .nm { overflow: hidden; text-overflow: ellipsis; }
137
+ .tree-file .bd { margin-left: auto; padding: 0 3px; border: 1px solid currentColor; border-radius: 2px; font: 9px/1.5 var(--mono); }
138
+ .side-foot { flex: 0 0 auto; border-top: 1px solid var(--border); padding: 9px 14px; font: 10px/1.75 var(--mono); color: var(--muted); }
139
+ .side-foot b { color: var(--text); font-weight: 700; }
140
+
141
+ /* ---- 画布 ---- */
142
+ #canvas-wrap { min-width: 0; min-height: 0; display: flex; flex-direction: column; background: transparent; position: relative; }
143
+ #view-tabs {
144
+ flex: 0 0 42px; display: flex; align-items: stretch; padding: 0 16px;
145
+ background: transparent; border-bottom: 1px solid var(--border); overflow-x: auto; z-index: 3;
146
+ }
147
+ #view-tabs .tab {
148
+ position: relative; border: 0; background: transparent; color: var(--muted);
149
+ font-family: inherit; font-size: 13px; letter-spacing: .02em; padding: 0 13px;
150
+ white-space: nowrap; flex: 0 0 auto; transition: color .15s ease-out;
151
+ }
152
+ #view-tabs .tab::after { content: ""; position: absolute; left: 13px; right: 13px; bottom: 0; height: 2px; background: var(--accent); transform: scaleX(0); transition: transform .2s var(--ease); }
153
+ #view-tabs .tab:hover { color: var(--text); }
154
+ #view-tabs .tab.on { color: var(--text); font-weight: 600; }
155
+ #view-tabs .tab.on::after { transform: scaleX(1); }
156
+ #view-tabs .spacer { flex: 1; }
157
+ .layout-btn {
158
+ display: none; align-self: center; margin: 0 0 0 8px; padding: 5px 9px;
159
+ background: var(--card); border: 1px solid var(--border); border-radius: 3px;
160
+ color: var(--muted); font: 11px/1.4 var(--mono); white-space: nowrap;
161
+ transition: border-color .15s ease-out, color .15s ease-out;
162
+ }
163
+ .layout-btn:hover { border-color: var(--border-strong); color: var(--text); }
164
+ body.focus #sidebar, body.focus #detail { display: none; }
165
+ body.focus #main { grid-template-columns: minmax(0, 1fr); }
166
+ #pan-hint { display: none; position: absolute; right: 18px; bottom: 14px; z-index: 2; padding: 4px 8px; border: 1px solid var(--border); border-radius: 3px; background: var(--card); font: 10px/1.6 var(--mono); color: var(--muted); white-space: nowrap; }
167
+ .zoombar { display: flex; align-items: center; gap: 4px; flex: 0 0 auto; }
168
+ .zoombar button {
169
+ width: 29px; height: 26px; background: var(--card); border: 1px solid var(--border); border-radius: 3px;
170
+ color: var(--muted); font: 12px/1 var(--mono);
171
+ transition: border-color .15s ease-out, color .15s ease-out;
172
+ }
173
+ .zoombar button:hover { border-color: var(--border-strong); color: var(--text); }
174
+ .zoombar .wide { width: auto; padding: 0 9px; font-size: 11px; }
175
+ #zoom-label { min-width: 38px; text-align: center; font: 10px var(--mono); color: var(--muted); font-variant-numeric: tabular-nums; }
176
+ #canvas-scroll { flex: 1; min-height: 0; overflow: auto; position: relative; background: transparent; }
177
+ #canvas { min-width: 100%; min-height: 0; transform-origin: top left; }
178
+ #canvas.zoomed { transition: transform .15s ease-out; }
179
+
180
+ /* 全景图:网格总览(默认) */
181
+ #canvas.view-panorama.layout-grid { display: block; width: auto; padding: 26px 26px 64px; }
182
+ .station-grid {
183
+ display: grid;
184
+ grid-template-columns: repeat(auto-fit, minmax(min(340px, 100%), 1fr));
185
+ gap: 24px; align-items: start; max-width: 1180px; margin: 0 auto;
186
+ }
187
+ #canvas.layout-grid .station { width: auto; flex: none; }
188
+ #canvas.layout-grid .station::after,
189
+ #canvas.layout-grid .station::before { display: none; }
190
+ #canvas.layout-grid .st-card { padding: 16px 15px 14px; }
191
+ #canvas.layout-grid .st-no { width: 34px; height: 34px; flex-basis: 34px; }
192
+ #canvas.layout-grid .st-title { font-size: 16px; }
193
+ #canvas.layout-grid .st-summary { font-size: 13px; max-height: none; }
194
+
195
+ /* 全景图:站点串(时间线布局,站点间朱色细虚线衔接) */
196
+ #canvas.view-panorama { display: flex; align-items: flex-start; gap: 56px; padding: 34px 34px 80px; width: max-content; }
197
+ .station { width: 268px; min-width: 0; flex: 0 0 268px; position: relative; }
198
+ .station::after {
199
+ content: ""; position: absolute; right: -47px; top: 30px; width: 38px;
200
+ border-top: 1px dashed var(--accent); opacity: .7;
201
+ }
202
+ .station::before {
203
+ content: ""; position: absolute; right: -48px; top: 25px; width: 0; height: 0;
204
+ border-left: 7px solid var(--accent); border-top: 4px solid transparent; border-bottom: 4px solid transparent;
205
+ opacity: .9;
206
+ }
207
+ .station:last-child::after, .station:last-child::before { display: none; }
208
+ .st-top { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 10px; }
209
+ .st-no { width: 30px; height: 30px; flex: 0 0 30px; display: grid; place-items: center; border-radius: 3px; background: var(--text); color: var(--bg); font: 700 12px/1 var(--mono); font-variant-numeric: tabular-nums; }
210
+ .station.warn .st-no { background: var(--warn); }
211
+ .st-headtext { min-width: 0; display: flex; flex-direction: column; gap: 3px; }
212
+ .st-label { font: 700 10px/1 var(--mono); letter-spacing: .12em; color: var(--accent); }
213
+ .station.warn .st-label { color: var(--warn); }
214
+ .station:hover .st-label::after { content: " · 点击看全文"; letter-spacing: 0; font-family: var(--sans); font-weight: 400; color: var(--faint); }
215
+ /* 卡片是索引,不是全文:标题/摘要各收 3 行,全文在右侧「变更说明」承接 */
216
+ .st-title { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; font-size: 15px; font-weight: 700; line-height: 1.4; color: var(--text); overflow-wrap: anywhere; }
217
+ .st-card {
218
+ background: var(--card); border: 1px solid var(--border); border-left: 3px solid var(--border-strong);
219
+ border-radius: 4px; padding: 12px 14px; cursor: pointer;
220
+ transition: background .15s ease-out, border-color .15s ease-out, transform .15s ease-out, box-shadow .15s ease-out;
221
+ }
222
+ .station:hover .st-card { background: var(--card-hover); border-color: var(--border-strong); transform: translateY(-1px); box-shadow: var(--shadow-1); }
223
+ .station.on .st-card { border-left-color: var(--accent); }
224
+ .st-summary { display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; font-size: 12.5px; line-height: 1.65; color: var(--muted); margin: 0 0 10px; }
225
+ .st-summary .st-k { display: inline-block; margin-right: 5px; padding: 1px 3px; border: 1px solid rgba(62,113,87,.5); border-radius: 2px; vertical-align: 1px; color: var(--ok); font: 700 9px/1 var(--mono); letter-spacing: .08em; }
226
+ .st-files { display: flex; flex-direction: column; gap: 0; }
227
+ .st-file {
228
+ display: flex; align-items: center; gap: 6px; padding: 5px 0;
229
+ border-bottom: 1px dashed var(--border); background: transparent;
230
+ font: 11px/1.5 var(--mono); color: var(--text); white-space: nowrap; overflow: hidden;
231
+ }
232
+ .st-file:last-child { border-bottom: 0; }
233
+ .st-file .dot { flex: 0 0 6px; width: 6px; height: 6px; border-radius: 50%; }
234
+ .st-file .nm { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: 6px; overflow: hidden; }
235
+ .st-file .nm .bn { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
236
+ .st-file .nm .dir { flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; color: var(--faint); font-size: 10px; }
237
+ .st-file > span:last-child { flex: 0 0 auto; font-size: 10px !important; }
238
+ .st-more { padding: 8px 0 0; font: 10.5px var(--mono); color: var(--accent); cursor: pointer; }
239
+ .st-more:hover { text-decoration: underline; }
240
+ .st-empty { font: 11.5px/1.7 var(--sans); color: var(--muted); font-style: normal; }
241
+ .st-empty .lnk { color: var(--accent); cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }
242
+ .station.appendix .st-no { background: var(--muted); }
243
+ .station.appendix .st-label { color: var(--muted); letter-spacing: .08em; }
244
+ .st-objs { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 8px; padding-top: 8px; border-top: 1px dashed var(--border); }
245
+
246
+ /* 数据对象徽标:dto/entity/table 各一枚低饱和标签色,仅文字 + 1px 边 */
247
+ .kbadge { font: 700 9.5px/1.5 var(--mono); border: 1px solid var(--border-strong); border-radius: 2px; padding: 1px 5px; color: var(--muted); white-space: nowrap; }
248
+ .kbadge.dto { color: var(--plan); border-color: var(--plan); }
249
+ .kbadge.entity { color: var(--intent); border-color: var(--intent); }
250
+ .kbadge.table { color: var(--spec); border-color: var(--spec); }
251
+ .kbadge.on { color: var(--accent); border-color: var(--accent); }
252
+
253
+ /* 时序图 */
254
+ #canvas.view-sequence { padding: 26px 30px 70px; }
255
+ .seq-wrap { min-width: 560px; max-width: 1200px; margin: 0 auto; }
256
+ .seq-titlebar { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 0 2px 12px; }
257
+ .seq-titlebar b { font-size: 13px; font-weight: 600; }
258
+ .seq-titlebar span { font: 11px var(--mono); color: var(--muted); }
259
+ .seq-head { display: grid; grid-template-columns: repeat(var(--n, 5), minmax(0, 1fr)); gap: 8px; padding: 6px 0 0; }
260
+ .seq-part { text-align: center; }
261
+ .seq-part .box {
262
+ display: inline-block; max-width: 100%; padding: 5px 12px;
263
+ border: 1px solid var(--border-strong); border-radius: 3px;
264
+ background: var(--card); color: var(--text); font: 500 12px/1.6 var(--mono);
265
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
266
+ }
267
+ .seq-body { position: relative; margin: 14px 0 0; }
268
+ .seq-lifelines { position: absolute; inset: 0; display: grid; grid-template-columns: repeat(var(--n, 5), minmax(0, 1fr)); pointer-events: none; }
269
+ .seq-lifelines i { justify-self: center; width: 0; border-left: 1px dashed var(--border-strong); }
270
+ .seq-msg { position: relative; height: 58px; cursor: pointer; transition: background .15s ease-out; }
271
+ .seq-msg:hover { background: rgba(188,67,39,.06); }
272
+ .seq-msg.on { background: rgba(188,67,39,.08); }
273
+ .seq-msg svg { position: absolute; inset: 0; width: 100%; height: 58px; overflow: visible; }
274
+ .seq-msg .lbl {
275
+ position: absolute; top: 7px; transform: translateX(-50%); max-width: 300px; padding: 3px 8px;
276
+ border: 1px solid var(--border); border-radius: 3px; background: var(--card);
277
+ font: 500 10.5px/1.4 var(--mono); color: var(--muted);
278
+ white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
279
+ }
280
+ .seq-msg.on .lbl { border-color: var(--accent); color: var(--text); font-weight: 600; }
281
+ .seq-msg .lbl b { margin-right: 5px; color: var(--accent); font: 700 9px var(--mono); }
282
+ .seq-empty { padding: 50px; text-align: center; color: var(--faint); font: 12px var(--mono); }
283
+
284
+ /* 流程图 */
285
+ #canvas.view-flow { padding: 30px 24px 80px; }
286
+ .flow-wrap { max-width: 780px; margin: 0 auto; }
287
+ .flow-node {
288
+ background: var(--card); border: 1px solid var(--border); border-left: 3px solid var(--border-strong);
289
+ border-radius: 4px; padding: 12px 14px; cursor: pointer;
290
+ transition: background .15s ease-out, border-color .15s ease-out, transform .15s ease-out, box-shadow .15s ease-out;
291
+ }
292
+ .flow-node:hover { background: var(--card-hover); border-color: var(--border-strong); transform: translateY(-1px); box-shadow: var(--shadow-1); }
293
+ .flow-node.on { border-left-color: var(--accent); }
294
+ .flow-node .fn-head { display: flex; align-items: center; gap: 9px; }
295
+ .flow-node .fn-no { padding: 2px 5px; border: 1px solid var(--border-strong); border-radius: 2px; color: var(--muted); font: 700 10px/1.4 var(--mono); font-variant-numeric: tabular-nums; }
296
+ .flow-node .fn-title { font-size: 13px; font-weight: 600; overflow-wrap: anywhere; }
297
+ .flow-node .fn-sum { font-size: 12px; line-height: 1.65; color: var(--muted); margin: 5px 0 0 33px; }
298
+ .flow-node .fn-files { margin: 8px 0 0 33px; display: flex; flex-wrap: wrap; gap: 5px; }
299
+ .flow-node .fn-files span { padding: 1px 6px; border: 1px solid var(--border); border-radius: 2px; background: transparent; font: 10px/1.6 var(--mono); color: var(--muted); }
300
+ .flow-arrow { height: 32px; display: flex; flex-direction: column; align-items: center; justify-content: center; color: var(--accent); }
301
+ .flow-arrow i { width: 0; height: 18px; border-left: 1px dashed var(--accent); }
302
+ .flow-arrow b { width: 0; height: 0; margin-top: -2px; border-top: 5px solid var(--accent); border-left: 4px solid transparent; border-right: 4px solid transparent; }
303
+ .flow-pill {
304
+ display: flex; align-items: center; justify-content: center; gap: 8px; padding: 9px 16px;
305
+ border: 1px solid var(--border-strong); border-radius: 3px; background: var(--card);
306
+ font: 11px/1.6 var(--mono); color: var(--muted);
307
+ }
308
+ .flow-pill.end { color: var(--ok); border-color: var(--ok); }
309
+ .flow-gate {
310
+ padding: 12px 14px; border: 1px solid var(--border); border-left: 3px solid var(--warn);
311
+ border-radius: 4px; background: rgba(125,98,36,.08);
312
+ }
313
+ .flow-gate h4 { margin: 0 0 6px; font: 600 11px/1.6 var(--mono); letter-spacing: .06em; color: var(--warn); }
314
+ .flow-gate ul { margin: 0; padding-left: 18px; font: 11.5px/1.7 var(--mono); color: var(--muted); }
315
+ /* 需求视图:本档目标 + 上游 intent/spec/incident 叙事卡 */
316
+ .reqs-wrap { max-width: 860px; padding: 26px 30px 60px; margin: 0 auto; }
317
+ .req-card { position: relative; background: var(--card); border: 1px solid var(--border); border-radius: 5px; padding: 16px 18px; margin-bottom: 14px; box-shadow: var(--shadow-1); }
318
+ .req-card.self { border-left: 3px solid var(--accent); }
319
+ .req-eyebrow { display: flex; align-items: center; gap: 8px; font: 600 10px/1 var(--mono); letter-spacing: .12em; color: var(--muted); }
320
+ .req-badge { display: inline-block; padding: 2px 7px; border-radius: 2px; font: 700 9.5px/1.5 var(--mono); letter-spacing: .06em; color: var(--bg); background: var(--muted); }
321
+ .req-badge[data-type="INTENT"] { background: var(--intent); }
322
+ .req-badge[data-type="SPEC"] { background: var(--spec); }
323
+ .req-badge[data-type="PLAN"] { background: var(--plan); }
324
+ .req-badge[data-type="INCIDENT"] { background: var(--incident); }
325
+ .req-card h3 { margin: 8px 0 4px; font-size: 14.5px; font-weight: 600; }
326
+ .req-card .req-path { font: 10.5px/1.4 var(--mono); color: var(--faint); }
327
+ .req-card .req-why { margin: 9px 0 0; font-size: 13px; line-height: 1.75; }
328
+ .req-card .req-why b { color: var(--accent); font: 600 10px/1 var(--mono); letter-spacing: .1em; margin-right: 7px; }
329
+ .req-points { margin: 10px 0 0; padding: 0; list-style: none; }
330
+ .req-points li { position: relative; padding: 5px 0 5px 16px; font-size: 12.5px; line-height: 1.7; color: var(--text); border-top: 1px dashed var(--border); }
331
+ .req-points li::before { content: "·"; position: absolute; left: 3px; color: var(--accent); font-weight: 700; }
332
+ .req-points code, .req-why code { font: .92em var(--mono); background: var(--bg); border: 1px solid var(--border); border-radius: 2px; padding: 0 4px; }
333
+ /* 验证视图:左计划清单 + 右实际凭证表,状态条在顶 */
334
+ .vfy-status { display: flex; align-items: center; gap: 8px; max-width: 860px; margin: 0 auto; padding: 18px 30px 0; font: 12px/1.6 var(--mono); color: var(--muted); }
335
+ .vfy-status i { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 8px; background: var(--ok); }
336
+ .vfy-status.bad i { background: var(--bad); }
337
+ .vfy-status b { color: var(--text); font-weight: 700; }
338
+ .vfy-cols { display: grid; grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); gap: 14px; max-width: 1160px; margin: 0 auto; padding: 14px 30px 60px; align-items: start; }
339
+ .vfy-plan h4, .vfy-ev h4 { font: 600 10px/1 var(--mono); letter-spacing: .12em; color: var(--muted); margin-bottom: 8px; }
340
+ .vfy-plan ul { margin: 0; padding: 0; list-style: none; background: var(--card); border: 1px solid var(--border); border-radius: 5px; box-shadow: var(--shadow-1); }
341
+ .vfy-plan li { padding: 9px 14px; font-size: 12.5px; line-height: 1.65; border-bottom: 1px dashed var(--border); }
342
+ .vfy-plan li:last-child { border-bottom: 0; }
343
+ .vfy-table { width: 100%; border-collapse: collapse; background: var(--card); border: 1px solid var(--border); border-radius: 5px; overflow: hidden; box-shadow: var(--shadow-1); font: 11.5px/1.5 var(--mono); }
344
+ .vfy-table th { text-align: left; padding: 8px 10px; font: 600 9.5px/1 var(--mono); letter-spacing: .1em; color: var(--muted); border-bottom: 1px solid var(--border-strong); background: var(--surface); }
345
+ .vfy-table td { padding: 7px 10px; border-bottom: 1px dashed var(--border); color: var(--text); font-variant-numeric: tabular-nums; }
346
+ .vfy-table tr:last-child td { border-bottom: 0; }
347
+ .vfy-exit { font-weight: 700; }
348
+ .vfy-exit.ok { color: var(--ok); }
349
+ .vfy-exit.bad { color: var(--bad); }
350
+ .vfy-src { display: inline-block; padding: 1px 5px; border: 1px solid var(--border); border-radius: 2px; font-size: 9.5px; color: var(--muted); }
351
+ .vfy-skip { color: var(--faint); font-size: 10px; }
352
+ .vfy-note { max-width: 860px; margin: 0 auto; padding: 0 30px 40px; font: 10.5px/1.6 var(--mono); color: var(--faint); }
353
+ /* 验证 tab 门禁化(2026-11-11 P2):套件卡 + 可展开凭证明细 + 失败日志尾部 */
354
+ .vfy-status { max-width: 1160px; }
355
+ .vfy-status-main { display: flex; flex-direction: column; gap: 2px; }
356
+ .vfy-green { color: var(--ok); }
357
+ .vfy-sum { max-width: 1160px; margin: 6px auto 0; padding: 0 30px; font: 10.5px/1.7 var(--mono); color: var(--faint); }
358
+ .vfy-cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 10px; max-width: 1160px; margin: 0 auto; padding: 12px 30px 0; }
359
+ .vfy-card { background: var(--card); border: 1px solid var(--border); border-left: 3px solid var(--border-strong); border-radius: 5px; padding: 10px 12px; box-shadow: var(--shadow-1); }
360
+ .vfy-card.ok { border-left-color: var(--ok); }
361
+ .vfy-card.bad { border-left-color: var(--bad); }
362
+ .vfy-card-h { display: flex; align-items: baseline; gap: 8px; }
363
+ .vfy-card-h b { font: 600 12.5px/1.5 var(--mono); }
364
+ .vfy-card-h time { margin-left: auto; font: 10.5px/1.5 var(--mono); color: var(--faint); }
365
+ .vfy-state { flex: 0 0 auto; padding: 1px 6px; border: 1px solid currentColor; border-radius: 2px; font: 600 10px/1.6 var(--mono); }
366
+ .vfy-card.ok .vfy-state { color: var(--ok); }
367
+ .vfy-card.bad .vfy-state { color: var(--bad); }
368
+ .vfy-card-meta { margin-top: 4px; font: 10.5px/1.6 var(--mono); color: var(--muted); }
369
+ .vfy-card-note { margin-top: 5px; font: 11px/1.6 var(--sans); color: var(--muted); overflow-wrap: anywhere; }
370
+ .vfy-copy { margin-left: 6px; border: 1px solid var(--border); background: transparent; color: var(--accent); border-radius: 2px; padding: 0 5px; font: 10px/1.6 var(--mono); cursor: pointer; }
371
+ .vfy-copy:hover { border-color: var(--accent); }
372
+ .vfy-skips { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 6px; }
373
+ .vfy-skips span { padding: 1px 5px; border: 1px solid var(--border); border-radius: 2px; font: 9.5px/1.6 var(--mono); color: var(--muted); }
374
+ .vfy-log { margin-top: 7px; }
375
+ .vfy-log summary { cursor: pointer; font: 10.5px/1.7 var(--mono); color: var(--accent); }
376
+ .vfy-log pre { max-height: 220px; overflow: auto; margin: 5px 0 0; padding: 8px; background: #26241d; color: #d8d4c8; border-radius: 3px; font: 10px/1.55 var(--mono); white-space: pre-wrap; }
377
+ .vfy-gates { margin-top: 7px; }
378
+ .vfy-gates summary { cursor: pointer; font: 10.5px/1.7 var(--mono); color: var(--accent); }
379
+ .vfy-gate-grid { display: flex; flex-wrap: wrap; gap: 3px; margin-top: 6px; }
380
+ .vfy-gate-grid span { display: inline-flex; align-items: baseline; gap: 4px; padding: 1px 5px; border: 1px solid var(--border); border-radius: 2px; font: 9.5px/1.6 var(--mono); color: var(--muted); }
381
+ .vfy-gate-grid span.pass { border-color: rgba(62,113,87,.45); color: var(--ok); }
382
+ .vfy-gate-grid span.skip { color: var(--faint); border-style: dashed; }
383
+ .vfy-gate-grid b { font-weight: 600; }
384
+ .vfy-row { background: var(--card); border: 1px solid var(--border); border-radius: 4px; margin-bottom: 7px; box-shadow: var(--shadow-1); }
385
+ .vfy-row summary { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 10px; padding: 8px 10px; cursor: pointer; font: 10.5px/1.6 var(--mono); color: var(--muted); }
386
+ .vfy-row[open] summary { border-bottom: 1px solid var(--border); }
387
+ .vfy-row summary::-webkit-details-marker { display: none; }
388
+ .vfy-row summary::before { content: "▸"; color: var(--faint); }
389
+ .vfy-row[open] summary::before { content: "▾"; }
390
+ .vfy-rs { color: var(--text); font-weight: 600; }
391
+ .vfy-re.ok { color: var(--ok); font-weight: 700; }
392
+ .vfy-re.bad { color: var(--bad); font-weight: 700; }
393
+ .vfy-rg, .vfy-rk, .vfy-rh { color: var(--faint); }
394
+ .vfy-row-body { padding: 9px 12px; font: 11.5px/1.7 var(--sans); color: var(--muted); }
395
+ .vfy-row-body > div + div { margin-top: 4px; }
396
+ .vfy-row-meta { font: 10px/1.6 var(--mono); color: var(--faint); }
397
+ .vfy-gate-ev { margin-top: 8px; padding-top: 7px; border-top: 1px dashed var(--border); font: 10px/1.5 var(--mono); color: var(--muted); }
398
+ .vfy-gate-ev b { color: var(--ok); font-weight: 700; }
399
+ .vfy-gate-ev.bad b { color: var(--bad); }
400
+ .flow-fail { margin: 8px 0 0 27px; padding-left: 10px; border-left: 2px dashed var(--bad); font: 10.5px/1.6 var(--mono); color: var(--bad); }
401
+ /* 依赖旁路弧:声明的跨步依赖(主链相邻边不重复画),左侧细轨,hover 步骤高亮其上下游 */
402
+ .flow-wrap.has-deps { position: relative; padding-left: 46px; }
403
+ .flow-deps { position: absolute; left: 0; top: 0; pointer-events: none; overflow: visible; }
404
+ .flow-deps .dep { fill: none; stroke: var(--border-strong); stroke-width: 1.1; opacity: .7; transition: stroke .15s ease-out, opacity .15s ease-out; }
405
+ .flow-deps .dep.back { stroke: var(--bad); stroke-dasharray: 3 3; }
406
+ .flow-deps .dep.on { stroke: var(--accent); stroke-width: 1.6; opacity: 1; }
407
+ .flow-deps .dep-head { fill: var(--border-strong); opacity: .7; transition: fill .15s ease-out, opacity .15s ease-out; }
408
+ .flow-deps .dep-head.on { fill: var(--accent); opacity: 1; }
409
+ .flow-node.rel { border-color: var(--accent); }
410
+
411
+ /* 数据流 */
412
+ #canvas.view-dataflow { padding: 26px 30px 70px; }
413
+ .df-wrap { max-width: 980px; margin: 0 auto; display: flex; flex-direction: column; gap: 10px; }
414
+ .df-sum { font: 11px/1.6 var(--mono); color: var(--muted); padding: 0 2px 4px; }
415
+ .df-sec { font: 600 11px/1 var(--mono); letter-spacing: .12em; color: var(--muted); padding: 14px 2px 2px; }
416
+ .df-obj {
417
+ display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
418
+ background: var(--card); border: 1px solid var(--border); border-left: 3px solid var(--border-strong);
419
+ border-radius: 4px; padding: 12px 14px; cursor: pointer;
420
+ transition: background .15s ease-out, border-color .15s ease-out, transform .15s ease-out, box-shadow .15s ease-out;
421
+ }
422
+ .df-obj:hover { background: var(--card-hover); border-color: var(--border-strong); transform: translateY(-1px); box-shadow: var(--shadow-1); }
423
+ .df-obj.on { border-left-color: var(--accent); }
424
+ .df-name { font: 600 12.5px/1.6 var(--mono); }
425
+ .df-nfiles { font: 10px/1.6 var(--mono); color: var(--faint); }
426
+ .df-lane { margin-left: auto; display: flex; align-items: flex-start; }
427
+ .df-slot { display: flex; flex-direction: column; align-items: center; gap: 5px; }
428
+ .df-slot .lyr { padding: 3px 10px; border: 1px solid var(--border-strong); border-radius: 3px; background: var(--card); font: 500 11px/1.6 var(--mono); color: var(--text); white-space: nowrap; }
429
+ .df-slot .fs { max-width: 150px; text-align: center; font: 9.5px/1.5 var(--mono); color: var(--faint); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
430
+ .df-link { width: 24px; height: 24px; align-self: center; position: relative; flex: 0 0 24px; }
431
+ .df-link::before { content: ""; position: absolute; left: 0; right: 0; top: 50%; border-top: 1px solid var(--border-strong); }
432
+ .df-link::after { content: ""; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); width: 0; height: 0; border-left: 5px solid var(--accent); border-top: 3px solid transparent; border-bottom: 3px solid transparent; }
433
+ .df-flow { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; padding: 8px 12px; background: var(--card); border: 1px solid var(--border); border-radius: 3px; font: 11.5px/1.6 var(--mono); color: var(--muted); }
434
+ .df-flow .arr { color: var(--faint); }
435
+ .df-flow .kbadge { font-size: 9px; }
436
+ .df-step { border: 0; background: none; padding: 0; font: 600 11.5px/1.6 var(--mono); color: var(--text); }
437
+ .df-step:hover { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }
438
+ .df-step b { color: var(--accent); font-weight: 700; margin-right: 4px; }
439
+
440
+ /* ---------- 右轨:变更 dossier ---------- */
441
+ #detail { min-width: 0; min-height: 0; display: flex; flex-direction: column; background: var(--surface); border-left: 1px solid var(--border); }
442
+ .detail-head { flex: 0 0 auto; padding: 14px 16px 11px; border-bottom: 1px solid var(--border); }
443
+ .detail-head .dh { display: flex; align-items: center; gap: 9px; }
444
+ .detail-head h2 { font-size: 15px; font-weight: 700; }
445
+ #detail-sub { font: 10.5px var(--mono); color: var(--faint); }
446
+ .dback { margin-left: auto; border: 1px solid var(--border); background: var(--card); color: var(--muted); border-radius: 3px; padding: 1px 8px; font: 10.5px/1.7 var(--mono); cursor: pointer; }
447
+ .dback:hover { color: var(--text); border-color: var(--accent); }
448
+ .dback[hidden] { display: none; }
449
+ /* 右侧总览:范围 / 未归属 / 验证 / 解析风险;选中步骤时再下钻全文,避免复述中间全景 */
450
+ .ov { display: flex; flex-direction: column; gap: 13px; }
451
+ .ov-sec { padding-bottom: 12px; border-bottom: 1px dashed var(--border); }
452
+ .ov-sec:last-child { padding-bottom: 0; border-bottom: 0; }
453
+ .ov-eyebrow { margin-bottom: 7px; font: 600 10px/1 var(--mono); letter-spacing: .12em; color: var(--muted); }
454
+ .ov-eyebrow b { margin-left: 3px; font-variant-numeric: tabular-nums; }
455
+ .ov-kpis { font: 12.5px/1.7 var(--sans); color: var(--text); }
456
+ .ov-kpis b { font: 600 14px/1 var(--mono); }
457
+ .ov-sub { margin-top: 3px; color: var(--muted); font: 11.5px/1.7 var(--sans); }
458
+ .ov-sub code { font: 11px/1.5 var(--mono); background: rgba(38,36,29,.06); border-radius: 2px; padding: 0 3px; }
459
+ .ov-add { color: var(--ok); font-family: var(--mono); }
460
+ .ov-del { color: var(--bad); font-family: var(--mono); }
461
+ .ov-link { display: inline-flex; align-items: center; gap: 4px; margin-top: 6px; padding: 0; border: 0; background: transparent; color: var(--accent); font: 11.5px/1.7 var(--mono); cursor: pointer; }
462
+ .ov-link:hover { text-decoration: underline; }
463
+ .ov-status { display: flex; align-items: baseline; gap: 6px; font: 11.5px/1.7 var(--mono); color: var(--muted); overflow-wrap: anywhere; }
464
+ .ov-status i { flex: 0 0 6px; width: 6px; height: 6px; border-radius: 50%; background: var(--faint); }
465
+ .ov-status.ok { color: var(--ok); } .ov-status.ok i { background: var(--ok); }
466
+ .ov-status.bad { color: var(--bad); } .ov-status.bad i { background: var(--bad); }
467
+ #filter-tabs { display: flex; margin-top: 9px; overflow-x: auto; padding-bottom: 1px; }
468
+ #filter-tabs button {
469
+ border: 0; border-bottom: 2px solid transparent; background: transparent;
470
+ color: var(--muted); padding: 6px 9px; font: 11.5px/1.6 var(--mono); white-space: nowrap;
471
+ transition: color .15s ease-out, border-color .15s ease-out;
472
+ }
473
+ #filter-tabs button:hover { color: var(--text); }
474
+ #filter-tabs button.on { color: var(--text); border-bottom-color: var(--accent); font-weight: 700; }
475
+ #filter-tabs button .n { margin-left: 4px; color: var(--faint); font: 10.5px var(--mono); font-weight: 400; }
476
+ #detail-body { flex: 1; overflow: auto; padding: 13px 14px 54px; }
477
+ .step-card {
478
+ background: var(--card); border: 1px solid var(--border); border-left: 3px solid var(--border-strong);
479
+ border-radius: 4px; margin-bottom: 12px;
480
+ transition: border-color .15s ease-out;
481
+ }
482
+ .step-card.on { border-color: var(--border-strong); border-left-color: var(--accent); }
483
+ .step-card.warn { border-left-color: var(--warn); }
484
+ .sc-head {
485
+ display: flex; align-items: flex-start; gap: 9px; padding: 10px 12px;
486
+ border-bottom: 1px solid var(--border); cursor: pointer;
487
+ }
488
+ .sc-head .sc-no { flex: 0 0 auto; padding: 2px 5px; border: 1px solid var(--border-strong); border-radius: 2px; color: var(--muted); font: 700 10px/1.4 var(--mono); font-variant-numeric: tabular-nums; }
489
+ .step-card.appendix .sc-no { border-style: dashed; color: var(--faint); }
490
+ .step-card.warn .sc-head .sc-no { color: var(--warn); border-color: var(--warn); }
491
+ .sc-titlewrap { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 3px; }
492
+ .sc-eyebrow { font: 600 10px/1.2 var(--mono); letter-spacing: .1em; color: var(--muted); }
493
+ .sc-eyebrow .done { margin-left: 6px; color: var(--ok); }
494
+ .sc-head h3 { flex: none; font-size: 13px; line-height: 1.5; overflow-wrap: anywhere; }
495
+ .sc-head .meta { font: 10.5px var(--mono); color: var(--faint); white-space: nowrap; font-variant-numeric: tabular-nums; }
496
+ .sc-body { padding: 9px 12px 11px; }
497
+ .sc-summary { margin: 0 0 6px; font-size: 13px; line-height: 1.65; }
498
+ .sc-detail { margin: 0 0 7px; padding-left: 16px; font-size: 12.5px; line-height: 1.65; color: var(--muted); }
499
+ .sc-detail li { margin: 2px 0; }
500
+ .sc-files { display: flex; flex-direction: column; gap: 0; margin-top: 0; }
501
+ .sc-sec-h { margin: 9px 0 3px; font: 600 10px/1 var(--mono); letter-spacing: .12em; color: var(--muted); }
502
+ .sc-sec-h b { color: var(--text); font-variant-numeric: tabular-nums; }
503
+ .sc-sec { margin-top: 11px; }
504
+ .sc-sec-empty { font: 11.5px/1.7 var(--sans); color: var(--muted); }
505
+ .sc-req { margin-bottom: 6px; }
506
+ .sc-req-title { margin-left: 6px; font: 12px/1.6 var(--sans); color: var(--text); }
507
+ .sc-req-why { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; margin-top: 2px; font: 12px/1.65 var(--sans); color: var(--muted); }
508
+ .sc-req-why code { font: 11.5px/1.5 var(--mono); background: rgba(38,36,29,.06); border-radius: 2px; padding: 0 3px; }
509
+ .sc-proof-verify { display: flex; flex-wrap: wrap; align-items: baseline; gap: 5px 7px; margin-top: 6px; padding-top: 6px; border-top: 1px dashed var(--border); font: 11.5px/1.7 var(--sans); color: var(--muted); }
510
+ .sc-proof-verify b { font: 600 10px/1 var(--mono); letter-spacing: .08em; }
511
+ .sc-proof-verify .ok { color: var(--ok); }
512
+ .sc-proof-verify .bad { color: var(--bad); }
513
+ .sc-proof { margin-top: 0; padding: 8px 10px; border: 1px solid var(--border); border-left: 3px solid var(--ok); border-radius: 3px; background: rgba(62,113,87,.05); }
514
+ .sc-proof.empty { border-left-color: var(--border-strong); background: transparent; }
515
+ .sc-proof-h { margin-bottom: 5px; font: 600 10px/1 var(--mono); letter-spacing: .12em; color: var(--ok); }
516
+ .sc-proof.empty .sc-proof-h { color: var(--muted); }
517
+ .sc-accept { margin: 0; padding-left: 16px; font: 12.5px/1.7 var(--sans); color: var(--text); }
518
+ .sc-accept li { margin: 2px 0; }
519
+ .sc-accept code, .sc-proof-meta code { font: 11.5px/1.5 var(--mono); background: rgba(38,36,29,.06); border-radius: 2px; padding: 0 3px; }
520
+ .sc-proof-empty { font: 11.5px/1.7 var(--sans); color: var(--muted); }
521
+ .sc-proof-meta { margin-top: 5px; font: 11.5px/1.7 var(--sans); color: var(--muted); }
522
+ .sc-proof-meta b { margin-right: 5px; font: 600 10px/1 var(--mono); letter-spacing: .08em; color: var(--muted); }
523
+ .file-row {
524
+ display: flex; align-items: center; gap: 6px; padding: 6px 4px; border-radius: 3px;
525
+ border-bottom: 1px dashed var(--border); cursor: pointer;
526
+ transition: background .15s ease-out;
527
+ }
528
+ .file-row:hover { background: var(--bg); }
529
+ .file-row.on { background: rgba(188,67,39,.08); }
530
+ .file-row .dot { flex: 0 0 6px; width: 6px; height: 6px; border-radius: 50%; }
531
+ .file-row .path { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: 11.5px/1.5 var(--mono); direction: rtl; text-align: left; }
532
+ .file-row .num { font: 10px var(--mono); color: var(--ok); white-space: nowrap; font-variant-numeric: tabular-nums; }
533
+ .file-row .num del { margin-left: 3px; color: var(--bad); text-decoration: none; }
534
+ .badge {
535
+ padding: 1px 4px; border: 1px solid currentColor; border-radius: 2px;
536
+ font: 700 9.5px/1.5 var(--mono); white-space: nowrap;
537
+ }
538
+ .badge.new { color: var(--ok); }
539
+ .badge.modified { color: var(--accent); }
540
+ .badge.deleted { color: var(--bad); }
541
+ .badge.renamed { color: var(--muted); border-color: var(--border-strong); }
542
+ .badge.planned { color: var(--warn); }
543
+ .badge.test { color: var(--intent); }
544
+ .badge.doc { color: var(--muted); border-color: var(--border-strong); }
545
+ .diff-btn {
546
+ border: 1px solid var(--border); border-radius: 2px; background: transparent;
547
+ padding: 0 4px; font: 9px/1.6 var(--mono); color: var(--muted);
548
+ }
549
+ .diff-btn:hover { border-color: var(--accent); color: var(--accent); }
550
+ .diff {
551
+ display: none; margin: 3px 0 8px; padding: 9px 10px;
552
+ border-left: 3px solid var(--accent); border-radius: 0 2px 2px 0;
553
+ background: #26241d; color: #d8d4c8;
554
+ font: 11px/1.65 var(--mono); overflow: auto; max-height: 320px; white-space: pre;
555
+ }
556
+ .diff.open { display: block; }
557
+ .diff .add { color: #8fbf9c; }
558
+ .diff .del { color: #e0a498; }
559
+ .diff .hunk { color: #d8b56a; }
560
+ .diff .meta { color: #8a8474; }
561
+ .none { padding: 38px 16px; text-align: center; color: var(--faint); font: 12px var(--mono); }
562
+ /* 解析告警条:折叠摘要 + 可滚动逐条清单(2026-11-11 版面/字号修订)
563
+ 旧版用 <br> 拼长串、11px 等宽且不换行,条数一多就横向溢出并吃掉整页;现改为
564
+ 标题行常驻(计数 + 行号预览)、明细默认折叠且限高滚动,正文换行、路径走 code。 */
565
+ .warnbox { margin: 0 0 11px; padding: 0; border: 1px solid var(--border); border-left: 3px solid var(--warn); border-radius: 4px; background: rgba(125,98,36,.07); font: 13px/1.7 var(--sans); color: var(--text); overflow: hidden; }
566
+ #parse-warn { flex: 0 0 auto; margin: 8px 12px 0; }
567
+ .warn-head { display: flex; align-items: center; gap: 8px; padding: 7px 10px; }
568
+ .warn-head-main { display: flex; align-items: baseline; gap: 8px; flex: 1; min-width: 0; }
569
+ .warn-icon { flex: 0 0 auto; color: var(--warn); font-size: 13px; }
570
+ .warn-head b { flex: 0 0 auto; font-weight: 600; color: var(--warn); }
571
+ .warn-count { flex: 0 0 auto; font: 600 11px/1 var(--mono); color: var(--warn); background: rgba(125,98,36,.14); border-radius: 9px; padding: 3px 7px; font-variant-numeric: tabular-nums; }
572
+ .warn-sub { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font-size: 12px; }
573
+ .warn-toggle { flex: 0 0 auto; border: 1px solid var(--border-strong); background: var(--card); color: var(--muted); border-radius: 3px; padding: 2px 9px; font: 11.5px/1.7 var(--mono); }
574
+ .warn-toggle:hover { color: var(--text); border-color: var(--accent); }
575
+ .warn-body { max-height: min(32vh, 260px); overflow: auto; padding: 4px 10px 8px; border-top: 1px solid var(--border); }
576
+ .warn-note { margin: 4px 0; color: var(--warn); font-size: 12.5px; }
577
+ .warn-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: 1px 8px; padding: 4px 6px; border-radius: 3px; }
578
+ .warn-row + .warn-row { border-top: 1px dashed rgba(125,98,36,.16); }
579
+ .warn-row:hover { background: rgba(125,98,36,.07); }
580
+ .warn-line { flex: 0 0 auto; font: 600 11.5px/1.7 var(--mono); color: var(--warn); font-variant-numeric: tabular-nums; }
581
+ .warn-text { flex: 1 1 420px; min-width: 0; overflow-wrap: anywhere; }
582
+ .warn-text code { font: 11.5px/1.5 var(--mono); background: rgba(38,36,29,.06); border-radius: 2px; padding: 0 3px; }
583
+ .warn-kind { flex: 0 0 auto; font: 10.5px/1.7 var(--mono); color: var(--warn); border: 1px solid rgba(125,98,36,.5); border-radius: 2px; padding: 0 4px; white-space: nowrap; }
584
+ .warn-kind.bad { color: var(--bad); border-color: rgba(160,75,53,.5); }
585
+ .warn-kind.verify { color: var(--ok); border-color: rgba(62,113,87,.5); }
586
+ .warn-group { border-top: 1px dashed rgba(125,98,36,.16); }
587
+ .warn-group:first-of-type { border-top: 0; }
588
+ .warn-group-head { display: flex; align-items: baseline; gap: 7px; width: 100%; padding: 5px 6px; border: 0; border-radius: 3px; background: transparent; text-align: left; color: var(--text); font: 13px/1.7 var(--sans); cursor: pointer; }
589
+ .warn-group-head:hover { background: rgba(125,98,36,.07); }
590
+ .wg-chev { flex: 0 0 auto; width: 10px; color: var(--faint); font-size: 10px; }
591
+ .wg-name { flex: 0 0 auto; font-weight: 600; color: var(--warn); }
592
+ .wg-hint { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--muted); font-size: 12px; }
593
+ .wg-n { flex: 0 0 auto; font: 10.5px/1.7 var(--mono); color: var(--muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
594
+ .warn-group-body { padding: 0 0 3px; }
595
+
596
+ /* ---------- 入场编排 ---------- */
597
+ @keyframes rise {
598
+ from { opacity: 0; transform: translateY(8px); }
599
+ to { opacity: 1; transform: none; }
600
+ }
601
+ #topbar { animation: rise 280ms var(--ease) both; }
602
+ #sidebar { animation: rise 280ms var(--ease) 30ms both; }
603
+ #canvas-wrap { animation: rise 280ms var(--ease) 60ms both; }
604
+ #detail { animation: rise 280ms var(--ease) 90ms both; }
605
+ #app.booting .station { animation: rise 280ms var(--ease) both; animation-delay: calc(60ms + var(--i, 0) * 20ms); }
606
+ #app.booting .flow-node { animation: rise 280ms var(--ease) both; animation-delay: calc(60ms + var(--i, 0) * 20ms); }
607
+ #app.booting .df-obj { animation: rise 280ms var(--ease) both; animation-delay: calc(60ms + var(--i, 0) * 20ms); }
608
+ #app.booting .step-card { animation: rise 280ms var(--ease) both; animation-delay: calc(60ms + var(--i, 0) * 20ms); }
609
+
610
+ @media (prefers-reduced-motion: reduce) {
611
+ *, *::before, *::after { animation: none !important; transition: none !important; }
612
+ }
613
+
614
+ @media (max-width: 1280px) {
615
+ #main { grid-template-columns: 280px minmax(0, 1fr) 380px; }
616
+ .brand h1, .brand .sub { max-width: 340px; }
617
+ .stat:nth-child(n+7) { display: none; }
618
+ }
619
+ @media (max-width: 1180px) {
620
+ #main { grid-template-columns: 260px minmax(0, 1fr) 350px; }
621
+ .stat:nth-child(n+5) { display: none; }
622
+ .vfy-cols { grid-template-columns: 1fr; }
623
+ }
624
+ @media (max-width: 900px) {
625
+ #main { grid-template-columns: 240px minmax(0, 1fr); }
626
+ .vfy-cols { grid-template-columns: 1fr; padding-left: 20px; padding-right: 20px; }
627
+ .vfy-status, .vfy-sum, .vfy-cards { padding-left: 20px; padding-right: 20px; }
628
+ #detail { display: none; }
629
+ .stats { display: none; }
630
+ .brand h1, .brand .sub { max-width: 280px; }
631
+ }
632
+ @media (max-width: 760px) {
633
+ #topbar { flex: 0 0 auto; min-height: 58px; flex-wrap: wrap; padding: 10px 14px; gap: 8px; }
634
+ .brand { flex: 1 1 100%; }
635
+ .brand h1, .brand .sub { max-width: 100%; }
636
+ .stats { display: none; }
637
+ .actions { width: 100%; overflow-x: auto; padding-bottom: 2px; }
638
+ .actions .btn { flex: 0 0 auto; }
639
+ #main { grid-template-columns: minmax(0, 1fr); }
640
+ #sidebar { display: none; }
641
+ #detail { display: none; }
642
+ #canvas.view-panorama { padding: 22px 16px 68px; gap: 38px; }
643
+ .station { width: 236px; flex-basis: 236px; }
644
+ #view-tabs { padding: 0 10px; }
645
+ #pan-hint { right: 10px; bottom: 10px; }
646
+ }
647
+
648
+ </style>
649
+ </head>
650
+ <body>
651
+ <div id="app" class="booting">
652
+ <header id="topbar">
653
+ <div class="brand">
654
+ <h1 id="title">开发全景图</h1>
655
+ <div class="sub" id="subtitle">加载中…</div>
656
+ </div>
657
+ <div class="stats" id="stats"></div>
658
+ <div class="actions">
659
+ <button class="btn" id="btn-follow" title="按步骤自动播放">▶ 跟着流程走</button>
660
+ <button class="btn" id="btn-clear" title="清除步骤/文件联动">清除联动 <kbd>Esc</kbd></button>
661
+ <button class="btn" id="btn-copy" title="复制当前文档路径">复制路径</button>
662
+ <span id="live"><i></i><span>实时</span></span>
663
+ </div>
664
+ </header>
665
+
666
+ <div id="parse-warn" class="warnbox" hidden></div>
667
+
668
+ <main id="main">
669
+ <aside id="sidebar" aria-label="步骤与文件">
670
+ <div class="side-head">
671
+ <div class="eyebrow">PROJECT</div>
672
+ <div class="proj"><span id="proj-name">{{projectName}}</span></div>
673
+ </div>
674
+ <div class="side-tools">
675
+ <input id="search" placeholder="搜索文件 / 步骤…" autocomplete="off" aria-label="搜索文件或步骤">
676
+ <button class="mini" id="expand-all" title="展开全部">展开</button>
677
+ <button class="mini" id="collapse-all" title="折叠全部">折叠</button>
678
+ </div>
679
+ <div class="side-sec"><b>01 / 步骤</b><span id="step-count"></span></div>
680
+ <div id="step-nav"></div>
681
+ <div class="side-sec"><b>02 / 文件</b><span id="file-count"></span></div>
682
+ <div id="file-tree"></div>
683
+ <div class="side-foot" id="side-foot"></div>
684
+ </aside>
685
+
686
+ <section id="canvas-wrap" aria-label="开发全景画布">
687
+ <div id="view-tabs">
688
+ <button class="tab on" data-view="panorama">全景图</button>
689
+ <button class="tab" data-view="sequence">时序图</button>
690
+ <button class="tab" data-view="flow">流程图</button>
691
+ <button class="tab" data-view="reqs" style="display:none">需求</button>
692
+ <button class="tab" data-view="verify" style="display:none">验证</button>
693
+ <button class="tab" data-view="dataflow" style="display:none">数据流</button>
694
+ <button id="layout-toggle" class="layout-btn" title="切换全景布局(网格 / 时间线)">布局:网格</button>
695
+ <button id="btn-focus" class="layout-btn" title="隐藏左右栏,专注全景">专注全景</button>
696
+ <span class="spacer"></span>
697
+ <div class="zoombar">
698
+ <button class="wide" id="fit" title="缩放适配">适配</button>
699
+ <button id="zoom-out" title="缩小">−</button>
700
+ <span id="zoom-label">100%</span>
701
+ <button id="zoom-in" title="放大">+</button>
702
+ </div>
703
+ </div>
704
+ <div id="canvas-scroll"><div id="canvas"></div></div>
705
+ <span id="pan-hint">← 左右滚动查看全部 →</span>
706
+ </section>
707
+
708
+ <aside id="detail" aria-label="变更说明">
709
+ <div class="detail-head">
710
+ <div class="dh"><h2>变更说明</h2><span id="detail-sub"></span><button id="detail-back" class="dback" type="button" hidden>← 总览</button></div>
711
+ <div id="filter-tabs"></div>
712
+ </div>
713
+ <div id="detail-body"></div>
714
+ </aside>
715
+ </main>
716
+ </div>
717
+
718
+ <script>
719
+ const EMBEDDED = __TOUR_DATA__;
720
+ const DOC_FILE = __TOUR_FILE__;
721
+ const MODE = __TOUR_MODE__;
722
+ </script>
723
+ <script>
724
+ (() => {
725
+ 'use strict';
726
+ const $ = (s, el = document) => el.querySelector(s);
727
+ const $$ = (s, el = document) => [...el.querySelectorAll(s)];
728
+ const esc = (s) => String(s == null ? '' : s).replace(/[&<>"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c]));
729
+ // 行内 markdown(需求叙事用):反引号 → code,**x** → b;先转义再替换
730
+ const mdInline = (s) => esc(s)
731
+ .replace(/`([^`]+)`/g, '<code>$1</code>')
732
+ .replace(/\*\*([^*]+)\*\*/g, '<b>$1</b>');
733
+ const fmt = (n) => (n >= 1000 ? (n / 1000).toFixed(1).replace(/\.0$/, '') + 'k' : String(n));
734
+
735
+ let DATA = null;
736
+ let FILE_BY_PATH = new Map();
737
+ let liveTimer = null;
738
+ let sse = null;
739
+ let followTimer = null;
740
+ let lastRefresh = 0;
741
+ let lastSig = '';
742
+ let lastDetailKey = '';
743
+
744
+ const state = {
745
+ view: 'panorama',
746
+ layout: (() => { try { return localStorage.getItem('devTourLayout') || 'grid'; } catch { return 'grid'; } })(),
747
+ step: null,
748
+ file: null,
749
+ dataflowObj: null,
750
+ dfPrevStep: null,
751
+ dfPrevFile: null,
752
+ filter: 'all',
753
+ zoom: 1,
754
+ query: '',
755
+ collapsed: new Set(),
756
+ follow: false,
757
+ warnOpen: null, // 解析告警明细开合:null=按条数自动(>3 折叠),用户点击后保持
758
+ warnGroups: new Set(), // 告警分组展开态(按根因 kind)
759
+ };
760
+
761
+ const FILTERS = [
762
+ ['all', '全部'], ['new', '新增'], ['modified', '修改'], ['deleted', '删除'],
763
+ ['planned', '计划'], ['test', '测试'],
764
+ ];
765
+ const LAYER_LABEL = { frontend: '前端', api: '接口', app: '应用', domain: '领域', infra: '基础设施', db: '数据库', test: '测试' };
766
+
767
+ function hashState() {
768
+ const p = new URLSearchParams();
769
+ if (state.step) p.set('step', state.step);
770
+ if (state.file) p.set('file', state.file);
771
+ if (state.view !== 'panorama') p.set('view', state.view);
772
+ return p.toString();
773
+ }
774
+ function syncHash() {
775
+ if (MODE === 'snapshot') return;
776
+ const h = hashState();
777
+ history.replaceState(null, '', h ? '#' + h : location.pathname + location.search);
778
+ }
779
+ function readHash() {
780
+ const p = new URLSearchParams(location.hash.slice(1));
781
+ if (p.get('step')) state.step = p.get('step');
782
+ if (p.get('file')) state.file = p.get('file');
783
+ if (p.get('view')) state.view = p.get('view');
784
+ }
785
+
786
+ function fileOf(path) { return FILE_BY_PATH.get(path) || null; }
787
+ function stepOf(id) { return DATA ? DATA.steps.find((s) => s.id === id) : null; }
788
+ // 附录步(改动面/未分步)不是任务:不编号、不参与“N 步”计数,避免和真实任务抢序位。
789
+ const APPENDIX_ID = 'A';
790
+ const isAppendix = (s) => !!s && s.id === APPENDIX_ID;
791
+ const stepNo = (s) => isAppendix(s) ? '附' : String(s && s.index != null ? s.index : '').padStart(2, '0');
792
+ const stepName = (s) => isAppendix(s) ? '附录' : `步骤 ${stepNo(s)}`;
793
+ const stepTitle = (s) => isAppendix(s) ? `附:改动清单(${(s.files || []).length} 个文件)` : (s ? s.title : '');
794
+ const stepAria = (s) => isAppendix(s) ? `附录:改动清单(${(s.files || []).length} 个文件)` : `${stepName(s)}:${s ? s.title : ''}`;
795
+ const SOURCE_LABEL = { tasks: '任务拆解', steps: '步骤', headings: '步骤', blocks: '改动块', layers: '层归集', git: '实际改动', doc: '文档' };
796
+ const sourceLabel = (s) => SOURCE_LABEL[s && s.source] || '步骤';
797
+ // 验证步骤(单测 / UI 实测 / 静态门)天然没有代码文件;用于区分“解析失败”和“本来就没有文件”。
798
+ const VERIFY_STEP_RE = /(单测|集成测试|测试收口|冒烟|UI 实测|静态门|门禁|验证步骤)/;
799
+ const isVerificationStep = (s) => !!s && VERIFY_STEP_RE.test(s.title || '');
800
+ // 文件筛选口径单源:计数与列表都走这里,避免“状态 planned + planned 标记”重复计数。
801
+ const matchesFilter = (f, filter) => filter === 'all' ? true
802
+ : filter === 'test' ? !!f.test
803
+ : filter === 'planned' ? !!(f.planned && !f.actual)
804
+ : f.status === filter;
805
+ function stepsFor(filter = state.filter) {
806
+ return DATA.steps.filter((s) => {
807
+ if (filter === 'all') return true;
808
+ return s.files.some((p) => {
809
+ const f = fileOf(p);
810
+ return !!f && matchesFilter(f, filter);
811
+ });
812
+ });
813
+ }
814
+ function filesForStep(id) { return (stepOf(id)?.files || []).map(fileOf).filter(Boolean); }
815
+
816
+ // —— 数据流视图:模型缺 dataflow 字段(旧缓存)时整视图降级隐藏 ——
817
+ function dfObjects() { return (DATA && DATA.dataflow && Array.isArray(DATA.dataflow.objects)) ? DATA.dataflow.objects : []; }
818
+ function hasDataflow() { return dfObjects().length > 0; }
819
+ function dataflowObj() {
820
+ if (!state.dataflowObj) return null;
821
+ return dfObjects().find((o) => o.name === state.dataflowObj) || null;
822
+ }
823
+ function relatedSteps() {
824
+ const o = dataflowObj();
825
+ return o ? new Set(o.steps || []) : null;
826
+ }
827
+ // 退出对象联动:restore 时还原进入联动前的步骤/文件现场,防止选中期间点过的步骤/文件残留
828
+ function clearDataflowSel(restore) {
829
+ if (restore) { state.step = state.dfPrevStep; state.file = state.dfPrevFile; }
830
+ state.dfPrevStep = null; state.dfPrevFile = null;
831
+ state.dataflowObj = null;
832
+ }
833
+ function stepObjects(stepId) {
834
+ if (!hasDataflow()) return [];
835
+ return dfObjects().filter((o) => (o.steps || []).includes(stepId));
836
+ }
837
+ function syncDataflowTab() {
838
+ const tab = $('.tab[data-view="dataflow"]');
839
+ if (tab) tab.style.display = hasDataflow() ? '' : 'none';
840
+ if (state.view === 'dataflow' && !hasDataflow()) {
841
+ state.view = 'panorama';
842
+ $$('#view-tabs .tab').forEach((b) => b.classList.toggle('on', b.dataset.view === state.view));
843
+ }
844
+ }
845
+ // —— 需求视图:本档目标 + 上游 intent/spec/incident 叙事(模型 doc.upstream)——
846
+ function hasReqs() {
847
+ return !!(DATA.doc.purpose || (DATA.doc.upstream && DATA.doc.upstream.length));
848
+ }
849
+ function syncReqsTab() {
850
+ const tab = $('.tab[data-view="reqs"]');
851
+ if (tab) tab.style.display = hasReqs() ? '' : 'none';
852
+ if (state.view === 'reqs' && !hasReqs()) {
853
+ state.view = 'panorama';
854
+ $$('#view-tabs .tab').forEach((b) => b.classList.toggle('on', b.dataset.view === state.view));
855
+ }
856
+ }
857
+ function reqCard(up, self) {
858
+ const points = (up.points || []).map((p) => `<li>${mdInline(p)}</li>`).join('');
859
+ const purposeLabel = self ? '目标' : (up.type === 'SPEC' ? '范围' : up.type === 'INCIDENT' ? '现象' : '目标');
860
+ return `<div class="req-card${self ? ' self' : ''}">` +
861
+ `<div class="req-eyebrow"><span class="req-badge" data-type="${esc(up.type)}">${esc(up.type)}</span>` +
862
+ `<span>${self ? '本档目标' : '上游 ' + up.type.toLowerCase() + ' · 为什么改'}</span>` +
863
+ (self ? '' : `<span style="margin-left:auto">${esc(up.path)}</span>`) + `</div>` +
864
+ `<h3>${esc(up.title)}</h3>` +
865
+ (up.purpose ? `<p class="req-why"><b>${purposeLabel}</b>${mdInline(up.purpose)}</p>` : '') +
866
+ (points ? `<ul class="req-points">${points}</ul>` : '') +
867
+ `</div>`;
868
+ }
869
+ function reqsHtml() {
870
+ const d = DATA.doc;
871
+ const ups = d.upstream || [];
872
+ if (!d.purpose && !ups.length) return '<div class="none">文档未登记目标或关联需求</div>';
873
+ return '<div class="reqs-wrap">' +
874
+ (d.purpose ? reqCard({ type: d.type, title: d.title, purpose: d.purpose, points: [] }, true) : '') +
875
+ ups.map((u) => reqCard(u, false)).join('') +
876
+ (ups.length ? `<p class="req-path" style="margin:4px 2px">实现拆解:${ups.length} 篇上游需求 → ${DATA.steps.length} 步 · ${DATA.stats.files} 个文件(见全景图 / 流程图)</p>` : '') +
877
+ `</div>`;
878
+ }
879
+ // —— 验证视图:计划清单(plan 验证计划) vs 实际凭证(verify 台账/仓库凭证)——
880
+ function hasEvidence() {
881
+ return !!(DATA.evidence && DATA.evidence.rows && DATA.evidence.rows.length);
882
+ }
883
+ function latestEvidence() {
884
+ return hasEvidence() ? DATA.evidence.rows[0] : null;
885
+ }
886
+ function syncEvidenceTab() {
887
+ const tab = $('.tab[data-view="verify"]');
888
+ if (tab) tab.style.display = hasEvidence() ? '' : 'none';
889
+ if (state.view === 'verify' && !hasEvidence()) {
890
+ state.view = 'panorama';
891
+ $$('#view-tabs .tab').forEach((b) => b.classList.toggle('on', b.dataset.view === state.view));
892
+ }
893
+ }
894
+ function fmtMs(ms) {
895
+ const n = Number(ms);
896
+ if (!isFinite(n)) return '—';
897
+ return n >= 1000 ? (n / 1000).toFixed(1).replace(/\.0$/, '') + 's' : Math.round(n) + 'ms';
898
+ }
899
+ function fmtTs(ts) {
900
+ const d = new Date(ts);
901
+ if (isNaN(d)) return ts || '—';
902
+ const p = (n) => String(n).padStart(2, '0');
903
+ return `${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
904
+ }
905
+ function verifyHtml() {
906
+ const ev = DATA.evidence || { rows: [] };
907
+ const rows = ev.rows || [];
908
+ if (!rows.length) return '<div class="none">时间窗内无验证凭证</div>';
909
+ const GATE_LABEL = {
910
+ 'backend-build': '后端构建', 'backend-test': '后端测试', 'frontend-lint': '前端 lint',
911
+ 'frontend-unit-test': '前端单测', 'frontend-build': '前端构建',
912
+ 'code-architecture': '架构约束', 'code-audit-fields': '审计字段',
913
+ 'code-delete-guard': '删除守卫', 'code-notify-guard': '通知守卫',
914
+ 'check-scope': 'scope 对账', 'verify': 'verify 全量',
915
+ 'wiki-consistency': 'wiki 一致性', 'check-loop': '闭环校验', 'all-gates': '全量门',
916
+ 'scripts-syntax': '脚本语法', 'workflows-check': '工作流校验', 'local-gate-config': '本地门配置',
917
+ 'rule-budget': '规则预算', 'engine-artifacts': '引擎产物', 'ledger-doc-presence': '台账文档',
918
+ 'lane-surface-ci': '泳道面 CI', 'secrets-scan': '密钥扫描', 'ledger-invariant': '台账不变量',
919
+ };
920
+ const gateLabel = (id) => GATE_LABEL[id] || id;
921
+ const latest = rows[0];
922
+ const latestSkips = [...new Set([...(latest.skipped || []), ...(latest.envSkips || [])])];
923
+ const latestOk = latest.exitCode === 0;
924
+ const latestGreen = rows.find((r) => r.exitCode === 0);
925
+ const failed = rows.filter((r) => r.exitCode !== 0).length;
926
+ const status = `<div class="vfy-status${latestOk ? '' : ' bad'}"><i></i><div class="vfy-status-main">` +
927
+ `<span>${latestOk ? '最近一条 通过' + (latestSkips.length ? `(跳过 ${latestSkips.length})` : '') : `⚠ 最近一条 exit=${latest.exitCode}`}(${fmtTs(latest.ts)} · ${esc(latest.suite || '?')})</span>` +
928
+ (latestGreen
929
+ ? `<span class="vfy-green">最近绿行 ${fmtTs(latestGreen.ts)}${latestGreen.gates != null ? ` · ${latestGreen.gates} 门执行` : ''}${(latestGreen.skipped || []).length ? ` · 跳过 ${latestGreen.skipped.length}` : ''}</span>`
930
+ : '<span>窗内暂无绿行</span>') +
931
+ `</div></div>`;
932
+
933
+ // 套件级门禁卡:同 suite 取最新一条;失败优先、其余按时间倒序。
934
+ const bySuite = new Map();
935
+ for (const r of rows) {
936
+ const k = r.suite || '—';
937
+ if (!bySuite.has(k)) bySuite.set(k, r);
938
+ }
939
+ const suites = [...bySuite.values()].sort((a, b) => {
940
+ const fa = a.exitCode === 0 ? 1 : 0;
941
+ const fb = b.exitCode === 0 ? 1 : 0;
942
+ return fa - fb || Date.parse(b.ts) - Date.parse(a.ts);
943
+ });
944
+ const cards = suites.map((r) => {
945
+ const skips = [...new Set([...(r.skipped || []), ...(r.envSkips || [])])];
946
+ const ok = r.exitCode === 0;
947
+ const stateText = ok ? '通过' : (r.exitCode == null ? '未知' : `失败 exit=${r.exitCode}`);
948
+ const note = r.note || (r.exitCode !== 0 ? `台账未记录失败原因(仅 exit=${r.exitCode})` : '');
949
+ const copy = r.logPath ? `<button class="vfy-copy" type="button" data-copy="${esc(r.logPath)}">复制日志路径</button>` : '';
950
+ const tail = r.logTail ? `<details class="vfy-log"><summary>失败日志尾部</summary><pre>${esc(r.logTail)}</pre></details>` : '';
951
+ const gateEntries = (r.durations && r.exitCode === 0) ? Object.entries(r.durations) : [];
952
+ const gateGrid = gateEntries.length
953
+ ? `<details class="vfy-gates"><summary>门禁明细 · 执行 ${gateEntries.length} 门(耗时)</summary><div class="vfy-gate-grid">` +
954
+ gateEntries.map(([id, ms]) => {
955
+ const skip = skips.includes(id);
956
+ return `<span class="${skip ? 'skip' : 'pass'}" title="${esc(id)} · ${skip ? '跳过(本地缺环境)' : '通过'}${ms != null ? ' · ' + fmtMs(ms) : ''}">${esc(gateLabel(id))}<b>${skip ? '跳过' : fmtMs(ms)}</b></span>`;
957
+ }).join('') + `</div></details>`
958
+ : '';
959
+ return `<article class="vfy-card ${ok ? 'ok' : 'bad'}">` +
960
+ `<div class="vfy-card-h"><b>${esc(r.suite || '—')}</b><span class="vfy-state">${esc(stateText)}</span><time>${fmtTs(r.ts)}</time></div>` +
961
+ `<div class="vfy-card-meta">${r.gates != null ? `${r.gates} 门执行` : '门数未记'}${skips.length ? ` · 跳过 ${skips.length}` : ''}${r.headShort ? ` · head ${esc(r.headShort)}` : ''}${r.source ? ` · ${r.source === 'ledger' ? '台账' : '凭证'}` : ''}</div>` +
962
+ (note ? `<div class="vfy-card-note">${esc(note)}${copy}</div>` : '') +
963
+ (skips.length ? `<div class="vfy-skips">${skips.map((x) => `<span title="${esc(x)}">${esc(gateLabel(x))}</span>`).join('')}</div>` : '') +
964
+ gateGrid +
965
+ tail +
966
+ `</article>`;
967
+ }).join('');
968
+
969
+ const gates = (DATA.gates && DATA.gates.length ? DATA.gates : ['构建 / 类型检查', '自动化测试', 'verify.mjs --scope']);
970
+ const planCol = `<div class="vfy-plan"><h4>计划清单(${gates.length})· 未逐条绑定</h4><ul>` +
971
+ gates.map((g) => `<li>${mdInline(g)}</li>`).join('') + `</ul></div>`;
972
+
973
+ const body = rows.map((r) => {
974
+ const skips = [...new Set([...(r.skipped || []), ...(r.envSkips || [])])];
975
+ const ok = r.exitCode === 0;
976
+ const stateText = ok ? '通过' : (r.exitCode == null ? '未知' : `失败 exit=${r.exitCode}`);
977
+ const note = r.note || (r.exitCode !== 0 ? `台账未记录失败原因(仅 exit=${r.exitCode})` : '');
978
+ const copy = r.logPath ? `<button class="vfy-copy" type="button" data-copy="${esc(r.logPath)}">复制日志路径</button>` : '';
979
+ const tail = r.logTail ? `<details class="vfy-log"><summary>日志尾部</summary><pre>${esc(r.logTail)}</pre></details>` : '';
980
+ return `<details class="vfy-row">` +
981
+ `<summary><span>${fmtTs(r.ts)}</span><span class="vfy-rs">${esc(r.suite || '—')}</span>` +
982
+ `<span class="vfy-re ${ok ? 'ok' : 'bad'}">${esc(stateText)}</span>` +
983
+ `<span class="vfy-rg">${r.gates != null ? r.gates + ' 门执行' : '—'}</span>` +
984
+ `<span class="vfy-rk">${skips.length ? '跳过 ' + skips.length : ''}</span>` +
985
+ `<span class="vfy-rh">${r.headShort ? 'head ' + esc(r.headShort) : ''}</span>` +
986
+ `<span>${r.source === 'ledger' ? '台账' : '凭证'}</span></summary>` +
987
+ `<div class="vfy-row-body">` +
988
+ (note ? `<div>${esc(note)}${copy}</div>` : '') +
989
+ (skips.length ? `<div class="vfy-skips">${skips.map((x) => `<span title="${esc(x)}">${esc(gateLabel(x))}</span>`).join('')}</div>` : '') +
990
+ (r.attribution ? `<div>归因:${esc(r.attribution)}</div>` : '') +
991
+ tail +
992
+ `</div></details>`;
993
+ }).join('');
994
+
995
+ const summary = `<div class="vfy-sum">窗内 ${ev.totalInWindow ?? rows.length} 条凭证 · ${suites.length} 个套件 · 失败 ${failed} 条 · 计划门禁 ${gates.length} 项(未逐条绑定)</div>`;
996
+ const evCol = `<div class="vfy-ev"><h4>凭证明细(窗内 ${ev.totalInWindow ?? rows.length} 条,示最近 ${rows.length})</h4>${body}</div>`;
997
+ return status + summary +
998
+ `<div class="vfy-cards">${cards}</div>` +
999
+ `<div class="vfy-cols">${planCol}${evCol}</div>` +
1000
+ `<p class="vfy-note">窗起点 ${esc(ev.windowStart || '—')}(文档日期);关联语义为「任务时间窗内的验证凭证」,非逐条绑定到本档。台账 = 本机 .agents/verifications.jsonl;失败原因取台账 note,并附本地日志尾部。</p>`;
1001
+ }
1002
+
1003
+ function renderHeader() {
1004
+ const d = DATA.doc;
1005
+ document.title = d.title + ' · 开发全景图';
1006
+ $('#title').textContent = d.title;
1007
+ const bits = [d.type, d.level, d.status, d.module, d.date].filter(Boolean);
1008
+ $('#subtitle').textContent = (bits.join(' · ') || d.path) + (d.purpose ? ' — ' + d.purpose : '');
1009
+ $('#proj-name').textContent = DATA.rootLabel || '{{projectName}}';
1010
+ const s = DATA.stats;
1011
+ const realStepCount = DATA.steps.filter((x) => !isAppendix(x)).length;
1012
+ const hasAppendix = DATA.steps.some(isAppendix);
1013
+ $('#stats').innerHTML =
1014
+ `<span class="stat" title="真实任务步骤数;附录(改动面/未分步)不计入"><b>${realStepCount}</b>步${hasAppendix ? ' + 附' : ''}</span>` +
1015
+ `<span class="stat" title="代码面文件(backend / frontend / tests 等);文档/工具脚本单列,不含在代码计数内"><b>${s.files}</b>代码${s.docs ? ` · <b>${s.docs}</b>文档` : ''}</span>` +
1016
+ `<span class="stat"><b>${s.new}</b>新增</span>` +
1017
+ `<span class="stat"><b>${s.modified}</b>修改</span>` +
1018
+ (s.deleted ? `<span class="stat"><b>${s.deleted}</b>删除</span>` : '') +
1019
+ (s.planned ? `<span class="stat" title="代码面中尚未实际改动的文件(不含文档/工具)"><b>${s.planned}</b>计划中</span>` : '') +
1020
+ `<span class="stat add"><b>+${fmt(s.additions)}</b>行</span>` +
1021
+ `<span class="stat del"><b>-${fmt(s.deletions)}</b>行</span>`;
1022
+ const live = $('#live');
1023
+ if (MODE === 'live') {
1024
+ live.classList.remove('off');
1025
+ live.querySelector('span').textContent = '实时';
1026
+ } else {
1027
+ live.classList.add('off');
1028
+ live.querySelector('span').textContent = '快照';
1029
+ }
1030
+ $('#side-foot').innerHTML =
1031
+ `<div><b>基线</b> ${esc(DATA.base.ref)}${DATA.base.sha ? ' · ' + esc(DATA.base.sha) : ''}</div>` +
1032
+ `<div><b>生成</b> ${esc(new Date(DATA.generatedAt).toLocaleString())}</div>` +
1033
+ (DATA.note ? `<div style="color:var(--warn)">${esc(DATA.note)}</div>` : '') +
1034
+ `<div style="margin-top:3px">● 新增 ● 修改 ● 计划</div>`;
1035
+ // 解析健康度警告条(2026-11-09):层兜底或直认率 <50% 时提示文档标题失约——
1036
+ // 与 gen-dev-tour 的「解析命中」行同源(parseHealth,实时/快照同一模型自动生效)。
1037
+ // 2026-11-10 起同条列出 parseHealth.issues 逐条定位(第 N 行),文案与 gen 打印等价。
1038
+ // 2026-11-11 版面修订:标题行常驻(计数/行号预览),明细默认折叠 + 限高滚动 + 自动换行。
1039
+ const phw = DATA.parseHealth;
1040
+ const warnEl = $('#parse-warn');
1041
+ const ISSUE_LABEL = {
1042
+ 'step-no-files': '步骤无对齐文件块(改动方案缺该步的文件条目)',
1043
+ 'block-unmatched': '文件块锚点未对齐任何步骤(编号/标题与任务拆解不一致)',
1044
+ 'dep-unresolved': '依赖引用未解析到步骤(编号与步骤序号/Tn 不一致)',
1045
+ 'layers-fallback': '全文无结构化步骤标题,按架构层兜底分组',
1046
+ };
1047
+ const ISSUE_SHORT = {
1048
+ 'step-no-files': '步骤缺文件',
1049
+ 'block-unmatched': '文件块未对齐',
1050
+ 'dep-unresolved': '依赖未解析',
1051
+ 'layers-fallback': '层兜底',
1052
+ };
1053
+ const issues = (phw && phw.issues) || [];
1054
+ const lowHit = !!(phw && (phw.layersFallback || (phw.total && phw.hitRate < 0.5)));
1055
+ if (!lowHit && !issues.length) {
1056
+ warnEl.hidden = true;
1057
+ } else {
1058
+ // 同根因归并(2026-11-11):10 条 block-unmatched 是“改动面没按任务归组”一条信息,
1059
+ // 不再逐行铺满;按 kind 分组,组内明细默认收起。
1060
+ const GROUP_ORDER = ['block-unmatched', 'step-no-files', 'dep-unresolved', 'layers-fallback'];
1061
+ const GROUP_HINT = {
1062
+ 'block-unmatched': '改动面/文件块没有按任务归组,解析器无法归属到步骤',
1063
+ 'step-no-files': '实现步骤缺「- 文件:」条目;纯验证步骤可忽略',
1064
+ 'dep-unresolved': '执行顺序 / 依赖引用与任务编号对不上',
1065
+ 'layers-fallback': '全文没有结构化步骤标题,只能按架构层兜底',
1066
+ };
1067
+ const groups = [...new Set([...GROUP_ORDER, ...issues.map((it) => it.kind)])]
1068
+ .map((kind) => ({ kind, items: issues.filter((it) => it.kind === kind) }))
1069
+ .filter((g) => g.items.length);
1070
+ const open = state.warnOpen == null ? issues.length <= 3 : state.warnOpen;
1071
+ const groupHtml = groups.map((g) => {
1072
+ const isOpen = state.warnGroups.has(g.kind);
1073
+ const verifyCount = g.kind === 'step-no-files'
1074
+ ? g.items.filter((it) => isVerificationStep({ title: it.title })).length : 0;
1075
+ const fixedCount = g.items.length - verifyCount;
1076
+ const rows = g.items.map((it) => {
1077
+ const verify = g.kind === 'step-no-files' && isVerificationStep({ title: it.title });
1078
+ return `<div class="warn-row" title="${esc(ISSUE_LABEL[it.kind] || it.kind)}">` +
1079
+ `<span class="warn-line">第 ${esc(it.line)} 行</span>` +
1080
+ `<span class="warn-text">${mdInline(it.title)}</span>` +
1081
+ (verify ? '<span class="warn-kind verify">验证步骤</span>' : '') +
1082
+ `</div>`;
1083
+ }).join('');
1084
+ const countText = verifyCount ? `${fixedCount} 项待修 / ${verifyCount} 项验证` : `${g.items.length} 项`;
1085
+ return `<div class="warn-group">` +
1086
+ `<button class="warn-group-head" type="button" data-wg="${esc(g.kind)}" aria-expanded="${isOpen}">` +
1087
+ `<span class="wg-chev">${isOpen ? '▾' : '▸'}</span>` +
1088
+ `<span class="wg-name">${esc(ISSUE_SHORT[g.kind] || g.kind)}</span>` +
1089
+ `<span class="wg-hint">${esc(GROUP_HINT[g.kind] || '')}</span>` +
1090
+ `<span class="wg-n">${esc(countText)}</span>` +
1091
+ `</button>` +
1092
+ `<div class="warn-group-body"${isOpen ? '' : ' hidden'}>${rows}</div>` +
1093
+ `</div>`;
1094
+ }).join('');
1095
+ const headLines = issues.slice(0, 6).map((it) => it.line);
1096
+ const preview = headLines.length
1097
+ ? `第 ${headLines.join('、')} 行${issues.length > headLines.length ? '…' : ''}`
1098
+ : '';
1099
+ warnEl.hidden = false;
1100
+ warnEl.innerHTML =
1101
+ `<div class="warn-head">` +
1102
+ `<span class="warn-icon">⚠</span>` +
1103
+ `<div class="warn-head-main">` +
1104
+ `<b>解析告警</b>` +
1105
+ (issues.length ? `<span class="warn-count">${issues.length} 项 / ${groups.length} 类</span>` : '') +
1106
+ `<span class="warn-sub">${lowHit ? `文档步骤未被完整识别(直认 ${phw.direct}/${phw.total})` : '文档与图示对齐存疑'}${preview ? ' · ' + preview : ''}</span>` +
1107
+ `</div>` +
1108
+ `<button class="warn-toggle" type="button" aria-expanded="${open}">${open ? '收起' : '展开'}</button>` +
1109
+ `</div>` +
1110
+ `<div class="warn-body"${open ? '' : ' hidden'}>` +
1111
+ (lowHit ? `<div class="warn-note">⚠ 文档步骤未被完整识别(直认 ${phw.direct}/${phw.total}),图示含兜底归集近似——规范 ## 任务拆解 / ## 改动方案 标题后重新生成更准</div>` : '') +
1112
+ groupHtml +
1113
+ `</div>`;
1114
+ const toggle = $('.warn-toggle', warnEl);
1115
+ const body = $('.warn-body', warnEl);
1116
+ toggle.onclick = () => {
1117
+ const next = body.hidden;
1118
+ body.hidden = !next;
1119
+ toggle.textContent = next ? '收起' : '展开';
1120
+ toggle.setAttribute('aria-expanded', String(next));
1121
+ state.warnOpen = next;
1122
+ };
1123
+ $$('.warn-group-head', warnEl).forEach((btn) => {
1124
+ btn.onclick = () => {
1125
+ const gb = btn.nextElementSibling;
1126
+ const next = gb.hidden;
1127
+ gb.hidden = !next;
1128
+ btn.setAttribute('aria-expanded', String(next));
1129
+ const chev = $('.wg-chev', btn);
1130
+ if (chev) chev.textContent = next ? '▾' : '▸';
1131
+ if (next) state.warnGroups.add(btn.dataset.wg); else state.warnGroups.delete(btn.dataset.wg);
1132
+ };
1133
+ });
1134
+ }
1135
+ syncDataflowTab();
1136
+ syncReqsTab();
1137
+ syncEvidenceTab();
1138
+ }
1139
+
1140
+ function renderStepNav() {
1141
+ const q = state.query.trim().toLowerCase();
1142
+ const related = relatedSteps();
1143
+ const list = DATA.steps.filter((s) => !q || s.title.toLowerCase().includes(q) || s.id.toLowerCase().includes(q) || s.files.some((p) => p.toLowerCase().includes(q)));
1144
+ const realTotal = DATA.steps.filter((s) => !isAppendix(s)).length;
1145
+ const realList = list.filter((s) => !isAppendix(s)).length;
1146
+ const hasAppendix = list.some(isAppendix);
1147
+ $('#step-count').textContent = `${realList} / ${realTotal}${hasAppendix ? ' + 附' : ''}`;
1148
+ $('#step-nav').innerHTML = list.map((s) => {
1149
+ const n = s.files.length;
1150
+ const on = state.step === s.id || (related && related.has(s.id));
1151
+ return `<button class="nav-step ${on ? 'on' : ''} ${isAppendix(s) ? 'appendix' : ''}" data-step="${esc(s.id)}" title="${esc(stepTitle(s) + (s.summary ? ' — ' + s.summary : ''))}">` +
1152
+ `<span class="n">${stepNo(s)}</span><span class="t">${esc(isAppendix(s) ? '附:改动清单' : s.title)}</span><span class="c">${n}</span></button>`;
1153
+ }).join('') || '<div class="none">无匹配步骤</div>';
1154
+ }
1155
+
1156
+ function buildTree(files) {
1157
+ const root = { name: '', path: '', dirs: new Map(), files: [] };
1158
+ for (const f of files) {
1159
+ const parts = f.path.split('/');
1160
+ let node = root;
1161
+ for (let i = 0; i < parts.length - 1; i++) {
1162
+ const name = parts[i];
1163
+ if (!node.dirs.has(name)) node.dirs.set(name, { name, path: parts.slice(0, i + 1).join('/'), dirs: new Map(), files: [] });
1164
+ node = node.dirs.get(name);
1165
+ }
1166
+ node.files.push(f);
1167
+ }
1168
+ return root;
1169
+ }
1170
+
1171
+ function statusDot(f) {
1172
+ if (f.status === 'new') return 'var(--ok)';
1173
+ if (f.status === 'modified') return 'var(--accent)';
1174
+ if (f.status === 'deleted') return 'var(--bad)';
1175
+ if (f.status === 'renamed') return 'var(--muted)';
1176
+ return 'var(--warn)';
1177
+ }
1178
+ function badgeOf(f) {
1179
+ let b = '';
1180
+ if (f.test) b += `<span class="badge test">测试</span>`;
1181
+ else if (f.badge) b += `<span class="badge ${esc(f.status)}">${esc(f.badge)}</span>`;
1182
+ if (f.pathGuess) b += `<span class="badge planned" title="路径按同名多候选推断,建议在文档中写全路径">猜?</span>`;
1183
+ return b;
1184
+ }
1185
+
1186
+ function renderTree() {
1187
+ const q = state.query.trim().toLowerCase();
1188
+ const dfo = dataflowObj();
1189
+ let files = DATA.files.filter((f) => !q || f.path.toLowerCase().includes(q) || (stepOf(state.step)?.title || '').toLowerCase().includes(q));
1190
+ if (dfo) {
1191
+ const set = new Set(dfo.files || []);
1192
+ files = files.filter((f) => set.has(f.path));
1193
+ } else if (state.step) {
1194
+ const set = new Set(stepOf(state.step)?.files || []);
1195
+ files = files.filter((f) => set.has(f.path));
1196
+ }
1197
+ $('#file-count').textContent = files.length + ' / ' + DATA.files.length;
1198
+ const tree = buildTree(files);
1199
+ const dirs = [...tree.dirs.values()].sort((a, b) => a.name.localeCompare(b.name));
1200
+ const rootFiles = tree.files.slice().sort((a, b) => a.path.localeCompare(b.path));
1201
+ const html = [];
1202
+ for (const d of dirs) html.push(renderDir(d, 0));
1203
+ for (const f of rootFiles) html.push(renderFile(f, 0));
1204
+ $('#file-tree').innerHTML = html.join('') || '<div class="none">无文件</div>';
1205
+ }
1206
+
1207
+ function renderDir(node, depth) {
1208
+ const collapsed = state.collapsed.has(node.path);
1209
+ const dirs = [...node.dirs.values()].sort((a, b) => a.name.localeCompare(b.name));
1210
+ const files = node.files.slice().sort((a, b) => a.path.localeCompare(b.path));
1211
+ const n = countFiles(node);
1212
+ return `<div class="tree-dir ${collapsed ? 'closed' : ''}" data-dir="${esc(node.path)}">` +
1213
+ `<div class="tree-row" style="padding-left:${4 + depth * 12}px" role="button" tabindex="0" aria-label="目录 ${esc(node.path)}"><span class="chev">▼</span><span class="folder">▸</span><span>${esc(node.name)}</span><span style="margin-left:auto;font:9px var(--mono);color:var(--faint)">${n}</span></div>` +
1214
+ `<div class="tree-children">` +
1215
+ dirs.map((d) => renderDir(d, depth + 1)).join('') +
1216
+ files.map((f) => renderFile(f, depth + 1)).join('') +
1217
+ `</div></div>`;
1218
+ }
1219
+ function countFiles(node) {
1220
+ let n = node.files.length;
1221
+ for (const d of node.dirs.values()) n += countFiles(d);
1222
+ return n;
1223
+ }
1224
+ function renderFile(f, depth) {
1225
+ return `<div class="tree-file ${state.file === f.path ? 'on' : ''}" data-file="${esc(f.path)}" style="padding-left:${20 + depth * 12}px" title="${esc(f.path)}" role="button" tabindex="0" aria-label="文件 ${esc(f.path)}">` +
1226
+ `<span class="dot" style="background:${statusDot(f)}"></span><span class="nm">${esc(f.path.split('/').pop())}</span>` +
1227
+ `${f.test ? '<span class="bd" style="color:var(--intent)">测</span>' : (f.badge ? `<span class="bd" style="color:${statusDot(f)}">${esc(f.badge)}</span>` : '')}` +
1228
+ `${f.pathGuess ? '<span class="bd" style="color:var(--warn)" title="路径按同名多候选推断,建议在文档中写全路径">猜?</span>' : ''}</div>`;
1229
+ }
1230
+
1231
+ function renderCanvas() {
1232
+ const c = $('#canvas');
1233
+ const grid = state.view === 'panorama' && state.layout === 'grid';
1234
+ c.className = 'view-' + state.view + (grid ? ' layout-grid' : '');
1235
+ if (state.view === 'sequence') c.innerHTML = sequenceHtml();
1236
+ else if (state.view === 'flow') { c.innerHTML = flowHtml(); requestAnimationFrame(drawFlowDeps); }
1237
+ else if (state.view === 'reqs') c.innerHTML = reqsHtml();
1238
+ else if (state.view === 'verify') c.innerHTML = verifyHtml();
1239
+ else if (state.view === 'dataflow') c.innerHTML = dataflowHtml();
1240
+ else c.innerHTML = panoramaHtml();
1241
+ c.style.transform = state.zoom === 1 ? '' : `scale(${state.zoom})`;
1242
+ $('#zoom-label').textContent = Math.round(state.zoom * 100) + '%';
1243
+ const lt = $('#layout-toggle');
1244
+ if (lt) {
1245
+ lt.style.display = state.view === 'panorama' ? 'inline-block' : 'none';
1246
+ lt.textContent = '布局:' + (state.layout === 'grid' ? '网格' : '时间线');
1247
+ }
1248
+ const fb = $('#btn-focus');
1249
+ if (fb) {
1250
+ fb.style.display = state.view === 'panorama' ? 'inline-block' : 'none';
1251
+ fb.textContent = document.body.classList.contains('focus') ? '退出专注' : '专注全景';
1252
+ }
1253
+ const hint = $('#pan-hint');
1254
+ if (hint) {
1255
+ const sc = $('#canvas-scroll');
1256
+ requestAnimationFrame(() => {
1257
+ hint.style.display = (state.view === 'panorama' && state.layout === 'timeline' && state.zoom === 1 && c.scrollWidth > sc.clientWidth + 4) ? 'block' : 'none';
1258
+ });
1259
+ }
1260
+ }
1261
+
1262
+ function panoramaHtml() {
1263
+ const list = stepsFor(state.filter);
1264
+ if (!list.length) return '<div class="none">当前筛选无步骤</div>';
1265
+ const related = relatedSteps();
1266
+ const hasDf = hasDataflow();
1267
+ const stations = list.map((s, i) => {
1268
+ const files = filesForStep(s.id);
1269
+ const shown = files.slice(0, 5);
1270
+ const objs = stepObjects(s.id);
1271
+ const objBadges = hasDf && objs.length
1272
+ ? `<div class="st-objs">` +
1273
+ objs.slice(0, 3).map((o) => `<span class="kbadge ${esc(o.kind || '')} ${state.dataflowObj === o.name ? 'on' : ''}" data-df="${esc(o.name)}" title="数据对象 ${esc(o.name)}(点击联动相关步骤与文件)">${esc(o.name)}</span>`).join('') +
1274
+ (objs.length > 3 ? `<span class="kbadge">+${objs.length - 3}</span>` : '') +
1275
+ `</div>`
1276
+ : '';
1277
+ const app = isAppendix(s);
1278
+ const hasAppendix = DATA.steps.some(isAppendix);
1279
+ const emptyLabel = isVerificationStep(s)
1280
+ ? '验证步骤 · 无代码文件'
1281
+ : `该步未列文件${hasAppendix ? ' · <span class="lnk" data-step="A">查看附录改动清单</span>' : ''}`;
1282
+ return `<div class="station ${state.step === s.id || (related && related.has(s.id)) ? 'on' : ''} ${s.status === 'warn' ? 'warn' : ''} ${app ? 'appendix' : ''}" data-step="${esc(s.id)}" style="--i:${i}" role="button" tabindex="0" aria-label="${esc(stepAria(s))}">` +
1283
+ `<div class="st-top"><span class="st-no">${stepNo(s)}</span><div class="st-headtext"><span class="st-label">${app ? '附录 · 改动清单' : stepName(s)}</span><span class="st-title" title="${esc(s.title)}">${esc(stepTitle(s))}</span></div></div>` +
1284
+ `<div class="st-card">` +
1285
+ (s.summary ? `<div class="st-summary" title="${esc(s.summary)}">${s.source === 'tasks' ? '<span class="st-k">判据</span>' : ''}${esc(s.summary)}</div>` : '') +
1286
+ `<div class="st-files">` +
1287
+ (shown.length ? shown.map((f) => {
1288
+ const base = f.path.split('/').pop();
1289
+ const dir = f.path.split('/').slice(-2, -1)[0] || '';
1290
+ return `<div class="st-file" data-file="${esc(f.path)}" title="${esc(f.path)}"><span class="dot" style="background:${statusDot(f)}"></span><span class="nm"><span class="bn">${esc(base)}</span>${dir ? `<span class="dir">${esc(dir)}</span>` : ''}</span><span style="margin-left:auto;color:${statusDot(f)};font-size:10px">${esc(f.badge || '')}</span></div>`;
1291
+ }).join('') : `<div class="st-empty">${emptyLabel}</div>`) +
1292
+ (files.length > shown.length ? `<div class="st-more" title="在右侧查看全部 ${files.length} 个文件">查看全部 ${files.length} 个文件 →</div>` : '') +
1293
+ `</div>` + objBadges + `</div></div>`;
1294
+ }).join('');
1295
+ return state.layout === 'grid' ? `<div class="station-grid">${stations}</div>` : stations;
1296
+ }
1297
+
1298
+ function sequenceHtml() {
1299
+ const seq = DATA.sequence;
1300
+ if (!seq || !seq.participants.length || !seq.messages.length) return '<div class="seq-empty">暂无可推导的时序(步骤未关联到架构层文件)</div>';
1301
+ const parts = seq.participants;
1302
+ const idx = new Map(parts.map((p, i) => [p.id, i]));
1303
+ const n = parts.length;
1304
+ const width = 1000;
1305
+ const head = parts.map((p) => `<div class="seq-part" data-layer="${esc(p.id)}" title="架构层:${esc(LAYER_LABEL[p.id] || p.id)}"><span class="box">${esc(p.label)}</span></div>`).join('');
1306
+ const lines = parts.map(() => '<i></i>').join('');
1307
+ const msgs = seq.messages.map((m) => {
1308
+ const a = idx.get(m.from);
1309
+ const b = idx.get(m.to);
1310
+ if (a == null || b == null) return '';
1311
+ const x1 = (a + 0.5) / n * width;
1312
+ const x2 = (b + 0.5) / n * width;
1313
+ const st = stepOf(m.stepId);
1314
+ const color = m.dashed ? '#7d6224' : '#bc4327';
1315
+ let svg = '';
1316
+ if (a === b) {
1317
+ const x = x1;
1318
+ svg = `<path d="M ${x - 7} 30 L ${x - 7} 19 L ${x + 7} 19 L ${x + 7} 27" fill="none" stroke="${color}" stroke-width="1" ${m.dashed ? 'stroke-dasharray="4 3"' : ''}/><polygon points="${x + 3},27 ${x + 11},27 ${x + 7},33" fill="${color}"/>`;
1319
+ } else {
1320
+ const dir = x2 > x1 ? 1 : -1;
1321
+ svg = `<line x1="${x1}" y1="31" x2="${x2 - dir * 6}" y2="31" stroke="${color}" stroke-width="1" ${m.dashed ? 'stroke-dasharray="4 3"' : ''}/>` +
1322
+ `<polygon points="${x2 - dir * 12},27 ${x2 - dir * 4},31 ${x2 - dir * 12},35" fill="${color}"/>`;
1323
+ }
1324
+ const mx = a === b ? x1 : (x1 + x2) / 2;
1325
+ return `<div class="seq-msg ${state.step === m.stepId ? 'on' : ''} ${isAppendix(st) ? 'appendix' : ''}" data-step="${esc(m.stepId)}" title="${esc(st ? stepTitle(st) : m.stepId)}">` +
1326
+ `<svg viewBox="0 0 ${width} 50" preserveAspectRatio="none">${svg}</svg>` +
1327
+ `<span class="lbl" style="left:${(mx / width * 100).toFixed(2)}%"><b>${esc(isAppendix(st) ? '附' : m.stepId)}</b>${esc(m.label)}</span></div>`;
1328
+ }).join('');
1329
+ return `<div class="seq-wrap" style="--n:${n}">` +
1330
+ `<div class="seq-titlebar"><b>调用时序</b><span>${parts.length} 个参与层 · ${seq.messages.length} 条步骤消息 · 点击消息联动</span></div>` +
1331
+ `<div class="seq-head">${head}</div>` +
1332
+ `<div class="seq-body"><div class="seq-lifelines">${lines}</div>${msgs}</div></div>`;
1333
+ }
1334
+
1335
+ // —— 依赖旁路弧:模型 deps.edges(声明依赖,已传递归约),主链相邻边不重复画 ——
1336
+ function depEdges(list) {
1337
+ const raw = (DATA && DATA.deps && Array.isArray(DATA.deps.edges)) ? DATA.deps.edges : [];
1338
+ if (!raw.length) return [];
1339
+ const pos = new Map(list.map((s, i) => [s.id, i]));
1340
+ return raw.filter((e) => pos.has(e.from) && pos.has(e.to))
1341
+ .map((e) => ({ from: e.from, to: e.to, a: pos.get(e.from), b: pos.get(e.to) }))
1342
+ .filter((e) => e.a !== e.b && Math.abs(e.b - e.a) > 1);
1343
+ }
1344
+ function drawFlowDeps() {
1345
+ const svg = $('#canvas .flow-deps');
1346
+ if (!svg) return;
1347
+ const wrap = $('#canvas .flow-wrap');
1348
+ const nodes = [...wrap.querySelectorAll('.flow-node')];
1349
+ const edges = depEdges(stepsFor(state.filter));
1350
+ if (!nodes.length || !edges.length) return;
1351
+ // offsetTop/offsetHeight 取布局坐标(canvas 可能整体 scale,getBoundingClientRect 会失真)
1352
+ const ys = nodes.map((el) => ({ top: el.offsetTop, bot: el.offsetTop + el.offsetHeight, mid: el.offsetTop + el.offsetHeight / 2 }));
1353
+ const H = wrap.offsetHeight;
1354
+ svg.setAttribute('width', '40');
1355
+ svg.setAttribute('height', String(H));
1356
+ svg.setAttribute('viewBox', `0 0 40 ${H}`);
1357
+ // 车道分配:跨度大的占外道,减少弧间相交
1358
+ const laneOf = new Map();
1359
+ edges.slice().sort((p, q) => Math.abs(q.b - q.a) - Math.abs(p.b - p.a))
1360
+ .forEach((e, i) => laneOf.set(e.from + '>' + e.to, i));
1361
+ const X0 = 38; // 节点左缘(wrap padding-left 46 - 8)
1362
+ const parts = [];
1363
+ for (const e of edges) {
1364
+ const lane = laneOf.get(e.from + '>' + e.to) % 6;
1365
+ const x = 30 - lane * 4.2;
1366
+ const back = e.b < e.a;
1367
+ const y1 = back ? ys[e.a].top + 4 : ys[e.a].bot - 5;
1368
+ const y2 = back ? ys[e.b].bot - 5 : ys[e.b].top + 4;
1369
+ const k = e.from + '>' + e.to;
1370
+ parts.push(`<path class="dep${back ? ' back' : ''}" data-dep="${esc(k)}" d="M ${X0} ${y1} C ${x} ${y1}, ${x} ${y2}, ${X0} ${y2}"/>`);
1371
+ parts.push(`<path class="dep-head" data-dep="${esc(k)}" d="M ${X0 + 1} ${y2} l -6.5 -3.2 v 6.4 z" transform="rotate(${back ? 180 : 0} ${X0} ${y2})"/>`);
1372
+ }
1373
+ svg.innerHTML = parts.join('');
1374
+ }
1375
+ function flowDepHover(stepId, on) {
1376
+ const svg = $('#canvas .flow-deps');
1377
+ if (!svg) return;
1378
+ const wrap = $('#canvas .flow-wrap');
1379
+ for (const e of depEdges(stepsFor(state.filter))) {
1380
+ if (e.from !== stepId && e.to !== stepId) continue;
1381
+ const k = e.from + '>' + e.to;
1382
+ svg.querySelectorAll(`[data-dep="${cssEsc(k)}"]`).forEach((el) => el.classList.toggle('on', on));
1383
+ const other = e.from === stepId ? e.to : e.from;
1384
+ const el = wrap.querySelector(`.flow-node[data-step="${cssEsc(other)}"]`);
1385
+ if (el) el.classList.toggle('rel', on);
1386
+ }
1387
+ }
1388
+
1389
+ function flowHtml() {
1390
+ const list = stepsFor(state.filter);
1391
+ if (!list.length) return '<div class="none">当前筛选无步骤</div>';
1392
+ const related = relatedSteps();
1393
+ const deps = depEdges(list);
1394
+ const gates = (DATA.gates && DATA.gates.length ? DATA.gates : ['构建 / 类型检查', '自动化测试', 'verify.mjs --scope']);
1395
+ const stepHtml = list.map((s, i) => {
1396
+ const files = filesForStep(s.id);
1397
+ const chips = files.slice(0, 4).map((f) => `<span title="${esc(f.path)}">${esc(f.path.split('/').pop())}</span>`).join('') +
1398
+ (files.length > 4 ? `<span>+${files.length - 4}</span>` : '');
1399
+ return `<div class="flow-node ${state.step === s.id || (related && related.has(s.id)) ? 'on' : ''} ${isAppendix(s) ? 'appendix' : ''}" data-step="${esc(s.id)}" style="--i:${i}" role="button" tabindex="0" aria-label="${esc(stepAria(s))}">` +
1400
+ `<div class="fn-head"><span class="fn-no">${stepNo(s)}</span><span class="fn-title">${esc(stepTitle(s))}</span><span style="margin-left:auto;font:9px var(--mono);color:var(--faint)">${files.length} 文件</span></div>` +
1401
+ (s.summary ? `<div class="fn-sum">${esc(s.summary)}</div>` : '') +
1402
+ (chips ? `<div class="fn-files">${chips}</div>` : '') +
1403
+ `</div>` + (i < list.length - 1 ? '<div class="flow-arrow"><i></i><b></b></div>' : '');
1404
+ }).join('');
1405
+ return `<div class="flow-wrap${deps.length ? ' has-deps' : ''}">` +
1406
+ (deps.length ? '<svg class="flow-deps" aria-hidden="true"></svg>' : '') +
1407
+ `<div class="flow-pill">◉ 计划确认 · ${esc(DATA.doc.type)} ${esc(DATA.doc.status || 'approved')}</div>` +
1408
+ `<div class="flow-arrow"><i></i><b></b></div>${stepHtml}` +
1409
+ `<div class="flow-arrow"><i></i><b></b></div>` +
1410
+ `<div class="flow-gate"><h4>验证门(全部通过才算完成)</h4><ul>${gates.map((g) => `<li>${esc(g)}</li>`).join('')}</ul>` +
1411
+ (() => { const le = latestEvidence(); return le ? `<div class="vfy-gate-ev${le.exitCode === 0 ? '' : ' bad'}">窗内最近凭证:<b>${le.exitCode === 0 ? '✓ ' + fmtTs(le.ts) + ' · ' + (le.gates ?? '?') + ' 门全过' : '✗ ' + fmtTs(le.ts) + ' · exit ' + le.exitCode}</b></div>` : ''; })() +
1412
+ `<div class="flow-fail">失败 → 回修当前步骤,不越过门禁</div></div>` +
1413
+ `<div class="flow-arrow"><i></i><b></b></div>` +
1414
+ `<div class="flow-pill end">✓ 关单 / 提交</div>` +
1415
+ `</div>`;
1416
+ }
1417
+
1418
+ function dataflowHtml() {
1419
+ const objs = dfObjects();
1420
+ if (!objs.length) return '<div class="none">未识别到跨文件数据对象(需 diff 或文档正文含类型定义)</div>';
1421
+ const flows = (DATA.dataflow && Array.isArray(DATA.dataflow.flows)) ? DATA.dataflow.flows : [];
1422
+ const cross = objs.reduce((n, o) => n + Math.max(0, ((o.layers || []).length - 1)), 0);
1423
+ const kindOf = (name) => (objs.find((o) => o.name === name) || {}).kind || '';
1424
+ const objRow = (o, i) => {
1425
+ const layers = o.layers || [];
1426
+ const slots = layers.map((ly) => {
1427
+ const names = (o.files || []).filter((p) => (fileOf(p)?.layer) === ly).map((p) => p.split('/').pop());
1428
+ const show = names.slice(0, 2).join('、') + (names.length > 2 ? ` +${names.length - 2}` : '');
1429
+ return `<div class="df-slot"><span class="lyr">${esc(LAYER_LABEL[ly] || ly)}</span><span class="fs" title="${esc(names.join(' · '))}">${esc(show || '—')}</span></div>`;
1430
+ });
1431
+ const lane = slots.map((s, j) => (j ? '<span class="df-link"></span>' : '') + s).join('');
1432
+ return `<div class="df-obj ${state.dataflowObj === o.name ? 'on' : ''}" data-df="${esc(o.name)}" style="--i:${i}" role="button" tabindex="0" aria-label="数据对象 ${esc(o.name)}" title="点击联动相关步骤与文件(再次点击取消)">` +
1433
+ `<span class="df-name">${esc(o.name)}</span>` +
1434
+ `<span class="kbadge ${esc(o.kind || '')}">${esc(o.kind || '?')}</span>` +
1435
+ `<span class="df-nfiles">${(o.files || []).length} 文件</span>` +
1436
+ (lane ? `<span class="df-lane">${lane}</span>` : '') +
1437
+ `</div>`;
1438
+ };
1439
+ const stepLbl = (id) => {
1440
+ const s = stepOf(id);
1441
+ return s
1442
+ ? `<button class="df-step" data-step="${esc(id)}" title="联动步骤 ${esc(s.title)}"><b>${esc(id)}</b>${esc(s.title)}</button>`
1443
+ : `<span>${esc(id)}</span>`;
1444
+ };
1445
+ return `<div class="df-wrap">` +
1446
+ `<div class="df-sum">${objs.length} 个数据对象 · 跨层 ${cross} 处 · ${flows.length} 条步骤间传递</div>` +
1447
+ objs.map(objRow).join('') +
1448
+ (flows.length ? `<div class="df-sec">步骤间传递</div>` + flows.map((f) =>
1449
+ `<div class="df-flow">${stepLbl(f.from)}<span class="arr">→</span>` +
1450
+ `<span class="kbadge ${esc(kindOf(f.object))}" title="数据对象 ${esc(f.object)}">${esc(f.object)}</span>` +
1451
+ `<span class="arr">→</span>${stepLbl(f.to)}</div>`).join('') : '') +
1452
+ `</div>`;
1453
+ }
1454
+
1455
+ // 右侧总览:只放“结论/缺口/门禁”,步骤全文在选中后下钻;不再复述中间 9 张卡片。
1456
+ function overviewHtml() {
1457
+ const s = DATA.stats || {};
1458
+ const realSteps = DATA.steps.filter((x) => !isAppendix(x)).length;
1459
+ const app = DATA.steps.find(isAppendix);
1460
+ const appFiles = app ? filesForStep(app.id) : [];
1461
+ const issues = (DATA.parseHealth && DATA.parseHealth.issues) || [];
1462
+ const issueKinds = new Set(issues.map((it) => it.kind)).size;
1463
+ const ph = DATA.parseHealth || {};
1464
+ const lowHit = !!(ph.layersFallback || (ph.total && ph.hitRate < 0.5));
1465
+ const ev = latestEvidence();
1466
+ const evOk = ev && ev.exitCode === 0;
1467
+ const evText = ev
1468
+ ? (evOk
1469
+ ? `最近绿行 ${fmtTs(ev.ts)} · ${ev.gates ?? '?'} 门全过`
1470
+ : `最近 exit=${ev.exitCode}(${fmtTs(ev.ts)} · ${ev.suite || '?'})未达全绿`)
1471
+ : '时间窗内无验证凭证';
1472
+ const skipped = ev && (ev.skipped || []).length;
1473
+ const gateList = (DATA.gates && DATA.gates.length ? DATA.gates : ['构建 / 类型检查', '自动化测试', 'verify.mjs --scope']);
1474
+ const gateText = gateList.map((g) => String(g).replace(/`/g, '').split(/[::((]/)[0].trim()).join(' · ');
1475
+ const gateFull = gateList.map((g) => String(g).replace(/`/g, '')).join(' | ');
1476
+ const risks = {};
1477
+ for (const st of DATA.steps) {
1478
+ for (const d of (st.detail || [])) {
1479
+ const m = String(d).match(/^-\s*风险[::]\s*(高|中|低)/);
1480
+ if (m) risks[m[1]] = (risks[m[1]] || 0) + 1;
1481
+ }
1482
+ }
1483
+ const riskText = ['高', '中', '低'].filter((k) => risks[k]).map((k) => `${k} ${risks[k]}`).join(' · ') || '未登记';
1484
+ const hotSteps = DATA.steps.filter((st) => (st.detail || []).some((d) => /^-\s*风险[::]\s*(高|中)/.test(String(d)))).map((st) => st.id);
1485
+ const unresolved = (DATA.deps && DATA.deps.unresolved) || 0;
1486
+ const ups = DATA.doc.upstream || [];
1487
+ const reqSource = ups.length
1488
+ ? ups.map((u) => `<div class="ov-sub"><b>${esc(u.type)}</b> ${esc(u.title)}${u.purpose ? `<br><span class="ov-req-purpose" title="${esc(u.purpose)}">${mdInline(u.purpose)}</span>` : ''}</div>`).join('')
1489
+ : (DATA.doc.purpose
1490
+ ? `<div class="ov-sub">${mdInline(DATA.doc.purpose)}</div>`
1491
+ : '<div class="ov-sub">未登记上游需求;在 plan 写「对应入口 / 对应 spec」后自动出现。</div>');
1492
+ return `<div class="ov">` +
1493
+ `<section class="ov-sec">` +
1494
+ `<div class="ov-eyebrow">需求来源</div>` +
1495
+ reqSource +
1496
+ (hasReqs() ? `<button class="ov-link" type="button" data-view="reqs">查看需求与验收 →</button>` : '') +
1497
+ `</section>` +
1498
+ `<section class="ov-sec">` +
1499
+ `<div class="ov-eyebrow">范围</div>` +
1500
+ `<div class="ov-kpis"><b>${realSteps}</b> 步 · <b>${s.files || 0}</b> 代码${s.docs ? ` · <b>${s.docs}</b> 文档` : ''}</div>` +
1501
+ `<div class="ov-sub">修改 ${s.modified || 0} · 计划中 ${s.planned || 0} · <span class="ov-add">+${fmt(s.additions || 0)}</span> / <span class="ov-del">−${fmt(s.deletions || 0)}</span></div>` +
1502
+ `</section>` +
1503
+ `<section class="ov-sec">` +
1504
+ `<div class="ov-eyebrow">未归属文件${app ? ` <b>${appFiles.length}</b>` : ''}</div>` +
1505
+ (app
1506
+ ? `<button class="ov-link" type="button" data-step="${esc(app.id)}">附:改动清单(${appFiles.length} 个文件)→</button>` +
1507
+ `<div class="ov-sub">任务拆解未覆盖;补 <code>- 文件:</code> 后会自动归位。</div>`
1508
+ : `<div class="ov-sub">无未归属文件。</div>`) +
1509
+ `</section>` +
1510
+ `<section class="ov-sec">` +
1511
+ `<div class="ov-eyebrow">验证 / 门禁</div>` +
1512
+ `<div class="ov-status ${ev ? (evOk ? 'ok' : 'bad') : ''}"><i></i><span>${esc(evText)}</span></div>` +
1513
+ (skipped ? `<div class="ov-sub">本地跳过 ${skipped} 项(缺环境),未计入失败。</div>` : '') +
1514
+ `<div class="ov-sub" title="${esc(gateFull)}">计划门禁 ${gateList.length} 项:${esc(gateText)}</div>` +
1515
+ (hasEvidence() ? `<button class="ov-link" type="button" data-view="verify">查看验证 →</button>` : "") +
1516
+ `</section>` +
1517
+ `<section class="ov-sec">` +
1518
+ `<div class="ov-eyebrow">解析与风险</div>` +
1519
+ `<div class="ov-sub">${issues.length ? `解析告警 ${issues.length} 项 / ${issueKinds} 类` : '解析正常'}${lowHit ? ` · 直认 ${ph.direct ?? '?'}/${ph.total ?? '?'}` : ''} · 依赖未解析 ${unresolved} 处</div>` +
1520
+ `<div class="ov-sub">风险:${riskText}${hotSteps.length ? `(关注 ${hotSteps.join('/')})` : ''}</div>` +
1521
+ `</section>` +
1522
+ `</div>`;
1523
+ }
1524
+
1525
+ // 选中步骤的「需求来源」:只放 INTENT/SPEC 的一句话,详细要点去需求 tab。
1526
+ function stepReqHtml() {
1527
+ const ups = DATA.doc.upstream || [];
1528
+ if (!ups.length && !DATA.doc.purpose) {
1529
+ return `<div class="sc-sec"><div class="sc-sec-h">需求来源</div><div class="sc-sec-empty">未登记上游需求;在 plan 写「对应入口 / 对应 spec」。</div></div>`;
1530
+ }
1531
+ const items = ups.length
1532
+ ? ups.map((u) => `<div class="sc-req"><span class="req-badge" data-type="${esc(u.type)}">${esc(u.type)}</span><span class="sc-req-title">${esc(u.title)}</span>${u.purpose ? `<div class="sc-req-why" title="${esc(u.purpose)}">${mdInline(u.purpose)}</div>` : ''}</div>`).join('')
1533
+ : `<div class="sc-req-why">${mdInline(DATA.doc.purpose)}</div>`;
1534
+ return `<div class="sc-sec"><div class="sc-sec-h">需求来源</div>${items}${hasReqs() ? '<button class="ov-link" type="button" data-view="reqs">查看需求与验收 →</button>' : ''}</div>`;
1535
+ }
1536
+
1537
+ function renderDetail() {
1538
+ const curStep = state.step ? stepOf(state.step) : null;
1539
+ const detailKey = curStep ? curStep.id : '__overview__';
1540
+ const resetScroll = detailKey !== lastDetailKey;
1541
+ lastDetailKey = detailKey;
1542
+ const back = $('#detail-back');
1543
+ if (back) back.hidden = !curStep;
1544
+ const heading = $('.detail-head h2');
1545
+ if (heading) heading.textContent = curStep ? '变更说明' : '总览';
1546
+ const filterTabs = $('#filter-tabs');
1547
+ if (!curStep) {
1548
+ if (filterTabs) { filterTabs.style.display = 'none'; filterTabs.innerHTML = ''; }
1549
+ $('#detail-sub').textContent = '范围 · 验证 · 未归属';
1550
+ $('#detail-body').innerHTML = overviewHtml();
1551
+ if (resetScroll) $('#detail-body').scrollTop = 0;
1552
+ return;
1553
+ }
1554
+ if (filterTabs) filterTabs.style.display = '';
1555
+
1556
+ // 计数与筛选共用 matchesFilter,修掉 planned 状态与 planned 标记重复计数(12 → 6)。
1557
+ const counts = {};
1558
+ for (const [k] of FILTERS) counts[k] = DATA.files.filter((f) => matchesFilter(f, k)).length;
1559
+ filterTabs.innerHTML = FILTERS.map(([k, label]) =>
1560
+ `<button data-filter="${k}" title="${k === 'planned' ? '计划中:文档已登记、工作树尚未实际改动(含文档/工具)' : '文件筛选(含文档/工具)'}" class="${state.filter === k ? 'on' : ''}">${label}<span class="n">${counts[k]}</span></button>`).join('');
1561
+ $('#detail-sub').textContent = isAppendix(curStep) ? '附录 · 未分步文件' : `第 ${curStep.index} 步`;
1562
+ const list = [curStep];
1563
+
1564
+ const dfo = dataflowObj();
1565
+ const related = relatedSteps();
1566
+ const html = list.map((s, i) => {
1567
+ let files = filesForStep(s.id);
1568
+ const totalFiles = files.length;
1569
+ if (state.filter !== 'all') files = files.filter((f) => matchesFilter(f, state.filter));
1570
+ const rawDetails = (s.detail || []).map((d) => d.replace(/^[-*]\s*/, '').trim()).filter(Boolean);
1571
+ const acceptance = (s.source === 'tasks' && s.summary) ? [s.summary] : rawDetails
1572
+ .filter((d) => /^判据[::]/.test(d))
1573
+ .map((d) => d.replace(/^判据[::]\s*/, ''))
1574
+ .filter(Boolean);
1575
+ const proof = rawDetails
1576
+ .filter((d) => /^留痕[::]/.test(d))
1577
+ .map((d) => d.replace(/^留痕[::]\s*/, ''))
1578
+ .filter(Boolean);
1579
+ const details = rawDetails
1580
+ .filter((d) => !/^(判据|留痕)[::]/.test(d) && !/^(留痕|风险|依赖|判据)[::]?$/.test(d))
1581
+ .slice(0, 8);
1582
+ const ev = latestEvidence();
1583
+ const evOk = ev && ev.exitCode === 0;
1584
+ const evText = ev
1585
+ ? (evOk
1586
+ ? `最近绿行 ${fmtTs(ev.ts)} · ${ev.gates ?? '?'} 门全过`
1587
+ : `最近 exit=${ev.exitCode}(${fmtTs(ev.ts)} · ${ev.suite || '?'})未达全绿`)
1588
+ : '时间窗内无验证凭证';
1589
+ const verifyLine = `<div class="sc-proof-verify"><b>计划级验证</b><span class="${evOk ? 'ok' : ev ? 'bad' : ''}">${esc(evText)}</span>${hasEvidence() ? '<button class="ov-link" type="button" data-view="verify">查看验证 →</button>' : ''}</div>`;
1590
+ const proofHtml = isAppendix(s) ? '' :
1591
+ `<div class="sc-proof${acceptance.length || proof.length || ev ? '' : ' empty'}">` +
1592
+ `<div class="sc-proof-h">怎么证明 · 验收判据</div>` +
1593
+ (acceptance.length
1594
+ ? `<ul class="sc-accept">${acceptance.map((a) => `<li>${mdInline(a)}</li>`).join('')}</ul>`
1595
+ : `<div class="sc-proof-empty">未登记验收判据</div>`) +
1596
+ (proof.length ? `<div class="sc-proof-meta"><b>留痕</b>${proof.map((p) => mdInline(p)).join(';')}</div>` : '') +
1597
+ verifyLine +
1598
+ `</div>`;
1599
+ const emptyText = isVerificationStep(s) ? '验证步骤(无代码文件)' : (totalFiles ? '当前筛选无文件' : '该步未列文件');
1600
+ const statusTag = s.status === 'done' ? '<span class="done">已完成</span>' : s.status === 'warn' ? '<span class="done" style="color:var(--warn)">待复核</span>' : '';
1601
+ const eyebrow = isAppendix(s) ? '附录 · 改动清单' : `做什么 · ${sourceLabel(s)}${statusTag}`;
1602
+ return `<div class="step-card ${state.step === s.id || (related && related.has(s.id)) ? 'on' : ''} ${s.status === 'warn' ? 'warn' : ''} ${isAppendix(s) ? 'appendix' : ''}" data-step="${esc(s.id)}" style="--i:${i}">` +
1603
+ `<div class="sc-head" data-step="${esc(s.id)}" role="button" tabindex="0" aria-label="${esc(stepAria(s))}"><span class="sc-no">${stepNo(s)}</span><div class="sc-titlewrap"><div class="sc-eyebrow">${eyebrow}</div><h3>${esc(stepTitle(s))}</h3></div><span class="meta">${files.length} 文件</span></div>` +
1604
+ `<div class="sc-body">` +
1605
+ (s.summary && !(s.source === 'tasks') ? `<p class="sc-summary">${esc(s.summary)}</p>` : '') +
1606
+ (!isAppendix(s) ? stepReqHtml() : '') +
1607
+ (!isAppendix(s) ? `<div class="sc-sec"><div class="sc-sec-h">怎么做 · 实现要点 / 依赖 / 风险</div>${details.length ? `<ul class="sc-detail">${details.map((d) => `<li>${mdInline(d)}</li>`).join('')}</ul>` : '<div class="sc-sec-empty">任务拆解未写实现要点 / 依赖 / 风险</div>'}</div>` : '') +
1608
+ `<div class="sc-sec"><div class="sc-sec-h">改动文件 <b>${files.length}</b></div>` +
1609
+ `<div class="sc-files">${files.map((f) => fileRow(f, dfo)).join('') || `<div class="none" style="padding:8px">${emptyText}</div>`}</div></div>` +
1610
+ (proofHtml ? `<div class="sc-sec">${proofHtml}</div>` : '') +
1611
+ `</div></div>`;
1612
+ }).join('');
1613
+ $('#detail-body').innerHTML = html || '<div class="none">无步骤</div>';
1614
+ if (resetScroll) $('#detail-body').scrollTop = 0;
1615
+ }
1616
+
1617
+ function fileRow(f, dfo) {
1618
+ const diffBtn = f.diff ? `<button class="diff-btn" data-diff="${esc(f.path)}">diff</button>` : '';
1619
+ const nums = (f.additions || f.deletions) ? `<span class="num">+${f.additions || 0}${f.deletions ? `<del>-${f.deletions}</del>` : ''}</span>` : '';
1620
+ const active = state.file === f.path || (dfo && (dfo.files || []).includes(f.path)) ? 'on' : '';
1621
+ return `<div class="file-row ${active}" data-file="${esc(f.path)}" title="${esc(f.path)}" role="button" tabindex="0" aria-label="文件 ${esc(f.path)}">` +
1622
+ `<span class="dot" style="background:${statusDot(f)}"></span>` +
1623
+ `<span class="path">${esc(f.path)}</span>${nums}${badgeOf(f)}${diffBtn}</div>` +
1624
+ (f.diff ? `<pre class="diff ${state.file === f.path ? 'open' : ''}" data-diff-for="${esc(f.path)}">${diffHtml(f.diff)}</pre>` : '');
1625
+ }
1626
+
1627
+ function diffHtml(diff) {
1628
+ const lines = String(diff).split(/\r?\n/);
1629
+ return lines.map((l) => {
1630
+ let cls = '';
1631
+ if (/^diff --git|^index |^--- |^\+\+\+ |^new file|^deleted file|^similarity|^rename /.test(l)) cls = 'meta';
1632
+ else if (/^@@/.test(l)) cls = 'hunk';
1633
+ else if (/^\+/.test(l)) cls = 'add';
1634
+ else if (/^-/.test(l)) cls = 'del';
1635
+ return cls ? `<span class="${cls}">${esc(l)}</span>` : esc(l);
1636
+ }).join('\n');
1637
+ }
1638
+
1639
+ function renderAll(opts = {}) {
1640
+ if (!DATA) return;
1641
+ const scroll = {
1642
+ tree: $('#file-tree').scrollTop,
1643
+ detail: $('#detail-body').scrollTop,
1644
+ nav: $('#step-nav').scrollTop,
1645
+ canvas: $('#canvas-scroll').scrollLeft,
1646
+ };
1647
+ const detailKeyBefore = lastDetailKey;
1648
+ renderHeader();
1649
+ renderStepNav();
1650
+ renderTree();
1651
+ renderCanvas();
1652
+ renderDetail();
1653
+ $('#file-tree').scrollTop = scroll.tree;
1654
+ $('#detail-body').scrollTop = lastDetailKey === detailKeyBefore ? scroll.detail : 0;
1655
+ $('#step-nav').scrollTop = scroll.nav;
1656
+ if (opts.keepCanvas) $('#canvas-scroll').scrollLeft = scroll.canvas;
1657
+ else $('#canvas-scroll').scrollLeft = 0;
1658
+ if (opts.scrollFile) {
1659
+ const el = $(`.file-row[data-file="${cssEsc(state.file)}"]`);
1660
+ if (el) el.scrollIntoView({ block: 'center', behavior: 'smooth' });
1661
+ }
1662
+ if (opts.scrollStep) {
1663
+ const el = $(`.station[data-step="${cssEsc(state.step)}"], .flow-node[data-step="${cssEsc(state.step)}"]`);
1664
+ if (el) el.scrollIntoView({ block: 'center', inline: 'center', behavior: 'smooth' });
1665
+ }
1666
+ syncHash();
1667
+ }
1668
+ function cssEsc(s) { return String(s).replace(/["\\]/g, '\\$&'); }
1669
+
1670
+ function selectStep(id) {
1671
+ state.step = state.step === id ? null : id;
1672
+ state.file = null;
1673
+ renderAll({ scrollStep: true });
1674
+ }
1675
+ function selectFile(p) {
1676
+ const f = fileOf(p);
1677
+ if (!f) return;
1678
+ state.file = p;
1679
+ state.step = (f.steps && f.steps[0]) || state.step;
1680
+ renderAll({ scrollFile: true });
1681
+ }
1682
+ function toggleDataflowObj(name) {
1683
+ if (state.dataflowObj === name) {
1684
+ clearDataflowSel(true);
1685
+ } else {
1686
+ if (!state.dataflowObj) { state.dfPrevStep = state.step; state.dfPrevFile = state.file; }
1687
+ state.dataflowObj = name;
1688
+ }
1689
+ renderAll();
1690
+ }
1691
+ function clearLink() {
1692
+ state.step = null; state.file = null;
1693
+ clearDataflowSel(false);
1694
+ state.filter = 'all';
1695
+ stopFollow();
1696
+ renderAll();
1697
+ }
1698
+ function setView(v) {
1699
+ if (v === 'dataflow' && !hasDataflow()) v = 'panorama';
1700
+ if (v === 'reqs' && !hasReqs()) v = 'panorama';
1701
+ if (v === 'verify' && !hasEvidence()) v = 'panorama';
1702
+ state.view = v;
1703
+ state.zoom = 1;
1704
+ $$('#view-tabs .tab').forEach((b) => b.classList.toggle('on', b.dataset.view === v));
1705
+ renderAll();
1706
+ // 视图用量埋点(2026-11-11 tour-usage):fire-and-forget,失败静默不影响页面;
1707
+ // 静态 file:// 快照无服务时自然跳过。数据只落本机 .agents/cache/tour-usage.jsonl(口径见 dev-tour SKILL 边界节)。
1708
+ if (DATA && DATA.doc && DATA.doc.path && typeof fetch === 'function') {
1709
+ try {
1710
+ fetch('/api/tour/usage', { method: 'POST', headers: { 'Content-Type': 'application/json' },
1711
+ body: JSON.stringify({ doc: DATA.doc.path, view: v, ts: new Date().toISOString() }) }).catch(() => {});
1712
+ } catch { /* file:// 下同步抛错也不影响页面 */ }
1713
+ }
1714
+ }
1715
+ function setFilter(f) { state.filter = f; renderAll(); }
1716
+
1717
+ function fitZoom() {
1718
+ if (state.view === 'panorama' && state.layout === 'grid') { state.zoom = 1; renderCanvas(); return; }
1719
+ const wrap = $('#canvas');
1720
+ const scroller = $('#canvas-scroll');
1721
+ const contentW = wrap.scrollWidth;
1722
+ const avail = scroller.clientWidth - 36;
1723
+ const z = contentW > avail ? Math.max(0.25, avail / contentW) : 1;
1724
+ state.zoom = Math.round(z * 100) / 100;
1725
+ renderCanvas();
1726
+ }
1727
+ function zoomBy(d) {
1728
+ state.zoom = Math.max(0.25, Math.min(1.6, Math.round((state.zoom + d) * 10) / 10));
1729
+ renderCanvas();
1730
+ }
1731
+
1732
+ function startFollow() {
1733
+ state.follow = true;
1734
+ $('#btn-follow').classList.add('on');
1735
+ $('#btn-follow').textContent = '❚❚ 暂停跟走';
1736
+ const order = DATA.steps.map((s) => s.id);
1737
+ const cur = state.step ? order.indexOf(state.step) : -1;
1738
+ let i = cur < 0 ? 0 : cur;
1739
+ state.step = order[i];
1740
+ renderAll({ scrollStep: true });
1741
+ followTimer = setInterval(() => {
1742
+ i++;
1743
+ if (i >= order.length) { stopFollow(); return; }
1744
+ state.step = order[i];
1745
+ state.file = null;
1746
+ renderAll({ scrollStep: true });
1747
+ }, 4200);
1748
+ }
1749
+ function stopFollow() {
1750
+ state.follow = false;
1751
+ clearInterval(followTimer); followTimer = null;
1752
+ $('#btn-follow').classList.remove('on');
1753
+ $('#btn-follow').textContent = '▶ 跟着流程走';
1754
+ }
1755
+ function toggleFollow() { state.follow ? stopFollow() : startFollow(); }
1756
+
1757
+ function bind() {
1758
+ document.addEventListener('click', (e) => {
1759
+ const dir = e.target.closest('.tree-row');
1760
+ if (dir) {
1761
+ const box = dir.closest('.tree-dir');
1762
+ const p = box.dataset.dir;
1763
+ if (state.collapsed.has(p)) state.collapsed.delete(p); else state.collapsed.add(p);
1764
+ renderTree();
1765
+ return;
1766
+ }
1767
+ const cp = e.target.closest('[data-copy]');
1768
+ if (cp) {
1769
+ e.stopPropagation();
1770
+ const text = cp.dataset.copy || '';
1771
+ if (navigator.clipboard && navigator.clipboard.writeText) {
1772
+ const old = cp.textContent;
1773
+ navigator.clipboard.writeText(text).then(() => { cp.textContent = '已复制'; setTimeout(() => { cp.textContent = old; }, 1200); }).catch(() => {});
1774
+ }
1775
+ return;
1776
+ }
1777
+ const df = e.target.closest('[data-diff]');
1778
+ if (df) {
1779
+ e.stopPropagation();
1780
+ const pre = $(`[data-diff-for="${cssEsc(df.dataset.diff)}"]`);
1781
+ if (pre) pre.classList.toggle('open');
1782
+ return;
1783
+ }
1784
+ const dfc = e.target.closest('[data-df]');
1785
+ if (dfc) { e.stopPropagation(); toggleDataflowObj(dfc.dataset.df); return; }
1786
+ const file = e.target.closest('[data-file]');
1787
+ if (file) { e.stopPropagation(); selectFile(file.dataset.file); return; }
1788
+ const step = e.target.closest('[data-step]');
1789
+ if (step) { selectStep(step.dataset.step); return; }
1790
+ const view = e.target.closest('[data-view]');
1791
+ if (view) { setView(view.dataset.view); return; }
1792
+ const filter = e.target.closest('[data-filter]');
1793
+ if (filter) { setFilter(filter.dataset.filter); return; }
1794
+ });
1795
+ document.addEventListener('mouseover', (e) => {
1796
+ const n = e.target.closest('.flow-node');
1797
+ if (n && n.dataset.step) flowDepHover(n.dataset.step, true);
1798
+ });
1799
+ document.addEventListener('mouseout', (e) => {
1800
+ const n = e.target.closest('.flow-node');
1801
+ if (n && n.dataset.step) flowDepHover(n.dataset.step, false);
1802
+ });
1803
+ window.addEventListener('resize', () => { if (state.view === 'flow') drawFlowDeps(); });
1804
+ $('#search').addEventListener('input', (e) => { state.query = e.target.value; renderStepNav(); renderTree(); });
1805
+ $('#btn-clear').addEventListener('click', clearLink);
1806
+ $('#detail-back').addEventListener('click', clearLink);
1807
+ $('#btn-follow').addEventListener('click', toggleFollow);
1808
+ $('#btn-copy').addEventListener('click', async () => {
1809
+ try { await navigator.clipboard.writeText(DATA.doc.path); $('#btn-copy').textContent = '已复制'; setTimeout(() => $('#btn-copy').textContent = '复制路径', 1200); } catch { /* 忽略 */ }
1810
+ });
1811
+ $('#expand-all').addEventListener('click', () => { state.collapsed.clear(); renderTree(); });
1812
+ $('#collapse-all').addEventListener('click', () => {
1813
+ state.collapsed.clear();
1814
+ const walk = (node) => { for (const d of node.dirs.values()) { state.collapsed.add(d.path); walk(d); } };
1815
+ walk(buildTree(DATA.files));
1816
+ renderTree();
1817
+ });
1818
+ $('#btn-focus').addEventListener('click', () => {
1819
+ document.body.classList.toggle('focus');
1820
+ renderAll();
1821
+ });
1822
+ $('#layout-toggle').addEventListener('click', () => {
1823
+ state.layout = state.layout === 'grid' ? 'timeline' : 'grid';
1824
+ try { localStorage.setItem('devTourLayout', state.layout); } catch { /* 本地禁用时忽略 */ }
1825
+ state.zoom = 1;
1826
+ renderAll();
1827
+ });
1828
+ $('#fit').addEventListener('click', fitZoom);
1829
+ $('#zoom-in').addEventListener('click', () => zoomBy(0.1));
1830
+ $('#zoom-out').addEventListener('click', () => zoomBy(-0.1));
1831
+ document.addEventListener('keydown', (e) => {
1832
+ if (e.key === 'Escape') { document.body.classList.remove('focus'); clearLink(); return; }
1833
+ if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
1834
+ if ((e.key === 'Enter' || e.key === ' ') && e.target.tagName !== 'BUTTON') {
1835
+ const hit = e.target.closest('[data-step],[data-file],[data-diff],[data-df]');
1836
+ if (hit) { e.preventDefault(); hit.click(); return; }
1837
+ }
1838
+ if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') {
1839
+ const order = DATA.steps.map((s) => s.id);
1840
+ const i = state.step ? order.indexOf(state.step) : -1;
1841
+ const next = e.key === 'ArrowRight' ? Math.min(order.length - 1, i + 1) : Math.max(0, i < 0 ? 0 : i - 1);
1842
+ state.step = order[next]; state.file = null;
1843
+ renderAll({ scrollStep: true });
1844
+ }
1845
+ });
1846
+ window.addEventListener('hashchange', () => {
1847
+ const p = new URLSearchParams(location.hash.slice(1));
1848
+ state.step = p.get('step') || null;
1849
+ state.file = p.get('file') || null;
1850
+ if (p.get('view')) setView(p.get('view')); else renderAll();
1851
+ });
1852
+ }
1853
+
1854
+ async function fetchModel() {
1855
+ const file = DOC_FILE || new URLSearchParams(location.search).get('file');
1856
+ if (!file) throw new Error('缺少 file 参数');
1857
+ const r = await fetch('/api/tour?file=' + encodeURIComponent(file), { cache: 'no-store' });
1858
+ const j = await r.json();
1859
+ if (!r.ok || j.error) throw new Error(j.error || ('HTTP ' + r.status));
1860
+ if (j.doc && !j.doc.path) j.doc.path = file;
1861
+ return j;
1862
+ }
1863
+
1864
+ function applyModel(model) {
1865
+ const dfo = (model.dataflow && Array.isArray(model.dataflow.objects)) ? model.dataflow.objects : [];
1866
+ const dfl = (model.dataflow && Array.isArray(model.dataflow.flows)) ? model.dataflow.flows : [];
1867
+ const dpe = (model.deps && Array.isArray(model.deps.edges)) ? model.deps.edges : [];
1868
+ const sig = model.files.length + '|' + model.stats.additions + '/' + model.stats.deletions + '|' + model.steps.map((s) => s.id + s.files.length).join(',') + '|' + model.files.map((f) => f.path + f.status + f.additions + f.deletions + (f.diff ? f.diff.length : 0)).join(',') +
1869
+ '|df:' + dfo.length + ':' + dfo.map((o) => o.name + ':' + (o.kind || '') + ':' + (o.files || []).length + ':' + (o.steps || []).join('+')).join(';') + '|' + dfl.map((f) => f.object + '>' + f.from + '>' + f.to).join(';') +
1870
+ '|deps:' + dpe.map((e) => e.from + '>' + e.to).join(';') +
1871
+ '|reqs:' + ((model.doc.upstream || []).map((u) => u.path + u.purpose + (u.points || []).length).join(';')) +
1872
+ '|ev:' + ((model.evidence && model.evidence.rows || []).map((r) => r.ts + r.exitCode + ':' + (r.note || '').length + ':' + (r.logTail || '').length + ':' + Object.keys(r.durations || {}).length).join(';')) +
1873
+ '|ph:' + JSON.stringify(model.parseHealth || null);
1874
+ if (sig === lastSig) return false;
1875
+ lastSig = sig;
1876
+ DATA = model;
1877
+ FILE_BY_PATH = new Map(DATA.files.map((f) => [f.path, f]));
1878
+ if (state.step && !DATA.steps.some((s) => s.id === state.step)) state.step = null;
1879
+ if (state.file && !FILE_BY_PATH.has(state.file)) state.file = null;
1880
+ if (state.dataflowObj && !dfo.some((o) => o.name === state.dataflowObj)) clearDataflowSel(true);
1881
+ return true;
1882
+ }
1883
+
1884
+ async function refresh() {
1885
+ if (MODE !== 'live' || document.visibilityState === 'hidden') return;
1886
+ if (Date.now() - lastRefresh < 1200) return;
1887
+ lastRefresh = Date.now();
1888
+ try {
1889
+ const m = await fetchModel();
1890
+ if (applyModel(m)) renderAll({ keepCanvas: true });
1891
+ $('#live').querySelector('span').textContent = '实时';
1892
+ } catch (e) {
1893
+ $('#live').classList.add('off');
1894
+ $('#live').querySelector('span').textContent = '断线';
1895
+ }
1896
+ }
1897
+
1898
+ async function init() {
1899
+ try {
1900
+ if (EMBEDDED) applyModel(EMBEDDED);
1901
+ else applyModel(await fetchModel());
1902
+ } catch (e) {
1903
+ $('#title').textContent = '开发全景图加载失败';
1904
+ $('#subtitle').textContent = e.message;
1905
+ $('#canvas').innerHTML = `<div class="none">${esc(e.message)}<br><br>实时视图需先启动 <code>node .agents/scripts/ensure-board.mjs</code></div>`;
1906
+ return;
1907
+ }
1908
+ readHash();
1909
+ if (!['panorama', 'sequence', 'flow', 'dataflow', 'reqs', 'verify'].includes(state.view)) state.view = 'panorama';
1910
+ if (state.view === 'dataflow' && !hasDataflow()) state.view = 'panorama';
1911
+ if (state.view === 'reqs' && !hasReqs()) state.view = 'panorama';
1912
+ if (state.view === 'verify' && !hasEvidence()) state.view = 'panorama';
1913
+ $$('#view-tabs .tab').forEach((b) => b.classList.toggle('on', b.dataset.view === state.view));
1914
+ bind();
1915
+ renderAll();
1916
+ setTimeout(() => document.getElementById('app').classList.remove('booting'), 1700);
1917
+ if (state.file) {
1918
+ const el = $(`.file-row[data-file="${cssEsc(state.file)}"]`);
1919
+ if (el) el.scrollIntoView({ block: 'center' });
1920
+ }
1921
+ if (MODE === 'live') {
1922
+ try {
1923
+ sse = new EventSource('/api/events');
1924
+ sse.addEventListener('changed', () => setTimeout(refresh, 400));
1925
+ sse.addEventListener('watchdead', () => {
1926
+ $('#live').classList.add('off');
1927
+ $('#live').querySelector('span').textContent = '轮询';
1928
+ });
1929
+ sse.onerror = () => { $('#live').classList.add('off'); $('#live').querySelector('span').textContent = '轮询'; };
1930
+ } catch { /* 无 SSE 时靠轮询 */ }
1931
+ liveTimer = setInterval(refresh, 5000);
1932
+ document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') refresh(); });
1933
+ }
1934
+ }
1935
+
1936
+ init();
1937
+ })();
1938
+ </script>
1939
+ </body>
1940
+ </html>