oh-my-opencode 4.16.2 → 4.17.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 (234) hide show
  1. package/.agents/command/omomomo.md +1 -1
  2. package/.agents/skills/hyperplan/SKILL.md +3 -3
  3. package/.agents/skills/omomomo/SKILL.md +1 -1
  4. package/.agents/skills/pre-publish-review/SKILL.md +3 -0
  5. package/.opencode/command/omomomo.md +1 -1
  6. package/.opencode/skills/hyperplan/SKILL.md +3 -3
  7. package/.opencode/skills/pre-publish-review/SKILL.md +3 -0
  8. package/README.ja.md +4 -4
  9. package/README.ko.md +4 -4
  10. package/README.md +4 -4
  11. package/README.ru.md +4 -4
  12. package/README.zh-cn.md +4 -4
  13. package/dist/agents/hephaestus/agent.d.ts +1 -1
  14. package/dist/agents/hephaestus/gpt-5-6.d.ts +2 -0
  15. package/dist/agents/momus-gpt-5-6.d.ts +1 -0
  16. package/dist/agents/types.d.ts +2 -0
  17. package/dist/cli/index.js +947 -567
  18. package/dist/cli-node/index.js +947 -567
  19. package/dist/features/background-agent/error-classifier.d.ts +5 -0
  20. package/dist/features/builtin-commands/templates/refactor-sections/intro-and-analysis.d.ts +1 -1
  21. package/dist/features/builtin-commands/templates/remove-ai-slops.d.ts +1 -1
  22. package/dist/features/team-mode/test-support/async-test-helpers.d.ts +2 -2
  23. package/dist/index.js +523 -81
  24. package/dist/plugin/chat-message/types.d.ts +4 -0
  25. package/dist/plugin/command-execute-before.d.ts +1 -0
  26. package/dist/plugin/stop-continuation.d.ts +17 -0
  27. package/dist/shared/live-server-route.d.ts +1 -1
  28. package/dist/skills/remove-ai-slops/SKILL.md +2 -2
  29. package/dist/skills/review-work/SKILL.md +1 -1
  30. package/dist/skills/start-work/SKILL.md +5 -2
  31. package/dist/skills/ulw-plan/references/full-workflow.md +1 -1
  32. package/dist/skills/ulw-plan/scripts/scaffold-plan.mjs +2 -2
  33. package/dist/skills/ulw-research/SKILL.md +2 -2
  34. package/dist/skills/visual-qa/SKILL.md +1 -1
  35. package/dist/tui.js +195 -41
  36. package/package.json +15 -14
  37. package/packages/git-bash-mcp/package.json +27 -0
  38. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +3 -1
  39. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +465 -248
  40. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  41. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  42. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  43. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  44. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  45. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  46. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  47. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/AGENTS.md +2 -2
  48. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/dist/cli.js +6 -2
  49. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/dist/codex-hook.js +6 -2
  50. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +2 -2
  51. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  52. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/src/codex-hook.ts +6 -2
  53. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/test/cli.test.ts +1 -1
  54. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/test/codex-hook.test.ts +67 -2
  55. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  56. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  57. package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +0 -13
  58. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.6.md +93 -0
  59. package/packages/omo-codex/plugin/components/rules/dist/cli.js +43 -24
  60. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  61. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  62. package/packages/omo-codex/plugin/components/rules/src/codex-hook.ts +2 -1
  63. package/packages/omo-codex/plugin/components/rules/src/dynamic-target-fingerprints.ts +15 -6
  64. package/packages/omo-codex/plugin/components/rules/src/rules-engine-factory.ts +7 -2
  65. package/packages/omo-codex/plugin/components/rules/src/static-injection.ts +2 -2
  66. package/packages/omo-codex/plugin/components/rules/test/codex-hook.test.ts +32 -0
  67. package/packages/omo-codex/plugin/components/rules/test/hephaestus-model-variant.test.ts +106 -0
  68. package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
  69. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
  70. package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +1 -79
  71. package/packages/omo-codex/plugin/components/teammode/AGENTS.md +12 -9
  72. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  73. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  74. package/packages/omo-codex/plugin/components/teammode/skills/teammode/SKILL.md +136 -76
  75. package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-guide.mjs +94 -23
  76. package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-state.mjs +94 -14
  77. package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-transport.mjs +55 -0
  78. package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team.mjs +72 -18
  79. package/packages/omo-codex/plugin/components/teammode/test/thread-title-hook.test.ts +23 -0
  80. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  81. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  82. package/packages/omo-codex/plugin/components/ultrawork/agents/explorer.toml +23 -40
  83. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-clone-fidelity-reviewer.toml +1 -1
  84. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-code-reviewer.toml +3 -3
  85. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-gate-reviewer.toml +7 -7
  86. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-qa-executor.toml +5 -5
  87. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-worker-high.toml +26 -0
  88. package/packages/omo-codex/plugin/components/ultrawork/agents/{lazycodex-executor.toml → lazycodex-worker-low.toml} +7 -5
  89. package/packages/omo-codex/plugin/components/ultrawork/agents/lazycodex-worker-medium.toml +26 -0
  90. package/packages/omo-codex/plugin/components/ultrawork/agents/librarian.toml +20 -48
  91. package/packages/omo-codex/plugin/components/ultrawork/agents/metis.toml +17 -29
  92. package/packages/omo-codex/plugin/components/ultrawork/agents/momus.toml +20 -50
  93. package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +22 -43
  94. package/packages/omo-codex/plugin/components/ultrawork/directive.md +55 -28
  95. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  96. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  97. package/packages/omo-codex/plugin/components/ultrawork/skills/ultrawork/SKILL.md +55 -28
  98. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +1 -1
  99. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/scripts/scaffold-plan.mjs +2 -2
  100. package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook.test.ts +22 -0
  101. package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +0 -68
  102. package/packages/omo-codex/plugin/components/ulw-loop/AGENTS.md +1 -1
  103. package/packages/omo-codex/plugin/components/ulw-loop/CHANGELOG.md +2 -0
  104. package/packages/omo-codex/plugin/components/ulw-loop/README.md +3 -1
  105. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +55 -28
  106. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +6 -1
  107. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-subcommands.d.ts +1 -1
  108. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-subcommands.js +13 -2
  109. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +405 -25
  110. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +12 -1
  111. package/packages/omo-codex/plugin/components/ulw-loop/dist/domain-types.d.ts +4 -2
  112. package/packages/omo-codex/plugin/components/ulw-loop/dist/paths.d.ts +7 -0
  113. package/packages/omo-codex/plugin/components/ulw-loop/dist/paths.js +16 -1
  114. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-crud.js +1 -0
  115. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-verdicts.d.ts +6 -0
  116. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-verdicts.js +20 -0
  117. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.d.ts +1 -0
  118. package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.js +12 -9
  119. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.d.ts +3 -0
  120. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.js +148 -0
  121. package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.d.ts +2 -0
  122. package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +209 -0
  123. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +25 -2
  124. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  125. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +14 -15
  126. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +29 -27
  127. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +6 -1
  128. package/packages/omo-codex/plugin/components/ulw-loop/src/cli-subcommands.ts +14 -3
  129. package/packages/omo-codex/plugin/components/ulw-loop/src/cli.ts +10 -0
  130. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +12 -1
  131. package/packages/omo-codex/plugin/components/ulw-loop/src/domain-types.ts +4 -2
  132. package/packages/omo-codex/plugin/components/ulw-loop/src/paths.ts +27 -1
  133. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-crud.ts +1 -0
  134. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-verdicts.ts +23 -0
  135. package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts +16 -9
  136. package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts +138 -0
  137. package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +208 -0
  138. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-create-goals.test.ts +12 -0
  139. package/packages/omo-codex/plugin/components/ulw-loop/test/cli-entrypoint.test.ts +4 -1
  140. package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-instruction.test.ts +8 -35
  141. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/quality-gate-builder.ts +24 -13
  142. package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +5 -2
  143. package/packages/omo-codex/plugin/components/ulw-loop/test/paths.test.ts +43 -8
  144. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate.test.ts +55 -2
  145. package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-guard.test.ts +228 -0
  146. package/packages/omo-codex/plugin/components/ulw-loop/test/stop-resume-hook.test.ts +193 -0
  147. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  148. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  149. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  150. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
  151. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  152. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  153. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  154. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  155. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  156. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +18 -0
  157. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  158. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  159. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  160. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
  161. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  162. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  163. package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
  164. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +17 -0
  165. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
  166. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +2 -2
  167. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  168. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  169. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  170. package/packages/omo-codex/plugin/model-catalog.json +16 -7
  171. package/packages/omo-codex/plugin/package-lock.json +13 -13
  172. package/packages/omo-codex/plugin/package.json +1 -1
  173. package/packages/omo-codex/plugin/scripts/auto-update.mjs +9 -2
  174. package/packages/omo-codex/plugin/scripts/entry-guard.mjs +26 -0
  175. package/packages/omo-codex/plugin/scripts/materialize-shared-upstreams.mjs +3 -1
  176. package/packages/omo-codex/plugin/scripts/migrate-codex-config/catalog.mjs +16 -7
  177. package/packages/omo-codex/plugin/scripts/migrate-codex-config/multi-agent-v2-guard.mjs +39 -4
  178. package/packages/omo-codex/plugin/scripts/migrate-codex-config/subagent-limit-guard.mjs +24 -4
  179. package/packages/omo-codex/plugin/scripts/migrate-codex-config.mjs +3 -3
  180. package/packages/omo-codex/plugin/scripts/migrate-omo-sot.mjs +2 -2
  181. package/packages/omo-codex/plugin/scripts/sync-hook-status-messages.mjs +2 -2
  182. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +14 -6
  183. package/packages/omo-codex/plugin/scripts/sync-version.mjs +4 -2
  184. package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +3 -3
  185. package/packages/omo-codex/plugin/skills/refactor/SKILL.md +3 -3
  186. package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +5 -5
  187. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +9 -3
  188. package/packages/omo-codex/plugin/skills/start-work/SKILL.md +6 -3
  189. package/packages/omo-codex/plugin/skills/teammode/SKILL.md +136 -76
  190. package/packages/omo-codex/plugin/skills/teammode/scripts/team-guide.mjs +94 -23
  191. package/packages/omo-codex/plugin/skills/teammode/scripts/team-state.mjs +94 -14
  192. package/packages/omo-codex/plugin/skills/teammode/scripts/team-transport.mjs +55 -0
  193. package/packages/omo-codex/plugin/skills/teammode/scripts/team.mjs +72 -18
  194. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +55 -28
  195. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +14 -15
  196. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +29 -27
  197. package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +1 -1
  198. package/packages/omo-codex/plugin/skills/ulw-plan/scripts/scaffold-plan.mjs +2 -2
  199. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +4 -4
  200. package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +4 -4
  201. package/packages/omo-codex/plugin/test/aggregate-agents.test.mjs +116 -10
  202. package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +23 -2
  203. package/packages/omo-codex/plugin/test/aggregate-manifest.test.mjs +1 -1
  204. package/packages/omo-codex/plugin/test/aggregate-model-catalog.test.mjs +4 -4
  205. package/packages/omo-codex/plugin/test/aggregate-skills.test.mjs +4 -2
  206. package/packages/omo-codex/plugin/test/auto-update-restart-notice.test.mjs +33 -0
  207. package/packages/omo-codex/plugin/test/auto-update.test.mjs +4 -4
  208. package/packages/omo-codex/plugin/test/component-hook-contract-cases.mjs +2 -2
  209. package/packages/omo-codex/plugin/test/lcx-bug-skills.test.mjs +4 -101
  210. package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +160 -23
  211. package/packages/omo-codex/plugin/test/multi-agent-v2-regression.test.mjs +71 -0
  212. package/packages/omo-codex/plugin/test/subagent-limit-migration.test.mjs +32 -0
  213. package/packages/omo-codex/plugin/test/sync-skills-orchestration.test.mjs +20 -2
  214. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +1 -1
  215. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +4 -2
  216. package/packages/omo-codex/plugin/test/teammode-transport.test.mjs +345 -0
  217. package/packages/omo-codex/plugin/test/ulw-plan-skill-contract.test.mjs +9 -40
  218. package/packages/omo-codex/plugin/test/ulw-research-skill-contract.test.mjs +4 -277
  219. package/packages/omo-codex/scripts/install-config-reasoning.test.mjs +5 -5
  220. package/packages/omo-codex/scripts/install-config.test.mjs +22 -3
  221. package/packages/omo-codex/scripts/install-dist/install-local.mjs +508 -286
  222. package/packages/omo-codex/scripts/install-generated-bundle.test.mjs +111 -1
  223. package/packages/shared-skills/skills/remove-ai-slops/SKILL.md +2 -2
  224. package/packages/shared-skills/skills/review-work/SKILL.md +1 -1
  225. package/packages/shared-skills/skills/start-work/SKILL.md +5 -2
  226. package/packages/shared-skills/skills/ulw-plan/references/full-workflow.md +1 -1
  227. package/packages/shared-skills/skills/ulw-plan/scripts/scaffold-plan.mjs +2 -2
  228. package/packages/shared-skills/skills/ulw-research/SKILL.md +2 -2
  229. package/packages/shared-skills/skills/visual-qa/SKILL.md +1 -1
  230. package/dist/skills/visual-qa/scripts/skill-prompt-contract.test.ts +0 -296
  231. package/packages/omo-codex/plugin/components/ulw-loop/test/skill-contract.test.ts +0 -70
  232. package/packages/omo-codex/plugin/test/ulw-research-epistemic-contract.test.mjs +0 -98
  233. package/packages/shared-skills/skills/visual-qa/scripts/skill-prompt-contract.test.ts +0 -296
  234. /package/packages/omo-codex/plugin/components/rules/bundled-rules/{hephaestus.md → hephaestus/gpt-5.5.md} +0 -0
@@ -1,9 +1,12 @@
1
1
  import assert from "node:assert/strict";
2
- import { readFile } from "node:fs/promises";
2
+ import { mkdtemp, readFile, writeFile } from "node:fs/promises";
3
+ import { tmpdir } from "node:os";
3
4
  import { dirname, join } from "node:path";
4
5
  import test from "node:test";
5
6
  import { fileURLToPath } from "node:url";
6
7
 
8
+ import { updateCodexConfig } from "./install-dist/install-local.mjs";
9
+
7
10
  const scriptsDir = dirname(fileURLToPath(import.meta.url));
8
11
  const entrypointPath = join(scriptsDir, "install-local.mjs");
9
12
 
@@ -53,3 +56,110 @@ test("#given generated installer output #when importing direct bundle #then comp
53
56
  assert.equal(typeof module[name], "function", `${name} must be exported`);
54
57
  }
55
58
  });
59
+
60
+ test("#given no root model #when generated bundle updates config #then it does not introduce agents max_threads", async () => {
61
+ // given
62
+ const root = await mkdtemp(join(tmpdir(), "omo-codex-generated-no-root-model-"));
63
+ const configPath = join(root, "config.toml");
64
+ await writeFile(configPath, ['model_reasoning_effort = "high"', "", "[features]", "plugins = false", ""].join("\n"));
65
+
66
+ // when
67
+ await updateCodexConfig({
68
+ configPath,
69
+ repoRoot: "/repo/packages/omo-codex",
70
+ marketplaceName: "debug",
71
+ marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex" },
72
+ pluginNames: ["omo"],
73
+ });
74
+
75
+ // then
76
+ const config = await readFile(configPath, "utf8");
77
+ assert.doesNotMatch(config, /^\s*max_threads\s*=/m);
78
+ assert.match(config, /max_concurrent_threads_per_session = 1000/);
79
+ });
80
+
81
+ test("#given explicit v1 model_catalog_json and stale models_cache v2 #when generated bundle updates config #then explicit catalog preserves disable and cap", async () => {
82
+ // given
83
+ const root = await mkdtemp(join(tmpdir(), "omo-codex-generated-catalog-v1-"));
84
+ const configPath = join(root, "config.toml");
85
+ const catalogPath = join(root, "custom-catalog.json");
86
+ await writeFile(
87
+ configPath,
88
+ [
89
+ 'model = "gpt-5.6-sol"',
90
+ `model_catalog_json = "${catalogPath}"`,
91
+ "",
92
+ "[features]",
93
+ "multi_agent_v2 = false",
94
+ "",
95
+ "[agents]",
96
+ "max_threads = 16",
97
+ "",
98
+ ].join("\n"),
99
+ );
100
+ await writeFile(catalogPath, JSON.stringify({ models: [{ slug: "gpt-5.6-sol", multi_agent_version: "v1" }] }));
101
+ await writeFile(join(root, "models_cache.json"), JSON.stringify({ models: [{ slug: "gpt-5.6-sol", multi_agent_version: "v2" }] }));
102
+
103
+ // when
104
+ await updateCodexConfig({
105
+ configPath,
106
+ repoRoot: "/repo/packages/omo-codex",
107
+ marketplaceName: "debug",
108
+ marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex" },
109
+ pluginNames: ["omo"],
110
+ });
111
+
112
+ // then
113
+ const config = await readFile(configPath, "utf8");
114
+ const v2Section = sectionText(config, "[features.multi_agent_v2]");
115
+ assert.match(v2Section, /^enabled = false$/m);
116
+ assert.match(config, /\[agents\][\s\S]*?max_threads = 1000/);
117
+ assert.doesNotMatch(config, /max_threads = 16/);
118
+ });
119
+
120
+ test("#given explicit v2 model_catalog_json and stale models_cache v1 #when generated bundle updates config #then explicit catalog clears managed disable and cap", async () => {
121
+ // given
122
+ const root = await mkdtemp(join(tmpdir(), "omo-codex-generated-catalog-v2-"));
123
+ const configPath = join(root, "config.toml");
124
+ const catalogPath = join(root, "custom-catalog.json");
125
+ await writeFile(
126
+ configPath,
127
+ [
128
+ 'model = "gpt-5.6-sol"',
129
+ `model_catalog_json = "${catalogPath}"`,
130
+ "",
131
+ "[features]",
132
+ "multi_agent_v2 = false",
133
+ "",
134
+ "[agents]",
135
+ "max_threads = 16",
136
+ "",
137
+ ].join("\n"),
138
+ );
139
+ await writeFile(catalogPath, JSON.stringify({ models: [{ slug: "gpt-5.6-sol", multi_agent_version: "v2" }] }));
140
+ await writeFile(join(root, "models_cache.json"), JSON.stringify({ models: [{ slug: "gpt-5.6-sol", multi_agent_version: "v1" }] }));
141
+
142
+ // when
143
+ await updateCodexConfig({
144
+ configPath,
145
+ repoRoot: "/repo/packages/omo-codex",
146
+ marketplaceName: "debug",
147
+ marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex" },
148
+ pluginNames: ["omo"],
149
+ });
150
+
151
+ // then
152
+ const config = await readFile(configPath, "utf8");
153
+ const v2Section = sectionText(config, "[features.multi_agent_v2]");
154
+ assert.doesNotMatch(v2Section, /^enabled\s*=/m);
155
+ assert.doesNotMatch(config, /^\s*max_threads\s*=/m);
156
+ assert.match(v2Section, /max_concurrent_threads_per_session = 1000/);
157
+ });
158
+
159
+ function sectionText(config, header) {
160
+ const start = config.indexOf(header);
161
+ if (start === -1) return "";
162
+ const afterStart = config.slice(start + header.length);
163
+ const nextSectionOffset = afterStart.search(/\n\[/);
164
+ return nextSectionOffset === -1 ? config.slice(start) : config.slice(start, start + header.length + nextSectionOffset);
165
+ }
@@ -62,7 +62,7 @@ The agent looks for these nine categories. The first three are stylistic, the ne
62
62
  **Hard rule**: only apply when behavior equivalence is obvious. Do NOT change algorithms with subtle correctness implications. Do NOT micro-optimize hot paths without a benchmark. If in doubt, SKIP.
63
63
 
64
64
  ### Behavior coverage
65
- 9. **Missing tests** — behavior present in changed files that is not locked by any regression test. The fix is not to remove code but to ADD the narrowest test that pins the behavior.
65
+ 9. **Missing tests** — behavior present in changed files that is not locked by any regression test. The fix is not to remove code but to ADD the narrowest test that pins the behavior. EXCEPTION: a PROSE file (prompt, `SKILL.md`, rule, markdown) has no behavioral seam — do NOT add a text/word-count/phrase pin for it; that guards a diff, not behavior. Cover only a machine-consumed value (parsed field, sentinel a runtime greps, a doc JSON sample through its real validator) or leave it to review.
66
66
 
67
67
  ### Structural
68
68
  10. **Oversized modules** — any source file exceeding **250 pure LOC** (non-blank, non-comment lines). This is an architectural defect, not a style preference. Measure: `awk '!/^[[:space:]]*$/ && !/^[[:space:]]*(#|\/\/)/' <file> | wc -l`.
@@ -122,7 +122,7 @@ For each in-scope source file:
122
122
 
123
123
  1. Identify the public/observable behavior the file exposes (exported functions, HTTP handlers, CLI commands, classes used elsewhere).
124
124
  2. Check whether existing tests cover that behavior. Use `git grep` / project test conventions to find related test files.
125
- 3. **If behavior is uncovered or weakly covered, write the narrowest regression test that pins current behavior BEFORE editing the file.** Tests should pin observable outputs, not implementation details.
125
+ 3. **If behavior is uncovered or weakly covered, write the narrowest regression test that pins current behavior BEFORE editing the file.** Tests should pin observable outputs, not implementation details. A PROSE file (prompt/`SKILL.md`/rule/markdown) is exempt — its wording is not behavior; skip the test and rely on review, or assert only a machine-consumed value.
126
126
  4. Run the test suite (or at minimum the relevant tests). They must be **green** before any cleanup begins.
127
127
 
128
128
  If you cannot establish a green baseline (e.g., test runner is broken), STOP and report. Do not proceed with cleanup on unverified ground.
@@ -14,7 +14,7 @@ This skill may include examples copied from the OpenCode harness. In Codex, do n
14
14
  | `task(subagent_type="oracle", ...)` for final verification | `multi_agent_v1.spawn_agent({"message":"TASK: act as a rigorous reviewer. ...","agent_type":"lazycodex-gate-reviewer","fork_context":false})` |
15
15
  | `task(category="...", ...)` for implementation or QA | `multi_agent_v1.spawn_agent({"message":"TASK: act as an implementation or QA worker. ...","fork_context":false})` |
16
16
  | `background_output(task_id="...")` | `multi_agent_v1.wait_agent(...)` for mailbox signals |
17
- | `team_*(...)` | Use Codex native subagents via `multi_agent_v1.spawn_agent`, `multi_agent_v1.send_input`, `multi_agent_v1.wait_agent`, and `multi_agent_v1.close_agent` |
17
+ | `team_*(...)` | Use Codex native subagents via `multi_agent_v1.spawn_agent` and `multi_agent_v1.wait_agent`; use `multi_agent_v1.send_input` and `multi_agent_v1.close_agent` only when exposed in the active tools list |
18
18
 
19
19
  Role-specific behavior must be described in a self-contained `message`. Use `fork_context: false` to start the child with only the initial prompt (no parent history); use `fork_context: true` only when full parent history is truly required. Include any required conversation context, files, diffs, constraints, and requested skill names directly in the spawned agent's `message`. OMO installs these selectable agent roles into `~/.codex/agents/`: `explorer`, `librarian`, `plan`, `momus`, `metis`, `lazycodex-code-reviewer`, `lazycodex-qa-executor`, and `lazycodex-gate-reviewer` — pass the matching name as `agent_type` so the child gets that role's model and instructions. If the spawn tool exposes no `agent_type` parameter, omit it and describe the role inside `message`. If a code block below conflicts with this section, this section wins.
20
20
 
@@ -14,13 +14,16 @@ Translate any OpenCode-only tool name in an inherited example to its Codex equiv
14
14
  | OpenCode example | Codex tool to use |
15
15
  | --- | --- |
16
16
  | final-review `task(...)` | `multi_agent_v1.spawn_agent({"message":"TASK: act as a rigorous reviewer. ...","agent_type":"lazycodex-gate-reviewer","fork_context":false})` |
17
- | worker `task(...)` | `multi_agent_v1.spawn_agent({"message":"TASK: act as <role>. ...","fork_context":false})` |
17
+ | worker `task(...)` | `multi_agent_v1.spawn_agent({"message":"TASK: act as <role>. ...","fork_context":false})` — for implementation workers add `agent_type: "lazycodex-worker-<low|medium|high>"` when the spawn schema exposes `agent_type` |
18
18
  | `background_output(task_id="...")` | `multi_agent_v1.wait_agent(...)` for mailbox signals |
19
19
  | `team_*(...)` | `multi_agent_v1.spawn_agent` + `multi_agent_v1.send_input` + `multi_agent_v1.wait_agent` + `multi_agent_v1.close_agent` |
20
20
 
21
21
  When translating `load_skills=[...]`, name the skills inside the spawned agent's `message`. If a code block below conflicts with this section, this section wins.
22
22
 
23
- Codex exposes ONE of two subagent tool surfaces per session; check your own tool list and route accordingly. If `multi_agent_v1.*` tools exist, use the table above as written. If instead a flat `spawn_agent` with a required `task_name` exists (`multi_agent_v2`), rewrite every `multi_agent_v1.*` example: `multi_agent_v1.spawn_agent({...,"fork_context":false})` becomes `spawn_agent({"task_name":"<lowercase_digits_underscores>","message":...,"agent_type":...,"fork_turns":"none"})` (`"all"` only when full parent history is truly required); `send_input` becomes `send_message`; do not call `close_agent`/`resume_agent` (finished agents end on their own; `followup_task` re-tasks one, `interrupt_agent` stops one); `wait_agent` takes only `timeout_ms` and returns on any child mailbox activity. `agent_type` works the same on both surfaces. If a code block below conflicts with this section, this section wins.
23
+ Codex exposes ONE of two subagent tool surfaces per session; check your own tool list and route accordingly. If `multi_agent_v1.*` tools exist, use the table above as written. If instead a flat `spawn_agent` with a required `task_name` exists (`multi_agent_v2`), rewrite every `multi_agent_v1.*` example: `multi_agent_v1.spawn_agent({...,"fork_context":false})` becomes `spawn_agent({"task_name":"<lowercase_digits_underscores>","message":...,"agent_type":...,"fork_turns":"none"})` (`"all"` only when full parent history is truly required); `send_input` becomes `send_message`; do not call `close_agent`/`resume_agent` (finished agents end on their own; `followup_task` re-tasks one, `interrupt_agent` stops one); `wait_agent` takes only `timeout_ms` and returns on any child mailbox activity. On the v2 surface `agent_type` may be absent from the spawn schema when absent, omit it and describe the role inside `message`. If a code block below conflicts with this section, this section wins.
24
+
25
+ ### Delegation by difficulty (Codex tier workers)
26
+ When tier worker agents are installed (Codex), size each implementation lane by difficulty and pass the matching `agent_type` where the spawn schema exposes it: LOW (one-file fix, boilerplate, config/copy) -> `lazycodex-worker-low`; MEDIUM (standard feature, few files, known patterns) -> `lazycodex-worker-medium`; HIGH (new module, cross-module refactor, concurrency/security/migration) -> `lazycodex-worker-high`. Explorer/librarian research lanes keep their own roles. Difficulty (model power) is orthogonal to the LIGHT/HEAVY rigor tier in step 4 — judge each on its own facts. On spawn surfaces without `agent_type` (deployed v2), state the tier inside `message`.
24
27
 
25
28
  ## Codex Subagent Reliability
26
29
 
@@ -85,7 +85,7 @@ Runs in parallel; ALL must APPROVE; surface results and wait for the user's expl
85
85
  - UNCLEAR: run the high-accuracy review AUTOMATICALLY before presenting (unless Classify=Trivial), then present a brief that LEADS with the derived approach and the adopted defaults; still wait for the user's explicit okay.
86
86
 
87
87
  ### High-accuracy review (dual review)
88
- The high-accuracy review is DUAL and both passes must return OKAY before handoff: (1) the native `momus` reviewer subagent, and (2) an independent Oracle review via `task(subagent_type="oracle", ...)` on the strongest available reasoning model, in a fully isolated sub-session with normal approval and sandbox policy. Do not add flags that disable approvals or sandboxing. One round = exactly ONE `momus` + ONE independent review, dispatched together against the COMPLETE plan file (todos + TL;DR filled) - never a second `momus` while a round is in flight. After both verdicts return, fix every cited issue and resubmit both fresh until each approves. CLEAR: runs when the user opts in or `review_required: true`. UNCLEAR: runs automatically unless Classify=Trivial.
88
+ The high-accuracy review is DUAL and both passes must return OKAY before handoff: (1) the native `momus` reviewer subagent, and (2) an independent Oracle review via `task(subagent_type="oracle", ...)` on the strongest available reasoning model, in a fully isolated sub-session with normal approval and sandbox policy. Do not add flags that disable approvals or sandboxing. Momus runs at Ultra and may take substantially longer than other agents. One round = exactly ONE `momus` + ONE independent review, dispatched together against the COMPLETE plan file (todos + TL;DR filled). Keep Momus in flight and wait for its terminal result: elapsed time alone never justifies cancelling, duplicating, replacing, or treating it as failed. After both verdicts return, fix every cited issue and resubmit both fresh until each approves. CLEAR: runs when the user opts in or `review_required: true`. UNCLEAR: runs automatically unless Classify=Trivial.
89
89
 
90
90
  The draft must record the native Momus session/result, the independent review session/result, and the fix/retry summary. Do not say "high-accuracy review completed" unless both receipts exist and both final verdicts are unconditional approval.
91
91
 
@@ -221,7 +221,7 @@ Your next move: <fill - e.g. approve, or run a high-accuracy review>. Full execu
221
221
  ## Verification strategy
222
222
  > Zero human intervention - all verification is agent-executed.
223
223
  - Test decision: <TDD | tests-after | none> + framework
224
- - Evidence: .omo/evidence/task-<N>-${slug}.<ext>
224
+ - Evidence: <attemptDir>/task-<N>-${slug}.<ext> (attemptDir = currentAttemptDir from 'omo ulw-loop status --json', .omo/evidence/ulw/<session>/<goalId>/a<attempt>; outside ulw-loop use .omo/evidence/)
225
225
 
226
226
  ## Execution strategy
227
227
  ### Parallel execution waves
@@ -239,7 +239,7 @@ Your next move: <fill - e.g. approve, or run a high-accuracy review>. Full execu
239
239
  Parallelization: Wave <N> | Blocked by: <...> | Blocks: <...>
240
240
  References (executor has NO interview context - be exhaustive): <src/path:lines>
241
241
  Acceptance criteria (agent-executable): <exact command or assertion>
242
- QA scenarios (name the exact tool + invocation): happy + failure, Evidence .omo/evidence/task-1-${slug}.<ext>
242
+ QA scenarios (name the exact tool + invocation): happy + failure, Evidence <attemptDir>/task-1-${slug}.<ext>
243
243
  Commit: <Y/N> | <type>(<scope>): <summary>
244
244
 
245
245
  ## Final verification wave
@@ -15,7 +15,7 @@ This skill may include examples copied from the OpenCode harness. In Codex, do n
15
15
  | `task(subagent_type="oracle", ...)` for final verification | `multi_agent_v1.spawn_agent({"message":"TASK: act as a rigorous reviewer. ...","agent_type":"lazycodex-gate-reviewer","fork_context":false})` |
16
16
  | `task(category="...", ...)` for implementation or QA | `multi_agent_v1.spawn_agent({"message":"TASK: act as an implementation or QA worker. ...","fork_context":false})` |
17
17
  | `background_output(task_id="...")` | `multi_agent_v1.wait_agent(...)` for mailbox signals |
18
- | `team_*(...)` | Use Codex native subagents via `multi_agent_v1.spawn_agent`, `multi_agent_v1.send_input`, `multi_agent_v1.wait_agent`, and `multi_agent_v1.close_agent` |
18
+ | `team_*(...)` | Use Codex native subagents via `multi_agent_v1.spawn_agent` and `multi_agent_v1.wait_agent`; use `multi_agent_v1.send_input` and `multi_agent_v1.close_agent` only when exposed in the active tools list |
19
19
 
20
20
  Role-specific behavior must be described in a self-contained `message`. Use `fork_context: false` to start the child with only the initial prompt (no parent history); use `fork_context: true` only when full parent history is truly required. Include any required conversation context, files, diffs, constraints, and requested skill names directly in the spawned agent's `message`. OMO installs these selectable agent roles into `~/.codex/agents/`: `explorer`, `librarian`, `plan`, `momus`, `metis`, `lazycodex-code-reviewer`, `lazycodex-qa-executor`, and `lazycodex-gate-reviewer` — pass the matching name as `agent_type` so the child gets that role's model and instructions. If the spawn tool exposes no `agent_type` parameter, omit it and describe the role inside `message`. If a code block below conflicts with this section, this section wins.
21
21
 
@@ -69,7 +69,7 @@ Observation candidates and claim candidates travel back from workers as message
69
69
 
70
70
  ## Run the swarm as a cooperating team
71
71
 
72
- Saturation research defaults to teammode, not isolated fire-and-forget workers: a lead one worker surfaces almost always reshapes what another should search next. When your harness gives you real cooperating members — Codex: the `teammode` skill (`codex_app` threads); OpenCode: `team_mode` — run this swarm as a team. Fall back to the background-worker swarm below only when team mode is unavailable, or the axes are genuinely independent with no cross-pollination expected.
72
+ Saturation research defaults to teammode, not isolated fire-and-forget workers: a lead one worker surfaces almost always reshapes what another should search next. When your harness gives you real cooperating members — Codex: the `teammode` skill (MultiAgentV2 agents, or `codex_app` threads as its fallback); OpenCode: `team_mode` — run this swarm as a team. Fall back to the background-worker swarm below only when team mode is unavailable, or the axes are genuinely independent with no cross-pollination expected.
73
73
 
74
74
  - **One member per axis — by part, ownership, or perspective, never a job title.** Each Phase 0 axis is one member owning one concrete slice: a codebase part, a source territory, or a question lens. No two members share an angle. "Backend researcher" or "the web person" gives no real boundary and invites overlap — name what the member owns.
75
75
  - **Many teammates by default.** Prefer a larger roster, usually 5-8 teammates, whenever the axes can be made distinct. Add at least one skeptic or red-team perspective for hyperdebate/ultradebate: cross-critique claims, evidence quality, synthesis structure, and visual-report choices before they reach the final deliverable.
@@ -249,7 +249,7 @@ If any page fails, you are not done: fix it, re-capture the full set, re-dispatc
249
249
 
250
250
  Run this step IN ADDITION to Steps 1-4 when the original user task has a concrete visual target: "clone this site", "move this Figma design to code", "rebuild this screen", "make it look exactly like X", or "build this Imagen/Stitch/generated mockup and overview". For these tasks the normal dual-oracle is necessary but NOT sufficient. After it returns, run the following TWO additional MANDATORY verifications and LOOP until BOTH pass.
251
251
 
252
- 1. Pixel-perfect design-compare subagent (visual oracle). Dispatch a focused, read-only design-compare reviewer (recommend `gpt-5.5` with medium reasoning). It must crop/zoom BOTH the reference (target / Figma export / source-site screenshot / generated page snapshot) and the ACTUAL screenshot into matching regions and read them **pixel-by-pixel** - header, nav, each card, spacing, type ramp, color tokens - not at a glance. It must also compare the overview text or annotations against the rendered content and DOM text. Anchor every claim with the bundled tool:
252
+ 1. Pixel-perfect design-compare subagent (visual oracle). Dispatch a focused, read-only design-compare reviewer (recommend `gpt-5.6-sol` with xhigh reasoning). It must crop/zoom BOTH the reference (target / Figma export / source-site screenshot / generated page snapshot) and the ACTUAL screenshot into matching regions and read them **pixel-by-pixel** - header, nav, each card, spacing, type ramp, color tokens - not at a glance. It must also compare the overview text or annotations against the rendered content and DOM text. Anchor every claim with the bundled tool:
253
253
 
254
254
  ```
255
255
  node "$SKILL_DIR/scripts/visual-qa.mjs" image-diff <reference.png> <actual.png>
@@ -1,296 +0,0 @@
1
- import { describe, expect, test } from "bun:test"
2
- import { existsSync, readFileSync } from "node:fs"
3
- import { dirname, join } from "node:path"
4
-
5
- const repoRoot = findRepoRoot(import.meta.dir)
6
- const sharedSkillPath = join(repoRoot, "packages", "shared-skills", "skills", "visual-qa", "SKILL.md")
7
- const codexSkillPath = join(repoRoot, "packages", "omo-codex", "plugin", "skills", "visual-qa", "SKILL.md")
8
- const referencesPath = join(
9
- repoRoot,
10
- "packages",
11
- "shared-skills",
12
- "skills",
13
- "visual-qa",
14
- "references",
15
- "agent-browser-setup.md",
16
- )
17
-
18
- type PromptFixture = {
19
- readonly label: string
20
- readonly text: string
21
- }
22
-
23
- function readPrompt(path: string): string {
24
- return readFileSync(path, "utf8")
25
- }
26
-
27
- function findRepoRoot(start: string): string {
28
- let current = start
29
- while (true) {
30
- if (existsSync(join(current, "package.json")) && existsSync(join(current, "packages"))) {
31
- return current
32
- }
33
- const parent = dirname(current)
34
- if (parent === current) {
35
- throw new Error(`repository root not found from ${start}`)
36
- }
37
- current = parent
38
- }
39
- }
40
-
41
- function fixtures(): readonly PromptFixture[] {
42
- const promptFixtures: PromptFixture[] = [{ label: "shared skill", text: readPrompt(sharedSkillPath) }]
43
- if (existsSync(codexSkillPath)) {
44
- promptFixtures.push({ label: "codex plugin copy", text: readPrompt(codexSkillPath) })
45
- }
46
- return promptFixtures
47
- }
48
-
49
- function sectionBetween(text: string, startMarker: string, endMarker: string): string {
50
- const start = text.indexOf(startMarker)
51
- if (start < 0) {
52
- throw new Error(`missing start marker: ${startMarker}`)
53
- }
54
- const end = text.indexOf(endMarker, start + startMarker.length)
55
- if (end < 0) {
56
- throw new Error(`missing end marker: ${endMarker}`)
57
- }
58
- return text.slice(start, end)
59
- }
60
-
61
- describe("visual-qa skill prompt contract", () => {
62
- test("#given visual QA prompts #when dispatching pass B #then the oracle must directly inspect screenshots and content for CJK wrapping", () => {
63
- for (const fixture of fixtures()) {
64
- const passB = sectionBetween(fixture.text, "### Pass B", "## Step 4")
65
- const lowerPassB = passB.toLowerCase()
66
-
67
- expect(lowerPassB, fixture.label).toContain("directly open")
68
- expect(lowerPassB, fixture.label).toContain("view_image")
69
- expect(lowerPassB, fixture.label).toContain("source code")
70
- expect(passB, fixture.label).toContain("[Image #1]")
71
- expect(passB, fixture.label).toContain("semantic phrases")
72
- expect(passB, fixture.label).toContain("놀라운 변 / 화")
73
- expect(passB, fixture.label).toContain("에이전트 오케스트")
74
- expect(passB, fixture.label).toContain("레이션 현황 및 미")
75
- expect(passB, fixture.label).toContain("래")
76
- expect(passB, fixture.label).toContain("REVISE/FAIL")
77
- }
78
- })
79
-
80
- test("#given visual QA prompts #when dispatching pass A #then the oracle must reject mock-only UI instead of accepting superficial screenshots", () => {
81
- for (const fixture of fixtures()) {
82
- const passA = sectionBetween(fixture.text, "### Pass A", "### Pass B")
83
- const checkBlock = sectionBetween(passA, "CHECK EACH:", "OUTPUT:")
84
- const outputBlock = sectionBetween(passA, "OUTPUT:", '"""')
85
- const lowerCheckBlock = checkBlock.toLowerCase()
86
-
87
- expect(lowerCheckBlock, fixture.label).toContain("mock-only")
88
- expect(lowerCheckBlock, fixture.label).toContain("faked-with-an-image")
89
- expect(lowerCheckBlock, fixture.label).toContain("coherent design tokens")
90
- expect(lowerCheckBlock, fixture.label).toContain("reused primitives")
91
- expect(lowerCheckBlock, fixture.label).toContain("blocking")
92
- expect(outputBlock, fixture.label).toContain("BLOCKING:")
93
- }
94
- })
95
-
96
- test("#given the Web capture path #when no browser tooling is configured #then it falls back to agent-browser", () => {
97
- for (const fixture of fixtures()) {
98
- const web = sectionBetween(fixture.text, "### Web", "### TUI")
99
-
100
- expect(web, fixture.label).toContain("agent-browser")
101
- expect(fixture.text, fixture.label).toContain("npm install -g agent-browser")
102
- expect(fixture.text, fixture.label).toContain("https://github.com/vercel-labs/agent-browser")
103
- expect(fixture.text, fixture.label).toContain("references/agent-browser-setup.md")
104
- }
105
- })
106
-
107
- test("#given the agent-browser fallback #when documenting setup #then a references doc lists install, link, and help", () => {
108
- expect(existsSync(referencesPath)).toBe(true)
109
- const doc = readFileSync(referencesPath, "utf8")
110
-
111
- expect(doc).toContain("npm install -g agent-browser")
112
- expect(doc).toContain("agent-browser install")
113
- expect(doc).toContain("https://github.com/vercel-labs/agent-browser")
114
- expect(doc).toContain("agent-browser --help")
115
- })
116
-
117
- test("#given the visual QA evidence commands #when documented #then they use the Node bundle, not a Bun-only TypeScript launcher", () => {
118
- for (const fixture of fixtures()) {
119
- expect(fixture.text, fixture.label).toContain('node "$SKILL_DIR/scripts/visual-qa.mjs" image-diff')
120
- expect(fixture.text, fixture.label).toContain('node "$SKILL_DIR/scripts/visual-qa.mjs" tui-check')
121
- expect(fixture.text, fixture.label).not.toContain('bun "$SKILL_DIR/scripts/cli.ts"')
122
- }
123
- })
124
-
125
- test("#given a concrete visual target #when in reference-fidelity mode #then dual pixel + code-fidelity verification loops until both pass", () => {
126
- for (const fixture of fixtures()) {
127
- const cloneMode = sectionBetween(fixture.text, "## Step 5", "## Reference evidence is not the verdict")
128
- const lowerCloneMode = cloneMode.toLowerCase()
129
-
130
- expect(lowerCloneMode, fixture.label).toContain("clone")
131
- expect(lowerCloneMode, fixture.label).toContain("imagen")
132
- expect(lowerCloneMode, fixture.label).toContain("stitch")
133
- expect(lowerCloneMode, fixture.label).toContain("generated mockup")
134
- expect(lowerCloneMode, fixture.label).toContain("overview")
135
- expect(cloneMode, fixture.label).toContain("pixel-by-pixel")
136
- expect(cloneMode, fixture.label).toContain("image-diff")
137
- expect(cloneMode, fixture.label).toContain("lazycodex-clone-fidelity-reviewer")
138
- expect(lowerCloneMode, fixture.label).toContain("extensible state variants")
139
- expect(lowerCloneMode, fixture.label).toContain("retry")
140
- }
141
- })
142
- })
143
-
144
- describe("visual-qa skill exhaustive-coverage and review-gate contract", () => {
145
- test("#given a multi-page surface #when capturing #then every page is enumerated and verified per page, not sampled", () => {
146
- for (const fixture of fixtures()) {
147
- const capture = sectionBetween(fixture.text, "## Step 2", "## Step 3")
148
- const lower = capture.toLowerCase()
149
-
150
- expect(lower, fixture.label).toContain("every page")
151
- expect(lower, fixture.label).toContain("enumerate")
152
- expect(lower, fixture.label).toContain("never sample")
153
- expect(lower, fixture.label).toContain("per page")
154
- expect(lower, fixture.label).toContain("one failing page fails")
155
- }
156
- })
157
-
158
- test("#given prior QA artifacts #when verifying #then stale evidence older than the source must be regenerated", () => {
159
- for (const fixture of fixtures()) {
160
- const capture = sectionBetween(fixture.text, "## Step 2", "## Step 3")
161
- const lower = capture.toLowerCase()
162
-
163
- expect(lower, fixture.label).toContain("stale")
164
- expect(lower, fixture.label).toContain("older than")
165
- expect(lower, fixture.label).toContain("regenerate")
166
- }
167
- })
168
-
169
- test("#given Step 3 dispatch #when running the review #then it is required pre-done, harness-native, and covers every enumerated page", () => {
170
- for (const fixture of fixtures()) {
171
- const dispatch = sectionBetween(fixture.text, "## Step 3", "### Pass A")
172
- const lower = dispatch.toLowerCase()
173
-
174
- expect(lower, fixture.label).toContain("required before")
175
- expect(lower, fixture.label).toContain("do not self-review")
176
- expect(dispatch, fixture.label).toContain("spawn_agent")
177
- expect(dispatch, fixture.label).toContain("lazycodex-gate-reviewer")
178
- expect(lower, fixture.label).toContain("every enumerated page")
179
- }
180
- })
181
-
182
- test("#given the completion gate #when deciding done #then an independent reviewer must PASS on a fresh full capture, looping until clean", () => {
183
- for (const fixture of fixtures()) {
184
- const gate = sectionBetween(fixture.text, "## Step 4", "## Step 5")
185
- const lower = gate.toLowerCase()
186
-
187
- expect(lower, fixture.label).toContain("hard stop rule")
188
- expect(lower, fixture.label).toContain("independent")
189
- expect(lower, fixture.label).toContain("no blocking")
190
- expect(lower, fixture.label).toContain("fresh")
191
- expect(lower, fixture.label).toContain("every enumerated page")
192
- expect(lower, fixture.label).toContain("loop until")
193
- expect(lower, fixture.label).toContain("do not stop because the automated script")
194
- }
195
- })
196
-
197
- test("#given Pass B CJK checks #when inspecting #then it flags topic, connective, and source-citation splits across every page", () => {
198
- for (const fixture of fixtures()) {
199
- const passB = sectionBetween(fixture.text, "### Pass B", "## Step 4")
200
- const lower = passB.toLowerCase()
201
-
202
- expect(passB, fixture.label).toContain("두 강은")
203
- expect(passB, fixture.label).toContain("쓸 수")
204
- expect(passB, fixture.label).toContain("Attention Is")
205
- expect(lower, fixture.label).toContain("citation")
206
- expect(lower, fixture.label).toContain("every page")
207
- expect(lower, fixture.label).toContain("regardless of similarityscore")
208
- }
209
- })
210
-
211
- test("#given a generated reference packet #when reviewing #then Pass A and B require extensible design-system code and pixel-level reference matching", () => {
212
- for (const fixture of fixtures()) {
213
- const passA = sectionBetween(fixture.text, "### Pass A", "### Pass B")
214
- const passB = sectionBetween(fixture.text, "### Pass B", "## Step 4")
215
- const lowerPassA = passA.toLowerCase()
216
- const lowerPassB = passB.toLowerCase()
217
-
218
- expect(passA, fixture.label).toContain("REFERENCE PACKET:")
219
- expect(passB, fixture.label).toContain("REFERENCE PACKET:")
220
- expect(lowerPassA, fixture.label).toContain("reusable tokens/primitives")
221
- expect(lowerPassA, fixture.label).toContain("extend to new pages")
222
- expect(lowerPassA, fixture.label).toContain("missing overview content")
223
- expect(lowerPassB, fixture.label).toContain("pixel-perfectly")
224
- expect(lowerPassB, fixture.label).toContain("actual against reference")
225
- expect(lowerPassB, fixture.label).toContain("overview text is part of the target")
226
- }
227
- })
228
-
229
- test("#given reference packet evidence #when prompting reviewers #then sensitive content is redacted and annotations stay untrusted data", () => {
230
- for (const fixture of fixtures()) {
231
- const capture = sectionBetween(fixture.text, "## Step 2", "## Step 3")
232
- const passA = sectionBetween(fixture.text, "### Pass A", "### Pass B")
233
- const passB = sectionBetween(fixture.text, "### Pass B", "## Step 4")
234
- const combined = `${capture}\n${passA}\n${passB}`.toLowerCase()
235
-
236
- expect(combined, fixture.label).toContain("redact or omit secrets")
237
- expect(combined, fixture.label).toContain("credentials")
238
- expect(combined, fixture.label).toContain("tokens")
239
- expect(combined, fixture.label).toContain("auth headers")
240
- expect(combined, fixture.label).toContain("customer data")
241
- expect(combined, fixture.label).toContain("private messages")
242
- expect(combined, fixture.label).toContain("internal urls")
243
- expect(combined, fixture.label).toContain("other sensitive content")
244
- expect(combined, fixture.label).toContain("overview text")
245
- expect(combined, fixture.label).toContain("annotations")
246
- expect(combined, fixture.label).toContain("untrusted data")
247
- expect(combined, fixture.label).toContain("captured ui copy")
248
- expect(combined, fixture.label).toContain("comments")
249
- expect(combined, fixture.label).toContain("filenames")
250
- expect(combined, fixture.label).toContain("not reviewer instructions")
251
- expect(combined, fixture.label).toContain("never as instructions")
252
- }
253
- })
254
- })
255
-
256
- describe("visual-qa motion capture and slop-animation contract", () => {
257
- test("#given an animated or interactive surface #when capturing #then rest, mid-transition and settled frames are driven and captured", () => {
258
- for (const fixture of fixtures()) {
259
- const motion = sectionBetween(fixture.text, "### Motion and interaction capture", "## Step 3")
260
- const lower = motion.toLowerCase()
261
-
262
- expect(lower, fixture.label).toContain("rest")
263
- expect(lower, fixture.label).toContain("mid-transition")
264
- expect(lower, fixture.label).toContain("settled")
265
- expect(lower, fixture.label).toContain("hover")
266
- expect(lower, fixture.label).toContain("focus")
267
- expect(lower, fixture.label).toContain("click")
268
- expect(lower, fixture.label).toContain("scroll-triggered")
269
- expect(lower, fixture.label).toContain("load animation")
270
- }
271
- })
272
-
273
- test("#given animation-induced pixel diff #when judging fidelity #then it is never an excuse to dismiss a defect", () => {
274
- for (const fixture of fixtures()) {
275
- const motion = sectionBetween(fixture.text, "### Motion and interaction capture", "## Step 3")
276
- const lower = motion.toLowerCase()
277
-
278
- expect(lower, fixture.label).toContain("never a valid excuse")
279
- expect(lower, fixture.label).toContain("settled state to settled state")
280
- expect(lower, fixture.label).toContain("reference's own motion")
281
- }
282
- })
283
-
284
- test("#given meaningless motion #when reviewing #then Pass A flags slop animation, especially hover-without-action", () => {
285
- for (const fixture of fixtures()) {
286
- const passA = sectionBetween(fixture.text, "### Pass A", "### Pass B")
287
- const checkBlock = sectionBetween(passA, "CHECK EACH:", "OUTPUT:")
288
- const lower = checkBlock.toLowerCase()
289
-
290
- expect(lower, fixture.label).toContain("slop animation")
291
- expect(lower, fixture.label).toContain("non-interactive")
292
- expect(lower, fixture.label).toContain("no state change")
293
- expect(lower, fixture.label).toContain("hover-without-action")
294
- }
295
- })
296
- })
@@ -1,70 +0,0 @@
1
- import { readFile } from "node:fs/promises";
2
-
3
- import { describe, expect, it } from "vitest";
4
-
5
- const SKILL_URL = new URL("../skills/ulw-loop/SKILL.md", import.meta.url);
6
- const FULL_WORKFLOW_URL = new URL("../skills/ulw-loop/references/full-workflow.md", import.meta.url);
7
-
8
- function wordCount(text: string): number {
9
- return text.split(/\s+/).filter(Boolean).length;
10
- }
11
-
12
- describe("ulw-loop skill contract", () => {
13
- it("#given full workflow #when tier triage is inspected #then criteria scale by LIGHT/HEAVY with upgrade-only ratchet", async () => {
14
- // given
15
- const workflow = await readFile(FULL_WORKFLOW_URL, "utf8");
16
-
17
- // then
18
- expect(workflow).toMatch(/[Tt]ier triage/);
19
- expect(workflow).toMatch(/LIGHT/);
20
- expect(workflow).toMatch(/HEAVY/);
21
- expect(workflow).toMatch(/1-2 successCriteria/);
22
- expect(workflow).toMatch(/3\+ criteria|3\+ successCriteria/);
23
- expect(workflow).toMatch(/When unsure[^.]{0,30}HEAVY/);
24
- expect(workflow).toMatch(/never downgrade/i);
25
- });
26
-
27
- it("#given full workflow #when evidence rules are inspected #then tautological tests are rejected and the light quality gate is named", async () => {
28
- // given
29
- const workflow = await readFile(FULL_WORKFLOW_URL, "utf8");
30
-
31
- // then
32
- expect(workflow).toMatch(/mirrors its implementation/);
33
- expect(workflow).toMatch(/none-applicable/);
34
- });
35
-
36
- it("#given full workflow #when optimization work is planned #then speed and behavior evidence are required per attempt", async () => {
37
- // given
38
- const workflow = await readFile(FULL_WORKFLOW_URL, "utf8");
39
-
40
- // then
41
- expect(workflow).toMatch(/(?:optimization|performance) work[^.]+baseline speed[^.]+before/i);
42
- expect(workflow).toMatch(/baseline speed[^.]+behavior[^.]+regression/i);
43
- expect(workflow).toMatch(
44
- /(?:each|every) (?:try|attempt)[^.]+speed[^.]+(?:behavior|regression)[^.]+(?:keep|revert|iterate)/i,
45
- );
46
- });
47
-
48
- it("#given full workflow #when checkpoint guidance is inspected #then non-final and final criteria gates differ", async () => {
49
- // given
50
- const workflow = await readFile(FULL_WORKFLOW_URL, "utf8");
51
-
52
- // then
53
- expect(workflow).toMatch(/non-final aggregate goal[^.]+essential[^.]+pass/i);
54
- expect(workflow).toMatch(/non-essential criteria may remain pending/i);
55
- expect(workflow).toMatch(/final aggregate goal[^.]+every criterion across the whole plan/i);
56
- expect(workflow).toMatch(/final aggregate completion requires all criteria across the whole plan/i);
57
- expect(workflow).toMatch(/5 cycles on one goal without required criteria passing/i);
58
- });
59
-
60
- it("#given full workflow #when echo discipline is inspected #then the ultraqa class list is enumerated once and budgets hold", async () => {
61
- // given
62
- const workflow = await readFile(FULL_WORKFLOW_URL, "utf8");
63
- const skill = await readFile(SKILL_URL, "utf8");
64
-
65
- // then
66
- expect(workflow.match(/malformed input, prompt injection/g)?.length ?? 0).toBe(1);
67
- expect(wordCount(workflow)).toBeLessThanOrEqual(3697);
68
- expect(wordCount(skill)).toBeLessThanOrEqual(625);
69
- });
70
- });