oh-my-opencode 4.13.0 → 4.14.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 (445) hide show
  1. package/.agents/skills/codex-qa/SKILL.md +15 -0
  2. package/.agents/skills/codex-qa/references/logging-debug.md +12 -0
  3. package/.agents/skills/opencode-qa/SKILL.md +17 -0
  4. package/.agents/skills/opencode-qa/references/tui-tmux.md +17 -0
  5. package/.agents/skills/work-with-pr/SKILL.md +18 -10
  6. package/.opencode/skills/work-with-pr/SKILL.md +18 -10
  7. package/README.md +2 -2
  8. package/dist/cli/doctor/checks/telemetry.d.ts +2 -0
  9. package/dist/cli/doctor/framework/constants.d.ts +1 -0
  10. package/dist/cli/doctor/framework/doctor-target.d.ts +1 -1
  11. package/dist/cli/index.js +24651 -16452
  12. package/dist/cli-node/index.js +24651 -16452
  13. package/dist/config/schema/oh-my-opencode-config.d.ts +1 -0
  14. package/dist/features/team-mode/test-support/async-test-helpers.d.ts +19 -0
  15. package/dist/hooks/atlas/final-wave-approval-gate.d.ts +1 -0
  16. package/dist/hooks/atlas/subagent-completion-reminder.d.ts +30 -0
  17. package/dist/hooks/atlas/types.d.ts +2 -0
  18. package/dist/hooks/atlas/verification-reminders.d.ts +3 -0
  19. package/dist/hooks/team-session-events/team-idle-wake-hint.d.ts +1 -0
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.js +33232 -19559
  22. package/dist/mcp/context7.d.ts +9 -8
  23. package/dist/oh-my-opencode.schema.json +4 -0
  24. package/dist/shared/posthog.d.ts +17 -10
  25. package/dist/shared/telemetry-product-identity.d.ts +2 -0
  26. package/dist/skills/coding-agent-sessions/SKILL.md +128 -0
  27. package/dist/skills/coding-agent-sessions/agents/openai.yaml +4 -0
  28. package/dist/skills/coding-agent-sessions/references/all-platforms.md +71 -0
  29. package/dist/skills/coding-agent-sessions/references/claude.md +32 -0
  30. package/dist/skills/coding-agent-sessions/references/codex.md +30 -0
  31. package/dist/skills/coding-agent-sessions/references/opencode.md +43 -0
  32. package/dist/skills/coding-agent-sessions/references/senpi.md +17 -0
  33. package/dist/skills/coding-agent-sessions/scripts/agent_sessions/__init__.py +1 -0
  34. package/dist/skills/coding-agent-sessions/scripts/agent_sessions/claude.py +66 -0
  35. package/dist/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +283 -0
  36. package/dist/skills/coding-agent-sessions/scripts/agent_sessions/codex.py +105 -0
  37. package/dist/skills/coding-agent-sessions/scripts/agent_sessions/file_scanners.py +275 -0
  38. package/dist/skills/coding-agent-sessions/scripts/agent_sessions/jsonio.py +55 -0
  39. package/dist/skills/coding-agent-sessions/scripts/agent_sessions/kiro_scanner.py +91 -0
  40. package/dist/skills/coding-agent-sessions/scripts/agent_sessions/opencode.py +289 -0
  41. package/dist/skills/coding-agent-sessions/scripts/agent_sessions/scanners.py +92 -0
  42. package/dist/skills/coding-agent-sessions/scripts/agent_sessions/sqlite_optional_scanners.py +203 -0
  43. package/dist/skills/coding-agent-sessions/scripts/agent_sessions/sqlite_scanners.py +177 -0
  44. package/dist/skills/coding-agent-sessions/scripts/agent_sessions/timeparse.py +51 -0
  45. package/dist/skills/coding-agent-sessions/scripts/agent_sessions/transcript.py +149 -0
  46. package/dist/skills/coding-agent-sessions/scripts/agent_sessions/types.py +57 -0
  47. package/dist/skills/coding-agent-sessions/scripts/find-agent-sessions.py +19 -0
  48. package/dist/skills/frontend/ATTRIBUTION.md +52 -8
  49. package/dist/skills/frontend/SKILL.md +29 -8
  50. package/dist/skills/frontend/references/design/README.md +29 -12
  51. package/dist/skills/frontend/references/design/_INDEX.md +3 -3
  52. package/dist/skills/frontend/references/design/design-system-architecture.md +22 -19
  53. package/dist/skills/frontend/references/designpowers/EVIDENCE.md +89 -0
  54. package/dist/skills/frontend/references/designpowers/README.md +48 -0
  55. package/dist/skills/frontend/references/designpowers/UPSTREAM.md +80 -0
  56. package/dist/skills/frontend/references/designpowers/lane-a-direction.md +64 -0
  57. package/dist/skills/frontend/references/designpowers/lane-b-execution.md +65 -0
  58. package/dist/skills/frontend/references/designpowers/lane-c-review.md +65 -0
  59. package/dist/skills/frontend/references/designpowers/lane-d-memory.md +83 -0
  60. package/dist/skills/frontend/references/designpowers/orchestration.md +80 -0
  61. package/dist/skills/frontend/references/designpowers/routing.md +79 -0
  62. package/dist/skills/frontend/references/designpowers/vendor/LICENSE +21 -0
  63. package/dist/skills/frontend/references/designpowers/vendor/agents/accessibility-reviewer.md +83 -0
  64. package/dist/skills/frontend/references/designpowers/vendor/agents/content-writer.md +132 -0
  65. package/dist/skills/frontend/references/designpowers/vendor/agents/design-builder.md +109 -0
  66. package/dist/skills/frontend/references/designpowers/vendor/agents/design-critic.md +89 -0
  67. package/dist/skills/frontend/references/designpowers/vendor/agents/design-lead.md +113 -0
  68. package/dist/skills/frontend/references/designpowers/vendor/agents/design-scout.md +78 -0
  69. package/dist/skills/frontend/references/designpowers/vendor/agents/design-strategist.md +121 -0
  70. package/dist/skills/frontend/references/designpowers/vendor/agents/heuristic-evaluator.md +268 -0
  71. package/dist/skills/frontend/references/designpowers/vendor/agents/inspiration-scout.md +107 -0
  72. package/dist/skills/frontend/references/designpowers/vendor/agents/motion-designer.md +120 -0
  73. package/dist/skills/frontend/references/designpowers/vendor/skills/accessible-content/SKILL.md +101 -0
  74. package/dist/skills/frontend/references/designpowers/vendor/skills/adaptive-interfaces/SKILL.md +109 -0
  75. package/dist/skills/frontend/references/designpowers/vendor/skills/cognitive-accessibility/SKILL.md +107 -0
  76. package/dist/skills/frontend/references/designpowers/vendor/skills/design-debate/SKILL.md +199 -0
  77. package/dist/skills/frontend/references/designpowers/vendor/skills/design-debt-tracker/SKILL.md +174 -0
  78. package/dist/skills/frontend/references/designpowers/vendor/skills/design-handoff/SKILL.md +125 -0
  79. package/dist/skills/frontend/references/designpowers/vendor/skills/design-md/SKILL.md +106 -0
  80. package/dist/skills/frontend/references/designpowers/vendor/skills/design-retrospective/SKILL.md +266 -0
  81. package/dist/skills/frontend/references/designpowers/vendor/skills/design-review/SKILL.md +123 -0
  82. package/dist/skills/frontend/references/designpowers/vendor/skills/design-system-alignment/SKILL.md +120 -0
  83. package/dist/skills/frontend/references/designpowers/vendor/skills/designpowers-critique/SKILL.md +164 -0
  84. package/dist/skills/frontend/references/designpowers/vendor/skills/heuristic-evaluation/SKILL.md +85 -0
  85. package/dist/skills/frontend/references/designpowers/vendor/skills/inclusive-personas/SKILL.md +98 -0
  86. package/dist/skills/frontend/references/designpowers/vendor/skills/inspiration-scouting/SKILL.md +165 -0
  87. package/dist/skills/frontend/references/designpowers/vendor/skills/interaction-design/SKILL.md +122 -0
  88. package/dist/skills/frontend/references/designpowers/vendor/skills/motion-choreography/SKILL.md +81 -0
  89. package/dist/skills/frontend/references/designpowers/vendor/skills/research-planning/SKILL.md +96 -0
  90. package/dist/skills/frontend/references/designpowers/vendor/skills/responsive-patterns/SKILL.md +77 -0
  91. package/dist/skills/frontend/references/designpowers/vendor/skills/synthetic-user-testing/SKILL.md +192 -0
  92. package/dist/skills/frontend/references/designpowers/vendor/skills/taste-feedback/SKILL.md +165 -0
  93. package/dist/skills/frontend/references/designpowers/vendor/skills/taste-report/SKILL.md +78 -0
  94. package/dist/skills/frontend/references/designpowers/vendor/skills/token-architecture/SKILL.md +75 -0
  95. package/dist/skills/frontend/references/designpowers/vendor/skills/ui-composition/SKILL.md +117 -0
  96. package/dist/skills/frontend/references/designpowers/vendor/skills/usability-testing/SKILL.md +78 -0
  97. package/dist/skills/frontend/references/designpowers/vendor/skills/verification-before-shipping/SKILL.md +125 -0
  98. package/dist/skills/frontend/references/designpowers/vendor/skills/voice-and-tone/SKILL.md +79 -0
  99. package/dist/skills/frontend/references/designpowers/vendor/skills/writing-design-plans/SKILL.md +119 -0
  100. package/dist/skills/frontend/references/ui-ux-db/scripts/core.py +0 -0
  101. package/dist/skills/git-master/SKILL.md +4 -0
  102. package/dist/skills/review-work/SKILL.md +3 -3
  103. package/dist/skills/start-work/SKILL.md +7 -5
  104. package/dist/skills/ultraresearch/SKILL.md +4 -252
  105. package/dist/skills/ulw-plan/SKILL.md +8 -6
  106. package/dist/skills/ulw-plan/references/full-workflow.md +11 -6
  107. package/dist/skills/ulw-plan/references/intent-clear.md +3 -3
  108. package/dist/skills/ulw-plan/references/intent-unclear.md +1 -1
  109. package/dist/skills/{ultraresearch → ulw-research}/ATTRIBUTION.md +1 -1
  110. package/dist/skills/ulw-research/SKILL.md +260 -0
  111. package/dist/skills/visual-qa/SKILL.md +105 -14
  112. package/dist/skills/visual-qa/scripts/skill-prompt-contract.test.ts +120 -1
  113. package/dist/testing/create-plugin-module.d.ts +2 -0
  114. package/dist/tui.js +23917 -16117
  115. package/docs/reference/github-attachment-upload.md +51 -0
  116. package/docs/reference/web-terminal-visual-qa.md +81 -0
  117. package/package.json +20 -14
  118. package/packages/lsp-core/src/lsp/process.ts +4 -1
  119. package/packages/lsp-core/src/mcp.ts +1 -0
  120. package/packages/lsp-daemon/dist/cli.js +5 -1
  121. package/packages/lsp-daemon/dist/index.js +5 -1
  122. package/packages/lsp-daemon/dist/proxy.js +1 -0
  123. package/packages/lsp-tools-mcp/dist/cli.js +5 -1
  124. package/packages/lsp-tools-mcp/dist/lsp/manager.js +4 -1
  125. package/packages/lsp-tools-mcp/dist/mcp.js +5 -1
  126. package/packages/lsp-tools-mcp/dist/tools.js +4 -1
  127. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  128. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +323 -62
  129. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  130. package/packages/omo-codex/plugin/components/bootstrap/scripts/bootstrap.ps1 +37 -68
  131. package/packages/omo-codex/plugin/components/bootstrap/scripts/node-dispatch.ps1 +75 -0
  132. package/packages/omo-codex/plugin/components/bootstrap/src/cli.ts +1 -1
  133. package/packages/omo-codex/plugin/components/bootstrap/src/setup.ts +2 -18
  134. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +575 -334
  135. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +204 -39
  136. package/packages/omo-codex/plugin/components/codegraph/package.json +2 -2
  137. package/packages/omo-codex/plugin/components/codegraph/src/hook-types.ts +7 -1
  138. package/packages/omo-codex/plugin/components/codegraph/src/hook.ts +103 -3
  139. package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +114 -0
  140. package/packages/omo-codex/plugin/components/codegraph/src/serve-invocation.ts +29 -0
  141. package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +31 -57
  142. package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +7 -4
  143. package/packages/omo-codex/plugin/components/codegraph/test/hook.test.ts +43 -9
  144. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +146 -0
  145. package/packages/omo-codex/plugin/components/codegraph/test/serve.test.ts +12 -7
  146. package/packages/omo-codex/plugin/components/codegraph/test/session-start-worker-flow.test.ts +71 -1
  147. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  148. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  149. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  150. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +4 -1
  151. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  152. package/packages/omo-codex/plugin/components/rules/dist/cli.js +2 -2
  153. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  154. package/packages/omo-codex/plugin/components/rules/src/sparkshell-awareness.ts +2 -2
  155. package/packages/omo-codex/plugin/components/rules/test/sparkshell-awareness.test.ts +11 -2
  156. package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +6 -6
  157. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
  158. package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +23 -0
  159. package/packages/omo-codex/plugin/components/teammode/dist/cli.js +22 -4
  160. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  161. package/packages/omo-codex/plugin/components/teammode/skills/teammode/SKILL.md +40 -2
  162. package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-guide.mjs +32 -5
  163. package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team.mjs +7 -3
  164. package/packages/omo-codex/plugin/components/teammode/src/codex-hook.ts +19 -4
  165. package/packages/omo-codex/plugin/components/teammode/test/thread-title-hook.test.ts +32 -2
  166. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  167. package/packages/omo-codex/plugin/components/ultrawork/README.md +1 -1
  168. package/packages/omo-codex/plugin/components/ultrawork/agents/explorer.toml +1 -1
  169. package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +2 -2
  170. package/packages/omo-codex/plugin/components/ultrawork/directive.md +23 -7
  171. package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +1 -1
  172. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  173. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +6 -4
  174. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +11 -6
  175. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/intent-clear.md +3 -3
  176. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/intent-unclear.md +1 -1
  177. package/packages/omo-codex/plugin/components/ultrawork/src/codex-hook.ts +1 -1
  178. package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook-trigger-policy.test.ts +24 -0
  179. package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +6 -1
  180. package/packages/omo-codex/plugin/components/ulw-loop/AGENTS.md +1 -1
  181. package/packages/omo-codex/plugin/components/ulw-loop/README.md +25 -21
  182. package/packages/omo-codex/plugin/components/ulw-loop/biome.json +1 -1
  183. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +381 -0
  184. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-steering.js +14 -1
  185. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +145 -14
  186. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-hook.d.ts +6 -3
  187. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-hook.js +16 -6
  188. package/packages/omo-codex/plugin/components/ulw-loop/dist/ultrawork-directive.d.ts +5 -0
  189. package/packages/omo-codex/plugin/components/ulw-loop/dist/ultrawork-directive.js +104 -0
  190. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +1 -1
  191. package/packages/omo-codex/plugin/components/ulw-loop/package.json +2 -1
  192. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +19 -11
  193. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-steering.ts +14 -1
  194. package/packages/omo-codex/plugin/components/ulw-loop/src/cli.ts +4 -2
  195. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-hook.ts +28 -6
  196. package/packages/omo-codex/plugin/components/ulw-loop/src/ultrawork-directive.ts +113 -0
  197. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-entrypoint.test.ts +33 -2
  198. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-steering-kind-guidance.test.ts +31 -0
  199. package/packages/omo-codex/plugin/components/ulw-loop/test/codex-hook.test.ts +13 -0
  200. package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +36 -0
  201. package/packages/omo-codex/plugin/components/ulw-loop/test/skill-contract.test.ts +12 -0
  202. package/packages/omo-codex/plugin/components/ulw-loop/test/ultrawork-directive.test.ts +64 -0
  203. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +2 -1
  204. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +2 -1
  205. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +2 -1
  206. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +2 -1
  207. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +2 -1
  208. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +2 -1
  209. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +2 -1
  210. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +2 -1
  211. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +2 -1
  212. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +2 -1
  213. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +2 -1
  214. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +2 -1
  215. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +2 -1
  216. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +2 -1
  217. package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +2 -1
  218. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +2 -1
  219. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +2 -1
  220. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +2 -1
  221. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +2 -1
  222. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +2 -1
  223. package/packages/omo-codex/plugin/package-lock.json +13 -13
  224. package/packages/omo-codex/plugin/package.json +1 -1
  225. package/packages/omo-codex/plugin/scripts/auto-update.mjs +9 -5
  226. package/packages/omo-codex/plugin/scripts/materialize-shared-upstreams.mjs +1 -0
  227. package/packages/omo-codex/plugin/scripts/migrate-codex-config/context7-placeholder-guard.mjs +131 -0
  228. package/packages/omo-codex/plugin/scripts/migrate-codex-config/multi-agent-mode-guard.mjs +60 -0
  229. package/packages/omo-codex/plugin/scripts/migrate-codex-config/multi-agent-v2-guard.mjs +3 -10
  230. package/packages/omo-codex/plugin/scripts/migrate-codex-config/root-settings.mjs +31 -1
  231. package/packages/omo-codex/plugin/scripts/migrate-codex-config/subagent-limit-guard.mjs +73 -0
  232. package/packages/omo-codex/plugin/scripts/migrate-codex-config.mjs +20 -3
  233. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +30 -8
  234. package/packages/omo-codex/plugin/skills/coding-agent-sessions/SKILL.md +128 -0
  235. package/packages/omo-codex/plugin/skills/coding-agent-sessions/agents/openai.yaml +4 -0
  236. package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/all-platforms.md +71 -0
  237. package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/claude.md +32 -0
  238. package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/codex.md +30 -0
  239. package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/opencode.md +43 -0
  240. package/packages/omo-codex/plugin/skills/coding-agent-sessions/references/senpi.md +17 -0
  241. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/__init__.py +1 -0
  242. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/claude.py +66 -0
  243. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +283 -0
  244. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/codex.py +105 -0
  245. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/file_scanners.py +275 -0
  246. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/jsonio.py +55 -0
  247. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/kiro_scanner.py +91 -0
  248. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/opencode.py +289 -0
  249. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/scanners.py +92 -0
  250. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/sqlite_optional_scanners.py +203 -0
  251. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/sqlite_scanners.py +177 -0
  252. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/timeparse.py +51 -0
  253. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/transcript.py +149 -0
  254. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/agent_sessions/types.py +57 -0
  255. package/packages/omo-codex/plugin/skills/coding-agent-sessions/scripts/find-agent-sessions.py +19 -0
  256. package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +52 -8
  257. package/packages/omo-codex/plugin/skills/frontend/SKILL.md +29 -8
  258. package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +29 -12
  259. package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +3 -3
  260. package/packages/omo-codex/plugin/skills/frontend/references/design/design-system-architecture.md +22 -19
  261. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/EVIDENCE.md +89 -0
  262. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/README.md +48 -0
  263. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/UPSTREAM.md +80 -0
  264. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/lane-a-direction.md +64 -0
  265. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/lane-b-execution.md +65 -0
  266. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/lane-c-review.md +65 -0
  267. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/lane-d-memory.md +83 -0
  268. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/orchestration.md +80 -0
  269. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/routing.md +79 -0
  270. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/LICENSE +21 -0
  271. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/agents/accessibility-reviewer.md +83 -0
  272. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/agents/content-writer.md +132 -0
  273. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/agents/design-builder.md +109 -0
  274. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/agents/design-critic.md +89 -0
  275. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/agents/design-lead.md +113 -0
  276. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/agents/design-scout.md +78 -0
  277. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/agents/design-strategist.md +121 -0
  278. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/agents/heuristic-evaluator.md +268 -0
  279. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/agents/inspiration-scout.md +107 -0
  280. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/agents/motion-designer.md +120 -0
  281. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/accessible-content/SKILL.md +101 -0
  282. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/adaptive-interfaces/SKILL.md +109 -0
  283. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/cognitive-accessibility/SKILL.md +107 -0
  284. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/design-debate/SKILL.md +199 -0
  285. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/design-debt-tracker/SKILL.md +174 -0
  286. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/design-handoff/SKILL.md +125 -0
  287. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/design-md/SKILL.md +106 -0
  288. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/design-retrospective/SKILL.md +266 -0
  289. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/design-review/SKILL.md +123 -0
  290. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/design-system-alignment/SKILL.md +120 -0
  291. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/designpowers-critique/SKILL.md +164 -0
  292. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/heuristic-evaluation/SKILL.md +85 -0
  293. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/inclusive-personas/SKILL.md +98 -0
  294. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/inspiration-scouting/SKILL.md +165 -0
  295. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/interaction-design/SKILL.md +122 -0
  296. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/motion-choreography/SKILL.md +81 -0
  297. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/research-planning/SKILL.md +96 -0
  298. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/responsive-patterns/SKILL.md +77 -0
  299. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/synthetic-user-testing/SKILL.md +192 -0
  300. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/taste-feedback/SKILL.md +165 -0
  301. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/taste-report/SKILL.md +78 -0
  302. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/token-architecture/SKILL.md +75 -0
  303. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/ui-composition/SKILL.md +117 -0
  304. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/usability-testing/SKILL.md +78 -0
  305. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/verification-before-shipping/SKILL.md +125 -0
  306. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/voice-and-tone/SKILL.md +79 -0
  307. package/packages/omo-codex/plugin/skills/frontend/references/designpowers/vendor/skills/writing-design-plans/SKILL.md +119 -0
  308. package/packages/omo-codex/plugin/skills/frontend/references/ui-ux-db/scripts/core.py +0 -0
  309. package/packages/omo-codex/plugin/skills/git-master/SKILL.md +4 -0
  310. package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +4 -0
  311. package/packages/omo-codex/plugin/skills/refactor/SKILL.md +4 -0
  312. package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +4 -0
  313. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +12 -4
  314. package/packages/omo-codex/plugin/skills/start-work/SKILL.md +14 -8
  315. package/packages/omo-codex/plugin/skills/teammode/SKILL.md +40 -2
  316. package/packages/omo-codex/plugin/skills/teammode/scripts/team-guide.mjs +32 -5
  317. package/packages/omo-codex/plugin/skills/teammode/scripts/team.mjs +7 -3
  318. package/packages/omo-codex/plugin/skills/ultraresearch/SKILL.md +4 -250
  319. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +19 -11
  320. package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +6 -4
  321. package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +11 -6
  322. package/packages/omo-codex/plugin/skills/ulw-plan/references/intent-clear.md +3 -3
  323. package/packages/omo-codex/plugin/skills/ulw-plan/references/intent-unclear.md +1 -1
  324. package/packages/{shared-skills/skills/ultraresearch → omo-codex/plugin/skills/ulw-research}/ATTRIBUTION.md +1 -1
  325. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +262 -0
  326. package/packages/omo-codex/plugin/skills/ulw-research/agents/openai.yaml +2 -0
  327. package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +109 -14
  328. package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +23 -0
  329. package/packages/omo-codex/plugin/test/aggregate-manifest.test.mjs +1 -0
  330. package/packages/omo-codex/plugin/test/auto-update-release-notes.test.mjs +1 -0
  331. package/packages/omo-codex/plugin/test/auto-update-restart-notice.test.mjs +24 -1
  332. package/packages/omo-codex/plugin/test/bootstrap-binlinks.test.mjs +23 -5
  333. package/packages/omo-codex/plugin/test/bootstrap-hooks.test.mjs +38 -0
  334. package/packages/omo-codex/plugin/test/bootstrap-ps-guard.test.mjs +25 -0
  335. package/packages/omo-codex/plugin/test/bootstrap-setup.test.mjs +1 -7
  336. package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +206 -6
  337. package/packages/omo-codex/plugin/test/subagent-limit-migration.test.mjs +44 -0
  338. package/packages/omo-codex/plugin/test/sync-skills-orchestration.test.mjs +50 -1
  339. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +10 -7
  340. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +28 -2
  341. package/packages/omo-codex/plugin/test/teammode-communication.test.mjs +20 -0
  342. package/packages/omo-codex/plugin/test/teammode-thread-links.test.mjs +117 -0
  343. package/packages/omo-codex/plugin/test/teammode-worktree.test.mjs +5 -2
  344. package/packages/omo-codex/plugin/test/ulw-research-skill-contract.test.mjs +285 -0
  345. package/packages/omo-codex/scripts/install-bin-links.test.mjs +6 -1
  346. package/packages/omo-codex/scripts/install-cache-copy.test.mjs +51 -0
  347. package/packages/omo-codex/scripts/install-config.test.mjs +198 -17
  348. package/packages/omo-codex/scripts/install-dist/install-local.mjs +701 -282
  349. package/packages/omo-codex/scripts/install-local-git-bash-preflight.test.mjs +24 -15
  350. package/packages/omo-codex/scripts/install-local.test.mjs +5 -1
  351. package/packages/omo-codex/scripts/install-mcp-context7-runtime.test.mjs +84 -0
  352. package/packages/shared-skills/skills/coding-agent-sessions/SKILL.md +128 -0
  353. package/packages/shared-skills/skills/coding-agent-sessions/agents/openai.yaml +4 -0
  354. package/packages/shared-skills/skills/coding-agent-sessions/references/all-platforms.md +71 -0
  355. package/packages/shared-skills/skills/coding-agent-sessions/references/claude.md +32 -0
  356. package/packages/shared-skills/skills/coding-agent-sessions/references/codex.md +30 -0
  357. package/packages/shared-skills/skills/coding-agent-sessions/references/opencode.md +43 -0
  358. package/packages/shared-skills/skills/coding-agent-sessions/references/senpi.md +17 -0
  359. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/__init__.py +1 -0
  360. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/claude.py +66 -0
  361. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/cli.py +283 -0
  362. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/codex.py +105 -0
  363. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/file_scanners.py +275 -0
  364. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/jsonio.py +55 -0
  365. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/kiro_scanner.py +91 -0
  366. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/opencode.py +289 -0
  367. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/scanners.py +92 -0
  368. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/sqlite_optional_scanners.py +203 -0
  369. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/sqlite_scanners.py +177 -0
  370. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/timeparse.py +51 -0
  371. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/transcript.py +149 -0
  372. package/packages/shared-skills/skills/coding-agent-sessions/scripts/agent_sessions/types.py +57 -0
  373. package/packages/shared-skills/skills/coding-agent-sessions/scripts/find-agent-sessions.py +19 -0
  374. package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +52 -8
  375. package/packages/shared-skills/skills/frontend/SKILL.md +29 -8
  376. package/packages/shared-skills/skills/frontend/references/design/README.md +29 -12
  377. package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +3 -3
  378. package/packages/shared-skills/skills/frontend/references/design/design-system-architecture.md +22 -19
  379. package/packages/shared-skills/skills/frontend/references/designpowers/EVIDENCE.md +89 -0
  380. package/packages/shared-skills/skills/frontend/references/designpowers/README.md +48 -0
  381. package/packages/shared-skills/skills/frontend/references/designpowers/UPSTREAM.md +80 -0
  382. package/packages/shared-skills/skills/frontend/references/designpowers/lane-a-direction.md +64 -0
  383. package/packages/shared-skills/skills/frontend/references/designpowers/lane-b-execution.md +65 -0
  384. package/packages/shared-skills/skills/frontend/references/designpowers/lane-c-review.md +65 -0
  385. package/packages/shared-skills/skills/frontend/references/designpowers/lane-d-memory.md +83 -0
  386. package/packages/shared-skills/skills/frontend/references/designpowers/orchestration.md +80 -0
  387. package/packages/shared-skills/skills/frontend/references/designpowers/routing.md +79 -0
  388. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/LICENSE +21 -0
  389. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/agents/accessibility-reviewer.md +83 -0
  390. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/agents/content-writer.md +132 -0
  391. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/agents/design-builder.md +109 -0
  392. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/agents/design-critic.md +89 -0
  393. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/agents/design-lead.md +113 -0
  394. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/agents/design-scout.md +78 -0
  395. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/agents/design-strategist.md +121 -0
  396. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/agents/heuristic-evaluator.md +268 -0
  397. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/agents/inspiration-scout.md +107 -0
  398. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/agents/motion-designer.md +120 -0
  399. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/accessible-content/SKILL.md +101 -0
  400. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/adaptive-interfaces/SKILL.md +109 -0
  401. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/cognitive-accessibility/SKILL.md +107 -0
  402. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/design-debate/SKILL.md +199 -0
  403. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/design-debt-tracker/SKILL.md +174 -0
  404. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/design-handoff/SKILL.md +125 -0
  405. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/design-md/SKILL.md +106 -0
  406. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/design-retrospective/SKILL.md +266 -0
  407. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/design-review/SKILL.md +123 -0
  408. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/design-system-alignment/SKILL.md +120 -0
  409. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/designpowers-critique/SKILL.md +164 -0
  410. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/heuristic-evaluation/SKILL.md +85 -0
  411. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/inclusive-personas/SKILL.md +98 -0
  412. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/inspiration-scouting/SKILL.md +165 -0
  413. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/interaction-design/SKILL.md +122 -0
  414. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/motion-choreography/SKILL.md +81 -0
  415. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/research-planning/SKILL.md +96 -0
  416. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/responsive-patterns/SKILL.md +77 -0
  417. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/synthetic-user-testing/SKILL.md +192 -0
  418. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/taste-feedback/SKILL.md +165 -0
  419. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/taste-report/SKILL.md +78 -0
  420. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/token-architecture/SKILL.md +75 -0
  421. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/ui-composition/SKILL.md +117 -0
  422. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/usability-testing/SKILL.md +78 -0
  423. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/verification-before-shipping/SKILL.md +125 -0
  424. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/voice-and-tone/SKILL.md +79 -0
  425. package/packages/shared-skills/skills/frontend/references/designpowers/vendor/skills/writing-design-plans/SKILL.md +119 -0
  426. package/packages/shared-skills/skills/frontend/references/ui-ux-db/scripts/core.py +0 -0
  427. package/packages/shared-skills/skills/git-master/SKILL.md +4 -0
  428. package/packages/shared-skills/skills/review-work/SKILL.md +3 -3
  429. package/packages/shared-skills/skills/start-work/SKILL.md +7 -5
  430. package/packages/shared-skills/skills/ultraresearch/SKILL.md +4 -252
  431. package/packages/shared-skills/skills/ulw-plan/SKILL.md +8 -6
  432. package/packages/shared-skills/skills/ulw-plan/references/full-workflow.md +11 -6
  433. package/packages/shared-skills/skills/ulw-plan/references/intent-clear.md +3 -3
  434. package/packages/shared-skills/skills/ulw-plan/references/intent-unclear.md +1 -1
  435. package/packages/{omo-codex/plugin/skills/ultraresearch → shared-skills/skills/ulw-research}/ATTRIBUTION.md +1 -1
  436. package/packages/shared-skills/skills/ulw-research/SKILL.md +260 -0
  437. package/packages/shared-skills/skills/visual-qa/SKILL.md +105 -14
  438. package/packages/shared-skills/skills/visual-qa/scripts/skill-prompt-contract.test.ts +120 -1
  439. package/script/qa/web-terminal-redaction.d.mts +13 -0
  440. package/script/qa/web-terminal-redaction.mjs +43 -0
  441. package/script/qa/web-terminal-renderer.mjs +218 -0
  442. package/script/qa/web-terminal-visual-qa.mjs +264 -0
  443. package/packages/omo-codex/plugin/skills/ultimate-browsing/scripts/tests/test_cookie_domain_filter.py +0 -110
  444. package/packages/omo-codex/plugin/skills/ultimate-browsing/scripts/tests/test_extract_cookies.py +0 -245
  445. package/packages/omo-codex/plugin/test/ultraresearch-skill-contract.test.mjs +0 -204
@@ -68,6 +68,8 @@ describe("visual-qa skill prompt contract", () => {
68
68
  expect(lowerPassB, fixture.label).toContain("view_image")
69
69
  expect(lowerPassB, fixture.label).toContain("source code")
70
70
  expect(passB, fixture.label).toContain("[Image #1]")
71
+ expect(passB, fixture.label).toContain("semantic phrases")
72
+ expect(passB, fixture.label).toContain("놀라운 변 / 화")
71
73
  expect(passB, fixture.label).toContain("에이전트 오케스트")
72
74
  expect(passB, fixture.label).toContain("레이션 현황 및 미")
73
75
  expect(passB, fixture.label).toContain("래")
@@ -112,16 +114,133 @@ describe("visual-qa skill prompt contract", () => {
112
114
  expect(doc).toContain("agent-browser --help")
113
115
  })
114
116
 
115
- test("#given a clone/design-port task #when in clone-coding mode #then dual pixel + code-fidelity verification loops until both pass", () => {
117
+ test("#given a concrete visual target #when in reference-fidelity mode #then dual pixel + code-fidelity verification loops until both pass", () => {
116
118
  for (const fixture of fixtures()) {
117
119
  const cloneMode = sectionBetween(fixture.text, "## Step 5", "## Reference evidence is not the verdict")
118
120
  const lowerCloneMode = cloneMode.toLowerCase()
119
121
 
120
122
  expect(lowerCloneMode, fixture.label).toContain("clone")
123
+ expect(lowerCloneMode, fixture.label).toContain("imagen")
124
+ expect(lowerCloneMode, fixture.label).toContain("stitch")
125
+ expect(lowerCloneMode, fixture.label).toContain("generated mockup")
126
+ expect(lowerCloneMode, fixture.label).toContain("overview")
121
127
  expect(cloneMode, fixture.label).toContain("pixel-by-pixel")
122
128
  expect(cloneMode, fixture.label).toContain("image-diff")
123
129
  expect(cloneMode, fixture.label).toContain("lazycodex-clone-fidelity-reviewer")
130
+ expect(lowerCloneMode, fixture.label).toContain("extensible state variants")
124
131
  expect(lowerCloneMode, fixture.label).toContain("retry")
125
132
  }
126
133
  })
127
134
  })
135
+
136
+ describe("visual-qa skill exhaustive-coverage and review-gate contract", () => {
137
+ test("#given a multi-page surface #when capturing #then every page is enumerated and verified per page, not sampled", () => {
138
+ for (const fixture of fixtures()) {
139
+ const capture = sectionBetween(fixture.text, "## Step 2", "## Step 3")
140
+ const lower = capture.toLowerCase()
141
+
142
+ expect(lower, fixture.label).toContain("every page")
143
+ expect(lower, fixture.label).toContain("enumerate")
144
+ expect(lower, fixture.label).toContain("never sample")
145
+ expect(lower, fixture.label).toContain("per page")
146
+ expect(lower, fixture.label).toContain("one failing page fails")
147
+ }
148
+ })
149
+
150
+ test("#given prior QA artifacts #when verifying #then stale evidence older than the source must be regenerated", () => {
151
+ for (const fixture of fixtures()) {
152
+ const capture = sectionBetween(fixture.text, "## Step 2", "## Step 3")
153
+ const lower = capture.toLowerCase()
154
+
155
+ expect(lower, fixture.label).toContain("stale")
156
+ expect(lower, fixture.label).toContain("older than")
157
+ expect(lower, fixture.label).toContain("regenerate")
158
+ }
159
+ })
160
+
161
+ test("#given Step 3 dispatch #when running the review #then it is required pre-done, harness-native, and covers every enumerated page", () => {
162
+ for (const fixture of fixtures()) {
163
+ const dispatch = sectionBetween(fixture.text, "## Step 3", "### Pass A")
164
+ const lower = dispatch.toLowerCase()
165
+
166
+ expect(lower, fixture.label).toContain("required before")
167
+ expect(lower, fixture.label).toContain("do not self-review")
168
+ expect(dispatch, fixture.label).toContain("spawn_agent")
169
+ expect(dispatch, fixture.label).toContain("lazycodex-gate-reviewer")
170
+ expect(lower, fixture.label).toContain("every enumerated page")
171
+ }
172
+ })
173
+
174
+ test("#given the completion gate #when deciding done #then an independent reviewer must PASS on a fresh full capture, looping until clean", () => {
175
+ for (const fixture of fixtures()) {
176
+ const gate = sectionBetween(fixture.text, "## Step 4", "## Step 5")
177
+ const lower = gate.toLowerCase()
178
+
179
+ expect(lower, fixture.label).toContain("hard stop rule")
180
+ expect(lower, fixture.label).toContain("independent")
181
+ expect(lower, fixture.label).toContain("no blocking")
182
+ expect(lower, fixture.label).toContain("fresh")
183
+ expect(lower, fixture.label).toContain("every enumerated page")
184
+ expect(lower, fixture.label).toContain("loop until")
185
+ expect(lower, fixture.label).toContain("do not stop because the automated script")
186
+ }
187
+ })
188
+
189
+ test("#given Pass B CJK checks #when inspecting #then it flags topic, connective, and source-citation splits across every page", () => {
190
+ for (const fixture of fixtures()) {
191
+ const passB = sectionBetween(fixture.text, "### Pass B", "## Step 4")
192
+ const lower = passB.toLowerCase()
193
+
194
+ expect(passB, fixture.label).toContain("두 강은")
195
+ expect(passB, fixture.label).toContain("쓸 수")
196
+ expect(passB, fixture.label).toContain("Attention Is")
197
+ expect(lower, fixture.label).toContain("citation")
198
+ expect(lower, fixture.label).toContain("every page")
199
+ expect(lower, fixture.label).toContain("regardless of similarityscore")
200
+ }
201
+ })
202
+
203
+ test("#given a generated reference packet #when reviewing #then Pass A and B require extensible design-system code and pixel-level reference matching", () => {
204
+ for (const fixture of fixtures()) {
205
+ const passA = sectionBetween(fixture.text, "### Pass A", "### Pass B")
206
+ const passB = sectionBetween(fixture.text, "### Pass B", "## Step 4")
207
+ const lowerPassA = passA.toLowerCase()
208
+ const lowerPassB = passB.toLowerCase()
209
+
210
+ expect(passA, fixture.label).toContain("REFERENCE PACKET:")
211
+ expect(passB, fixture.label).toContain("REFERENCE PACKET:")
212
+ expect(lowerPassA, fixture.label).toContain("reusable tokens/primitives")
213
+ expect(lowerPassA, fixture.label).toContain("extend to new pages")
214
+ expect(lowerPassA, fixture.label).toContain("missing overview content")
215
+ expect(lowerPassB, fixture.label).toContain("pixel-perfectly")
216
+ expect(lowerPassB, fixture.label).toContain("actual against reference")
217
+ expect(lowerPassB, fixture.label).toContain("overview text is part of the target")
218
+ }
219
+ })
220
+
221
+ test("#given reference packet evidence #when prompting reviewers #then sensitive content is redacted and annotations stay untrusted data", () => {
222
+ for (const fixture of fixtures()) {
223
+ const capture = sectionBetween(fixture.text, "## Step 2", "## Step 3")
224
+ const passA = sectionBetween(fixture.text, "### Pass A", "### Pass B")
225
+ const passB = sectionBetween(fixture.text, "### Pass B", "## Step 4")
226
+ const combined = `${capture}\n${passA}\n${passB}`.toLowerCase()
227
+
228
+ expect(combined, fixture.label).toContain("redact or omit secrets")
229
+ expect(combined, fixture.label).toContain("credentials")
230
+ expect(combined, fixture.label).toContain("tokens")
231
+ expect(combined, fixture.label).toContain("auth headers")
232
+ expect(combined, fixture.label).toContain("customer data")
233
+ expect(combined, fixture.label).toContain("private messages")
234
+ expect(combined, fixture.label).toContain("internal urls")
235
+ expect(combined, fixture.label).toContain("other sensitive content")
236
+ expect(combined, fixture.label).toContain("overview text")
237
+ expect(combined, fixture.label).toContain("annotations")
238
+ expect(combined, fixture.label).toContain("untrusted data")
239
+ expect(combined, fixture.label).toContain("captured ui copy")
240
+ expect(combined, fixture.label).toContain("comments")
241
+ expect(combined, fixture.label).toContain("filenames")
242
+ expect(combined, fixture.label).toContain("not reviewer instructions")
243
+ expect(combined, fixture.label).toContain("never as instructions")
244
+ }
245
+ })
246
+ })
@@ -0,0 +1,13 @@
1
+ export type RedactionRule = {
2
+ readonly regex: RegExp
3
+ readonly preservePrefix: boolean
4
+ }
5
+
6
+ export declare const BUILT_IN_REDACTION_RULE_COUNT: number
7
+
8
+ export declare function compileRedactions(input: {
9
+ readonly redactions: readonly string[]
10
+ readonly redactRegexes: readonly string[]
11
+ }): RedactionRule[]
12
+
13
+ export declare function redactEvidence(text: string, rules: readonly RedactionRule[]): string
@@ -0,0 +1,43 @@
1
+ const BUILT_IN_REDACTION_REGEXES = [
2
+ /((?:authorization|proxy-authorization):\s*(?:bearer|basic)\s+)[^\s"'<>]+/gi,
3
+ /((?:"|')?(?:authorization|proxy-authorization)(?:"|')?\s*:\s*(?:"|')?(?:bearer|basic)\s+)[^"'\s,}\]\[]+((?:"|')?)/gi,
4
+ /\b((?:api[_-]?key|token|password|secret|access[_-]?token|refresh[_-]?token)=["'])[^\r\n"']+(["'])/gi,
5
+ /\b((?:api[_-]?key|token|password|secret|access[_-]?token|refresh[_-]?token)=)[^\s"'<>]+/gi,
6
+ /\b(?:gh[pousr]_[A-Za-z0-9_]{20,})\b/g,
7
+ /\b(?:github_pat_[A-Za-z0-9_]{20,})\b/g,
8
+ /\b(?:sk-[A-Za-z0-9_-]{20,})\b/g,
9
+ ];
10
+
11
+ export const BUILT_IN_REDACTION_RULE_COUNT = BUILT_IN_REDACTION_REGEXES.length;
12
+
13
+ function escapeRegex(value) {
14
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
15
+ }
16
+
17
+ export function compileRedactions({ redactions, redactRegexes }) {
18
+ const rules = BUILT_IN_REDACTION_REGEXES.map((regex) => ({
19
+ regex,
20
+ preservePrefix: true,
21
+ }));
22
+ for (const literal of redactions) {
23
+ if (literal.length > 0) {
24
+ rules.push({ regex: new RegExp(escapeRegex(literal), "g"), preservePrefix: false });
25
+ }
26
+ }
27
+ for (const source of redactRegexes) {
28
+ rules.push({ regex: new RegExp(source, "g"), preservePrefix: false });
29
+ }
30
+ return rules;
31
+ }
32
+
33
+ export function redactEvidence(text, rules) {
34
+ return rules.reduce(
35
+ (current, rule) =>
36
+ current.replace(rule.regex, (match, prefix, suffix) =>
37
+ rule.preservePrefix && typeof prefix === "string"
38
+ ? `${prefix}[REDACTED]${typeof suffix === "string" ? suffix : ""}`
39
+ : "[REDACTED]",
40
+ ),
41
+ text,
42
+ );
43
+ }
@@ -0,0 +1,218 @@
1
+ const ANSI_PATTERN = /\u001b\[[0-?]*[ -/]*[@-~]/g;
2
+ const OSC_PATTERN = /\u001b\][\s\S]*?(?:\u0007|\u001b\\|\u009c)/g;
3
+ const SGR_PATTERN = /\u001b\[([0-9;:]*)m/g;
4
+
5
+ const COLOR_NAMES = [
6
+ "black",
7
+ "red",
8
+ "green",
9
+ "yellow",
10
+ "blue",
11
+ "magenta",
12
+ "cyan",
13
+ "white",
14
+ ];
15
+
16
+ const BASIC_ANSI_COLORS = {
17
+ black: "#0c0d10",
18
+ red: "#ff6b6b",
19
+ green: "#51cf66",
20
+ yellow: "#ffd43b",
21
+ blue: "#4dabf7",
22
+ magenta: "#d0bfff",
23
+ cyan: "#66d9e8",
24
+ white: "#f1f3f5",
25
+ "bright-black": "#868e96",
26
+ "bright-red": "#ff8787",
27
+ "bright-green": "#69db7c",
28
+ "bright-yellow": "#ffe066",
29
+ "bright-blue": "#74c0fc",
30
+ "bright-magenta": "#e599f7",
31
+ "bright-cyan": "#99e9f2",
32
+ "bright-white": "#ffffff",
33
+ };
34
+
35
+ export const DEFAULT_TERMINAL_FOREGROUND = "#d8dee9";
36
+ export const DEFAULT_TERMINAL_BACKGROUND = "#090b10";
37
+
38
+ export function escapeHtml(value) {
39
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
40
+ }
41
+
42
+ export function stripAnsi(value) {
43
+ return value.replace(OSC_PATTERN, "").replace(ANSI_PATTERN, "");
44
+ }
45
+
46
+ function createState() {
47
+ return {
48
+ bold: false,
49
+ dim: false,
50
+ italic: false,
51
+ underline: false,
52
+ inverse: false,
53
+ strike: false,
54
+ fg: null,
55
+ bg: null,
56
+ };
57
+ }
58
+
59
+ function parseCodes(raw) {
60
+ if (raw === "") return { values: [0], separators: [null] };
61
+ const values = [];
62
+ const separators = [];
63
+ let separator = null;
64
+ for (const part of raw.split(/([;:])/)) {
65
+ if (part === ";" || part === ":") {
66
+ separator = part;
67
+ continue;
68
+ }
69
+ values.push(part === "" ? null : Number.parseInt(part, 10));
70
+ separators.push(separator);
71
+ separator = null;
72
+ }
73
+ return { values, separators };
74
+ }
75
+
76
+ function basicColor(code, base, brightBase, prefix) {
77
+ if (code >= base && code < base + COLOR_NAMES.length) return `${prefix}-${COLOR_NAMES[code - base]}`;
78
+ if (code >= brightBase && code < brightBase + COLOR_NAMES.length) {
79
+ return `${prefix}-bright-${COLOR_NAMES[code - brightBase]}`;
80
+ }
81
+ return null;
82
+ }
83
+
84
+ function rgbFrom256(index) {
85
+ if (index < 0 || index > 255) return null;
86
+ if (index < 16) {
87
+ const base = [
88
+ "#000000",
89
+ "#800000",
90
+ "#008000",
91
+ "#808000",
92
+ "#000080",
93
+ "#800080",
94
+ "#008080",
95
+ "#c0c0c0",
96
+ "#808080",
97
+ "#ff0000",
98
+ "#00ff00",
99
+ "#ffff00",
100
+ "#0000ff",
101
+ "#ff00ff",
102
+ "#00ffff",
103
+ "#ffffff",
104
+ ];
105
+ return base[index] ?? null;
106
+ }
107
+ if (index >= 232) {
108
+ const level = 8 + (index - 232) * 10;
109
+ return `rgb(${level}, ${level}, ${level})`;
110
+ }
111
+ const offset = index - 16;
112
+ const levels = [0, 95, 135, 175, 215, 255];
113
+ const r = levels[Math.floor(offset / 36) % 6] ?? 0;
114
+ const g = levels[Math.floor(offset / 6) % 6] ?? 0;
115
+ const b = levels[offset % 6] ?? 0;
116
+ return `rgb(${r}, ${g}, ${b})`;
117
+ }
118
+
119
+ function readExtendedColor(codes, separators, index) {
120
+ const mode = codes[index + 1];
121
+ if (mode === 5) return { color: rgbFrom256(codes[index + 2] ?? -1), next: index + 3 };
122
+ if (mode === 2) {
123
+ const offset = separators[index + 1] === ":" && codes[index + 5] !== undefined ? 3 : 2;
124
+ const [r, g, b] = [codes[index + offset], codes[index + offset + 1], codes[index + offset + 2]];
125
+ if ([r, g, b].every((value) => Number.isInteger(value) && value >= 0 && value <= 255)) {
126
+ return { color: `rgb(${r}, ${g}, ${b})`, next: index + offset + 3 };
127
+ }
128
+ }
129
+ return { color: null, next: index + 1 };
130
+ }
131
+
132
+ function applySgr(state, raw) {
133
+ const { values: codes, separators } = parseCodes(raw);
134
+ let nextState = { ...state };
135
+ for (let i = 0; i < codes.length; i += 1) {
136
+ const code = codes[i] ?? 0;
137
+ if (code === 0) nextState = createState();
138
+ else if (code === 1) nextState.bold = true;
139
+ else if (code === 2) nextState.dim = true;
140
+ else if (code === 3) nextState.italic = true;
141
+ else if (code === 4) nextState.underline = true;
142
+ else if (code === 7) nextState.inverse = true;
143
+ else if (code === 9) nextState.strike = true;
144
+ else if (code === 22) {
145
+ nextState.bold = false;
146
+ nextState.dim = false;
147
+ } else if (code === 23) nextState.italic = false;
148
+ else if (code === 24) nextState.underline = false;
149
+ else if (code === 27) nextState.inverse = false;
150
+ else if (code === 29) nextState.strike = false;
151
+ else if (code === 39) nextState.fg = null;
152
+ else if (code === 49) nextState.bg = null;
153
+ else if (code === 38 || code === 48) {
154
+ const extended = readExtendedColor(codes, separators, i);
155
+ if (code === 38) nextState.fg = extended.color;
156
+ else nextState.bg = extended.color;
157
+ i = extended.next - 1;
158
+ } else {
159
+ const fg = basicColor(code, 30, 90, "fg");
160
+ const bg = basicColor(code, 40, 100, "bg");
161
+ if (fg) nextState.fg = fg;
162
+ if (bg) nextState.bg = bg;
163
+ }
164
+ }
165
+ return nextState;
166
+ }
167
+
168
+ function spanAttributes(state) {
169
+ const classes = [];
170
+ const styles = [];
171
+ if (state.bold) classes.push("ansi-bold");
172
+ if (state.dim) classes.push("ansi-dim");
173
+ if (state.italic) classes.push("ansi-italic");
174
+ if (state.underline) classes.push("ansi-underline");
175
+ if (state.strike) classes.push("ansi-strike");
176
+ const effectiveFg = state.inverse ? state.bg || DEFAULT_TERMINAL_BACKGROUND : state.fg;
177
+ const effectiveBg = state.inverse ? state.fg || DEFAULT_TERMINAL_FOREGROUND : state.bg;
178
+ for (const [value, cssProperty, classPrefix] of [
179
+ [effectiveFg, "color", "fg-"],
180
+ [effectiveBg, "background-color", "bg-"],
181
+ ]) {
182
+ if (!value) continue;
183
+ const colorName = value.match(/^(?:fg|bg)-(.+)$/)?.[1];
184
+ if (colorName) classes.push(`ansi-${classPrefix}${colorName}`);
185
+ else styles.push(`${cssProperty}: ${value}`);
186
+ }
187
+ const attrs = [];
188
+ if (classes.length > 0) attrs.push(`class="${classes.join(" ")}"`);
189
+ if (styles.length > 0) attrs.push(`style="${styles.join("; ")}"`);
190
+ return attrs.join(" ");
191
+ }
192
+
193
+ function renderSegment(value, state) {
194
+ const escaped = escapeHtml(value);
195
+ const attrs = spanAttributes(state);
196
+ return attrs ? `<span ${attrs}>${escaped}</span>` : escaped;
197
+ }
198
+
199
+ export function renderAnsiToHtml(value) {
200
+ const cleanValue = value.replace(OSC_PATTERN, "");
201
+ let state = createState();
202
+ let cursor = 0;
203
+ let output = "";
204
+ for (const match of cleanValue.matchAll(SGR_PATTERN)) {
205
+ const index = match.index ?? 0;
206
+ if (index > cursor) output += renderSegment(cleanValue.slice(cursor, index), state);
207
+ state = applySgr(state, match[1] ?? "");
208
+ cursor = index + match[0].length;
209
+ }
210
+ if (cursor < cleanValue.length) output += renderSegment(cleanValue.slice(cursor), state);
211
+ return output.replace(ANSI_PATTERN, "");
212
+ }
213
+
214
+ export function ansiColorCss() {
215
+ return Object.entries(BASIC_ANSI_COLORS)
216
+ .map(([name, value]) => `.ansi-fg-${name} { color: ${value}; }\n.ansi-bg-${name} { background-color: ${value}; }`)
217
+ .join("\n");
218
+ }
@@ -0,0 +1,264 @@
1
+ #!/usr/bin/env node
2
+ import { spawnSync } from "node:child_process";
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { basename, join, resolve } from "node:path";
6
+ import { pathToFileURL } from "node:url";
7
+ import {
8
+ DEFAULT_TERMINAL_BACKGROUND,
9
+ DEFAULT_TERMINAL_FOREGROUND,
10
+ ansiColorCss,
11
+ escapeHtml,
12
+ renderAnsiToHtml,
13
+ stripAnsi,
14
+ } from "./web-terminal-renderer.mjs";
15
+ import {
16
+ BUILT_IN_REDACTION_RULE_COUNT,
17
+ compileRedactions,
18
+ redactEvidence,
19
+ } from "./web-terminal-redaction.mjs";
20
+
21
+ const HELP = `web-terminal-visual-qa
22
+
23
+ Render terminal/TUI evidence through a browser-capturable web page.
24
+
25
+ Usage:
26
+ node script/qa/web-terminal-visual-qa.mjs --title "Codex TUI" --from-file pane.txt --evidence-dir .omo/evidence/run
27
+ node script/qa/web-terminal-visual-qa.mjs --title "OpenCode TUI" --command "opencode --help" --evidence-dir .omo/evidence/run
28
+
29
+ Inputs:
30
+ --from-file <path> Replay an existing terminal/tmux transcript.
31
+ --command <command> Run a command through a tmux-backed PTY connector, capture the pane, then clean up.
32
+ --cwd <path> Working directory for --command. Defaults to current directory.
33
+ --cols <n> Terminal columns for tmux connector. Default: 140.
34
+ --rows <n> Terminal rows for tmux connector. Default: 40.
35
+ --dwell-ms <n> Milliseconds to let --command render before capture. Default: 3000.
36
+ --wrap Wrap long terminal lines in HTML/PNG evidence. Default.
37
+ --no-wrap Preserve long lines with horizontal scrolling.
38
+ --evidence-dir <path> Directory for terminal.txt, terminal-ansi.txt, terminal.html, terminal.png, metadata.json.
39
+ --chrome-bin <path> Chrome/Chromium executable for PNG capture.
40
+ --source-label <text> Safe label for --command metadata. The raw command is never written to metadata.
41
+ --redact <literal> Literal secret value to redact before writing evidence. Repeatable.
42
+ --redact-regex <expr> JavaScript regex source to redact before writing evidence. Repeatable.
43
+ --no-browser Skip PNG capture, but still write HTML/text/metadata.
44
+
45
+ Connector notes:
46
+ --command uses tmux as the tmux-backed PTY connector on macOS/Linux and on Windows environments that provide tmux.
47
+ Windows-native ConPTY live mode should plug into this same metadata contract later; until then use --from-file or Git Bash/tmux.
48
+
49
+ Secret handling:
50
+ Terminal evidence is redacted before terminal.txt, terminal-ansi.txt, terminal.html, and terminal.png are written.
51
+ Built-in rules cover common Authorization headers, token/password/key assignments, and GitHub/OpenAI-style tokens.
52
+ Add --redact for exact local values and --redact-regex for project-specific patterns.
53
+ The raw --command string is treated as secret-bearing process data and is never stored in metadata.json.
54
+ `;
55
+
56
+ function parseArgs(argv) {
57
+ const args = {
58
+ cols: 140,
59
+ rows: 40,
60
+ dwellMs: 3000,
61
+ cwd: process.cwd(),
62
+ browser: true,
63
+ wrap: true,
64
+ redactions: [],
65
+ redactRegexes: [],
66
+ };
67
+ for (let i = 0; i < argv.length; i += 1) {
68
+ const arg = argv[i];
69
+ if (arg === "--help" || arg === "-h") return { ...args, help: true };
70
+ if (arg === "--no-browser") {
71
+ args.browser = false;
72
+ continue;
73
+ }
74
+ if (arg === "--wrap") {
75
+ args.wrap = true;
76
+ continue;
77
+ }
78
+ if (arg === "--no-wrap") {
79
+ args.wrap = false;
80
+ continue;
81
+ }
82
+ const next = argv[i + 1];
83
+ if (!next) throw new Error(`missing value for ${arg}`);
84
+ i += 1;
85
+ if (arg === "--title") args.title = next;
86
+ else if (arg === "--from-file") args.fromFile = next;
87
+ else if (arg === "--command") args.command = next;
88
+ else if (arg === "--cwd") args.cwd = next;
89
+ else if (arg === "--evidence-dir") args.evidenceDir = next;
90
+ else if (arg === "--chrome-bin") args.chromeBin = next;
91
+ else if (arg === "--source-label") args.sourceLabel = next;
92
+ else if (arg === "--redact") args.redactions.push(next);
93
+ else if (arg === "--redact-regex") args.redactRegexes.push(next);
94
+ else if (arg === "--cols") args.cols = parsePositiveInt(arg, next);
95
+ else if (arg === "--rows") args.rows = parsePositiveInt(arg, next);
96
+ else if (arg === "--dwell-ms") args.dwellMs = parsePositiveInt(arg, next);
97
+ else throw new Error(`unknown argument: ${arg}`);
98
+ }
99
+ return args;
100
+ }
101
+
102
+ function parsePositiveInt(name, value) {
103
+ const parsed = Number.parseInt(value, 10);
104
+ if (!Number.isFinite(parsed) || parsed <= 0) throw new Error(`${name} must be a positive integer`);
105
+ return parsed;
106
+ }
107
+
108
+ function requireArgs(args) {
109
+ if (!args.evidenceDir) throw new Error("--evidence-dir is required");
110
+ if (!args.title) throw new Error("--title is required");
111
+ if (args.fromFile && args.command) throw new Error("choose exactly one of --from-file or --command");
112
+ if (!args.fromFile && !args.command) throw new Error("choose --from-file or --command");
113
+ }
114
+
115
+ function terminalWidthCh(cols) {
116
+ return Math.max(80, Math.min(cols, 160));
117
+ }
118
+
119
+ function screenshotSize({ cols, rows }) {
120
+ return {
121
+ width: Math.max(900, Math.min(1440, Math.round(cols * 8.2 + 120))),
122
+ height: Math.max(520, Math.min(1200, Math.round(rows * 18 + 120))),
123
+ };
124
+ }
125
+
126
+ function sourceMetadata(args) {
127
+ if (args.fromFile) return { kind: "file-replay", path: resolve(args.fromFile) };
128
+ return { kind: "command", label: args.sourceLabel || "redacted command" };
129
+ }
130
+
131
+ function writeHtml({ title, ansi, outPath, cols, wrap }) {
132
+ const whiteSpace = wrap ? "pre-wrap" : "pre";
133
+ const overflowWrap = wrap ? "anywhere" : "normal";
134
+ const terminalWidth = terminalWidthCh(cols);
135
+ const html = `<!doctype html>
136
+ <html lang="en">
137
+ <head>
138
+ <meta charset="utf-8">
139
+ <meta name="viewport" content="width=device-width, initial-scale=1">
140
+ <title>${escapeHtml(title)}</title>
141
+ <style>
142
+ :root { color-scheme: dark; }
143
+ body { margin: 0; background: #101217; color: ${DEFAULT_TERMINAL_FOREGROUND}; font: 13px/1.35 "SFMono-Regular", "Cascadia Mono", "JetBrains Mono", "Menlo", "Consolas", "Liberation Mono", ui-monospace, monospace; }
144
+ main { min-height: 100vh; box-sizing: border-box; padding: 20px; }
145
+ .terminal { width: min(100%, ${terminalWidth}ch); max-width: calc(100vw - 40px); border: 1px solid #3b4452; background: ${DEFAULT_TERMINAL_BACKGROUND}; box-shadow: 0 20px 80px rgb(0 0 0 / 40%); }
146
+ .bar { display: flex; gap: 8px; align-items: center; padding: 8px 12px; border-bottom: 1px solid #303846; color: #aab7c4; background: #171b22; font-size: 12px; }
147
+ .dot { width: 10px; height: 10px; border-radius: 999px; background: #6b7280; }
148
+ pre { margin: 0; padding: 14px 16px; white-space: ${whiteSpace}; overflow-wrap: ${overflowWrap}; tab-size: 8; overflow: auto; }
149
+ .ansi-bold { font-weight: 700; }
150
+ .ansi-dim { opacity: 0.72; }
151
+ .ansi-italic { font-style: italic; }
152
+ .ansi-underline { text-decoration: underline; }
153
+ .ansi-strike { text-decoration: line-through; }
154
+ ${ansiColorCss()}
155
+ </style>
156
+ </head>
157
+ <body><main><section class="terminal"><div class="bar"><span class="dot"></span><strong>${escapeHtml(title)}</strong></div><pre>${renderAnsiToHtml(ansi)}</pre></section></main></body>
158
+ </html>
159
+ `;
160
+ writeFileSync(outPath, html, "utf8");
161
+ }
162
+
163
+ function captureFromFile(path) {
164
+ const ansi = readFileSync(path, "utf8");
165
+ return { ansi, cleanup: "cleanup: file replay; no live process", connector: "file-replay" };
166
+ }
167
+
168
+ function shellQuote(value) {
169
+ return `'${value.replaceAll("'", "'\\''")}'`;
170
+ }
171
+
172
+ function runTmuxCommand(args) {
173
+ const tmux = spawnSync("tmux", ["-V"], { encoding: "utf8" });
174
+ if (tmux.status !== 0) throw new Error("--command requires tmux on PATH; use --from-file on hosts without tmux");
175
+ const session = `omo_webterm_${process.pid}_${Date.now()}`;
176
+ const wrapped = `${args.command}; printf '\\n[web-terminal-visual-qa exit:%s]\\n' "$?"; sleep 600`;
177
+ const launch = spawnSync("tmux", ["new-session", "-d", "-s", session, "-x", String(args.cols), "-y", String(args.rows), "-c", resolve(args.cwd), "sh", "-lc", wrapped], { encoding: "utf8" });
178
+ if (launch.status !== 0) throw new Error(`tmux launch failed: ${launch.stderr || launch.stdout}`);
179
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, args.dwellMs);
180
+ const plain = spawnSync("tmux", ["capture-pane", "-p", "-S", "-", "-t", session], { encoding: "utf8" });
181
+ const ansi = spawnSync("tmux", ["capture-pane", "-e", "-p", "-S", "-", "-t", session], { encoding: "utf8" });
182
+ spawnSync("tmux", ["kill-session", "-t", session], { encoding: "utf8" });
183
+ const plainCapture = plain.status === 0 ? plain.stdout : "";
184
+ const ansiCapture = ansi.status === 0 ? ansi.stdout : "";
185
+ const captured = ansiCapture || plainCapture;
186
+ if (!captured || stripAnsi(captured).trim().length === 0) {
187
+ const detail = [plain.stderr || plain.stdout, ansi.stderr || ansi.stdout].filter(Boolean).join("; ");
188
+ throw new Error(`tmux capture was empty${detail ? `: ${detail}` : ""}`);
189
+ }
190
+ return {
191
+ ansi: captured,
192
+ cleanup: `cleanup: tmux kill-session -t ${shellQuote(session)}`,
193
+ connector: "tmux-backed-pty",
194
+ };
195
+ }
196
+
197
+ function chromeCandidates(explicit) {
198
+ const candidates = [explicit, process.env.CHROME_BIN, process.env.GOOGLE_CHROME_BIN];
199
+ if (process.platform === "darwin") candidates.push("/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "/Applications/Chromium.app/Contents/MacOS/Chromium");
200
+ if (process.platform === "linux") candidates.push("google-chrome", "google-chrome-stable", "chromium", "chromium-browser");
201
+ if (process.platform === "win32") {
202
+ candidates.push(join(process.env.PROGRAMFILES || "C:\\Program Files", "Google\\Chrome\\Application\\chrome.exe"));
203
+ candidates.push(join(process.env["PROGRAMFILES(X86)"] || "C:\\Program Files (x86)", "Google\\Chrome\\Application\\chrome.exe"));
204
+ }
205
+ return candidates.filter((candidate) => candidate && (candidate.includes("/") || candidate.includes("\\") ? existsSync(candidate) : true));
206
+ }
207
+
208
+ function capturePng(args) {
209
+ for (const chrome of chromeCandidates(args.chromeBin)) {
210
+ const result = spawnSync(chrome, ["--headless=new", "--disable-gpu", `--window-size=${args.width},${args.height}`, `--screenshot=${args.pngPath}`, pathToFileURL(args.htmlPath).href], { encoding: "utf8" });
211
+ if (result.status === 0 && existsSync(args.pngPath)) return { status: "captured", chrome };
212
+ }
213
+ throw new Error("PNG capture failed: set --chrome-bin or --no-browser");
214
+ }
215
+
216
+ function main() {
217
+ const args = parseArgs(process.argv.slice(2));
218
+ if (args.help) {
219
+ process.stdout.write(HELP);
220
+ return;
221
+ }
222
+ requireArgs(args);
223
+ const evidenceDir = resolve(args.evidenceDir);
224
+ mkdirSync(evidenceDir, { recursive: true });
225
+ const htmlPath = join(evidenceDir, "terminal.html");
226
+ const textPath = join(evidenceDir, "terminal.txt");
227
+ const ansiPath = join(evidenceDir, "terminal-ansi.txt");
228
+ const pngPath = join(evidenceDir, "terminal.png");
229
+ const metadataPath = join(evidenceDir, "metadata.json");
230
+ const capture = args.fromFile ? captureFromFile(args.fromFile) : runTmuxCommand(args);
231
+ const redactionRules = compileRedactions(args);
232
+ const safeAnsi = redactEvidence(capture.ansi, redactionRules);
233
+ const text = stripAnsi(safeAnsi);
234
+ writeFileSync(textPath, text, "utf8");
235
+ writeFileSync(ansiPath, safeAnsi, "utf8");
236
+ writeHtml({ title: args.title, ansi: safeAnsi, outPath: htmlPath, cols: args.cols, wrap: args.wrap });
237
+ const size = screenshotSize({ cols: args.cols, rows: args.rows });
238
+ const browser = args.browser ? capturePng({ chromeBin: args.chromeBin, htmlPath, pngPath, ...size }) : { status: "skipped" };
239
+ const metadata = {
240
+ title: args.title,
241
+ connector: capture.connector,
242
+ browserCapture: browser.status,
243
+ source: sourceMetadata(args),
244
+ redaction: {
245
+ builtInRules: BUILT_IN_REDACTION_RULE_COUNT,
246
+ literalRules: args.redactions.length,
247
+ regexRules: args.redactRegexes.length,
248
+ },
249
+ wrap: args.wrap ? "on" : "off",
250
+ dimensions: { cols: args.cols, rows: args.rows, screenshotWidth: size.width, screenshotHeight: size.height },
251
+ cleanup: capture.cleanup,
252
+ files: { html: htmlPath, text: textPath, ansi: ansiPath, png: browser.status === "captured" ? pngPath : null, metadata: metadataPath },
253
+ };
254
+ writeFileSync(metadataPath, `${JSON.stringify(metadata, null, 2)}\n`, "utf8");
255
+ process.stdout.write(`web terminal visual QA evidence (${basename(evidenceDir)}):\n${JSON.stringify(metadata.files, null, 2)}\n${capture.cleanup}\n`);
256
+ }
257
+
258
+ try {
259
+ main();
260
+ } catch (error) {
261
+ const message = error instanceof Error ? error.message : String(error);
262
+ process.stderr.write(`${message}\n`);
263
+ process.exit(1);
264
+ }