oh-my-opencode 4.18.1 → 4.19.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 (217) hide show
  1. package/.agents/skills/codex-qa/scripts/lsp-e2e.sh +39 -16
  2. package/.agents/skills/opencode-qa/scripts/lsp-e2e.sh +111 -28
  3. package/README.ja.md +2 -2
  4. package/README.ko.md +2 -2
  5. package/README.md +4 -4
  6. package/README.ru.md +2 -2
  7. package/README.zh-cn.md +2 -2
  8. package/bin/AGENTS.md +33 -0
  9. package/dist/agents/atlas/agent.d.ts +6 -5
  10. package/dist/agents/sisyphus/index.d.ts +5 -0
  11. package/dist/agents/sisyphus/kimi-k3.d.ts +19 -0
  12. package/dist/agents/sisyphus-agent-factory.d.ts +1 -1
  13. package/dist/agents/sisyphus-junior/agent.d.ts +8 -4
  14. package/dist/agents/sisyphus-junior/index.d.ts +2 -0
  15. package/dist/agents/sisyphus-junior/kimi-k3.d.ts +12 -0
  16. package/dist/agents/types.d.ts +2 -2
  17. package/dist/cli/index.js +690 -139
  18. package/dist/cli-node/index.js +690 -139
  19. package/dist/config/index.d.ts +1 -1
  20. package/dist/config/schema/browser-automation.d.ts +1 -0
  21. package/dist/config/schema/commands.d.ts +1 -3
  22. package/dist/config/schema/default-mode.d.ts +1 -1
  23. package/dist/config/schema/goal.d.ts +7 -0
  24. package/dist/config/schema/hooks.d.ts +1 -1
  25. package/dist/config/schema/oh-my-opencode-config.d.ts +6 -10
  26. package/dist/config/schema/ralph-loop.d.ts +1 -1
  27. package/dist/config/schema.d.ts +1 -1
  28. package/dist/create-hooks.d.ts +1 -1
  29. package/dist/features/builtin-commands/templates/goal.d.ts +1 -0
  30. package/dist/features/builtin-commands/templates/stop-continuation.d.ts +1 -1
  31. package/dist/features/builtin-commands/types.d.ts +1 -1
  32. package/dist/hooks/anthropic-context-window-limit-recovery/empty-content-recovery-sdk.d.ts +6 -0
  33. package/dist/hooks/auto-slash-command/executor.d.ts +1 -0
  34. package/dist/hooks/category-skill-reminder/hook.d.ts +9 -1
  35. package/dist/hooks/comment-checker/hook.d.ts +8 -1
  36. package/dist/hooks/goal/command-arguments.d.ts +13 -0
  37. package/dist/hooks/goal/controller.d.ts +26 -0
  38. package/dist/hooks/goal/index.d.ts +23 -0
  39. package/dist/hooks/goal/prompt.d.ts +3 -0
  40. package/dist/hooks/goal/store.d.ts +8 -0
  41. package/dist/hooks/goal/tools.d.ts +10 -0
  42. package/dist/hooks/goal/types.d.ts +93 -0
  43. package/dist/hooks/goal/validation.d.ts +4 -0
  44. package/dist/hooks/index.d.ts +1 -0
  45. package/dist/hooks/keyword-detector/hook.d.ts +1 -2
  46. package/dist/hooks/ralph-loop/types.d.ts +1 -1
  47. package/dist/hooks/todo-continuation-enforcer/types.d.ts +3 -0
  48. package/dist/index.js +3706 -4329
  49. package/dist/oh-my-opencode.schema.json +23 -19
  50. package/dist/plugin/chat-message/loop-commands.d.ts +1 -1
  51. package/dist/plugin/chat-message/types.d.ts +23 -10
  52. package/dist/plugin/hooks/create-core-hooks.d.ts +1 -1
  53. package/dist/plugin/hooks/create-session-hooks.d.ts +3 -2
  54. package/dist/plugin/hooks/create-transform-hooks.d.ts +0 -2
  55. package/dist/plugin/messages-transform.d.ts +1 -0
  56. package/dist/plugin/stop-continuation.d.ts +2 -2
  57. package/dist/plugin-handlers/prometheus-agent-config-builder.d.ts +2 -0
  58. package/dist/shared/system-directive.d.ts +0 -1
  59. package/dist/skills/ulw-plan/SKILL.md +11 -7
  60. package/dist/skills/ulw-plan/references/full-workflow.md +117 -6
  61. package/dist/skills/ulw-plan/scripts/scaffold-plan.mjs +46 -10
  62. package/dist/skills/ulw-research/SKILL.md +3 -3
  63. package/dist/tools/delegate-task/types.d.ts +0 -1
  64. package/dist/tui.js +115 -28
  65. package/package.json +14 -14
  66. package/packages/git-bash-mcp/dist/cli.js +81 -19
  67. package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +4 -4
  68. package/packages/lsp-core/src/lsp/directory-diagnostics.test.ts +118 -1
  69. package/packages/lsp-core/src/lsp/directory-diagnostics.ts +1 -1
  70. package/packages/lsp-daemon/dist/cli.js +262 -63
  71. package/packages/lsp-daemon/dist/client.js +194 -50
  72. package/packages/lsp-daemon/dist/daemon-client.d.ts +2 -2
  73. package/packages/lsp-daemon/dist/daemon-client.js +26 -2
  74. package/packages/lsp-daemon/dist/ensure-daemon.d.ts +5 -4
  75. package/packages/lsp-daemon/dist/ensure-daemon.js +79 -18
  76. package/packages/lsp-daemon/dist/index.js +262 -63
  77. package/packages/lsp-daemon/dist/proxy.d.ts +2 -0
  78. package/packages/lsp-daemon/dist/proxy.js +79 -23
  79. package/packages/lsp-tools-mcp/dist/cli.js +82 -20
  80. package/packages/lsp-tools-mcp/dist/mcp.js +82 -20
  81. package/packages/lsp-tools-mcp/dist/tools.js +1 -1
  82. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  83. package/packages/omo-codex/plugin/.mcp.json +2 -1
  84. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +352 -17
  85. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  86. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  87. package/packages/omo-codex/plugin/components/codegraph/AGENTS.md +7 -6
  88. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +195 -56
  89. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +100 -28
  90. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  91. package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +21 -9
  92. package/packages/omo-codex/plugin/components/codegraph/test/hook-exclusion.test.ts +1 -1
  93. package/packages/omo-codex/plugin/components/codegraph/test/mcp-bridge-fixtures.ts +35 -0
  94. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge-lifecycle.test.ts +69 -0
  95. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +57 -1
  96. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  97. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  98. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  99. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  100. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  101. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  102. package/packages/omo-codex/plugin/components/lsp/.mcp.json +2 -1
  103. package/packages/omo-codex/plugin/components/lsp/AGENTS.md +1 -0
  104. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
  105. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +113 -31
  106. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  107. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  108. package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +1 -0
  109. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.5.md +1 -1
  110. package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus/gpt-5.6.md +5 -3
  111. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  112. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  113. package/packages/omo-codex/plugin/components/start-work-continuation/AGENTS.md +8 -0
  114. package/packages/omo-codex/plugin/components/start-work-continuation/README.md +2 -2
  115. package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +3 -3
  116. package/packages/omo-codex/plugin/components/start-work-continuation/dist/cli.js +148 -61
  117. package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
  118. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
  119. package/packages/omo-codex/plugin/components/start-work-continuation/src/boulder-reader.ts +4 -72
  120. package/packages/omo-codex/plugin/components/start-work-continuation/src/codex-hook.ts +1 -1
  121. package/packages/omo-codex/plugin/components/start-work-continuation/src/plan-checklist.ts +171 -0
  122. package/packages/omo-codex/plugin/components/start-work-continuation/test/boulder-reader.test.ts +213 -24
  123. package/packages/omo-codex/plugin/components/start-work-continuation/test/cli.test.ts +4 -3
  124. package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +25 -4
  125. package/packages/omo-codex/plugin/components/start-work-continuation/test/fixtures/plan-scaffold.md +15 -0
  126. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  127. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  128. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  129. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  130. package/packages/omo-codex/plugin/components/ultrawork/AGENTS.md +10 -10
  131. package/packages/omo-codex/plugin/components/ultrawork/agents/momus.toml +3 -3
  132. package/packages/omo-codex/plugin/components/ultrawork/directive.md +50 -33
  133. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  134. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  135. package/packages/omo-codex/plugin/components/ultrawork/skills/ultrawork/SKILL.md +50 -33
  136. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +11 -7
  137. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +117 -6
  138. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/scripts/scaffold-plan.mjs +46 -10
  139. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +50 -33
  140. package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +3 -3
  141. package/packages/omo-codex/plugin/components/ulw-loop/dist/stop-resume-hook.js +5 -6
  142. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
  143. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  144. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +1 -1
  145. package/packages/omo-codex/plugin/components/ulw-loop/src/stop-resume-hook.ts +5 -6
  146. package/packages/omo-codex/plugin/components/ulw-loop/test/stop-resume-hook.test.ts +2 -2
  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 +1 -1
  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 +1 -1
  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 +1 -1
  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/package-lock.json +13 -13
  171. package/packages/omo-codex/plugin/package.json +1 -1
  172. package/packages/omo-codex/plugin/scripts/migrate-codex-config/multi-agent-v2-guard.mjs +32 -28
  173. package/packages/omo-codex/plugin/scripts/migrate-codex-config/subagent-limit-guard.mjs +17 -45
  174. package/packages/omo-codex/plugin/scripts/migrate-codex-config/toml-section-editor.mjs +455 -0
  175. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +13 -20
  176. package/packages/omo-codex/plugin/skills/review-work/SKILL.md +7 -0
  177. package/packages/omo-codex/plugin/skills/start-work/SKILL.md +3 -2
  178. package/packages/omo-codex/plugin/skills/ultrawork/SKILL.md +50 -33
  179. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +1 -1
  180. package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +11 -7
  181. package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +117 -6
  182. package/packages/omo-codex/plugin/skills/ulw-plan/scripts/scaffold-plan.mjs +46 -10
  183. package/packages/omo-codex/plugin/test/aggregate-agents.test.mjs +2 -2
  184. package/packages/omo-codex/plugin/test/lcx-bug-skills.test.mjs +2 -2
  185. package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +1 -0
  186. package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +6 -6
  187. package/packages/omo-codex/plugin/test/multi-agent-v2-regression.test.mjs +3 -3
  188. package/packages/omo-codex/plugin/test/scaffold-plan.test.mjs +62 -0
  189. package/packages/omo-codex/plugin/test/subagent-limit-migration.test.mjs +271 -14
  190. package/packages/omo-codex/plugin/test/sync-skills-orchestration.test.mjs +7 -0
  191. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +38 -22
  192. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +0 -5
  193. package/packages/omo-codex/plugin/test/ulw-plan-review-state-contract.test.mjs +293 -0
  194. package/packages/omo-codex/scripts/install-config.test.mjs +10 -10
  195. package/packages/omo-codex/scripts/install-dist/install-local.mjs +353 -18
  196. package/packages/omo-codex/scripts/install-generated-bundle.test.mjs +39 -2
  197. package/packages/shared-skills/skills/ulw-plan/SKILL.md +11 -7
  198. package/packages/shared-skills/skills/ulw-plan/references/full-workflow.md +117 -6
  199. package/packages/shared-skills/skills/ulw-plan/scripts/scaffold-plan.mjs +46 -10
  200. package/packages/shared-skills/skills/ulw-research/SKILL.md +3 -3
  201. package/dist/features/builtin-commands/templates/ralph-loop.d.ts +0 -3
  202. package/dist/skills/ultraresearch/SKILL.md +0 -10
  203. package/packages/shared-skills/skills/lcx-contribute-bug-fix/SKILL.md +0 -273
  204. package/packages/shared-skills/skills/lcx-contribute-bug-fix/agents/openai.yaml +0 -12
  205. package/packages/shared-skills/skills/lcx-contribute-bug-fix/scripts/create-pr-body.mjs +0 -107
  206. package/packages/shared-skills/skills/lcx-doctor/SKILL.md +0 -128
  207. package/packages/shared-skills/skills/lcx-doctor/agents/openai.yaml +0 -11
  208. package/packages/shared-skills/skills/lcx-report-bug/SKILL.md +0 -268
  209. package/packages/shared-skills/skills/lcx-report-bug/agents/openai.yaml +0 -11
  210. package/packages/shared-skills/skills/ultraresearch/SKILL.md +0 -10
  211. /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-contribute-bug-fix/SKILL.md +0 -0
  212. /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-contribute-bug-fix/agents/openai.yaml +0 -0
  213. /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-contribute-bug-fix/scripts/create-pr-body.mjs +0 -0
  214. /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-doctor/SKILL.md +0 -0
  215. /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-doctor/agents/openai.yaml +0 -0
  216. /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-report-bug/SKILL.md +0 -0
  217. /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-report-bug/agents/openai.yaml +0 -0
@@ -5903,7 +5903,7 @@ var package_default;
5903
5903
  var init_package = __esm(() => {
5904
5904
  package_default = {
5905
5905
  name: "@oh-my-opencode/omo-codex",
5906
- version: "4.18.1",
5906
+ version: "4.19.0",
5907
5907
  type: "module",
5908
5908
  private: true,
5909
5909
  description: "Codex harness adapter for oh-my-openagent. Vendored Codex plugin namespace (omo) + TypeScript installer + telemetry.",
@@ -7837,9 +7837,16 @@ function findTomlSection(config, header) {
7837
7837
  const lines = config.match(/[^\n]*\n?|$/g) ?? [];
7838
7838
  let offset = 0;
7839
7839
  let start = -1;
7840
+ let multilineQuote = null;
7840
7841
  for (const line of lines) {
7841
7842
  if (line.length === 0)
7842
7843
  break;
7844
+ const multilineScan = scanTomlMultilineLine(line, multilineQuote);
7845
+ multilineQuote = multilineScan.nextQuote;
7846
+ if (multilineScan.wasInside) {
7847
+ offset += line.length;
7848
+ continue;
7849
+ }
7843
7850
  const trimmed = line.trim();
7844
7851
  if (start === -1) {
7845
7852
  if (tomlTableHeaderMatches(trimmed, headerLine, targetHeaderPath))
@@ -7854,8 +7861,37 @@ function findTomlSection(config, header) {
7854
7861
  return { start, end: config.length, text: config.slice(start) };
7855
7862
  }
7856
7863
  function replaceOrInsertSetting(config, section, key, value) {
7857
- const linePattern = new RegExp(`^[ \\t]*${escapeRegExp(key)}[ \\t]*=.*$`, "m");
7858
- const replacement = linePattern.test(section.text) ? section.text.replace(linePattern, `${key} = ${value}`) : insertSetting(section.text, key, value);
7864
+ const targetPath = parseTomlDottedKey(key);
7865
+ if (!targetPath)
7866
+ return config;
7867
+ const lines = section.text.match(/[^\n]*\n?|$/g) ?? [];
7868
+ let offset = 0;
7869
+ let multilineQuote = null;
7870
+ for (const line of lines) {
7871
+ if (line.length === 0)
7872
+ break;
7873
+ const multilineScan = scanTomlMultilineLine(line, multilineQuote);
7874
+ multilineQuote = multilineScan.nextQuote;
7875
+ if (multilineScan.wasInside) {
7876
+ offset += line.length;
7877
+ continue;
7878
+ }
7879
+ const assignmentIndex = findUnquotedAssignment(line);
7880
+ if (assignmentIndex < 0) {
7881
+ offset += line.length;
7882
+ continue;
7883
+ }
7884
+ const settingPath = parseTomlDottedKey(line.slice(0, assignmentIndex).trim());
7885
+ if (!settingPath || !tomlPathMatches(settingPath, targetPath)) {
7886
+ offset += line.length;
7887
+ continue;
7888
+ }
7889
+ const replacement2 = replaceTomlAssignmentValue(line, assignmentIndex, value);
7890
+ const assignmentEnd = multilineScan.nextQuote ? findTomlMultilineValueEnd(section.text, offset + line.length, multilineScan.nextQuote) : offset + line.length;
7891
+ const sectionReplacement = section.text.slice(0, offset) + replacement2 + section.text.slice(assignmentEnd);
7892
+ return config.slice(0, section.start) + sectionReplacement + config.slice(section.end);
7893
+ }
7894
+ const replacement = insertSetting(section.text, key, value);
7859
7895
  return config.slice(0, section.start) + replacement + config.slice(section.end);
7860
7896
  }
7861
7897
  function removeSetting(config, section, key) {
@@ -7875,6 +7911,50 @@ function replaceOrInsertRootSetting(config, key, value) {
7875
7911
  return replacement;
7876
7912
  return `${replacement.trimEnd()}
7877
7913
 
7914
+ ${suffix.trimStart()}`;
7915
+ }
7916
+ function replaceOrInsertRootDottedSetting(config, keyPath, value) {
7917
+ const targetPath = parseTomlDottedKey(keyPath);
7918
+ if (!targetPath)
7919
+ return config;
7920
+ const lines = config.match(/[^\n]*\n?|$/g) ?? [];
7921
+ let offset = 0;
7922
+ let multilineQuote = null;
7923
+ for (const line of lines) {
7924
+ if (line.length === 0)
7925
+ break;
7926
+ const multilineScan = scanTomlMultilineLine(line, multilineQuote);
7927
+ multilineQuote = multilineScan.nextQuote;
7928
+ if (multilineScan.wasInside) {
7929
+ offset += line.length;
7930
+ continue;
7931
+ }
7932
+ if (isTomlTableHeaderLine(line))
7933
+ break;
7934
+ const assignmentIndex = findUnquotedAssignment(line);
7935
+ if (assignmentIndex < 0) {
7936
+ offset += line.length;
7937
+ continue;
7938
+ }
7939
+ const settingPath = parseTomlDottedKey(line.slice(0, assignmentIndex).trim());
7940
+ if (!settingPath || !tomlPathMatches(settingPath, targetPath)) {
7941
+ offset += line.length;
7942
+ continue;
7943
+ }
7944
+ const replacement2 = replaceTomlAssignmentValue(line, assignmentIndex, value);
7945
+ const assignmentEnd = multilineScan.nextQuote ? findTomlMultilineValueEnd(config, offset + line.length, multilineScan.nextQuote) : offset + line.length;
7946
+ return config.slice(0, offset) + replacement2 + config.slice(assignmentEnd);
7947
+ }
7948
+ const sectionStart = findFirstTableStart(config);
7949
+ const root = config.slice(0, sectionStart).trimEnd();
7950
+ const suffix = config.slice(sectionStart);
7951
+ const replacement = `${root}${root.length > 0 ? `
7952
+ ` : ""}${keyPath} = ${value}
7953
+ `;
7954
+ if (suffix.length === 0)
7955
+ return replacement;
7956
+ return `${replacement.trimEnd()}
7957
+
7878
7958
  ${suffix.trimStart()}`;
7879
7959
  }
7880
7960
  function appendBlock(config, block) {
@@ -7887,9 +7967,16 @@ function appendBlock(config, block) {
7887
7967
  function findFirstTableStart(config) {
7888
7968
  const lines = config.match(/[^\n]*\n?|$/g) ?? [];
7889
7969
  let offset = 0;
7970
+ let multilineQuote = null;
7890
7971
  for (const line of lines) {
7891
7972
  if (line.length === 0)
7892
7973
  break;
7974
+ const multilineScan = scanTomlMultilineLine(line, multilineQuote);
7975
+ multilineQuote = multilineScan.nextQuote;
7976
+ if (multilineScan.wasInside) {
7977
+ offset += line.length;
7978
+ continue;
7979
+ }
7893
7980
  if (isTomlTableHeaderLine(line))
7894
7981
  return offset;
7895
7982
  offset += line.length;
@@ -7927,6 +8014,131 @@ function isTomlTableHeaderLine(line) {
7927
8014
  const normalizedLine = stripUnquotedInlineComment(line).trim();
7928
8015
  return normalizedLine.startsWith("[") && normalizedLine.endsWith("]");
7929
8016
  }
8017
+ function scanTomlMultilineLine(line, currentQuote) {
8018
+ if (currentQuote) {
8019
+ return {
8020
+ wasInside: true,
8021
+ nextQuote: findTomlMultilineDelimiter(line, currentQuote, 0) === -1 ? currentQuote : null
8022
+ };
8023
+ }
8024
+ let quote = null;
8025
+ let index = 0;
8026
+ while (index < line.length) {
8027
+ const char = line[index];
8028
+ if (quote === '"') {
8029
+ if (char === "\\") {
8030
+ index += 2;
8031
+ continue;
8032
+ }
8033
+ if (char === '"')
8034
+ quote = null;
8035
+ index += 1;
8036
+ continue;
8037
+ }
8038
+ if (quote === "'") {
8039
+ if (char === "'")
8040
+ quote = null;
8041
+ index += 1;
8042
+ continue;
8043
+ }
8044
+ if (char === "#")
8045
+ break;
8046
+ const delimiter2 = line.startsWith('"""', index) ? '"""' : line.startsWith("'''", index) ? "'''" : null;
8047
+ if (delimiter2) {
8048
+ const closingIndex = findTomlMultilineDelimiter(line, delimiter2, index + delimiter2.length);
8049
+ return { wasInside: false, nextQuote: closingIndex === -1 ? delimiter2 : null };
8050
+ }
8051
+ if (char === '"' || char === "'")
8052
+ quote = char;
8053
+ index += 1;
8054
+ }
8055
+ return { wasInside: false, nextQuote: null };
8056
+ }
8057
+ function findTomlMultilineDelimiter(line, delimiter2, startIndex) {
8058
+ let index = line.indexOf(delimiter2, startIndex);
8059
+ while (index !== -1) {
8060
+ if (delimiter2 === "'''" || countPrecedingBackslashes(line, index) % 2 === 0)
8061
+ return index;
8062
+ index = line.indexOf(delimiter2, index + 1);
8063
+ }
8064
+ return -1;
8065
+ }
8066
+ function countPrecedingBackslashes(line, index) {
8067
+ let count = 0;
8068
+ let cursor = index - 1;
8069
+ while (cursor >= 0 && line[cursor] === "\\") {
8070
+ count += 1;
8071
+ cursor -= 1;
8072
+ }
8073
+ return count;
8074
+ }
8075
+ function findUnquotedAssignment(line) {
8076
+ return findUnquotedCharacter(line, "=", 0);
8077
+ }
8078
+ function findUnquotedComment(line, startIndex) {
8079
+ return findUnquotedCharacter(line, "#", startIndex);
8080
+ }
8081
+ function findUnquotedCharacter(line, target, startIndex) {
8082
+ let quote = null;
8083
+ let index = startIndex;
8084
+ while (index < line.length) {
8085
+ const char = line[index];
8086
+ if (quote === '"') {
8087
+ if (char === "\\") {
8088
+ index += 2;
8089
+ continue;
8090
+ }
8091
+ if (char === '"')
8092
+ quote = null;
8093
+ index += 1;
8094
+ continue;
8095
+ }
8096
+ if (quote === "'") {
8097
+ if (char === "'")
8098
+ quote = null;
8099
+ index += 1;
8100
+ continue;
8101
+ }
8102
+ if (char === '"' || char === "'") {
8103
+ quote = char;
8104
+ index += 1;
8105
+ continue;
8106
+ }
8107
+ if (char === target)
8108
+ return index;
8109
+ if (char === "#")
8110
+ return -1;
8111
+ index += 1;
8112
+ }
8113
+ return -1;
8114
+ }
8115
+ function tomlPathMatches(candidate, target) {
8116
+ return candidate.length === target.length && candidate.every((part, index) => part === target[index]);
8117
+ }
8118
+ function replaceTomlAssignmentValue(line, assignmentIndex, value) {
8119
+ const newline = line.endsWith(`
8120
+ `) ? `
8121
+ ` : "";
8122
+ const lineBody = newline ? line.slice(0, -1) : line;
8123
+ const commentIndex = findUnquotedComment(lineBody, assignmentIndex + 1);
8124
+ const comment = commentIndex === -1 ? "" : ` ${lineBody.slice(commentIndex).trimStart()}`;
8125
+ return `${lineBody.slice(0, assignmentIndex + 1)} ${value}${comment}${newline}`;
8126
+ }
8127
+ function findTomlMultilineValueEnd(text, startOffset, quote) {
8128
+ const lines = text.slice(startOffset).match(/[^\n]*\n?|$/g) ?? [];
8129
+ let offset = startOffset;
8130
+ let currentQuote = quote;
8131
+ for (const line of lines) {
8132
+ if (line.length === 0)
8133
+ break;
8134
+ const scan = scanTomlMultilineLine(line, currentQuote);
8135
+ currentQuote = scan.nextQuote;
8136
+ offset += line.length;
8137
+ if (currentQuote === null)
8138
+ return offset;
8139
+ }
8140
+ return text.length;
8141
+ }
7930
8142
  function stripUnquotedInlineComment(line) {
7931
8143
  let quote = null;
7932
8144
  let index = 0;
@@ -8257,13 +8469,137 @@ function delay(milliseconds) {
8257
8469
  return new Promise((resolveDelay) => setTimeout(resolveDelay, milliseconds));
8258
8470
  }
8259
8471
 
8472
+ // packages/omo-codex/src/install/toml-setting-reader.ts
8473
+ function hasTomlSetting(config, keyPath) {
8474
+ const targetPath = parseTomlDottedKey(keyPath);
8475
+ if (!targetPath)
8476
+ return false;
8477
+ return hasTomlAssignment(config, (tablePath, settingPath) => {
8478
+ const fullPath = [...tablePath, ...settingPath];
8479
+ return fullPath.length === targetPath.length && fullPath.every((part, index) => part === targetPath[index]);
8480
+ });
8481
+ }
8482
+ function hasTomlRootDottedKeyPrefix(config, rootKey) {
8483
+ return hasTomlAssignment(config, (tablePath, settingPath) => tablePath.length === 0 && settingPath.length > 1 && settingPath[0] === rootKey);
8484
+ }
8485
+ function hasTomlAssignment(config, predicate) {
8486
+ let tablePath = [];
8487
+ let multilineQuote = null;
8488
+ for (const line of config.split(`
8489
+ `)) {
8490
+ const multilineScan = scanTomlMultilineLine(line, multilineQuote);
8491
+ multilineQuote = multilineScan.nextQuote;
8492
+ if (multilineScan.wasInside)
8493
+ continue;
8494
+ const normalizedLine = stripUnquotedInlineComment2(line).trim();
8495
+ if (normalizedLine.length === 0)
8496
+ continue;
8497
+ const headerPath = parseTomlTableHeader2(normalizedLine);
8498
+ if (headerPath) {
8499
+ tablePath = headerPath;
8500
+ continue;
8501
+ }
8502
+ if (isTomlTableHeaderLine2(normalizedLine)) {
8503
+ tablePath = null;
8504
+ continue;
8505
+ }
8506
+ if (!tablePath)
8507
+ continue;
8508
+ const assignmentIndex = findUnquotedAssignment2(normalizedLine);
8509
+ if (assignmentIndex < 0)
8510
+ continue;
8511
+ const settingPath = parseTomlDottedKey(normalizedLine.slice(0, assignmentIndex).trim());
8512
+ if (!settingPath)
8513
+ continue;
8514
+ if (predicate(tablePath, settingPath))
8515
+ return true;
8516
+ }
8517
+ return false;
8518
+ }
8519
+ function parseTomlTableHeader2(line) {
8520
+ if (!line.startsWith("[") || !line.endsWith("]") || line.startsWith("[["))
8521
+ return null;
8522
+ return parseTomlDottedKey(line.slice(1, -1).trim());
8523
+ }
8524
+ function isTomlTableHeaderLine2(line) {
8525
+ return line.startsWith("[") && line.endsWith("]");
8526
+ }
8527
+ function stripUnquotedInlineComment2(line) {
8528
+ let quote = null;
8529
+ let index = 0;
8530
+ while (index < line.length) {
8531
+ const char = line[index];
8532
+ if (quote === '"') {
8533
+ if (char === "\\") {
8534
+ index += 2;
8535
+ continue;
8536
+ }
8537
+ if (char === '"')
8538
+ quote = null;
8539
+ index += 1;
8540
+ continue;
8541
+ }
8542
+ if (quote === "'") {
8543
+ if (char === "'")
8544
+ quote = null;
8545
+ index += 1;
8546
+ continue;
8547
+ }
8548
+ if (char === '"' || char === "'") {
8549
+ quote = char;
8550
+ index += 1;
8551
+ continue;
8552
+ }
8553
+ if (char === "#")
8554
+ return line.slice(0, index);
8555
+ index += 1;
8556
+ }
8557
+ return line;
8558
+ }
8559
+ function findUnquotedAssignment2(line) {
8560
+ let quote = null;
8561
+ let index = 0;
8562
+ while (index < line.length) {
8563
+ const char = line[index];
8564
+ if (quote === '"') {
8565
+ if (char === "\\") {
8566
+ index += 2;
8567
+ continue;
8568
+ }
8569
+ if (char === '"')
8570
+ quote = null;
8571
+ index += 1;
8572
+ continue;
8573
+ }
8574
+ if (quote === "'") {
8575
+ if (char === "'")
8576
+ quote = null;
8577
+ index += 1;
8578
+ continue;
8579
+ }
8580
+ if (char === '"' || char === "'") {
8581
+ quote = char;
8582
+ index += 1;
8583
+ continue;
8584
+ }
8585
+ if (char === "=")
8586
+ return index;
8587
+ index += 1;
8588
+ }
8589
+ return -1;
8590
+ }
8591
+
8260
8592
  // packages/omo-codex/src/install/codex-config-features.ts
8261
8593
  function ensureFeatureEnabled(config, featureName) {
8262
8594
  const section = findTomlSection(config, "features");
8263
- if (!section)
8595
+ if (!section) {
8596
+ if (hasTomlRootDottedKeyPrefix(config, "features")) {
8597
+ return replaceOrInsertRootDottedSetting(config, `features.${featureName}`, "true");
8598
+ }
8264
8599
  return appendBlock(config, `[features]
8265
8600
  ${featureName} = true
8266
8601
  `);
8602
+ }
8267
8603
  return replaceOrInsertSetting(config, section, featureName, "true");
8268
8604
  }
8269
8605
 
@@ -8331,7 +8667,7 @@ function ensureAutonomousPermissions(config) {
8331
8667
  next = replaceOrInsertRootSetting(next, "sandbox_mode", JSON.stringify("danger-full-access"));
8332
8668
  next = replaceOrInsertRootSetting(next, "network_access", JSON.stringify("enabled"));
8333
8669
  for (const featureName of AUTONOMOUS_FEATURES) {
8334
- next = ensureFeatureEnabled2(next, featureName);
8670
+ next = ensureFeatureEnabled(next, featureName);
8335
8671
  }
8336
8672
  next = removeWindowsSandboxSetting(next);
8337
8673
  next = ensureNoticeEnabled(next, "hide_full_access_warning");
@@ -8349,14 +8685,6 @@ function ensureNoticeEnabled(config, key) {
8349
8685
  return appendNoticeBlock(config, key);
8350
8686
  return replaceOrInsertSetting(config, section, key, "true");
8351
8687
  }
8352
- function ensureFeatureEnabled2(config, key) {
8353
- const section = findTomlSection(config, "features");
8354
- if (section === null)
8355
- return appendBlock(config, `[features]
8356
- ${key} = true
8357
- `);
8358
- return replaceOrInsertSetting(config, section, key, "true");
8359
- }
8360
8688
  function appendNoticeBlock(config, key) {
8361
8689
  return appendBlock(config, `[notice]
8362
8690
  ${key} = true
@@ -8428,7 +8756,7 @@ function readStringArraySetting(sectionText, key) {
8428
8756
  const assignmentIndex = line.indexOf("=");
8429
8757
  if (assignmentIndex === -1)
8430
8758
  return null;
8431
- return parseTomlStringArray(stripUnquotedInlineComment2(line.slice(assignmentIndex + 1)).trim());
8759
+ return parseTomlStringArray(stripUnquotedInlineComment3(line.slice(assignmentIndex + 1)).trim());
8432
8760
  }
8433
8761
  return null;
8434
8762
  }
@@ -8472,7 +8800,7 @@ function parseTomlString(input, startIndex) {
8472
8800
  }
8473
8801
  return null;
8474
8802
  }
8475
- function stripUnquotedInlineComment2(line) {
8803
+ function stripUnquotedInlineComment3(line) {
8476
8804
  let quote = null;
8477
8805
  let index = 0;
8478
8806
  while (index < line.length) {
@@ -8686,24 +9014,27 @@ import { readFileSync } from "node:fs";
8686
9014
  import { dirname as dirname7, isAbsolute as isAbsolute6, join as join18 } from "node:path";
8687
9015
  var CODEX_AGENTS_HEADER = "agents";
8688
9016
  var CODEX_MULTI_AGENT_V2_HEADER = "features.multi_agent_v2";
9017
+ var CODEX_MULTI_AGENT_V2_THREAD_LIMIT_KEY = `${CODEX_MULTI_AGENT_V2_HEADER}.max_concurrent_threads_per_session`;
8689
9018
  var CODEX_SUBAGENT_THREAD_LIMIT = 1000;
9019
+ var CODEX_MULTI_AGENT_V2_THREAD_LIMIT = 16;
8690
9020
  function ensureCodexMultiAgentV2Config(config, options = {}) {
8691
9021
  const featureFlag = removeFeatureFlagSetting(config, "multi_agent_v2");
8692
9022
  const v2Preferred = options.multiAgentVersion === "v2";
8693
9023
  const modelKnown = options.multiAgentVersion != null || readRootModel(featureFlag.config) !== null;
8694
9024
  const agentsConfig = v2Preferred ? removeAgentsMaxThreads(featureFlag.config) : modelKnown ? ensureAgentsMaxThreads(featureFlag.config) : raiseExistingAgentsMaxThreads(featureFlag.config);
8695
- const maxThreadsValue = CODEX_SUBAGENT_THREAD_LIMIT.toString();
8696
9025
  const preserveDisable = featureFlag.value === false && !v2Preferred;
8697
9026
  const featureConfig = preserveDisable ? setMultiAgentV2Disable(agentsConfig) : v2Preferred ? removeMultiAgentV2Disable(agentsConfig) : agentsConfig;
9027
+ if (hasTomlSetting(featureConfig, CODEX_MULTI_AGENT_V2_THREAD_LIMIT_KEY))
9028
+ return featureConfig;
8698
9029
  const section = findTomlSection(featureConfig, CODEX_MULTI_AGENT_V2_HEADER);
8699
9030
  if (!section) {
8700
9031
  const enabledSetting = preserveDisable ? `enabled = false
8701
9032
  ` : "";
8702
9033
  return appendBlock(featureConfig, `[${CODEX_MULTI_AGENT_V2_HEADER}]
8703
- ${enabledSetting}max_concurrent_threads_per_session = ${maxThreadsValue}
9034
+ ${enabledSetting}max_concurrent_threads_per_session = ${CODEX_MULTI_AGENT_V2_THREAD_LIMIT}
8704
9035
  `);
8705
9036
  }
8706
- return replaceOrInsertSetting(featureConfig, section, "max_concurrent_threads_per_session", maxThreadsValue);
9037
+ return replaceOrInsertSetting(featureConfig, section, "max_concurrent_threads_per_session", CODEX_MULTI_AGENT_V2_THREAD_LIMIT.toString());
8707
9038
  }
8708
9039
  function resolveCodexMultiAgentVersion(config, configPath) {
8709
9040
  const model = readRootModel(config);
@@ -9061,6 +9392,10 @@ var MANAGED_REASONING_DEFAULT_UPGRADES = new Map([
9061
9392
  {
9062
9393
  previous: { model: "gpt-5.5", effort: "xhigh" },
9063
9394
  current: { model: "gpt-5.6-sol", effort: "ultra" }
9395
+ },
9396
+ {
9397
+ previous: { model: "gpt-5.6-sol", effort: "ultra" },
9398
+ current: { model: "gpt-5.6-terra", effort: "high" }
9064
9399
  }
9065
9400
  ]
9066
9401
  ],
@@ -75,7 +75,44 @@ test("#given no root model #when generated bundle updates config #then it does n
75
75
  // then
76
76
  const config = await readFile(configPath, "utf8");
77
77
  assert.doesNotMatch(config, /^\s*max_threads\s*=/m);
78
- assert.match(config, /max_concurrent_threads_per_session = 1000/);
78
+ assert.match(config, /max_concurrent_threads_per_session = 16/);
79
+ });
80
+
81
+ test("#given an inline-commented V2 thread cap #when generated bundle updates config twice #then preserves the line and ordering byte-for-byte", async () => {
82
+ // given
83
+ const root = await mkdtemp(join(tmpdir(), "omo-codex-generated-explicit-v2-cap-"));
84
+ const configPath = join(root, "config.toml");
85
+ await writeFile(
86
+ configPath,
87
+ [
88
+ "[features.multi_agent_v2]",
89
+ "usage_hint_enabled = false",
90
+ "max_concurrent_threads_per_session = 7 # user cap",
91
+ "show_tool_use = false",
92
+ "",
93
+ ].join("\n"),
94
+ );
95
+
96
+ // when
97
+ const updateInput = {
98
+ configPath,
99
+ repoRoot: "/repo/packages/omo-codex",
100
+ marketplaceName: "debug",
101
+ marketplaceSource: { sourceType: "local", source: "/repo/packages/omo-codex" },
102
+ pluginNames: ["omo"],
103
+ };
104
+ await updateCodexConfig(updateInput);
105
+ const firstPass = await readFile(configPath, "utf8");
106
+ const firstV2Section = sectionText(firstPass, "[features.multi_agent_v2]");
107
+ await updateCodexConfig(updateInput);
108
+
109
+ // then
110
+ const secondPass = await readFile(configPath, "utf8");
111
+ assert.equal(sectionText(secondPass, "[features.multi_agent_v2]"), firstV2Section);
112
+ assert.match(
113
+ sectionText(secondPass, "[features.multi_agent_v2]"),
114
+ /usage_hint_enabled = false\nmax_concurrent_threads_per_session = 7 # user cap\nshow_tool_use = false/,
115
+ );
79
116
  });
80
117
 
81
118
  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 () => {
@@ -153,7 +190,7 @@ test("#given explicit v2 model_catalog_json and stale models_cache v1 #when gene
153
190
  const v2Section = sectionText(config, "[features.multi_agent_v2]");
154
191
  assert.doesNotMatch(v2Section, /^enabled\s*=/m);
155
192
  assert.doesNotMatch(config, /^\s*max_threads\s*=/m);
156
- assert.match(v2Section, /max_concurrent_threads_per_session = 1000/);
193
+ assert.match(v2Section, /max_concurrent_threads_per_session = 16/);
157
194
  });
158
195
 
159
196
  function sectionText(config, header) {
@@ -24,24 +24,28 @@ After grounding, make ONE judgment, record `intent: clear|unclear` plus `review_
24
24
 
25
25
  - **OVERRIDE - explicit ask wins:** if the user explicitly asks to be questioned or interviewed ("ask me", "interview me", "why aren't you asking me" - in any language), route **CLEAR**, run the interview, and turn the adopt-default filter OFF: the user has claimed the forks, so every surviving one is ASKED, not defaulted. This beats the OUTCOME test below, even on a fuzzy brief.
26
26
  - **CLEAR** - the user knows the outcome; the only open items are preferences/tradeoffs the repo cannot answer (genuine owner-decisions). Read **`references/intent-clear.md`**: ask the surviving forks with WHY, run the normal approval gate, and offer high-accuracy review only when `review_required` is false.
27
- - **UNCLEAR** - the outcome itself is fuzzy (a vague brief, a bootstrap, `$start-work` with no selectable plan, a goal the user cannot yet articulate). Asking would offload your own job onto the user. Read **`references/intent-unclear.md`**: research maximally, adopt and ANNOUNCE best-practice defaults, do NOT ask the user extra questions, and run high-accuracy review AUTOMATICALLY (unless Classify sized the work Trivial).
27
+ - **UNCLEAR** - the outcome itself is fuzzy (a vague brief, a bootstrap, `$start-work` with no selectable plan, a goal the user cannot yet articulate). Asking would offload your own job onto the user. Read **`references/intent-unclear.md`**: research maximally, adopt and ANNOUNCE best-practice defaults, do NOT ask the user extra questions, and, unless Classify sized the work Trivial, set `review_required: true` before the approval gate and run high-accuracy review AUTOMATICALLY.
28
28
  - **ON THE FENCE** - when CLEAR vs UNCLEAR is genuinely ambiguous, treat it as CLEAR and ask exactly ONE question. A user wrongly silenced is worse than one extra question. The dominant failure to guard against is mis-routing a CLEAR request to UNCLEAR, which silently applies defaults and overrides forks the user wanted to own.
29
29
 
30
30
  WORKED: "add a 5/min-per-IP rate-limit to `/login`" = CLEAR. "make auth better" = UNCLEAR.
31
31
 
32
32
  Both intent paths ALSO read **`references/full-workflow.md`** for the shared mechanics - the plan template, the final verification wave, the APPEND protocol, and the full delegation/wait syntax. Read the phase you are in.
33
33
 
34
- ## RUN THE SCRIPT - do not hand-build the plan files
34
+ ## RUN THE SCRIPT - do not hand-build artifacts
35
35
 
36
- Before writing any plan or draft by hand, RUN:
36
+ As soon as `<slug>` and intent are known, before recording draft state, RUN:
37
37
 
38
38
  ```
39
- node "<skill-root>/scripts/scaffold-plan.mjs" <slug> [--clear|--unclear]
39
+ node "<skill-root>/scripts/scaffold-plan.mjs" <slug> [--clear|--unclear] --draft-only [--review-required]
40
40
  ```
41
41
 
42
- (Replace `<skill-root>` with this skill's own directory; `bun` is an accepted substitute for `node`.) It creates `.omo/drafts/<slug>.md` (your durable, compaction-safe resume point) and `.omo/plans/<slug>.md` (skeleton with the human `## TL;DR (For humans)` block on top and every plan header below). Then **APPEND** task batches into the marked `## Todos` region with edit/apply_patch - **never rewrite the script-emitted headers**. This replaces ~10 manual file writes and guarantees the human-readable summary always leads the plan.
42
+ (Replace `<skill-root>` with this skill's own directory; `bun` is accepted.) This creates only `.omo/drafts/<slug>.md`, the compaction-safe resume point; it does not create a plan before approval. Include `--review-required` when an explicit modifier requires review or the classified route is non-Trivial UNCLEAR, so the first durable write contains the complete pending review request. After approval, rerun without `--draft-only` to create `.omo/plans/<slug>.md`, then **APPEND** task batches into `## Todos` - never rewrite script-emitted headers.
43
43
 
44
- Run it ONCE at plan generation. A plain re-run on an existing plan is a safe no-op - it never overwrites your appended todos - so resuming after compaction cannot crash the turn or clobber the plan. Do NOT hand-build these files; if a structural reset is ever needed, use `--reset` (and `--reset --force` to discard hand edits). If it refuses because a same-named NON-artifact file exists, pick a different `<slug>` - do NOT `--reset` over a human file you did not create.
44
+ Both invocations are resume-safe no-ops for artifacts already present. Do NOT hand-build them; use `--reset` only for a structural reset (`--reset --force` discards edits). If a same-named non-artifact file exists, choose another slug.
45
+
46
+ ## Plan artifact producer contract
47
+
48
+ When producing the plan, encode every executable item as a column-zero Markdown task row: implementation rows MUST match `- [ ] N. <title>` (where `N` is a positive decimal integer), and final-verifier rows MUST match `- [ ] F<number>. <title>`. Prose headings, numbered paragraphs, and ordinary bullets are not task substitutes and MUST NOT be counted as implementation or final-verifier tasks. Before handoff, run a structural self-check over the plan: verify that every implementation row and final-verifier row is column-zero, matches its required grammar, and appears in the intended `## Todos` or `## Final verification wave` section; verify that no prose heading or bullet is being used as a task; and repair the plan before handoff if any check fails.
45
49
 
46
50
  ## Universal invariants (hold on every path)
47
51
 
@@ -58,7 +62,7 @@ Run it ONCE at plan generation. A plain re-run on an existing plan is a safe no-
58
62
 
59
63
  ## Approval gate
60
64
 
61
- When exploration is exhausted and the unknowns are answered, record the gate in the draft (`status: awaiting-approval`, the pending action `write .omo/plans/<slug>.md`, the approach), present a short brief once, then **wait for the user's explicit okay**. Read their next reply as a decision (approve / scope-change / still-unclear). Full gate mechanics: `references/full-workflow.md`.
65
+ When exploration is exhausted and the unknowns are answered, record the gate in the draft (`status: awaiting-approval`, approach, and the next workflow action), present a short brief once, then **wait for the user's explicit okay**. Approval authorizes plan creation only; any already-required review runs afterward under its existing authorization. Full mechanics: `references/full-workflow.md`.
62
66
 
63
67
  ## Delegation (OpenCode-native)
64
68