oh-my-opencode 4.11.1 → 4.12.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 (149) hide show
  1. package/.agents/skills/codex-qa/scripts/lib/app-server-client.mjs +132 -82
  2. package/.agents/skills/codex-qa/scripts/lib/app-server-client.test.js +48 -0
  3. package/.agents/skills/opencode-qa/scripts/serve-wake-split-probe.sh +361 -84
  4. package/.agents/skills/work-with-pr/SKILL.md +19 -5
  5. package/.opencode/skills/work-with-pr/SKILL.md +19 -5
  6. package/dist/agents/types.d.ts +7 -6
  7. package/dist/cli/index.js +438 -306
  8. package/dist/cli-node/index.js +438 -306
  9. package/dist/features/background-agent/parent-wake-flush-runner.d.ts +1 -0
  10. package/dist/features/background-agent/parent-wake-history-state.d.ts +1 -0
  11. package/dist/features/background-agent/parent-wake-session-history.d.ts +1 -47
  12. package/dist/features/background-agent/parent-wake-session-message.d.ts +47 -0
  13. package/dist/index.js +245 -69
  14. package/dist/plugin/tool-execute-before.d.ts +2 -0
  15. package/dist/plugin-handlers/plugin-components-loader.d.ts +1 -0
  16. package/dist/skills/ulw-plan/SKILL.md +2 -1
  17. package/dist/skills/ulw-plan/references/full-workflow.md +1 -1
  18. package/dist/skills/ulw-plan/references/intent-clear.md +2 -2
  19. package/dist/tools/delegate-task/sync-session-poller.d.ts +1 -2
  20. package/dist/tools/delegate-task/sync-session-turns.d.ts +3 -0
  21. package/dist/tui.js +16 -7
  22. package/package.json +13 -13
  23. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +22 -2
  24. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +25 -9
  25. package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
  26. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  27. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +10 -3
  28. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +9 -2
  29. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  30. package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +7 -2
  31. package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +2 -1
  32. package/packages/omo-codex/plugin/components/codegraph/test/hook.test.ts +9 -9
  33. package/packages/omo-codex/plugin/components/codegraph/test/provisioned-node-guard.test.ts +94 -0
  34. package/packages/omo-codex/plugin/components/codegraph/test/serve-node-support.test.ts +33 -0
  35. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  36. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  37. package/packages/omo-codex/plugin/components/comment-checker/test/package-smoke.test.ts +2 -71
  38. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  39. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  40. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
  41. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  42. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  43. package/packages/omo-codex/plugin/components/lsp/package.json +2 -2
  44. package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +26 -104
  45. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  46. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  47. package/packages/omo-codex/plugin/components/rules/test/package-smoke.test.ts +12 -92
  48. package/packages/omo-codex/plugin/components/start-work-continuation/dist/cli.js +157 -234
  49. package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
  50. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -2
  51. package/packages/omo-codex/plugin/components/start-work-continuation/src/boulder-reader.ts +227 -15
  52. package/packages/omo-codex/plugin/components/start-work-continuation/test/boulder-reader.test.ts +62 -2
  53. package/packages/omo-codex/plugin/components/start-work-continuation/test/cli.test.ts +34 -3
  54. package/packages/omo-codex/plugin/components/teammode/skills/teammode/SKILL.md +139 -0
  55. package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-guide.mjs +111 -0
  56. package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-state.mjs +250 -0
  57. package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team.mjs +220 -0
  58. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  59. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  60. package/packages/omo-codex/plugin/components/test-support/package-smoke-fixture.ts +158 -0
  61. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  62. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  63. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +2 -1
  64. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/agents/openai.yaml +1 -1
  65. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +1 -1
  66. package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/intent-clear.md +2 -2
  67. package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +76 -49
  68. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +2 -2
  69. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  70. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/agents/openai.yaml +1 -1
  71. package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +1 -1
  72. package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +17 -0
  73. package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +17 -0
  74. package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +17 -0
  75. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +17 -0
  76. package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +17 -0
  77. package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +17 -0
  78. package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +17 -0
  79. package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +17 -0
  80. package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +17 -0
  81. package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +17 -0
  82. package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +16 -0
  83. package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +16 -0
  84. package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +16 -0
  85. package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +16 -0
  86. package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +16 -0
  87. package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +17 -0
  88. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +16 -0
  89. package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +16 -0
  90. package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +16 -0
  91. package/packages/omo-codex/plugin/package-lock.json +12 -22
  92. package/packages/omo-codex/plugin/package.json +1 -1
  93. package/packages/omo-codex/plugin/scripts/hook-status-message.mjs +14 -11
  94. package/packages/omo-codex/plugin/scripts/sync-hook-status-messages.mjs +15 -4
  95. package/packages/omo-codex/plugin/scripts/sync-skills.mjs +37 -0
  96. package/packages/omo-codex/plugin/skills/ast-grep/agents/openai.yaml +2 -0
  97. package/packages/omo-codex/plugin/skills/comment-checker/agents/openai.yaml +2 -0
  98. package/packages/omo-codex/plugin/skills/debugging/agents/openai.yaml +2 -0
  99. package/packages/omo-codex/plugin/skills/frontend/agents/openai.yaml +2 -0
  100. package/packages/omo-codex/plugin/skills/git-master/agents/openai.yaml +1 -1
  101. package/packages/omo-codex/plugin/skills/init-deep/agents/openai.yaml +2 -0
  102. package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
  103. package/packages/omo-codex/plugin/skills/lcx-doctor/agents/openai.yaml +1 -1
  104. package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +1 -1
  105. package/packages/omo-codex/plugin/skills/lsp/agents/openai.yaml +2 -0
  106. package/packages/omo-codex/plugin/skills/lsp-setup/agents/openai.yaml +2 -0
  107. package/packages/omo-codex/plugin/skills/programming/agents/openai.yaml +2 -0
  108. package/packages/omo-codex/plugin/skills/refactor/agents/openai.yaml +2 -0
  109. package/packages/omo-codex/plugin/skills/remove-ai-slops/agents/openai.yaml +2 -0
  110. package/packages/omo-codex/plugin/skills/review-work/agents/openai.yaml +2 -0
  111. package/packages/omo-codex/plugin/skills/rules/agents/openai.yaml +2 -0
  112. package/packages/omo-codex/plugin/skills/start-work/agents/openai.yaml +2 -0
  113. package/packages/omo-codex/plugin/skills/teammode/SKILL.md +139 -0
  114. package/packages/omo-codex/plugin/skills/teammode/agents/openai.yaml +2 -0
  115. package/packages/omo-codex/plugin/skills/teammode/scripts/team-guide.mjs +111 -0
  116. package/packages/omo-codex/plugin/skills/teammode/scripts/team-state.mjs +250 -0
  117. package/packages/omo-codex/plugin/skills/teammode/scripts/team.mjs +220 -0
  118. package/packages/omo-codex/plugin/skills/ultraresearch/agents/openai.yaml +2 -0
  119. package/packages/omo-codex/plugin/skills/ulw-loop/agents/openai.yaml +1 -1
  120. package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +2 -1
  121. package/packages/omo-codex/plugin/skills/ulw-plan/agents/openai.yaml +1 -1
  122. package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +1 -1
  123. package/packages/omo-codex/plugin/skills/ulw-plan/references/intent-clear.md +2 -2
  124. package/packages/omo-codex/plugin/skills/visual-qa/agents/openai.yaml +2 -0
  125. package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +34 -33
  126. package/packages/omo-codex/plugin/test/aggregate-manifest.test.mjs +5 -2
  127. package/packages/omo-codex/plugin/test/aggregate-plugin-fixture.mjs +13 -0
  128. package/packages/omo-codex/plugin/test/bootstrap-binlinks.test.mjs +1 -1
  129. package/packages/omo-codex/plugin/test/bootstrap-hooks.test.mjs +14 -6
  130. package/packages/omo-codex/plugin/test/bootstrap-ps-guard.test.mjs +7 -3
  131. package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +3 -3
  132. package/packages/omo-codex/plugin/test/display-metadata.test.mjs +83 -0
  133. package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +30 -19
  134. package/packages/omo-codex/plugin/test/lcx-bug-skills.test.mjs +3 -3
  135. package/packages/omo-codex/plugin/test/sync-hook-status-messages.test.mjs +6 -6
  136. package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +2 -0
  137. package/packages/omo-codex/plugin/test/sync-skills.test.mjs +9 -4
  138. package/packages/omo-codex/plugin/test/teammode-safety-fixture.mjs +88 -0
  139. package/packages/omo-codex/plugin/test/teammode-safety.test.mjs +240 -0
  140. package/packages/omo-codex/scripts/install-dist/install-local.mjs +385 -206
  141. package/packages/omo-codex/scripts/install-hook-targets.test.mjs +93 -0
  142. package/packages/omo-codex/scripts/install-lazycodex-version-stamp.test.mjs +2 -2
  143. package/packages/omo-codex/scripts/install-local-bun-global-update.test.mjs +63 -0
  144. package/packages/omo-codex/scripts/install-local-entrypoint.test.mjs +109 -1
  145. package/packages/omo-codex/scripts/install-local.mjs +1 -0
  146. package/packages/shared-skills/skills/ulw-plan/SKILL.md +2 -1
  147. package/packages/shared-skills/skills/ulw-plan/references/full-workflow.md +1 -1
  148. package/packages/shared-skills/skills/ulw-plan/references/intent-clear.md +2 -2
  149. package/packages/omo-codex/plugin/hooks/hooks.json +0 -214
@@ -7,16 +7,24 @@ import {
7
7
  collectCommandHooks,
8
8
  exists,
9
9
  hookLocation,
10
+ readAggregateHookManifests,
10
11
  readComponentHookManifests,
11
- readJson,
12
- readPluginVersion,
13
12
  root,
14
13
  } from "./aggregate-plugin-fixture.mjs";
15
14
 
15
+ async function readAggregateCommandHooks() {
16
+ const manifests = await readAggregateHookManifests();
17
+ return manifests.flatMap(({ source, hooks }) => collectCommandHooks(hooks, source));
18
+ }
19
+
20
+ async function readAggregateHooksText() {
21
+ const manifests = await readAggregateHookManifests();
22
+ return manifests.map(({ hooks }) => JSON.stringify(hooks)).join("\n");
23
+ }
24
+
16
25
  test("#given isolated components #when hooks are inspected #then commands stay inside component roots", async () => {
17
26
  // given
18
- const hooks = await readJson("hooks/hooks.json");
19
- const text = JSON.stringify(hooks);
27
+ const text = await readAggregateHooksText();
20
28
 
21
29
  // when
22
30
  const componentMarkers = [
@@ -40,12 +48,11 @@ test("#given isolated components #when hooks are inspected #then commands stay i
40
48
 
41
49
  test("#given aggregate SubagentStop hooks #when inspected #then start-work and LazyCodex executor verifier are separate groups", async () => {
42
50
  // given
43
- const hooks = await readJson("hooks/hooks.json");
44
- const aggregateVersion = await readPluginVersion();
51
+ const manifests = await readAggregateHookManifests();
45
52
 
46
53
  // when
47
- const subagentStopGroups = hooks.hooks.SubagentStop;
48
- const verifierGroups = collectCommandHooks(hooks, "hooks/hooks.json").filter(
54
+ const subagentStopGroups = manifests.filter(({ hooks }) => hooks.hooks.SubagentStop).flatMap(({ hooks }) => hooks.hooks.SubagentStop);
55
+ const verifierGroups = manifests.flatMap(({ source, hooks }) => collectCommandHooks(hooks, source)).filter(
49
56
  (hook) =>
50
57
  hook.eventName === "SubagentStop" &&
51
58
  hook.handler.command ===
@@ -57,21 +64,17 @@ test("#given aggregate SubagentStop hooks #when inspected #then start-work and L
57
64
  assert.equal(subagentStopGroups[0]?.matcher, undefined);
58
65
  assert.equal(subagentStopGroups[1]?.matcher, "^lazycodex-executor$");
59
66
  assert.equal(verifierGroups.length, 1);
60
- assert.equal(verifierGroups[0]?.groupIndex, 1);
67
+ assert.equal(verifierGroups[0]?.groupIndex, 0);
61
68
  assert.equal(verifierGroups[0]?.handler.timeout, 10);
62
- assert.equal(
63
- verifierGroups[0]?.handler.statusMessage,
64
- `LazyCodex(${aggregateVersion}): Verifying LazyCodex Executor Evidence`,
65
- );
69
+ assert.equal(verifierGroups[0]?.handler.statusMessage, "(OmO) Verifying LazyCodex Executor Evidence");
66
70
  });
67
71
 
68
72
  test("#given aggregate PostCompact hooks #when hooks are inspected #then LSP diagnostics cache reset is registered", async () => {
69
73
  // given
70
- const hooks = await readJson("hooks/hooks.json");
71
- const aggregateVersion = await readPluginVersion();
74
+ const commandHooks = await readAggregateCommandHooks();
72
75
 
73
76
  // when
74
- const lspPostCompactHooks = collectCommandHooks(hooks, "hooks/hooks.json").filter(
77
+ const lspPostCompactHooks = commandHooks.filter(
75
78
  (hook) =>
76
79
  hook.eventName === "PostCompact" &&
77
80
  hook.handler.command === 'node "${PLUGIN_ROOT}/components/lsp/dist/cli.js" hook post-compact',
@@ -79,15 +82,13 @@ test("#given aggregate PostCompact hooks #when hooks are inspected #then LSP dia
79
82
 
80
83
  // then
81
84
  assert.equal(lspPostCompactHooks.length, 1);
82
- assert.equal(lspPostCompactHooks[0]?.handler.statusMessage, `LazyCodex(${aggregateVersion}): Resetting LSP Diagnostics Cache`);
85
+ assert.equal(lspPostCompactHooks[0]?.handler.statusMessage, "(OmO) Resetting LSP Diagnostics Cache");
83
86
  });
84
87
 
85
88
  test("#given aggregate hook commands #when inspected #then every command exposes a Codex status message", async () => {
86
89
  // given
87
- const hooks = await readJson("hooks/hooks.json");
88
-
89
90
  // when
90
- const commandHooks = collectCommandHooks(hooks, "hooks/hooks.json");
91
+ const commandHooks = await readAggregateCommandHooks();
91
92
  const missingStatusMessages = commandHooks
92
93
  .filter(({ handler }) => typeof handler.statusMessage !== "string" || handler.statusMessage.trim() === "")
93
94
  .map(hookLocation);
@@ -98,10 +99,8 @@ test("#given aggregate hook commands #when inspected #then every command exposes
98
99
 
99
100
  test("#given aggregate hook commands #when inspected #then commands stay Node-based and platform-neutral", async () => {
100
101
  // given
101
- const hooks = await readJson("hooks/hooks.json");
102
-
103
102
  // when
104
- const commands = collectCommandHooks(hooks, "hooks/hooks.json").map(({ handler }) => handler.command);
103
+ const commands = (await readAggregateCommandHooks()).map(({ handler }) => handler.command);
105
104
 
106
105
  // then
107
106
  assert(!commands.some((command) => /\bpython3?\b/i.test(command)));
@@ -126,12 +125,11 @@ test("#given component hook commands #when inspected #then standalone packages e
126
125
 
127
126
  test("#given hook status messages #when inspected #then labels describe OMO responsibilities instead of the hook runner", async () => {
128
127
  // given
129
- const aggregateHooks = await readJson("hooks/hooks.json");
130
128
  const componentHooks = await readComponentHookManifests();
131
129
 
132
130
  // when
133
131
  const commandHooks = [
134
- ...collectCommandHooks(aggregateHooks, "hooks/hooks.json"),
132
+ ...(await readAggregateCommandHooks()),
135
133
  ...componentHooks.flatMap(({ source, hooks }) => collectCommandHooks(hooks, source)),
136
134
  ];
137
135
  const genericStatusMessages = commandHooks
@@ -144,11 +142,11 @@ test("#given hook status messages #when inspected #then labels describe OMO resp
144
142
 
145
143
  test("#given aggregate OMO plugin is enabled #when hooks are inspected #then shell guidance and ulw-loop guard are registered", async () => {
146
144
  // given
147
- const hooks = await readJson("hooks/hooks.json");
148
- const text = JSON.stringify(hooks);
145
+ const manifests = await readAggregateHookManifests();
146
+ const text = await readAggregateHooksText();
149
147
 
150
148
  // when
151
- const preToolUseGroups = hooks.hooks.PreToolUse;
149
+ const preToolUseGroups = manifests.filter(({ hooks }) => hooks.hooks.PreToolUse).flatMap(({ hooks }) => hooks.hooks.PreToolUse);
152
150
 
153
151
  // then
154
152
  assert.match(text, /components\/git-bash\/dist\/cli\.js/);
@@ -162,14 +160,17 @@ test("#given aggregate OMO plugin is enabled #when hooks are inspected #then she
162
160
 
163
161
  test("#given aggregate SessionStart hooks #when inspected #then LazyCodex auto-update is registered", async () => {
164
162
  // given
165
- const hooks = await readJson("hooks/hooks.json");
166
- const text = JSON.stringify(hooks);
163
+ const manifests = await readAggregateHookManifests();
164
+ const text = await readAggregateHooksText();
167
165
 
168
166
  // when
169
- const sessionStartCommands = collectCommandHooks(hooks, "hooks/hooks.json")
167
+ const sessionStartCommands = (await readAggregateCommandHooks())
170
168
  .filter(({ eventName }) => eventName === "SessionStart")
171
169
  .map(({ handler }) => handler.command);
172
- const autoUpdateGroup = hooks.hooks.SessionStart.find((group) => JSON.stringify(group).includes("scripts/auto-update.mjs"));
170
+ const autoUpdateGroup = manifests
171
+ .filter(({ hooks }) => hooks.hooks.SessionStart)
172
+ .flatMap(({ hooks }) => hooks.hooks.SessionStart)
173
+ .find((group) => JSON.stringify(group).includes("scripts/auto-update.mjs"));
173
174
 
174
175
  // then
175
176
  assert.equal(autoUpdateGroup?.matcher, "^startup$");
@@ -180,7 +181,7 @@ test("#given aggregate SessionStart hooks #when inspected #then LazyCodex auto-u
180
181
 
181
182
  test("#given aggregate plugin packaging #when inspected #then hooks and compatibility sentinels stay Python-free", async () => {
182
183
  // given
183
- const hooksText = await readFile(join(root, "hooks/hooks.json"), "utf8");
184
+ const hooksText = (await Promise.all((await readAggregateHookManifests()).map(({ source }) => readFile(join(root, source), "utf8")))).join("\n");
184
185
  const aggregateTestText = await readFile(join(root, "test/aggregate.test.mjs"), "utf8");
185
186
 
186
187
  // when
@@ -10,13 +10,16 @@ test("#given aggregate plugin manifest #when inspected #then it owns the omo nam
10
10
  const manifest = await readJson(".codex-plugin/plugin.json");
11
11
 
12
12
  // when
13
- const hookPath = manifest.hooks;
13
+ const hookPaths = manifest.hooks;
14
14
  const skillsPath = manifest.skills;
15
15
  const mcpPath = manifest.mcpServers;
16
16
 
17
17
  // then
18
18
  assert.equal(manifest.name, "omo");
19
- assert.equal(hookPath, "./hooks/hooks.json");
19
+ assert(Array.isArray(hookPaths));
20
+ assert.equal(hookPaths.length, 19);
21
+ assert(hookPaths.every((hookPath) => typeof hookPath === "string" && hookPath.startsWith("./hooks/")));
22
+ assert(!hookPaths.includes("./hooks/hooks.json"));
20
23
  assert.equal(skillsPath, "./skills/");
21
24
  assert.equal(mcpPath, "./.mcp.json");
22
25
  });
@@ -17,6 +17,19 @@ export async function readPluginVersion() {
17
17
  return (await readJson(".codex-plugin/plugin.json")).version;
18
18
  }
19
19
 
20
+ export async function readAggregateHookManifests() {
21
+ const manifest = await readJson(".codex-plugin/plugin.json");
22
+ const hookPaths = Array.isArray(manifest.hooks) ? manifest.hooks : [manifest.hooks];
23
+ return Promise.all(
24
+ hookPaths
25
+ .filter((hookPath) => typeof hookPath === "string")
26
+ .map(async (hookPath) => {
27
+ const source = hookPath.replace(/^\.\//, "");
28
+ return { source, hooks: await readJson(source) };
29
+ }),
30
+ );
31
+ }
32
+
20
33
  export async function exists(relativePath) {
21
34
  try {
22
35
  await stat(join(root, relativePath));
@@ -50,7 +50,7 @@ async function writeVersionedRoot(root, version, { withRuntimeCli = false } = {}
50
50
  hooks: [
51
51
  {
52
52
  command: 'node "${PLUGIN_ROOT}/components/bootstrap/dist/cli.js" hook session-start',
53
- statusMessage: `LazyCodex(${version}): Checking Bootstrap Provisioning`,
53
+ statusMessage: "(OmO) Checking Bootstrap Provisioning",
54
54
  timeout: 30,
55
55
  type: "command",
56
56
  },
@@ -3,7 +3,13 @@ import { readdir, readFile, stat } from "node:fs/promises";
3
3
  import { dirname, join } from "node:path";
4
4
  import test from "node:test";
5
5
 
6
- import { collectCommandHooks, readComponentHookManifests, readJson, root } from "./aggregate-plugin-fixture.mjs";
6
+ import {
7
+ collectCommandHooks,
8
+ readAggregateHookManifests,
9
+ readComponentHookManifests,
10
+ readJson,
11
+ root,
12
+ } from "./aggregate-plugin-fixture.mjs";
7
13
 
8
14
  const PLUGIN_ROOT_TARGET_PATTERN = /\$\{PLUGIN_ROOT\}([^"']+)/g;
9
15
  const SKIPPED_DIRECTORY_NAMES = new Set([".git", "node_modules"]);
@@ -58,13 +64,13 @@ function collectPluginRootTargets(handler) {
58
64
  }
59
65
 
60
66
  async function readHookManifestsWithRoots() {
61
- const aggregate = { source: "hooks/hooks.json", hooks: await readJson("hooks/hooks.json"), roots: [root] };
67
+ const aggregate = (await readAggregateHookManifests()).map(({ source, hooks }) => ({ source, hooks, roots: [root] }));
62
68
  const components = (await readComponentHookManifests()).map(({ source, hooks }) => ({
63
69
  source,
64
70
  hooks,
65
71
  roots: [dirname(dirname(join(root, source))), root],
66
72
  }));
67
- return [aggregate, ...components];
73
+ return [...aggregate, ...components];
68
74
  }
69
75
 
70
76
  function findBootstrapSessionStartHandlers(hooks) {
@@ -123,11 +129,13 @@ test("#given aggregate and component hook manifests #when command targets are re
123
129
 
124
130
  test("#given the bootstrap component #when its SessionStart registration is inspected #then aggregate and component entries declare both platform commands", async () => {
125
131
  // given
126
- const aggregateHooks = await readJson("hooks/hooks.json");
132
+ const aggregateHooks = (await readAggregateHookManifests()).find(({ source }) =>
133
+ source.endsWith("session-start-checking-bootstrap-provisioning.json"),
134
+ )?.hooks;
127
135
  const componentHooks = await readJson("components/bootstrap/hooks/hooks.json");
128
136
 
129
137
  // when
130
- const aggregateEntries = findBootstrapSessionStartHandlers(aggregateHooks);
138
+ const aggregateEntries = findBootstrapSessionStartHandlers(aggregateHooks ?? {});
131
139
  const componentEntries = findBootstrapSessionStartHandlers(componentHooks);
132
140
 
133
141
  // then
@@ -144,7 +152,7 @@ test("#given the bootstrap component #when its SessionStart registration is insp
144
152
  assert.equal(typeof handler.timeout, "number");
145
153
  assert(handler.timeout <= 60, `${label} bootstrap timeout must stay <= 60 seconds`);
146
154
  assert.equal(typeof handler.statusMessage, "string");
147
- assert.match(handler.statusMessage, /^LazyCodex\([^)]+\): .+$/);
155
+ assert.match(handler.statusMessage, /^\(OmO\) .+$/);
148
156
  }
149
157
  });
150
158
 
@@ -3,10 +3,11 @@ import { readFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
4
  import test from "node:test";
5
5
 
6
- import { collectCommandHooks, readJson, root } from "./aggregate-plugin-fixture.mjs";
6
+ import { collectCommandHooks, readAggregateHookManifests, readJson, root } from "./aggregate-plugin-fixture.mjs";
7
7
 
8
8
  const BOOTSTRAP_SCRIPT_RELATIVE_PATH = join("components", "bootstrap", "scripts", "bootstrap.ps1");
9
- const HOOK_MANIFEST_SOURCES = ["hooks/hooks.json", "components/bootstrap/hooks/hooks.json"];
9
+ const BOOTSTRAP_AGGREGATE_HOOK_SOURCE = "hooks/session-start-checking-bootstrap-provisioning.json";
10
+ const HOOK_MANIFEST_SOURCES = [BOOTSTRAP_AGGREGATE_HOOK_SOURCE, "components/bootstrap/hooks/hooks.json"];
10
11
  const BOOTSTRAP_PS1_COMMAND_WINDOWS_TARGET = "\\components\\bootstrap\\scripts\\bootstrap.ps1";
11
12
  const TLS12_LINE_PATTERN =
12
13
  /\[Net\.ServicePointManager\]::SecurityProtocol\s*=\s*\[Net\.ServicePointManager\]::SecurityProtocol\s+-bor\s+\[Net\.SecurityProtocolType\]::Tls12/;
@@ -114,9 +115,12 @@ test("#given bootstrap.ps1 #when every character is inspected #then the script i
114
115
  });
115
116
 
116
117
  test("#given the aggregate and bootstrap component hook manifests #when commandWindows entries are read #then the bootstrap SessionStart hook launches bootstrap.ps1", async () => {
118
+ const aggregateHooks = await readAggregateHookManifests();
117
119
  for (const source of HOOK_MANIFEST_SOURCES) {
118
120
  // given
119
- const hooks = await readJson(source);
121
+ const hooks =
122
+ aggregateHooks.find((manifest) => manifest.source === source)?.hooks ??
123
+ (await readJson(source));
120
124
 
121
125
  // when
122
126
  const launchers = collectCommandHooks(hooks, source)
@@ -6,7 +6,7 @@ import { join } from "node:path";
6
6
  import { spawn, spawnSync } from "node:child_process";
7
7
  import test from "node:test";
8
8
 
9
- import { readJson, root } from "./aggregate-plugin-fixture.mjs";
9
+ import { readAggregateHookManifests, readJson, root } from "./aggregate-plugin-fixture.mjs";
10
10
  import { componentHookContractCases } from "./component-hook-contract-cases.mjs";
11
11
 
12
12
  const HOOK_EVENTS_BY_COMPONENT = {
@@ -142,8 +142,8 @@ test("#given malformed comment-checker stdin #when executed through dist CLI con
142
142
 
143
143
  test("#given aggregate hook manifest #when command hooks are inspected #then component CLI invocation contract is unchanged", async () => {
144
144
  // given
145
- const hooks = await readJson("hooks/hooks.json");
146
- const commands = collectHookCommands(hooks.hooks);
145
+ const manifests = await readAggregateHookManifests();
146
+ const commands = manifests.flatMap(({ hooks }) => collectHookCommands(hooks.hooks));
147
147
  const components = await workspaceComponents();
148
148
 
149
149
  // when
@@ -0,0 +1,83 @@
1
+ import assert from "node:assert/strict";
2
+ import { readdir, readFile, stat } from "node:fs/promises";
3
+ import { basename, dirname, join } from "node:path";
4
+ import test from "node:test";
5
+
6
+ import { readJson, root } from "./aggregate-plugin-fixture.mjs";
7
+
8
+ const hookSlugPattern = /^\.\/hooks\/[a-z0-9]+(?:-[a-z0-9]+)*\.json$/;
9
+ const displayNamePrefix = "(OmO) ";
10
+
11
+ async function exists(path) {
12
+ try {
13
+ await stat(path);
14
+ return true;
15
+ } catch (error) {
16
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
17
+ throw error;
18
+ }
19
+ }
20
+
21
+ async function readSkillName(skillPath) {
22
+ const content = await readFile(skillPath, "utf8");
23
+ const frontmatter = content.match(/^---\n(?<body>[\s\S]*?)\n---\n/);
24
+ assert(frontmatter?.groups?.body, `${skillPath} must have YAML frontmatter`);
25
+ const name = frontmatter.groups.body.match(/^name:\s*"?([^"\n]+)"?\s*$/m)?.[1]?.trim();
26
+ assert(name, `${skillPath} must have a frontmatter name`);
27
+ return name;
28
+ }
29
+
30
+ async function readOpenAiDisplayName(skillDir) {
31
+ const metadataPath = join(skillDir, "agents", "openai.yaml");
32
+ assert(await exists(metadataPath), `${basename(skillDir)} must include agents/openai.yaml`);
33
+ const content = await readFile(metadataPath, "utf8");
34
+ const displayName = content.match(/^\s*display_name:\s*"?([^"\n]+)"?\s*$/m)?.[1]?.trim();
35
+ assert(displayName, `${metadataPath} must define interface.display_name`);
36
+ return displayName;
37
+ }
38
+
39
+ test("#given Codex plugin hooks #when plugin metadata is inspected #then hook keys contain human-readable hook slugs", async () => {
40
+ // given
41
+ const manifest = await readJson(".codex-plugin/plugin.json");
42
+
43
+ // when
44
+ const hookPaths = manifest.hooks;
45
+
46
+ // then
47
+ assert(Array.isArray(hookPaths), "plugin manifest hooks must list per-hook metadata files");
48
+ assert(hookPaths.length > 0, "plugin manifest must expose at least one hook file");
49
+ for (const hookPath of hookPaths) {
50
+ assert.equal(typeof hookPath, "string");
51
+ assert.match(hookPath, hookSlugPattern);
52
+ assert.notEqual(hookPath, "./hooks/hooks.json");
53
+ const hooksJson = await readJson(hookPath.replace(/^\.\//, ""));
54
+ const commandHookCount = Object.values(hooksJson.hooks)
55
+ .flat()
56
+ .flatMap((group) => group.hooks)
57
+ .filter((hook) => hook.type === "command").length;
58
+ assert.equal(commandHookCount, 1, `${hookPath} should describe exactly one displayed hook`);
59
+ }
60
+ });
61
+
62
+ test("#given Codex plugin skills #when skill metadata is inspected #then every skill has an OmO display name", async () => {
63
+ // given
64
+ const skillsDir = join(root, "skills");
65
+ const skillEntries = await readdir(skillsDir, { withFileTypes: true });
66
+ const skillDirs = skillEntries.filter((entry) => entry.isDirectory()).map((entry) => join(skillsDir, entry.name));
67
+
68
+ // when
69
+ const invalidDisplayNames = [];
70
+ for (const skillDir of skillDirs) {
71
+ const skillPath = join(skillDir, "SKILL.md");
72
+ if (!(await exists(skillPath))) continue;
73
+ const skillName = await readSkillName(skillPath);
74
+ const displayName = await readOpenAiDisplayName(dirname(skillPath));
75
+ const expected = `${displayNamePrefix}${skillName}`;
76
+ if (displayName !== expected) {
77
+ invalidDisplayNames.push(`${basename(skillDir)}: expected ${expected}, got ${displayName}`);
78
+ }
79
+ }
80
+
81
+ // then
82
+ assert.deepEqual(invalidDisplayNames, []);
83
+ });
@@ -68,6 +68,19 @@ async function readPluginVersion() {
68
68
  return (await readJson(".codex-plugin/plugin.json")).version;
69
69
  }
70
70
 
71
+ async function readAggregateHookManifests() {
72
+ const manifest = await readJson(".codex-plugin/plugin.json");
73
+ const hookPaths = Array.isArray(manifest.hooks) ? manifest.hooks : [manifest.hooks];
74
+ return Promise.all(
75
+ hookPaths
76
+ .filter((hookPath) => typeof hookPath === "string")
77
+ .map(async (hookPath) => {
78
+ const source = hookPath.replace(/^\.\//, "");
79
+ return { source, version: await readPluginVersion(), hooks: await readJson(source) };
80
+ }),
81
+ );
82
+ }
83
+
71
84
  async function readComponentVersion(componentName) {
72
85
  return (await readJson(join("components", componentName, "package.json"))).version;
73
86
  }
@@ -114,7 +127,7 @@ function collectCommandHooks(hooks, source, version) {
114
127
  return commandHooks;
115
128
  }
116
129
 
117
- test("#given hook status label #when formatting #then prefixes LazyCodex with current version", async () => {
130
+ test("#given hook status label #when formatting #then prefixes OmO display namespace", async () => {
118
131
  // given
119
132
  const version = (await readRepoJson("package.json")).version;
120
133
  const label = "Checking Comments";
@@ -123,10 +136,10 @@ test("#given hook status label #when formatting #then prefixes LazyCodex with cu
123
136
  const message = formatLazyCodexHookStatusMessage(version, label);
124
137
 
125
138
  // then
126
- assert.equal(message, `LazyCodex(${version}): Checking Comments`);
139
+ assert.equal(message, "(OmO) Checking Comments");
127
140
  });
128
141
 
129
- test("#given hook status label with blank version #when formatting #then prefixes LazyCodex with local version", () => {
142
+ test("#given hook status label with blank version #when formatting #then still prefixes OmO display namespace", () => {
130
143
  // given
131
144
  const version = " ";
132
145
  const label = "Checking Comments";
@@ -135,7 +148,7 @@ test("#given hook status label with blank version #when formatting #then prefixe
135
148
  const message = formatLazyCodexHookStatusMessage(version, label);
136
149
 
137
150
  // then
138
- assert.equal(message, "LazyCodex(local): Checking Comments");
151
+ assert.equal(message, "(OmO) Checking Comments");
139
152
  });
140
153
 
141
154
  test("#given loose legacy status label #when normalizing #then removes OMO wording and title-cases label", async () => {
@@ -149,7 +162,7 @@ test("#given loose legacy status label #when normalizing #then removes OMO wordi
149
162
 
150
163
  // then
151
164
  assert.equal(normalized, "Checking Comments");
152
- assert.equal(message, `LazyCodex(${version}): Checking Comments`);
165
+ assert.equal(message, "(OmO) Checking Comments");
153
166
  });
154
167
 
155
168
  test("#given LazyCodex appears inside hook label #when normalizing #then product casing is preserved", async () => {
@@ -163,7 +176,7 @@ test("#given LazyCodex appears inside hook label #when normalizing #then product
163
176
 
164
177
  // then
165
178
  assert.equal(normalized, "Verifying LazyCodex Executor Evidence");
166
- assert.equal(message, `LazyCodex(${version}): Verifying LazyCodex Executor Evidence`);
179
+ assert.equal(message, "(OmO) Verifying LazyCodex Executor Evidence");
167
180
  });
168
181
 
169
182
  test("#given MCP appears inside hook label #when normalizing #then protocol casing is preserved", () => {
@@ -176,31 +189,29 @@ test("#given MCP appears inside hook label #when normalizing #then protocol casi
176
189
 
177
190
  // then
178
191
  assert.equal(normalized, "Recommending Git Bash MCP");
179
- assert.equal(message, "LazyCodex(4.10.0): Recommending Git Bash MCP");
192
+ assert.equal(message, "(OmO) Recommending Git Bash MCP");
180
193
  });
181
- test("#given aggregate comment-checker hook #when status is inspected #then it uses LazyCodex comments label", async () => {
194
+ test("#given aggregate comment-checker hook #when status is inspected #then it uses OmO comments label", async () => {
182
195
  // given
183
- const aggregateVersion = await readPluginVersion();
184
- const aggregateHooks = await readJson("hooks/hooks.json");
196
+ const aggregateManifests = await readAggregateHookManifests();
185
197
 
186
198
  // when
187
- const hooks = collectCommandHooks(aggregateHooks, "hooks/hooks.json", aggregateVersion);
188
- const commentCheckerHook = hooks.find((hook) => hook.id === "hooks/hooks.json:PostToolUse:0:0");
199
+ const hooks = aggregateManifests.flatMap((manifest) => collectCommandHooks(manifest.hooks, manifest.source, manifest.version));
200
+ const commentCheckerHook = hooks.find((hook) => hook.command.includes("components/comment-checker/dist/cli.js"));
189
201
 
190
202
  // then
191
- assert.equal(commentCheckerHook?.statusMessage, formatLazyCodexHookStatusMessage(aggregateVersion, "Checking Comments"));
192
- assert.doesNotMatch(JSON.stringify(aggregateHooks), /checking\s+OMO\s+comments/i);
203
+ assert.equal(commentCheckerHook?.statusMessage, formatLazyCodexHookStatusMessage("", "Checking Comments"));
204
+ assert.doesNotMatch(JSON.stringify(aggregateManifests), /checking\s+OMO\s+comments/i);
193
205
  });
194
206
 
195
- test("#given aggregate and component hooks #when status messages are inspected #then all use the LazyCodex formatter", async () => {
207
+ test("#given aggregate and component hooks #when status messages are inspected #then all use the OmO formatter", async () => {
196
208
  // given
197
- const aggregateVersion = await readPluginVersion();
198
- const aggregateHooks = await readJson("hooks/hooks.json");
209
+ const aggregateManifests = await readAggregateHookManifests();
199
210
  const componentManifests = await readComponentHookManifests();
200
211
 
201
212
  // when
202
213
  const commandHooks = [
203
- ...collectCommandHooks(aggregateHooks, "hooks/hooks.json", aggregateVersion),
214
+ ...aggregateManifests.flatMap((manifest) => collectCommandHooks(manifest.hooks, manifest.source, manifest.version)),
204
215
  ...componentManifests.flatMap((manifest) => collectCommandHooks(manifest.hooks, manifest.source, manifest.version)),
205
216
  ];
206
217
  const expectedLabels = new Map([...AGGREGATE_EXPECTED_LABELS, ...COMPONENT_EXPECTED_LABELS]);
@@ -219,6 +230,6 @@ test("#given aggregate and component hooks #when status messages are inspected #
219
230
  const actualLabels = new Set(commandHooks.map((hook) => parseLazyCodexHookStatusMessage(hook.statusMessage)?.label));
220
231
  assert.deepEqual([...expectedLabels.values()].filter((label) => !actualLabels.has(label)), []);
221
232
  for (const hook of commandHooks) {
222
- assert.doesNotMatch(hook.statusMessage, /\bOMO\b/i);
233
+ assert.match(hook.statusMessage, /^\(OmO\) /);
223
234
  }
224
235
  });
@@ -20,7 +20,7 @@ test("#given synced lcx-report-bug skill #when inspected #then it files LazyCode
20
20
  assert.match(skill, /gh pr create --repo openai\/codex/);
21
21
  assert.doesNotMatch(skill, /gh pr create --repo "\$TARGET_REPO"/);
22
22
  assert.doesNotMatch(skill, /gh pr create --repo code-yeongyu\/lazycodex/);
23
- assert.match(interfaceMetadata, /display_name: "lcx-report-bug \(omo\)"/);
23
+ assert.match(interfaceMetadata, /display_name: "\(OmO\) lcx-report-bug"/);
24
24
  assert.match(interfaceMetadata, /- "lazycodex bug"/);
25
25
  assert.match(interfaceMetadata, /- "openai codex bug"/);
26
26
  });
@@ -40,7 +40,7 @@ test("#given synced lcx-contribute-bug-fix skill #when inspected #then it delive
40
40
  assert.match(skill, /gh pr create --repo openai\/codex/);
41
41
  assert.doesNotMatch(skill, /gh pr create --repo "\$TARGET_REPO"/);
42
42
  assert.doesNotMatch(skill, /gh pr create --repo code-yeongyu\/lazycodex/);
43
- assert.match(interfaceMetadata, /display_name: "lcx-contribute-bug-fix \(omo\)"/);
43
+ assert.match(interfaceMetadata, /display_name: "\(OmO\) lcx-contribute-bug-fix"/);
44
44
  assert.match(interfaceMetadata, /- "contribute a bug fix"/);
45
45
  assert.match(interfaceMetadata, /- "fix bug pr"/);
46
46
  });
@@ -55,7 +55,7 @@ test("#given synced lcx-doctor skill #when inspected #then it diagnoses installs
55
55
 
56
56
  // then
57
57
  assert.match(skill, /^---\r?\nname: lcx-doctor\r?\n/m);
58
- assert.match(interfaceMetadata, /display_name: "lcx-doctor \(omo\)"/);
58
+ assert.match(interfaceMetadata, /display_name: "\(OmO\) lcx-doctor"/);
59
59
  assert.match(interfaceMetadata, /- "lazycodex doctor"/);
60
60
  assert.match(interfaceMetadata, /- "lazycodex health check"/);
61
61
  });
@@ -88,10 +88,10 @@ test("#given source package versions and component without hooks #when hook stat
88
88
  const aggregateHooks = await readJson(join(root, "hooks", "hooks.json"));
89
89
  const componentHooks = await readJson(join(root, "components", "comment-checker", "hooks", "hooks.json"));
90
90
  const lspHooks = await readJson(join(root, "components", "lsp", "hooks", "hooks.json"));
91
- assert.equal(aggregateHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "LazyCodex(0.1.0): Checking Comments");
92
- assert.equal(aggregateHooks.hooks.PostToolUse[0].hooks[1].statusMessage, "LazyCodex(0.1.0): Checking LSP Diagnostics");
93
- assert.equal(componentHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "LazyCodex(0.1.1): Checking Comments");
94
- assert.equal(lspHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "LazyCodex(0.2.0): Checking LSP Diagnostics");
91
+ assert.equal(aggregateHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "(OmO) Checking Comments");
92
+ assert.equal(aggregateHooks.hooks.PostToolUse[0].hooks[1].statusMessage, "(OmO) Checking LSP Diagnostics");
93
+ assert.equal(componentHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "(OmO) Checking Comments");
94
+ assert.equal(lspHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "(OmO) Checking LSP Diagnostics");
95
95
  });
96
96
 
97
97
  test("#given release version override #when hook status messages sync #then aggregate hooks use release version", async () => {
@@ -139,6 +139,6 @@ test("#given release version override #when hook status messages sync #then aggr
139
139
  // then
140
140
  const aggregateHooks = await readJson(join(root, "hooks", "hooks.json"));
141
141
  const componentHooks = await readJson(join(root, "components", "comment-checker", "hooks", "hooks.json"));
142
- assert.equal(aggregateHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "LazyCodex(4.8.0): Checking Comments");
143
- assert.equal(componentHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "LazyCodex(4.8.0): Checking Comments");
142
+ assert.equal(aggregateHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "(OmO) Checking Comments");
143
+ assert.equal(componentHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "(OmO) Checking Comments");
144
144
  });
@@ -22,6 +22,7 @@ export const expectedSkills = [
22
22
  "review-work",
23
23
  "rules",
24
24
  "start-work",
25
+ "teammode",
25
26
  "ultraresearch",
26
27
  "ulw-loop",
27
28
  "ulw-plan",
@@ -32,6 +33,7 @@ export const componentSkillSources = [
32
33
  ["comment-checker", "components/comment-checker/skills/comment-checker"],
33
34
  ["lsp", "components/lsp/skills/lsp"],
34
35
  ["rules", "components/rules/skills/rules"],
36
+ ["teammode", "components/teammode/skills/teammode"],
35
37
  ["ulw-loop", "components/ulw-loop/skills/ulw-loop"],
36
38
  ["ulw-plan", "components/ultrawork/skills/ulw-plan"],
37
39
  ];
@@ -17,6 +17,7 @@ import {
17
17
  const root = dirname(dirname(fileURLToPath(import.meta.url)));
18
18
  const repoRoot = join(root, "..", "..", "..");
19
19
  const opencodeOnlyToolPattern = /\b(?:call_omo_agent|background_output|team_[a-z_]+|task)\s*\(/;
20
+ const generatedSkillMetadataFiles = new Set(["agents/openai.yaml"]);
20
21
 
21
22
  async function readPackagedSkillFile(...segments) {
22
23
  const path = join(root, "skills", ...segments);
@@ -24,6 +25,10 @@ async function readPackagedSkillFile(...segments) {
24
25
  return { path, content };
25
26
  }
26
27
 
28
+ function excludeGeneratedSkillMetadata(files) {
29
+ return files.filter((file) => !generatedSkillMetadataFiles.has(file.replaceAll("\\", "/")));
30
+ }
31
+
27
32
  test("#given synced aggregate Codex skills #when inspected #then component and shared skills are present", async () => {
28
33
  // given
29
34
  const skillsRoot = join(root, "skills");
@@ -121,8 +126,8 @@ test("#given component skill sources #when aggregate Codex component skills are
121
126
  for (const [skillName, sourcePath] of componentSkillSources) {
122
127
  const sourceDir = join(root, sourcePath);
123
128
  const aggregateDir = join(aggregateSkillsRoot, skillName);
124
- const sourceFiles = await listSkillFiles(sourceDir);
125
- const aggregateFiles = await listSkillFiles(aggregateDir);
129
+ const sourceFiles = excludeGeneratedSkillMetadata(await listSkillFiles(sourceDir));
130
+ const aggregateFiles = excludeGeneratedSkillMetadata(await listSkillFiles(aggregateDir));
126
131
  assert.deepEqual(aggregateFiles, sourceFiles, `${skillName} resource set drifted from its component skill source`);
127
132
  for (const relativePath of sourceFiles) {
128
133
  const sourceContent = await readFile(join(sourceDir, relativePath), "utf8");
@@ -146,7 +151,7 @@ test("#given synced ulw-loop skill #when Codex hint metadata is inspected #then
146
151
 
147
152
  // then
148
153
  assert.match(skill, /^---\r?\nname: ulw-loop\r?\n/m);
149
- assert.match(interfaceMetadata, /display_name: "ulw-loop \(omo\)"/);
154
+ assert.match(interfaceMetadata, /display_name: "\(OmO\) ulw-loop"/);
150
155
  assert.doesNotMatch(interfaceMetadata, /ulw-loop \/ ulw-loop/);
151
156
  assert.match(interfaceMetadata, /short_description: "Goal-like ultrawork loop for systematic decomposition"/);
152
157
  assert.match(interfaceMetadata, /default_prompt: "Use \$ulw-loop/);
@@ -179,7 +184,7 @@ test("#given synced git-master skill #when inspected #then commits and git histo
179
184
  assert.match(skill, /Choose the Git tool by the question/);
180
185
  assert.match(skill, /git log -S "text"/);
181
186
  assert.match(skill, /git blame -L start,end -- file/);
182
- assert.match(interfaceMetadata, /display_name: "git-master \(omo\)"/);
187
+ assert.match(interfaceMetadata, /display_name: "\(OmO\) git-master"/);
183
188
  assert.match(interfaceMetadata, /- "git commit"/);
184
189
  assert.match(interfaceMetadata, /- "history search"/);
185
190
  });