oh-my-opencode 4.18.2 → 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 (164) hide show
  1. package/README.ja.md +2 -2
  2. package/README.ko.md +2 -2
  3. package/README.md +4 -4
  4. package/README.ru.md +2 -2
  5. package/README.zh-cn.md +2 -2
  6. package/dist/agents/atlas/agent.d.ts +6 -5
  7. package/dist/agents/sisyphus/index.d.ts +5 -0
  8. package/dist/agents/sisyphus/kimi-k3.d.ts +19 -0
  9. package/dist/agents/sisyphus-agent-factory.d.ts +1 -1
  10. package/dist/agents/sisyphus-junior/agent.d.ts +8 -4
  11. package/dist/agents/sisyphus-junior/index.d.ts +2 -0
  12. package/dist/agents/sisyphus-junior/kimi-k3.d.ts +12 -0
  13. package/dist/agents/types.d.ts +2 -2
  14. package/dist/cli/index.js +648 -136
  15. package/dist/cli-node/index.js +648 -136
  16. package/dist/config/index.d.ts +1 -1
  17. package/dist/config/schema/browser-automation.d.ts +1 -0
  18. package/dist/config/schema/commands.d.ts +1 -3
  19. package/dist/config/schema/default-mode.d.ts +1 -1
  20. package/dist/config/schema/goal.d.ts +7 -0
  21. package/dist/config/schema/hooks.d.ts +1 -1
  22. package/dist/config/schema/oh-my-opencode-config.d.ts +6 -10
  23. package/dist/config/schema/ralph-loop.d.ts +1 -1
  24. package/dist/config/schema.d.ts +1 -1
  25. package/dist/create-hooks.d.ts +1 -1
  26. package/dist/features/builtin-commands/templates/goal.d.ts +1 -0
  27. package/dist/features/builtin-commands/templates/stop-continuation.d.ts +1 -1
  28. package/dist/features/builtin-commands/types.d.ts +1 -1
  29. package/dist/hooks/auto-slash-command/executor.d.ts +1 -0
  30. package/dist/hooks/goal/command-arguments.d.ts +13 -0
  31. package/dist/hooks/goal/controller.d.ts +26 -0
  32. package/dist/hooks/goal/index.d.ts +23 -0
  33. package/dist/hooks/goal/prompt.d.ts +3 -0
  34. package/dist/hooks/goal/store.d.ts +8 -0
  35. package/dist/hooks/goal/tools.d.ts +10 -0
  36. package/dist/hooks/goal/types.d.ts +93 -0
  37. package/dist/hooks/goal/validation.d.ts +4 -0
  38. package/dist/hooks/index.d.ts +1 -0
  39. package/dist/hooks/keyword-detector/hook.d.ts +1 -2
  40. package/dist/hooks/ralph-loop/types.d.ts +1 -1
  41. package/dist/index.js +3515 -4365
  42. package/dist/oh-my-opencode.schema.json +23 -19
  43. package/dist/plugin/chat-message/loop-commands.d.ts +1 -1
  44. package/dist/plugin/chat-message/types.d.ts +23 -10
  45. package/dist/plugin/hooks/create-core-hooks.d.ts +1 -1
  46. package/dist/plugin/hooks/create-session-hooks.d.ts +3 -2
  47. package/dist/plugin/hooks/create-transform-hooks.d.ts +0 -2
  48. package/dist/plugin/stop-continuation.d.ts +2 -2
  49. package/dist/shared/system-directive.d.ts +0 -1
  50. package/dist/skills/ulw-plan/SKILL.md +11 -7
  51. package/dist/skills/ulw-plan/references/full-workflow.md +117 -6
  52. package/dist/skills/ulw-plan/scripts/scaffold-plan.mjs +46 -10
  53. package/dist/skills/ulw-research/SKILL.md +3 -3
  54. package/dist/tools/delegate-task/types.d.ts +0 -1
  55. package/dist/tui.js +73 -25
  56. package/package.json +14 -14
  57. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  58. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +352 -17
  59. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  60. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  61. package/packages/omo-codex/plugin/components/codegraph/AGENTS.md +7 -6
  62. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +97 -30
  63. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  64. package/packages/omo-codex/plugin/components/codegraph/test/hook-exclusion.test.ts +1 -1
  65. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  66. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  67. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  68. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  69. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  70. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  71. package/packages/omo-codex/plugin/components/lsp/AGENTS.md +1 -0
  72. package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +2 -2
  73. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  74. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  75. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  76. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  77. package/packages/omo-codex/plugin/components/start-work-continuation/AGENTS.md +8 -0
  78. package/packages/omo-codex/plugin/components/start-work-continuation/dist/cli.js +147 -60
  79. package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
  80. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
  81. package/packages/omo-codex/plugin/components/start-work-continuation/src/boulder-reader.ts +3 -71
  82. package/packages/omo-codex/plugin/components/start-work-continuation/src/plan-checklist.ts +171 -0
  83. package/packages/omo-codex/plugin/components/start-work-continuation/test/boulder-reader.test.ts +198 -23
  84. package/packages/omo-codex/plugin/components/start-work-continuation/test/cli.test.ts +4 -3
  85. package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +6 -5
  86. package/packages/omo-codex/plugin/components/start-work-continuation/test/fixtures/plan-scaffold.md +15 -0
  87. package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
  88. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  89. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  90. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  91. package/packages/omo-codex/plugin/components/ultrawork/AGENTS.md +10 -10
  92. package/packages/omo-codex/plugin/components/ultrawork/agents/momus.toml +3 -3
  93. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  94. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  95. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +11 -7
  96. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +117 -6
  97. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/scripts/scaffold-plan.mjs +46 -10
  98. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
  99. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  100. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
  101. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
  102. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
  103. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
  104. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
  105. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
  106. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
  107. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
  108. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
  109. package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
  110. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
  111. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
  112. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
  113. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
  114. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
  115. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
  116. package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +1 -1
  117. package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
  118. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +1 -1
  119. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
  120. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
  121. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
  122. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
  123. package/packages/omo-codex/plugin/package-lock.json +13 -13
  124. package/packages/omo-codex/plugin/package.json +1 -1
  125. package/packages/omo-codex/plugin/scripts/migrate-codex-config/multi-agent-v2-guard.mjs +32 -28
  126. package/packages/omo-codex/plugin/scripts/migrate-codex-config/subagent-limit-guard.mjs +17 -45
  127. package/packages/omo-codex/plugin/scripts/migrate-codex-config/toml-section-editor.mjs +455 -0
  128. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +3 -18
  129. package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +11 -7
  130. package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +117 -6
  131. package/packages/omo-codex/plugin/skills/ulw-plan/scripts/scaffold-plan.mjs +46 -10
  132. package/packages/omo-codex/plugin/test/aggregate-agents.test.mjs +2 -2
  133. package/packages/omo-codex/plugin/test/lcx-bug-skills.test.mjs +2 -2
  134. package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +6 -6
  135. package/packages/omo-codex/plugin/test/multi-agent-v2-regression.test.mjs +3 -3
  136. package/packages/omo-codex/plugin/test/scaffold-plan.test.mjs +62 -0
  137. package/packages/omo-codex/plugin/test/subagent-limit-migration.test.mjs +271 -14
  138. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +3 -18
  139. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +0 -5
  140. package/packages/omo-codex/plugin/test/ulw-plan-review-state-contract.test.mjs +293 -0
  141. package/packages/omo-codex/scripts/install-config.test.mjs +10 -10
  142. package/packages/omo-codex/scripts/install-dist/install-local.mjs +353 -18
  143. package/packages/omo-codex/scripts/install-generated-bundle.test.mjs +39 -2
  144. package/packages/shared-skills/skills/ulw-plan/SKILL.md +11 -7
  145. package/packages/shared-skills/skills/ulw-plan/references/full-workflow.md +117 -6
  146. package/packages/shared-skills/skills/ulw-plan/scripts/scaffold-plan.mjs +46 -10
  147. package/packages/shared-skills/skills/ulw-research/SKILL.md +3 -3
  148. package/dist/features/builtin-commands/templates/ralph-loop.d.ts +0 -3
  149. package/dist/skills/ultraresearch/SKILL.md +0 -10
  150. package/packages/shared-skills/skills/lcx-contribute-bug-fix/SKILL.md +0 -273
  151. package/packages/shared-skills/skills/lcx-contribute-bug-fix/agents/openai.yaml +0 -12
  152. package/packages/shared-skills/skills/lcx-contribute-bug-fix/scripts/create-pr-body.mjs +0 -107
  153. package/packages/shared-skills/skills/lcx-doctor/SKILL.md +0 -128
  154. package/packages/shared-skills/skills/lcx-doctor/agents/openai.yaml +0 -11
  155. package/packages/shared-skills/skills/lcx-report-bug/SKILL.md +0 -268
  156. package/packages/shared-skills/skills/lcx-report-bug/agents/openai.yaml +0 -11
  157. package/packages/shared-skills/skills/ultraresearch/SKILL.md +0 -10
  158. /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-contribute-bug-fix/SKILL.md +0 -0
  159. /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-contribute-bug-fix/agents/openai.yaml +0 -0
  160. /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-contribute-bug-fix/scripts/create-pr-body.mjs +0 -0
  161. /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-doctor/SKILL.md +0 -0
  162. /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-doctor/agents/openai.yaml +0 -0
  163. /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-report-bug/SKILL.md +0 -0
  164. /package/{dist → packages/omo-codex/plugin/components/lcx}/skills/lcx-report-bug/agents/openai.yaml +0 -0
@@ -0,0 +1,455 @@
1
+ export function findTomlSection(config, headerLine) {
2
+ const targetHeaderPath = parseTomlTableHeader(headerLine);
3
+ const lines = config.match(/[^\n]*\n?|$/g) ?? [];
4
+ let offset = 0;
5
+ let start = -1;
6
+ let multilineQuote = null;
7
+ for (const line of lines) {
8
+ if (line.length === 0) break;
9
+ const multilineScan = scanTomlMultilineLine(line, multilineQuote);
10
+ multilineQuote = multilineScan.nextQuote;
11
+ if (multilineScan.wasInside) {
12
+ offset += line.length;
13
+ continue;
14
+ }
15
+ if (start === -1) {
16
+ if (tomlTableHeaderMatches(line, targetHeaderPath)) start = offset;
17
+ } else if (isTomlTableHeaderLine(line)) {
18
+ return { start, end: offset, text: config.slice(start, offset) };
19
+ }
20
+ offset += line.length;
21
+ }
22
+ if (start === -1) return null;
23
+ return { start, end: config.length, text: config.slice(start) };
24
+ }
25
+
26
+ export function hasTomlSetting(config, keyPath) {
27
+ const targetPath = parseTomlDottedKey(keyPath);
28
+ if (!targetPath) return false;
29
+
30
+ let tablePath = [];
31
+ let multilineQuote = null;
32
+ for (const line of config.split("\n")) {
33
+ const multilineScan = scanTomlMultilineLine(line, multilineQuote);
34
+ multilineQuote = multilineScan.nextQuote;
35
+ if (multilineScan.wasInside) continue;
36
+ const normalizedLine = stripUnquotedInlineComment(line).trim();
37
+ if (normalizedLine.length === 0) continue;
38
+
39
+ const headerPath = parseTomlTableHeader(normalizedLine);
40
+ if (headerPath) {
41
+ tablePath = headerPath;
42
+ continue;
43
+ }
44
+ if (isTomlTableHeaderLine(normalizedLine)) {
45
+ tablePath = null;
46
+ continue;
47
+ }
48
+ if (!tablePath) continue;
49
+
50
+ const assignmentIndex = findUnquotedAssignment(normalizedLine);
51
+ if (assignmentIndex < 0) continue;
52
+ const settingPath = parseTomlDottedKey(normalizedLine.slice(0, assignmentIndex).trim());
53
+ if (!settingPath) continue;
54
+ const fullPath = [...tablePath, ...settingPath];
55
+ if (fullPath.length !== targetPath.length) continue;
56
+ if (fullPath.every((part, index) => part === targetPath[index])) return true;
57
+ }
58
+ return false;
59
+ }
60
+
61
+ export function replaceOrInsertRootTomlSetting(config, keyPath, value) {
62
+ const targetPath = parseTomlDottedKey(keyPath);
63
+ if (!targetPath) return config;
64
+ const match = findRootTomlSetting(config, targetPath);
65
+ if (match) {
66
+ const commentIndex = findUnquotedComment(match.lineBody, match.assignmentIndex + 1);
67
+ const comment = commentIndex === -1 ? "" : ` ${match.lineBody.slice(commentIndex).trimStart()}`;
68
+ const replacement = `${match.lineBody.slice(0, match.assignmentIndex + 1)} ${value}${comment}${match.newline}`;
69
+ return config.slice(0, match.offset) + replacement + config.slice(match.offset + match.line.length);
70
+ }
71
+ const firstTable = findFirstTomlTableStart(config);
72
+ const root = config.slice(0, firstTable).trimEnd();
73
+ const suffix = config.slice(firstTable);
74
+ const replacement = `${root}${root.length > 0 ? "\n" : ""}${keyPath} = ${value}\n`;
75
+ if (suffix.length === 0) return replacement;
76
+ return `${replacement.trimEnd()}\n\n${suffix.trimStart()}`;
77
+ }
78
+
79
+ export function replaceOrInsertTomlSectionSetting(config, section, keyPath, value) {
80
+ const targetPath = parseTomlDottedKey(keyPath);
81
+ if (!targetPath) return config;
82
+ const lines = section.text.match(/[^\n]*\n?|$/g) ?? [];
83
+ let offset = 0;
84
+ let multilineQuote = null;
85
+ for (const line of lines) {
86
+ if (line.length === 0) break;
87
+ const multilineScan = scanTomlMultilineLine(line, multilineQuote);
88
+ multilineQuote = multilineScan.nextQuote;
89
+ if (multilineScan.wasInside) {
90
+ offset += line.length;
91
+ continue;
92
+ }
93
+ const assignmentIndex = findUnquotedAssignment(line);
94
+ if (assignmentIndex >= 0) {
95
+ const settingPath = parseTomlDottedKey(line.slice(0, assignmentIndex).trim());
96
+ if (settingPath && tomlPathMatches(settingPath, targetPath)) {
97
+ const replacement = replaceTomlAssignmentValue(line, assignmentIndex, value);
98
+ const patched = section.text.slice(0, offset) + replacement + section.text.slice(offset + line.length);
99
+ return config.slice(0, section.start) + patched + config.slice(section.end);
100
+ }
101
+ }
102
+ offset += line.length;
103
+ }
104
+ const headerEnd = section.text.indexOf("\n");
105
+ const insertAt = headerEnd === -1 ? section.text.length : headerEnd + 1;
106
+ const patched = `${section.text.slice(0, insertAt)}${headerEnd === -1 ? "\n" : ""}${keyPath} = ${value}\n${section.text.slice(insertAt)}`;
107
+ return config.slice(0, section.start) + patched + config.slice(section.end);
108
+ }
109
+
110
+ export function removeTomlSectionSetting(config, section, keyPath, expectedValue) {
111
+ const targetPath = parseTomlDottedKey(keyPath);
112
+ if (!targetPath) return config;
113
+ const lines = section.text.match(/[^\n]*\n?|$/g) ?? [];
114
+ let offset = 0;
115
+ let multilineQuote = null;
116
+ for (const line of lines) {
117
+ if (line.length === 0) break;
118
+ const multilineScan = scanTomlMultilineLine(line, multilineQuote);
119
+ multilineQuote = multilineScan.nextQuote;
120
+ if (multilineScan.wasInside) {
121
+ offset += line.length;
122
+ continue;
123
+ }
124
+ const assignmentIndex = findUnquotedAssignment(line);
125
+ if (assignmentIndex >= 0) {
126
+ const settingPath = parseTomlDottedKey(line.slice(0, assignmentIndex).trim());
127
+ if (settingPath && tomlPathMatches(settingPath, targetPath)) {
128
+ const lineBody = line.endsWith("\n") ? line.slice(0, -1) : line;
129
+ const commentIndex = findUnquotedComment(lineBody, assignmentIndex + 1);
130
+ const valueEnd = commentIndex === -1 ? lineBody.length : commentIndex;
131
+ if (lineBody.slice(assignmentIndex + 1, valueEnd).trim() !== expectedValue) return config;
132
+ const patched = section.text.slice(0, offset) + section.text.slice(offset + line.length);
133
+ return config.slice(0, section.start) + patched + config.slice(section.end);
134
+ }
135
+ }
136
+ offset += line.length;
137
+ }
138
+ return config;
139
+ }
140
+
141
+ export function removeRootTomlSetting(config, keyPath, expectedValue) {
142
+ const targetPath = parseTomlDottedKey(keyPath);
143
+ if (!targetPath) return config;
144
+ const match = findRootTomlSetting(config, targetPath);
145
+ if (!match) return config;
146
+ const commentIndex = findUnquotedComment(match.lineBody, match.assignmentIndex + 1);
147
+ const valueEnd = commentIndex === -1 ? match.lineBody.length : commentIndex;
148
+ if (match.lineBody.slice(match.assignmentIndex + 1, valueEnd).trim() !== expectedValue) return config;
149
+ return config.slice(0, match.offset) + config.slice(match.offset + match.line.length);
150
+ }
151
+
152
+ function tomlTableHeaderMatches(line, targetHeaderPath) {
153
+ if (!targetHeaderPath) return false;
154
+ const candidateHeaderPath = parseTomlTableHeader(line);
155
+ if (!candidateHeaderPath || candidateHeaderPath.length !== targetHeaderPath.length) return false;
156
+ return candidateHeaderPath.every((part, index) => part === targetHeaderPath[index]);
157
+ }
158
+
159
+ function isTomlTableHeaderLine(line) {
160
+ const normalizedLine = stripUnquotedInlineComment(line).trim();
161
+ return normalizedLine.startsWith("[") && normalizedLine.endsWith("]");
162
+ }
163
+
164
+ function scanTomlMultilineLine(line, currentQuote) {
165
+ if (currentQuote) {
166
+ return {
167
+ wasInside: true,
168
+ nextQuote: findTomlMultilineDelimiter(line, currentQuote, 0) === -1 ? currentQuote : null,
169
+ };
170
+ }
171
+
172
+ let quote = null;
173
+ let index = 0;
174
+ while (index < line.length) {
175
+ const char = line[index];
176
+ if (quote === '"') {
177
+ if (char === "\\") {
178
+ index += 2;
179
+ continue;
180
+ }
181
+ if (char === '"') quote = null;
182
+ index += 1;
183
+ continue;
184
+ }
185
+ if (quote === "'") {
186
+ if (char === "'") quote = null;
187
+ index += 1;
188
+ continue;
189
+ }
190
+ if (char === "#") break;
191
+ const delimiter = line.startsWith('"""', index) ? '"""' : line.startsWith("'''", index) ? "'''" : null;
192
+ if (delimiter) {
193
+ const closingIndex = findTomlMultilineDelimiter(line, delimiter, index + delimiter.length);
194
+ return { wasInside: false, nextQuote: closingIndex === -1 ? delimiter : null };
195
+ }
196
+ if (char === '"' || char === "'") quote = char;
197
+ index += 1;
198
+ }
199
+ return { wasInside: false, nextQuote: null };
200
+ }
201
+
202
+ function findTomlMultilineDelimiter(line, delimiter, startIndex) {
203
+ let index = line.indexOf(delimiter, startIndex);
204
+ while (index !== -1) {
205
+ if (delimiter === "'''" || countPrecedingBackslashes(line, index) % 2 === 0) return index;
206
+ index = line.indexOf(delimiter, index + 1);
207
+ }
208
+ return -1;
209
+ }
210
+
211
+ function countPrecedingBackslashes(line, index) {
212
+ let count = 0;
213
+ let cursor = index - 1;
214
+ while (cursor >= 0 && line[cursor] === "\\") {
215
+ count += 1;
216
+ cursor -= 1;
217
+ }
218
+ return count;
219
+ }
220
+
221
+ function findRootTomlSetting(config, targetPath) {
222
+ const lines = config.match(/[^\n]*\n?|$/g) ?? [];
223
+ let offset = 0;
224
+ let multilineQuote = null;
225
+ for (const line of lines) {
226
+ if (line.length === 0) break;
227
+ const multilineScan = scanTomlMultilineLine(line, multilineQuote);
228
+ multilineQuote = multilineScan.nextQuote;
229
+ if (multilineScan.wasInside) {
230
+ offset += line.length;
231
+ continue;
232
+ }
233
+ if (isTomlTableHeaderLine(line)) break;
234
+ const assignmentIndex = findUnquotedAssignment(line);
235
+ if (assignmentIndex >= 0) {
236
+ const settingPath = parseTomlDottedKey(line.slice(0, assignmentIndex).trim());
237
+ if (settingPath && tomlPathMatches(settingPath, targetPath)) {
238
+ const newline = line.endsWith("\n") ? "\n" : "";
239
+ return { line, lineBody: newline ? line.slice(0, -1) : line, newline, offset, assignmentIndex };
240
+ }
241
+ }
242
+ offset += line.length;
243
+ }
244
+ return null;
245
+ }
246
+
247
+ function findFirstTomlTableStart(config) {
248
+ const lines = config.match(/[^\n]*\n?|$/g) ?? [];
249
+ let offset = 0;
250
+ let multilineQuote = null;
251
+ for (const line of lines) {
252
+ if (line.length === 0) break;
253
+ const multilineScan = scanTomlMultilineLine(line, multilineQuote);
254
+ multilineQuote = multilineScan.nextQuote;
255
+ if (!multilineScan.wasInside && isTomlTableHeaderLine(line)) return offset;
256
+ offset += line.length;
257
+ }
258
+ return config.length;
259
+ }
260
+
261
+ function findUnquotedComment(line, startIndex) {
262
+ let quote = null;
263
+ let index = startIndex;
264
+ while (index < line.length) {
265
+ const char = line[index];
266
+ if (quote === '"') {
267
+ if (char === "\\") {
268
+ index += 2;
269
+ continue;
270
+ }
271
+ if (char === '"') quote = null;
272
+ index += 1;
273
+ continue;
274
+ }
275
+ if (quote === "'") {
276
+ if (char === "'") quote = null;
277
+ index += 1;
278
+ continue;
279
+ }
280
+ if (char === '"' || char === "'") quote = char;
281
+ else if (char === "#") return index;
282
+ index += 1;
283
+ }
284
+ return -1;
285
+ }
286
+
287
+ function tomlPathMatches(candidate, target) {
288
+ return candidate.length === target.length && candidate.every((part, index) => part === target[index]);
289
+ }
290
+
291
+ function replaceTomlAssignmentValue(line, assignmentIndex, value) {
292
+ const newline = line.endsWith("\n") ? "\n" : "";
293
+ const lineBody = newline ? line.slice(0, -1) : line;
294
+ const commentIndex = findUnquotedComment(lineBody, assignmentIndex + 1);
295
+ const comment = commentIndex === -1 ? "" : ` ${lineBody.slice(commentIndex).trimStart()}`;
296
+ return `${lineBody.slice(0, assignmentIndex + 1)} ${value}${comment}${newline}`;
297
+ }
298
+
299
+ function parseTomlTableHeader(line) {
300
+ const normalizedLine = stripUnquotedInlineComment(line).trim();
301
+ if (!normalizedLine.startsWith("[") || !normalizedLine.endsWith("]") || normalizedLine.startsWith("[[")) {
302
+ return null;
303
+ }
304
+ return parseTomlDottedKey(normalizedLine.slice(1, -1).trim());
305
+ }
306
+
307
+ function stripUnquotedInlineComment(line) {
308
+ let quote = null;
309
+ let index = 0;
310
+ while (index < line.length) {
311
+ const char = line[index];
312
+ if (quote === '"') {
313
+ if (char === "\\") {
314
+ index += 2;
315
+ continue;
316
+ }
317
+ if (char === '"') quote = null;
318
+ index += 1;
319
+ continue;
320
+ }
321
+ if (quote === "'") {
322
+ if (char === "'") quote = null;
323
+ index += 1;
324
+ continue;
325
+ }
326
+ if (char === '"' || char === "'") {
327
+ quote = char;
328
+ index += 1;
329
+ continue;
330
+ }
331
+ if (char === "#") return line.slice(0, index);
332
+ index += 1;
333
+ }
334
+ return line;
335
+ }
336
+
337
+ function findUnquotedAssignment(line) {
338
+ let quote = null;
339
+ let index = 0;
340
+ while (index < line.length) {
341
+ const char = line[index];
342
+ if (quote === '"') {
343
+ if (char === "\\") {
344
+ index += 2;
345
+ continue;
346
+ }
347
+ if (char === '"') quote = null;
348
+ index += 1;
349
+ continue;
350
+ }
351
+ if (quote === "'") {
352
+ if (char === "'") quote = null;
353
+ index += 1;
354
+ continue;
355
+ }
356
+ if (char === '"' || char === "'") {
357
+ quote = char;
358
+ index += 1;
359
+ continue;
360
+ }
361
+ if (char === "=") return index;
362
+ index += 1;
363
+ }
364
+ return -1;
365
+ }
366
+
367
+ function parseTomlDottedKey(input) {
368
+ const parts = [];
369
+ let index = 0;
370
+ while (index < input.length) {
371
+ index = skipWhitespace(input, index);
372
+ const parsedKey = parseTomlKeyPart(input, index);
373
+ if (!parsedKey) return null;
374
+ parts.push(parsedKey.value);
375
+ index = skipWhitespace(input, parsedKey.nextIndex);
376
+ if (index === input.length) return parts;
377
+ if (input[index] !== ".") return null;
378
+ index += 1;
379
+ }
380
+ return parts.length > 0 ? parts : null;
381
+ }
382
+
383
+ function parseTomlKeyPart(input, startIndex) {
384
+ const quote = input[startIndex];
385
+ if (quote === "'") return parseLiteralTomlString(input, startIndex);
386
+ if (quote === '"') return parseBasicTomlString(input, startIndex);
387
+ return parseBareTomlKey(input, startIndex);
388
+ }
389
+
390
+ function parseLiteralTomlString(input, startIndex) {
391
+ let index = startIndex + 1;
392
+ let value = "";
393
+ while (index < input.length) {
394
+ const char = input[index];
395
+ if (char === "'") return { value, nextIndex: index + 1 };
396
+ value += char;
397
+ index += 1;
398
+ }
399
+ return null;
400
+ }
401
+
402
+ function parseBasicTomlString(input, startIndex) {
403
+ let index = startIndex + 1;
404
+ let value = "";
405
+ while (index < input.length) {
406
+ const char = input[index];
407
+ if (char === '"') return { value, nextIndex: index + 1 };
408
+ if (char !== "\\") {
409
+ value += char;
410
+ index += 1;
411
+ continue;
412
+ }
413
+ const escaped = parseBasicTomlEscape(input, index);
414
+ if (!escaped) return null;
415
+ value += escaped.value;
416
+ index = escaped.nextIndex;
417
+ }
418
+ return null;
419
+ }
420
+
421
+ function parseBasicTomlEscape(input, backslashIndex) {
422
+ const escape = input[backslashIndex + 1];
423
+ if (escape === undefined) return null;
424
+ if (escape === "b") return { value: "\b", nextIndex: backslashIndex + 2 };
425
+ if (escape === "t") return { value: "\t", nextIndex: backslashIndex + 2 };
426
+ if (escape === "n") return { value: "\n", nextIndex: backslashIndex + 2 };
427
+ if (escape === "f") return { value: "\f", nextIndex: backslashIndex + 2 };
428
+ if (escape === "r") return { value: "\r", nextIndex: backslashIndex + 2 };
429
+ if (escape === '"') return { value: '"', nextIndex: backslashIndex + 2 };
430
+ if (escape === "\\") return { value: "\\", nextIndex: backslashIndex + 2 };
431
+ if (escape === "u") return parseUnicodeEscape(input, backslashIndex + 2, 4);
432
+ if (escape === "U") return parseUnicodeEscape(input, backslashIndex + 2, 8);
433
+ return null;
434
+ }
435
+
436
+ function parseUnicodeEscape(input, digitsStart, digitCount) {
437
+ const digits = input.slice(digitsStart, digitsStart + digitCount);
438
+ if (digits.length !== digitCount || !/^[0-9A-Fa-f]+$/.test(digits)) return null;
439
+ const codePoint = Number.parseInt(digits, 16);
440
+ if (codePoint > 0x10ffff) return null;
441
+ return { value: String.fromCodePoint(codePoint), nextIndex: digitsStart + digitCount };
442
+ }
443
+
444
+ function parseBareTomlKey(input, startIndex) {
445
+ let index = startIndex;
446
+ while (index < input.length && /[A-Za-z0-9_-]/.test(input[index])) index += 1;
447
+ if (index === startIndex) return null;
448
+ return { value: input.slice(startIndex, index), nextIndex: index };
449
+ }
450
+
451
+ function skipWhitespace(input, startIndex) {
452
+ let index = startIndex;
453
+ while (index < input.length && /\s/.test(input[index])) index += 1;
454
+ return index;
455
+ }
@@ -14,6 +14,9 @@ const ignoredSkillSourceDirNames = new Set([".mypy_cache", ".omo", ".pytest_cach
14
14
  const ignoredSkillSourceFileNames = new Set([".gitignore", ".npmignore", "pyrightconfig.json"]);
15
15
  const skillSources = [
16
16
  ["comment-checker", "components/comment-checker/skills/comment-checker"],
17
+ ["lcx-contribute-bug-fix", "components/lcx/skills/lcx-contribute-bug-fix"],
18
+ ["lcx-doctor", "components/lcx/skills/lcx-doctor"],
19
+ ["lcx-report-bug", "components/lcx/skills/lcx-report-bug"],
17
20
  ["lsp", "components/lsp/skills/lsp"],
18
21
  ["rules", "components/rules/skills/rules"],
19
22
  ["teammode", "components/teammode/skills/teammode"],
@@ -22,7 +25,6 @@ const skillSources = [
22
25
  ["ultrawork", "components/ultrawork/skills/ultrawork"],
23
26
  ];
24
27
  const componentSkillNames = new Set(skillSources.map(([name]) => name));
25
- const codexHiddenSharedSkillNames = new Set(["ultraresearch"]);
26
28
  const skillDisplayPrefix = "(OmO) ";
27
29
 
28
30
  function shouldCopySkillSource(source) {
@@ -182,22 +184,6 @@ prefixes when identity is needed.
182
184
  `;
183
185
 
184
186
  function applyCodexSkillOverlays(skillName, content) {
185
- if (skillName === "ulw-research") {
186
- return content
187
- .replace(
188
- ", the legacy alias 'ultraresearch', any 'ulw' research wording,",
189
- ", any 'ulw' research wording,",
190
- )
191
- .replace(
192
- 'the legacy alias "ultraresearch" (also `/ultraresearch`, `$ultraresearch`), ',
193
- "",
194
- )
195
- .replace(
196
- "answer those normally, and mention that `ulw-research` is available (legacy alias: `ultraresearch`) when a question would clearly benefit from it.",
197
- "answer those normally, and mention that `ulw-research` is available when a question would clearly benefit from it.",
198
- )
199
- .replace("# Ultraresearch Synthesis: <query>", "# ULW-Research Synthesis: <query>");
200
- }
201
187
  if (skillName === "start-work") {
202
188
  return content
203
189
  .replace(startWorkOriginalCompletion, startWorkCodexCompletion)
@@ -270,7 +256,6 @@ async function syncSkills() {
270
256
 
271
257
  for (const skillName of sharedSkillNames) {
272
258
  if (componentSkillNames.has(skillName)) continue;
273
- if (codexHiddenSharedSkillNames.has(skillName)) continue;
274
259
  await cp(join(sharedSkillsRoot, skillName), join(skillsRoot, skillName), {
275
260
  filter: shouldCopySkillSource,
276
261
  recursive: true,
@@ -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 (Codex-native)
64
68