oh-my-opencode 5.0.0-beta.62 → 5.0.0-beta.64

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 (249) hide show
  1. package/THIRD-PARTY-NOTICES.md +8 -6
  2. package/dist/cli/doctor/checks/browser-provider.d.ts +2 -0
  3. package/dist/cli/index.js +70 -41
  4. package/dist/cli-node/index.js +70 -41
  5. package/dist/config/schema/agent-names.d.ts +0 -1
  6. package/dist/config/schema/browser-automation.d.ts +0 -2
  7. package/dist/config/schema/oh-my-opencode-config.d.ts +0 -1
  8. package/dist/hooks/comment-checker/__fixtures__/download-probe.fixture.d.ts +1 -0
  9. package/dist/index.js +89 -588
  10. package/dist/oh-my-opencode.schema.json +4 -5
  11. package/dist/skills/ast-grep/SKILL.md +12 -0
  12. package/dist/skills/debugging/references/tools/playwright-cli.md +79 -161
  13. package/dist/skills/frontend/SKILL.md +1 -1
  14. package/dist/skills/frontend/references/design/aside.md +1 -1
  15. package/dist/skills/frontend/references/design/clone-from-url.md +1 -1
  16. package/dist/skills/frontend/scripts/perfection/lighthouse-audit.py +2 -2
  17. package/dist/skills/review-work/SKILL.md +1 -1
  18. package/dist/skills/ultimate-browsing/ATTRIBUTION.md +1 -82
  19. package/dist/skills/ultimate-browsing/SKILL.md +17 -29
  20. package/dist/skills/ultimate-browsing/engine/AGENTS.md +2 -1
  21. package/dist/skills/ultimate-browsing/engine/templates/package.json +2 -2
  22. package/dist/skills/ultimate-browsing/engine/templates/playwright_mobile_chrome.js +5 -2
  23. package/dist/skills/ultimate-browsing/engine/templates/playwright_real_chrome.js +8 -6
  24. package/dist/skills/ultimate-browsing/engine/tests/test_playwright_stealth.py +86 -0
  25. package/dist/skills/ultimate-browsing/engine/tests/test_playwright_templates.py +4 -7
  26. package/dist/skills/ultimate-browsing/references/chrome-stealth.md +86 -100
  27. package/dist/skills/ultimate-browsing/references/insane-search/README.md +9 -3
  28. package/dist/skills/ultimate-browsing/references/insane-search/playwright.md +16 -13
  29. package/dist/skills/ultimate-browsing/scripts/extract_cookies.py +3 -3
  30. package/dist/skills/ulw-execute/SKILL.md +1 -1
  31. package/dist/skills/ulw-plan/scripts/scaffold-plan.mjs +1 -1
  32. package/dist/skills/ulw-research/SKILL.md +3 -3
  33. package/dist/skills/visual-qa/AGENTS.md +1 -1
  34. package/dist/skills/visual-qa/SKILL.md +1 -1
  35. package/dist/skills/visual-qa/references/browser-setup.md +75 -0
  36. package/dist/tui.js +18 -11
  37. package/package.json +16 -23
  38. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  39. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +4 -2
  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/comment-checker/hooks/hooks.json +1 -1
  43. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  44. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  45. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  46. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  47. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  48. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  49. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  50. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  51. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  52. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  53. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  54. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  55. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  56. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  57. package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +2 -2
  58. package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +5 -4
  59. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  60. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  61. package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +1 -1
  62. package/packages/omo-codex/plugin/components/ulw-execute-continuation/directive.md +1 -1
  63. package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +1 -1
  64. package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
  65. package/packages/omo-codex/plugin/components/ulw-loop/CHANGELOG.md +13 -0
  66. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +5 -4
  67. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.d.ts +7 -5
  68. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-codex-validation.js +7 -2
  69. package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +7 -4
  70. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +931 -442
  71. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.d.ts +6 -0
  72. package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.js +20 -12
  73. package/packages/omo-codex/plugin/components/ulw-loop/dist/domain-types.d.ts +8 -0
  74. package/packages/omo-codex/plugin/components/ulw-loop/dist/driver-objective-ack.d.ts +4 -0
  75. package/packages/omo-codex/plugin/components/ulw-loop/dist/driver-objective-ack.js +18 -0
  76. package/packages/omo-codex/plugin/components/ulw-loop/dist/evidence-artifacts.d.ts +1 -0
  77. package/packages/omo-codex/plugin/components/ulw-loop/dist/evidence-artifacts.js +37 -0
  78. package/packages/omo-codex/plugin/components/ulw-loop/dist/evidence.d.ts +1 -0
  79. package/packages/omo-codex/plugin/components/ulw-loop/dist/evidence.js +24 -12
  80. package/packages/omo-codex/plugin/components/ulw-loop/dist/ledger.d.ts +4 -0
  81. package/packages/omo-codex/plugin/components/ulw-loop/dist/ledger.js +34 -0
  82. package/packages/omo-codex/plugin/components/ulw-loop/dist/paths.d.ts +1 -0
  83. package/packages/omo-codex/plugin/components/ulw-loop/dist/paths.js +6 -0
  84. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-commit.d.ts +19 -0
  85. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-commit.js +107 -0
  86. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-crud.d.ts +6 -2
  87. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-crud.js +54 -25
  88. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-goal-factory.d.ts +10 -3
  89. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-goal-factory.js +38 -31
  90. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.d.ts +9 -7
  91. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.js +105 -114
  92. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-log.d.ts +15 -0
  93. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-log.js +86 -0
  94. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.d.ts +5 -1
  95. package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.js +15 -3
  96. package/packages/omo-codex/plugin/components/ulw-loop/dist/review-blockers.js +11 -8
  97. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/factory.js +28 -12
  98. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/manifest.d.ts +335 -0
  99. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/manifest.js +109 -10
  100. package/packages/omo-codex/plugin/components/ulw-loop/dist/sdk/types.d.ts +3 -0
  101. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-budget-io.d.ts +5 -0
  102. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-budget-io.js +67 -0
  103. package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.js +8 -62
  104. package/packages/omo-codex/plugin/components/ulw-loop/dist/state-lock.d.ts +11 -1
  105. package/packages/omo-codex/plugin/components/ulw-loop/dist/state-lock.js +120 -88
  106. package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.d.ts +3 -2
  107. package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.js +18 -10
  108. package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-batch.js +4 -4
  109. package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-mutations.d.ts +3 -2
  110. package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-mutations.js +4 -4
  111. package/packages/omo-codex/plugin/components/ulw-loop/dist/steering-types.d.ts +9 -1
  112. package/packages/omo-codex/plugin/components/ulw-loop/dist/steering.d.ts +3 -2
  113. package/packages/omo-codex/plugin/components/ulw-loop/dist/steering.js +11 -11
  114. package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +8 -17
  115. package/packages/omo-codex/plugin/components/ulw-loop/dist/success-criteria-input.d.ts +9 -0
  116. package/packages/omo-codex/plugin/components/ulw-loop/dist/success-criteria-input.js +41 -0
  117. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +5 -5
  118. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  119. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +2 -2
  120. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-codex-validation.ts +15 -3
  121. package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +7 -4
  122. package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-snapshot.ts +26 -14
  123. package/packages/omo-codex/plugin/components/ulw-loop/src/domain-types.ts +8 -0
  124. package/packages/omo-codex/plugin/components/ulw-loop/src/driver-objective-ack.ts +20 -0
  125. package/packages/omo-codex/plugin/components/ulw-loop/src/evidence-artifacts.ts +45 -0
  126. package/packages/omo-codex/plugin/components/ulw-loop/src/evidence.ts +23 -16
  127. package/packages/omo-codex/plugin/components/ulw-loop/src/ledger.ts +33 -0
  128. package/packages/omo-codex/plugin/components/ulw-loop/src/paths.ts +7 -0
  129. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-commit.ts +124 -0
  130. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-crud.ts +67 -54
  131. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-goal-factory.ts +63 -31
  132. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-io.ts +126 -140
  133. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-log.ts +84 -0
  134. package/packages/omo-codex/plugin/components/ulw-loop/src/plan-missing-recovery.ts +26 -3
  135. package/packages/omo-codex/plugin/components/ulw-loop/src/review-blockers.ts +11 -7
  136. package/packages/omo-codex/plugin/components/ulw-loop/src/sdk/factory.ts +47 -12
  137. package/packages/omo-codex/plugin/components/ulw-loop/src/sdk/manifest.ts +124 -10
  138. package/packages/omo-codex/plugin/components/ulw-loop/src/sdk/types.ts +3 -0
  139. package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-budget-io.ts +59 -0
  140. package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts +8 -59
  141. package/packages/omo-codex/plugin/components/ulw-loop/src/state-lock.ts +129 -111
  142. package/packages/omo-codex/plugin/components/ulw-loop/src/status-next-actions.ts +19 -10
  143. package/packages/omo-codex/plugin/components/ulw-loop/src/steering-batch.ts +4 -4
  144. package/packages/omo-codex/plugin/components/ulw-loop/src/steering-mutations.ts +5 -4
  145. package/packages/omo-codex/plugin/components/ulw-loop/src/steering-types.ts +13 -1
  146. package/packages/omo-codex/plugin/components/ulw-loop/src/steering.ts +12 -10
  147. package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +8 -16
  148. package/packages/omo-codex/plugin/components/ulw-loop/src/success-criteria-input.ts +56 -0
  149. package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-snapshot.test.ts +37 -1
  150. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/barrier.ts +13 -0
  151. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-child.ts +65 -0
  152. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-cli-qa.mjs +76 -0
  153. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-crash.ts +23 -0
  154. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-mutations.mjs +58 -0
  155. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/commit-writer.ts +65 -0
  156. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/lease-clock.ts +23 -0
  157. package/packages/omo-codex/plugin/components/ulw-loop/test/plan-commit-log.test.ts +252 -0
  158. package/packages/omo-codex/plugin/components/ulw-loop/test/plan-commit-recovery.test.ts +190 -0
  159. package/packages/omo-codex/plugin/components/ulw-loop/test/plan-io.test.ts +50 -35
  160. package/packages/omo-codex/plugin/components/ulw-loop/test/plan-log-newest.test.ts +72 -0
  161. package/packages/omo-codex/plugin/components/ulw-loop/test/sdk-completed-plan.test.ts +43 -0
  162. package/packages/omo-codex/plugin/components/ulw-loop/test/sdk-driver-states.test.ts +33 -4
  163. package/packages/omo-codex/plugin/components/ulw-loop/test/sdk-dx.test.ts +219 -0
  164. package/packages/omo-codex/plugin/components/ulw-loop/test/sdk-session-context.test.ts +21 -0
  165. package/packages/omo-codex/plugin/components/ulw-loop/test/state-lock.test.ts +92 -0
  166. package/packages/omo-codex/plugin/components/ulw-loop/test/status-next-actions-surface.test.ts +133 -0
  167. package/packages/omo-codex/plugin/components/ulw-loop/test/steering-batch.test.ts +12 -12
  168. package/packages/omo-codex/plugin/components/ulw-loop/test/steering.test.ts +4 -1
  169. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  170. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  171. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  172. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  173. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  174. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  175. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  176. package/packages/omo-codex/plugin/hooks/post-tool-use-recording-spawn-admission.json +1 -1
  177. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  178. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  179. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  180. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  181. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  182. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  183. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  184. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
  185. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  186. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  187. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  188. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  189. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  190. package/packages/omo-codex/plugin/package-lock.json +12 -12
  191. package/packages/omo-codex/plugin/package.json +1 -1
  192. package/packages/omo-codex/plugin/scripts/sync-skills.d.mts +4 -0
  193. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +15 -7
  194. package/packages/omo-codex/plugin/skills/ast-grep/SKILL.md +12 -0
  195. package/packages/omo-codex/plugin/skills/debugging/references/tools/playwright-cli.md +79 -161
  196. package/packages/omo-codex/plugin/skills/frontend/SKILL.md +1 -1
  197. package/packages/omo-codex/plugin/skills/frontend/references/design/aside.md +1 -1
  198. package/packages/omo-codex/plugin/skills/frontend/references/design/clone-from-url.md +1 -1
  199. package/packages/omo-codex/plugin/skills/frontend/scripts/perfection/lighthouse-audit.py +2 -2
  200. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +1 -1
  201. package/packages/omo-codex/plugin/skills/ultimate-browsing/ATTRIBUTION.md +1 -82
  202. package/packages/omo-codex/plugin/skills/ultimate-browsing/SKILL.md +17 -29
  203. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/AGENTS.md +2 -1
  204. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/templates/package.json +2 -2
  205. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/templates/playwright_mobile_chrome.js +5 -2
  206. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/templates/playwright_real_chrome.js +8 -6
  207. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_stealth.py +86 -0
  208. package/packages/omo-codex/plugin/skills/ultimate-browsing/engine/tests/test_playwright_templates.py +4 -7
  209. package/packages/omo-codex/plugin/skills/ultimate-browsing/references/chrome-stealth.md +86 -100
  210. package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +9 -3
  211. package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/playwright.md +16 -13
  212. package/packages/omo-codex/plugin/skills/ultimate-browsing/scripts/extract_cookies.py +3 -3
  213. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +5 -4
  214. package/packages/omo-codex/plugin/skills/ulw-execute/SKILL.md +1 -1
  215. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +2 -2
  216. package/packages/omo-codex/plugin/skills/ulw-research/SKILL.md +3 -3
  217. package/packages/omo-codex/plugin/skills/visual-qa/AGENTS.md +1 -1
  218. package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +1 -1
  219. package/packages/omo-codex/plugin/skills/visual-qa/references/browser-setup.md +75 -0
  220. package/packages/omo-codex/scripts/install-dist/install-local.mjs +6 -4
  221. package/packages/prompts-core/prompts/ultrawork/codex.md +5 -4
  222. package/packages/shared-skills/skills/ast-grep/SKILL.md +12 -0
  223. package/packages/shared-skills/skills/debugging/references/tools/playwright-cli.md +79 -161
  224. package/packages/shared-skills/skills/frontend/SKILL.md +1 -1
  225. package/packages/shared-skills/skills/frontend/references/design/aside.md +1 -1
  226. package/packages/shared-skills/skills/frontend/references/design/clone-from-url.md +1 -1
  227. package/packages/shared-skills/skills/frontend/scripts/perfection/lighthouse-audit.py +2 -2
  228. package/packages/shared-skills/skills/review-work/SKILL.md +1 -1
  229. package/packages/shared-skills/skills/ultimate-browsing/ATTRIBUTION.md +1 -82
  230. package/packages/shared-skills/skills/ultimate-browsing/SKILL.md +17 -29
  231. package/packages/shared-skills/skills/ultimate-browsing/engine/AGENTS.md +2 -1
  232. package/packages/shared-skills/skills/ultimate-browsing/engine/templates/package.json +2 -2
  233. package/packages/shared-skills/skills/ultimate-browsing/engine/templates/playwright_mobile_chrome.js +5 -2
  234. package/packages/shared-skills/skills/ultimate-browsing/engine/templates/playwright_real_chrome.js +8 -6
  235. package/packages/shared-skills/skills/ultimate-browsing/engine/tests/test_playwright_stealth.py +86 -0
  236. package/packages/shared-skills/skills/ultimate-browsing/engine/tests/test_playwright_templates.py +4 -7
  237. package/packages/shared-skills/skills/ultimate-browsing/references/chrome-stealth.md +86 -100
  238. package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +9 -3
  239. package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/playwright.md +16 -13
  240. package/packages/shared-skills/skills/ultimate-browsing/scripts/extract_cookies.py +3 -3
  241. package/packages/shared-skills/skills/ulw-execute/SKILL.md +1 -1
  242. package/packages/shared-skills/skills/ulw-plan/scripts/scaffold-plan.mjs +1 -1
  243. package/packages/shared-skills/skills/ulw-research/SKILL.md +3 -3
  244. package/packages/shared-skills/skills/visual-qa/AGENTS.md +1 -1
  245. package/packages/shared-skills/skills/visual-qa/SKILL.md +1 -1
  246. package/packages/shared-skills/skills/visual-qa/references/browser-setup.md +75 -0
  247. package/dist/skills/visual-qa/references/agent-browser-setup.md +0 -45
  248. package/packages/omo-codex/plugin/skills/visual-qa/references/agent-browser-setup.md +0 -45
  249. package/packages/shared-skills/skills/visual-qa/references/agent-browser-setup.md +0 -45
@@ -0,0 +1,33 @@
1
+ import { join } from "node:path";
2
+ import { type UlwLoopScope, ulwLoopDir } from "./paths.js";
3
+ import { readOptional, readRecords, reconcilePlan } from "./plan-log.js";
4
+ import type { UlwLoopLedgerEntry } from "./types.js";
5
+
6
+ export function readLedgerAt(dir: string): UlwLoopLedgerEntry[] {
7
+ const entries = new Map<string, UlwLoopLedgerEntry>();
8
+ const lines = (readOptional(join(dir, "ledger.jsonl")) ?? "").split(/\r?\n/);
9
+ for (const [index, line] of lines.entries()) {
10
+ if (line.trim().length === 0) continue;
11
+ try {
12
+ const entry: UlwLoopLedgerEntry = JSON.parse(line);
13
+ entry.revision ??= 0;
14
+ entry.id ??= `legacy-${index + 1}`;
15
+ entries.set(entry.id, entry);
16
+ } catch (error) {
17
+ if (!(error instanceof SyntaxError)) throw error;
18
+ }
19
+ }
20
+ for (const record of readRecords(dir))
21
+ for (const [seq, entry] of record.ledger.entries()) {
22
+ const id = entry.id ?? `${record.revision}-${seq}`;
23
+ entries.set(id, { ...entry, revision: record.revision, id });
24
+ }
25
+ const reset = reconcilePlan(dir)?.ledgerResetRevision ?? 0;
26
+ const sequence = (entry: UlwLoopLedgerEntry) => Number(entry.id?.split("-").at(-1) ?? 0);
27
+ return [...entries.values()]
28
+ .filter((entry) => (entry.revision ?? 0) >= reset)
29
+ .sort((a, b) => (a.revision ?? 0) - (b.revision ?? 0) || sequence(a) - sequence(b));
30
+ }
31
+ export function readLedger(repoRoot: string, scope?: UlwLoopScope): UlwLoopLedgerEntry[] {
32
+ return readLedgerAt(ulwLoopDir(repoRoot, scope));
33
+ }
@@ -85,6 +85,13 @@ export function repoRelative(absolutePath: string, repoRoot: string): string {
85
85
  return absolutePath.split("\\").join("/");
86
86
  }
87
87
 
88
+ // The plan-level evidence root stays fixed for the whole run, unlike the per-goal attempt dir, so
89
+ // long lanes that span goals have one stable place to write artifacts.
90
+ export function ulwLoopEvidenceRoot(scope?: UlwLoopScope): string {
91
+ const sessionId = normalizeUlwLoopSessionId(scope?.sessionId);
92
+ return sessionId === null ? ".omo/evidence" : `.omo/evidence/ulw/${sessionId}`;
93
+ }
94
+
88
95
  // Both the status --json emitter and the checkpoint enforcement resolve the attempt dir through
89
96
  // this function from the scope alone; a second resolution path (env, a literal placeholder)
90
97
  // would let the gate reject its own advertised directory.
@@ -0,0 +1,124 @@
1
+ import { AsyncLocalStorage } from "node:async_hooks";
2
+ import { randomUUID } from "node:crypto";
3
+ import { linkSync, mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
4
+ import { rename, writeFile } from "node:fs/promises";
5
+ import { join } from "node:path";
6
+ import { readLedgerAt } from "./ledger.js";
7
+ import { type UlwLoopScope, ulwLoopDir, ulwLoopStateLockPath } from "./paths.js";
8
+ import { assertStateLockOwned, migrationEntries } from "./plan-io.js";
9
+ import { hasCode, type PlanCommitRecord, readOptional, readRecords, reconcilePlan } from "./plan-log.js";
10
+ import { UlwLoopError, type UlwLoopLedgerEntry, type UlwLoopPlan } from "./types.js";
11
+
12
+ interface CommitHooks {
13
+ readonly beforeMutation?: () => void | Promise<void>;
14
+ readonly beforeCommit?: () => void | Promise<void>;
15
+ readonly afterCommit?: () => void | Promise<void>;
16
+ readonly link?: (source: string, destination: string) => void;
17
+ readonly writeView?: (path: string, content: string) => Promise<void>;
18
+ }
19
+ const hooks = new AsyncLocalStorage<CommitHooks>();
20
+ export function withCommitHooks<T>(value: CommitHooks, fn: () => Promise<T>): Promise<T> {
21
+ return hooks.run(value, fn);
22
+ }
23
+
24
+ export async function beforePlanMutation(): Promise<void> {
25
+ await hooks.getStore()?.beforeMutation?.();
26
+ }
27
+ export async function writeViewFile(path: string, content: string): Promise<void> {
28
+ await (hooks.getStore()?.writeView ?? writeFile)(path, content);
29
+ }
30
+
31
+ // link is the sole commit point; no copy fallback (Node copyFile is not atomic).
32
+ // A resumed zombie can publish first, but never overwrite an existing revision.
33
+ // Logical audit order is (revision, sequence), not materialization completion time.
34
+ export async function commit(
35
+ repoRoot: string,
36
+ scope: UlwLoopScope | undefined,
37
+ mutation: {
38
+ readonly plan: UlwLoopPlan;
39
+ readonly entries: readonly UlwLoopLedgerEntry[];
40
+ },
41
+ ): Promise<void> {
42
+ const { plan } = mutation;
43
+ const dir = ulwLoopDir(repoRoot, scope);
44
+ assertStateLockOwned(ulwLoopStateLockPath(repoRoot, scope));
45
+ await hooks.getStore()?.beforeCommit?.();
46
+ const next = (plan.revision ?? 0) + 1;
47
+ const brief = plan.brief ?? readOptional(join(dir, "brief.md")) ?? "";
48
+ const entries = [...migrationEntries(plan), ...mutation.entries].map((entry, seq) => ({
49
+ ...entry,
50
+ revision: next,
51
+ id: `${next}-${seq}`,
52
+ }));
53
+ const record: PlanCommitRecord = {
54
+ version: 1,
55
+ revision: next,
56
+ plan: { ...plan, revision: next, brief },
57
+ ledger: entries,
58
+ };
59
+ mkdirSync(join(dir, "revisions"), { recursive: true });
60
+ mkdirSync(join(dir, "tmp"), { recursive: true });
61
+ const temp = join(dir, "tmp", `${process.pid}-${randomUUID()}.json`);
62
+ try {
63
+ writeFileSync(temp, `${JSON.stringify(record)}\n`, { flag: "wx" });
64
+ try {
65
+ (hooks.getStore()?.link ?? linkSync)(temp, join(dir, "revisions", `${String(next).padStart(8, "0")}.json`));
66
+ } catch (error) {
67
+ if (hasCode(error, "EEXIST"))
68
+ throw new UlwLoopError("Another writer published the read revision.", "ULW_LOOP_PUBLISH_CONFLICT", {
69
+ cause: error,
70
+ });
71
+ if (["EPERM", "ENOTSUP", "EXDEV"].some((code) => hasCode(error, code)))
72
+ throw new UlwLoopError(
73
+ "Immutable ulw-loop publication requires atomic hard links.",
74
+ "ULW_LOOP_PUBLISH_UNSUPPORTED_FS",
75
+ { cause: error },
76
+ );
77
+ throw error;
78
+ }
79
+ } finally {
80
+ rmSync(temp, { force: true });
81
+ }
82
+ plan.revision = next;
83
+ plan.brief = brief;
84
+ for (const [index, entry] of mutation.entries.entries())
85
+ Object.assign(entry, entries[index + entries.length - mutation.entries.length]);
86
+ await hooks.getStore()?.afterCommit?.();
87
+ await materialize(dir);
88
+ }
89
+
90
+ function viewContents(dir: string): [string, string][] {
91
+ const plan = reconcilePlan(dir);
92
+ if (plan === undefined || readRecords(dir).length === 0) return [];
93
+ const ledger = readLedgerAt(dir);
94
+ const views: [string, string][] = [
95
+ ["goals.json", `${JSON.stringify(plan, null, 2)}\n`],
96
+ ["ledger.jsonl", ledger.length === 0 ? "" : `${ledger.map((entry) => JSON.stringify(entry)).join("\n")}\n`],
97
+ ["brief.md", plan.brief ?? readOptional(join(dir, "brief.md")) ?? ""],
98
+ ];
99
+ return views.filter(([name, content]) => readOptional(join(dir, name)) !== content);
100
+ }
101
+ export function materializeSync(dir: string): void {
102
+ for (const [name, content] of viewContents(dir)) {
103
+ mkdirSync(join(dir, "tmp"), { recursive: true });
104
+ const temp = join(dir, "tmp", `${process.pid}-${randomUUID()}-${name}`);
105
+ try {
106
+ writeFileSync(temp, content, { flag: "wx" });
107
+ renameSync(temp, join(dir, name));
108
+ } finally {
109
+ rmSync(temp, { force: true });
110
+ }
111
+ }
112
+ }
113
+ export async function materialize(dir: string): Promise<void> {
114
+ for (const [name, content] of viewContents(dir)) {
115
+ mkdirSync(join(dir, "tmp"), { recursive: true });
116
+ const temp = join(dir, "tmp", `${process.pid}-${randomUUID()}-${name}`);
117
+ try {
118
+ await writeViewFile(temp, content);
119
+ await rename(temp, join(dir, name));
120
+ } finally {
121
+ rmSync(temp, { force: true });
122
+ }
123
+ }
124
+ }
@@ -1,24 +1,27 @@
1
- import { existsSync } from "node:fs";
2
- import { mkdir, writeFile } from "node:fs/promises";
3
-
4
1
  import { aggregateCodexObjectiveForScope, isUlwLoopDone } from "./goal-status.js";
5
2
  import {
6
3
  type UlwLoopScope,
7
- ulwLoopBriefPath,
8
4
  ulwLoopBriefRelativePath,
9
- ulwLoopDir,
10
- ulwLoopGoalsPath,
11
5
  ulwLoopGoalsRelativePath,
12
- ulwLoopLedgerPath,
13
6
  ulwLoopLedgerRelativePath,
14
7
  } from "./paths.js";
8
+ import { beforePlanMutation, commit } from "./plan-commit.js";
15
9
  import { appendGoalToPlan, deriveGoalCandidates, makeGoal } from "./plan-goal-factory.js";
16
- import { appendLedger, readUlwLoopPlan, withUlwLoopMutationLock, writePlan } from "./plan-io.js";
17
- import type { UlwLoopCodexGoalMode, UlwLoopItem, UlwLoopPlan, UlwLoopSuccessCriterion } from "./types.js";
10
+ import { planExists, readUlwLoopPlan, withUlwLoopMutationLock } from "./plan-io.js";
11
+ import type { SuccessCriterionInput } from "./success-criteria-input.js";
12
+ import type { UlwLoopToolkitSurface } from "./surface.js";
13
+ import type {
14
+ UlwLoopCodexGoalMode,
15
+ UlwLoopItem,
16
+ UlwLoopLedgerEntry,
17
+ UlwLoopPlan,
18
+ UlwLoopSuccessCriterion,
19
+ } from "./types.js";
18
20
  import { iso, UlwLoopError } from "./types.js";
19
21
  import { parseValidationBatches } from "./validation-batch.js";
20
22
 
21
23
  export { deriveGoalCandidates, seedDefaultSuccessCriteria } from "./plan-goal-factory.js";
24
+ export type { SuccessCriterionInput } from "./success-criteria-input.js";
22
25
 
23
26
  export type UlwLoopPlanSummary = {
24
27
  readonly total: number;
@@ -60,11 +63,19 @@ export async function createUlwLoopPlan(
60
63
  repoRoot: string,
61
64
  args: { brief: string; codexGoalMode?: UlwLoopCodexGoalMode; force?: boolean; validationBatchesJson?: string },
62
65
  scope?: UlwLoopScope,
66
+ surface: UlwLoopToolkitSurface = "lazycodex",
63
67
  ): Promise<UlwLoopPlan> {
64
68
  return withUlwLoopMutationLock(repoRoot, scope, async () => {
65
- if (!args.force && existsSync(ulwLoopGoalsPath(repoRoot, scope))) {
66
- const existing = await readUlwLoopPlan(repoRoot, scope);
67
- if (isUlwLoopDone(existing)) throw completedPlanExistsError(scope);
69
+ let existing: UlwLoopPlan | undefined;
70
+ if (planExists(repoRoot, scope)) {
71
+ try {
72
+ existing = await readUlwLoopPlan(repoRoot, scope);
73
+ } catch (error) {
74
+ if (!args.force) throw error;
75
+ }
76
+ }
77
+ if (!args.force && existing !== undefined) {
78
+ if (isUlwLoopDone(existing)) throw completedPlanExistsError(scope, surface);
68
79
  throw new UlwLoopError(
69
80
  `Refusing to overwrite existing ${ulwLoopGoalsRelativePath(scope)}; pass --force to recreate it.`,
70
81
  "ULW_LOOP_PLAN_EXISTS",
@@ -72,10 +83,13 @@ export async function createUlwLoopPlan(
72
83
  }
73
84
  const now = iso();
74
85
  const goals = deriveGoalCandidates(args.brief).map((goal, index) =>
75
- makeGoal(goal.title, goal.objective, index, now),
86
+ makeGoal(goal.title, goal.objective, index, now, { surface }),
76
87
  );
77
88
  const plan: UlwLoopPlan = {
78
89
  version: 1,
90
+ revision: existing?.revision ?? 0,
91
+ ledgerResetRevision: (existing?.revision ?? 0) + 1,
92
+ brief: args.brief.endsWith("\n") ? args.brief : `${args.brief}\n`,
79
93
  evidenceLayoutVersion: 2,
80
94
  createdAt: now,
81
95
  updatedAt: now,
@@ -88,29 +102,27 @@ export async function createUlwLoopPlan(
88
102
  const validationBatches = await parseValidationBatches(args.validationBatchesJson, goals);
89
103
  if (validationBatches !== undefined) plan.validationBatches = validationBatches;
90
104
  if (plan.codexGoalMode === "aggregate") plan.codexObjective = aggregateCodexObjectiveForScope(scope);
91
- await mkdir(ulwLoopDir(repoRoot, scope), { recursive: true });
92
- await writeFile(
93
- ulwLoopBriefPath(repoRoot, scope),
94
- args.brief.endsWith("\n") ? args.brief : `${args.brief}\n`,
95
- "utf8",
96
- );
97
- await writePlan(repoRoot, plan, scope);
98
- await writeFile(ulwLoopLedgerPath(repoRoot, scope), "", "utf8");
99
- await appendLedger(
100
- repoRoot,
101
- { at: now, kind: "plan_created", message: `${goals.length} goal(s) created` },
102
- scope,
103
- );
105
+ await beforePlanMutation();
106
+ await commit(repoRoot, scope, {
107
+ plan,
108
+ entries: [{ at: now, kind: "plan_created", message: `${goals.length} goal(s) created` }],
109
+ });
104
110
  return plan;
105
111
  });
106
112
  }
107
113
 
108
- function completedPlanExistsError(scope?: UlwLoopScope): UlwLoopError {
114
+ function completedPlanExistsError(scope: UlwLoopScope | undefined, surface: UlwLoopToolkitSurface): UlwLoopError {
109
115
  return new UlwLoopError(
110
116
  [
111
117
  `Existing ulw-loop aggregate is already complete at ${ulwLoopGoalsRelativePath(scope)}.`,
112
- "Start a new run with `omo-agent-toolkit ulw-loop create-goals --session-id <new-id> ...` to isolate fresh state.",
113
- "Use --force only when you intentionally want to overwrite the completed evidence.",
118
+ ...(surface === "omo-senpi"
119
+ ? [
120
+ "Start a new run under a fresh session id (a new senpi session) or call agentToolkit.createGoals({ brief, force: true }) to recreate.",
121
+ ]
122
+ : [
123
+ "Start a new run with `omo-agent-toolkit ulw-loop create-goals --session-id <new-id> ...` to isolate fresh state.",
124
+ "Use --force only when you intentionally want to overwrite the completed evidence.",
125
+ ]),
114
126
  ].join(" "),
115
127
  "ULW_LOOP_PLAN_EXISTS_COMPLETE",
116
128
  );
@@ -118,19 +130,21 @@ function completedPlanExistsError(scope?: UlwLoopScope): UlwLoopError {
118
130
 
119
131
  export async function addUlwLoopGoal(
120
132
  repoRoot: string,
121
- args: { title: string; objective: string },
133
+ args: { title: string; objective: string; successCriteria?: readonly SuccessCriterionInput[] },
122
134
  scope?: UlwLoopScope,
135
+ surface: UlwLoopToolkitSurface = "lazycodex",
123
136
  ): Promise<{ plan: UlwLoopPlan; goal: UlwLoopItem }> {
124
137
  return withUlwLoopMutationLock(repoRoot, scope, async () => {
125
138
  const plan = await readUlwLoopPlan(repoRoot, scope);
126
139
  const now = iso();
127
- const goal = appendGoalToPlan(plan, args.title, args.objective, now);
128
- await writePlan(repoRoot, plan, scope);
129
- await appendLedger(
130
- repoRoot,
131
- { at: now, kind: "goal_added", goalId: goal.id, status: goal.status, message: goal.title },
132
- scope,
133
- );
140
+ const goal = appendGoalToPlan(plan, args.title, args.objective, now, {
141
+ surface,
142
+ ...(args.successCriteria === undefined ? {} : { successCriteria: args.successCriteria }),
143
+ });
144
+ await commit(repoRoot, scope, {
145
+ plan,
146
+ entries: [{ at: now, kind: "goal_added", goalId: goal.id, status: goal.status, message: goal.title }],
147
+ });
134
148
  return { plan, goal };
135
149
  });
136
150
  }
@@ -146,21 +160,18 @@ export async function startNextUlwLoop(
146
160
  if (plan.aggregateCompletion?.status === "complete") return { done: true, plan };
147
161
  const existing = plan.goals.find((goal) => goal.status === "in_progress" && isScheduleEligible(goal));
148
162
  if (existing) return { plan, goal: existing, resumed: true };
163
+ const entries: UlwLoopLedgerEntry[] = [];
149
164
  let next = plan.goals.find((goal) => goal.status === "pending" && isScheduleEligible(goal));
150
165
  if (!next && args.retryFailed) {
151
166
  next = plan.goals.find((goal) => goal.status === "failed" && !goal.nonRetriable && isScheduleEligible(goal));
152
167
  if (next)
153
- await appendLedger(
154
- repoRoot,
155
- {
156
- at: now,
157
- kind: "goal_retried",
158
- goalId: next.id,
159
- status: "pending",
160
- ...(next.failureReason ? { message: next.failureReason } : {}),
161
- },
162
- scope,
163
- );
168
+ entries.push({
169
+ at: now,
170
+ kind: "goal_retried",
171
+ goalId: next.id,
172
+ status: "pending",
173
+ ...(next.failureReason ? { message: next.failureReason } : {}),
174
+ });
164
175
  }
165
176
  if (!next) return { done: true, plan };
166
177
  next.status = "in_progress";
@@ -170,12 +181,14 @@ export async function startNextUlwLoop(
170
181
  next.updatedAt = now;
171
182
  plan.activeGoalId = next.id;
172
183
  plan.updatedAt = now;
173
- await writePlan(repoRoot, plan, scope);
174
- await appendLedger(
175
- repoRoot,
176
- { at: now, kind: "goal_started", goalId: next.id, status: next.status, message: `Attempt ${next.attempt}` },
177
- scope,
178
- );
184
+ entries.push({
185
+ at: now,
186
+ kind: "goal_started",
187
+ goalId: next.id,
188
+ status: next.status,
189
+ message: `Attempt ${next.attempt}`,
190
+ });
191
+ await commit(repoRoot, scope, { plan, entries });
179
192
  return { plan, goal: next, resumed: false };
180
193
  });
181
194
  }
@@ -1,6 +1,14 @@
1
+ import { criteriaFromInput, criterionId, type SuccessCriterionInput } from "./success-criteria-input.js";
2
+ import type { UlwLoopToolkitSurface } from "./surface.js";
1
3
  import type { UlwLoopItem, UlwLoopPlan, UlwLoopSuccessCriterion } from "./types.js";
2
4
  import { UlwLoopError } from "./types.js";
3
5
 
6
+ export interface GoalSeedOptions {
7
+ readonly goalId?: string;
8
+ readonly surface?: UlwLoopToolkitSurface;
9
+ readonly successCriteria?: readonly SuccessCriterionInput[];
10
+ }
11
+
4
12
  function cleanLine(line: string): string {
5
13
  return line.replace(/^\s*(?:[-*+]\s+|\d+[.)]\s+)/, "").trim();
6
14
  }
@@ -38,40 +46,44 @@ function truncateObjective(objective: string): string {
38
46
  return objective.length > 80 ? `${objective.slice(0, 77).trimEnd()}...` : objective;
39
47
  }
40
48
 
41
- export function seedDefaultSuccessCriteria(goalIndex: number, objective: string): UlwLoopSuccessCriterion[] {
49
+ // The placeholder must name the exact call that replaces it on the surface the plan is driven from,
50
+ // so a goal seeded without criteria costs one documented call per criterion instead of a doc hunt.
51
+ function replaceVia(goalId: string, id: string, surface: UlwLoopToolkitSurface): string {
52
+ return surface === "omo-senpi"
53
+ ? `Replace via agentToolkit.steer({ kind: "revise_criterion", source: "finding", goalId: "${goalId}", criterionId: "${id}", scenario, expectedEvidence, evidence, rationale }) (or pass successCriteria to addGoal)`
54
+ : `Replace via omo-agent-toolkit ulw-loop steer --kind revise_criterion --goal-id ${goalId} --criterion-id ${id} --scenario "<scenario>" --expected-evidence "<proof>" --evidence "<why>" --rationale "<why>"`;
55
+ }
56
+
57
+ export function seedDefaultSuccessCriteria(
58
+ goalIndex: number,
59
+ objective: string,
60
+ options: Pick<GoalSeedOptions, "goalId" | "surface"> = {},
61
+ ): UlwLoopSuccessCriterion[] {
42
62
  const subject = truncateObjective(normalizeObjective(objective) || `Goal ${goalIndex + 1}`);
63
+ const goalId = options.goalId ?? `G${String(goalIndex + 1).padStart(3, "0")}`;
64
+ const surface = options.surface ?? "lazycodex";
43
65
  const rows = [
66
+ ["happy", `happy path for: ${subject}`, `observable happy-path proof for goal ${goalIndex + 1}`, true],
67
+ ["edge", "edge case (boundary/empty/malformed)", `boundary or malformed-input proof for: ${subject}`, true],
44
68
  [
45
- "C001",
46
- "happy",
47
- `happy path for: ${subject}`,
48
- `Replace via revise_criterion with observable happy-path proof for goal ${goalIndex + 1}.`,
49
- true,
50
- ],
51
- [
52
- "C002",
53
- "edge",
54
- "edge case (boundary/empty/malformed)",
55
- `Replace via revise_criterion with boundary or malformed-input proof for: ${subject}.`,
56
- true,
57
- ],
58
- [
59
- "C003",
60
69
  "regression",
61
70
  "regression: adjacent surface still works",
62
- `Replace via revise_criterion with regression proof for neighboring behavior after: ${subject}.`,
71
+ `regression proof for neighboring behavior after: ${subject}`,
63
72
  false,
64
73
  ],
65
74
  ] as const;
66
- return rows.map(([id, userModel, scenario, expectedEvidence, essential]) => ({
67
- id,
68
- scenario,
69
- userModel,
70
- expectedEvidence,
71
- essential,
72
- capturedEvidence: null,
73
- status: "pending",
74
- }));
75
+ return rows.map(([userModel, scenario, proof, essential], index) => {
76
+ const id = criterionId(index);
77
+ return {
78
+ id,
79
+ scenario,
80
+ userModel,
81
+ expectedEvidence: `${replaceVia(goalId, id, surface)} with ${proof}.`,
82
+ essential,
83
+ capturedEvidence: null,
84
+ status: "pending",
85
+ };
86
+ });
75
87
  }
76
88
 
77
89
  export function deriveGoalCandidates(brief: string): Array<{ title: string; objective: string }> {
@@ -101,23 +113,43 @@ export function deriveGoalCandidates(brief: string): Array<{ title: string; obje
101
113
  }));
102
114
  }
103
115
 
104
- export function makeGoal(title: string, objective: string, index: number, now: string): UlwLoopItem {
116
+ export function makeGoal(
117
+ title: string,
118
+ objective: string,
119
+ index: number,
120
+ now: string,
121
+ options: GoalSeedOptions = {},
122
+ ): UlwLoopItem {
105
123
  const cleanTitle = assertNonEmpty(title, "title");
106
124
  const cleanObjective = assertNonEmpty(objective, "objective");
125
+ const id = normalizeGoalId(cleanTitle, index);
126
+ const successCriteria =
127
+ options.successCriteria === undefined
128
+ ? seedDefaultSuccessCriteria(index, cleanObjective, {
129
+ goalId: id,
130
+ ...(options.surface === undefined ? {} : { surface: options.surface }),
131
+ })
132
+ : criteriaFromInput(options.successCriteria);
107
133
  return {
108
- id: normalizeGoalId(cleanTitle, index),
134
+ id,
109
135
  title: cleanTitle,
110
136
  objective: cleanObjective,
111
137
  status: "pending",
112
- successCriteria: seedDefaultSuccessCriteria(index, cleanObjective),
138
+ successCriteria,
113
139
  attempt: 0,
114
140
  createdAt: now,
115
141
  updatedAt: now,
116
142
  };
117
143
  }
118
144
 
119
- export function appendGoalToPlan(plan: UlwLoopPlan, title: string, objective: string, now: string): UlwLoopItem {
120
- const goal = makeGoal(title, objective, plan.goals.length, now);
145
+ export function appendGoalToPlan(
146
+ plan: UlwLoopPlan,
147
+ title: string,
148
+ objective: string,
149
+ now: string,
150
+ options: GoalSeedOptions = {},
151
+ ): UlwLoopItem {
152
+ const goal = makeGoal(title, objective, plan.goals.length, now, options);
121
153
  plan.goals.push(goal);
122
154
  plan.updatedAt = now;
123
155
  return goal;