oh-my-opencode 4.14.0 → 4.14.1

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 (124) hide show
  1. package/.agents/skills/codex-qa/references/isolation.md +1 -1
  2. package/README.md +1 -1
  3. package/dist/cli/doctor/framework/types.d.ts +2 -0
  4. package/dist/cli/index.js +149 -1426
  5. package/dist/cli-node/index.js +149 -1426
  6. package/dist/index.js +4255 -714
  7. package/dist/plugin/native-skills.d.ts +9 -0
  8. package/dist/plugin/system-transform.d.ts +1 -1
  9. package/dist/skills/lcx-contribute-bug-fix/SKILL.md +44 -13
  10. package/dist/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
  11. package/dist/skills/lcx-doctor/SKILL.md +48 -13
  12. package/dist/skills/lcx-doctor/agents/openai.yaml +1 -1
  13. package/dist/skills/lcx-report-bug/SKILL.md +44 -13
  14. package/dist/skills/lcx-report-bug/agents/openai.yaml +1 -1
  15. package/dist/skills/visual-qa/SKILL.md +5 -5
  16. package/dist/skills/visual-qa/references/agent-browser-setup.md +5 -4
  17. package/dist/skills/visual-qa/scripts/cli.test.ts +7 -2
  18. package/dist/skills/visual-qa/scripts/skill-prompt-contract.test.ts +10 -2
  19. package/dist/skills/visual-qa/scripts/visual-qa.mjs +530 -0
  20. package/dist/tools/delegate-task/skill-resolver.d.ts +1 -0
  21. package/dist/tools/delegate-task/types.d.ts +2 -0
  22. package/dist/tools/skill/types.d.ts +1 -0
  23. package/package.json +26 -13
  24. package/packages/git-bash-mcp/dist/cli.js +2 -0
  25. package/packages/lsp-daemon/dist/cli.js +15 -4
  26. package/packages/lsp-daemon/dist/ensure-daemon.d.ts +1 -0
  27. package/packages/lsp-daemon/dist/ensure-daemon.js +9 -3
  28. package/packages/lsp-daemon/dist/index.js +15 -4
  29. package/packages/lsp-daemon/dist/paths.d.ts +1 -0
  30. package/packages/lsp-daemon/dist/paths.js +6 -1
  31. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  32. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +4 -7
  33. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  34. package/packages/omo-codex/plugin/components/bootstrap/src/setup.ts +1 -1
  35. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +123 -10
  36. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +123 -10
  37. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  38. package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +126 -9
  39. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +141 -0
  40. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  41. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  42. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  43. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +90 -25
  44. package/packages/omo-codex/plugin/components/lsp/dist/codex-hook.js +2 -0
  45. package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.d.ts +2 -0
  46. package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.js +56 -0
  47. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  48. package/packages/omo-codex/plugin/components/lsp/src/cli.ts +2 -5
  49. package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +2 -0
  50. package/packages/omo-codex/plugin/components/lsp/src/daemon-cli-path.ts +65 -0
  51. package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +6 -1
  52. package/packages/omo-codex/plugin/components/rules/dist/cli.js +8 -106
  53. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  54. package/packages/omo-codex/plugin/components/rules/src/static-injection.ts +4 -17
  55. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
  56. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  57. package/packages/omo-codex/plugin/components/teammode/skills/teammode/SKILL.md +17 -5
  58. package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-state.mjs +63 -1
  59. package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team.mjs +80 -50
  60. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  61. package/packages/omo-codex/plugin/components/ultrawork/agents/explorer.toml +1 -1
  62. package/packages/omo-codex/plugin/components/ultrawork/directive.md +4 -12
  63. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  64. package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +7 -33
  65. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +4 -12
  66. package/packages/omo-codex/plugin/components/ulw-loop/package.json +4 -2
  67. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +1 -1
  68. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +4 -4
  69. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/sample-quality-gate.json +7 -7
  70. package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +3 -1
  71. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-doc.test.ts +2 -2
  72. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate.test.ts +7 -6
  73. package/packages/omo-codex/plugin/package-lock.json +16 -56
  74. package/packages/omo-codex/plugin/package.json +1 -1
  75. package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/SKILL.md +44 -13
  76. package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
  77. package/packages/omo-codex/plugin/skills/lcx-doctor/SKILL.md +48 -13
  78. package/packages/omo-codex/plugin/skills/lcx-doctor/agents/openai.yaml +1 -1
  79. package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +44 -13
  80. package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +1 -1
  81. package/packages/omo-codex/plugin/skills/teammode/SKILL.md +17 -5
  82. package/packages/omo-codex/plugin/skills/teammode/scripts/team-state.mjs +63 -1
  83. package/packages/omo-codex/plugin/skills/teammode/scripts/team.mjs +80 -50
  84. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +1 -1
  85. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +4 -4
  86. package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +5 -5
  87. package/packages/omo-codex/plugin/skills/visual-qa/references/agent-browser-setup.md +5 -4
  88. package/packages/omo-codex/plugin/skills/visual-qa/scripts/visual-qa.mjs +530 -0
  89. package/packages/omo-codex/plugin/test/bootstrap-binlinks.test.mjs +38 -2
  90. package/packages/omo-codex/plugin/test/component-bin-names.test.mjs +12 -11
  91. package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +99 -1
  92. package/packages/omo-codex/plugin/test/lcx-bug-skills.test.mjs +62 -8
  93. package/packages/omo-codex/plugin/test/node-install-surface.test.mjs +1 -0
  94. package/packages/omo-codex/plugin/test/teammode-archive-ambiguity.test.mjs +102 -0
  95. package/packages/omo-codex/plugin/test/teammode-safety-fixture.mjs +41 -1
  96. package/packages/omo-codex/plugin/test/teammode-safety.test.mjs +93 -0
  97. package/packages/omo-codex/scripts/install-bin-links.test.mjs +6 -0
  98. package/packages/omo-codex/scripts/install-cache-copy.test.mjs +28 -0
  99. package/packages/omo-codex/scripts/install-config-autonomous-features.test.mjs +43 -3
  100. package/packages/omo-codex/scripts/install-delegated-command.test.mjs +93 -0
  101. package/packages/omo-codex/scripts/install-dist/install-local.mjs +103 -28
  102. package/packages/omo-codex/scripts/install-local-entrypoint.test.mjs +21 -2
  103. package/packages/omo-codex/scripts/install-local.test.mjs +0 -1
  104. package/packages/shared-skills/skills/lcx-contribute-bug-fix/SKILL.md +44 -13
  105. package/packages/shared-skills/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
  106. package/packages/shared-skills/skills/lcx-doctor/SKILL.md +48 -13
  107. package/packages/shared-skills/skills/lcx-doctor/agents/openai.yaml +1 -1
  108. package/packages/shared-skills/skills/lcx-report-bug/SKILL.md +44 -13
  109. package/packages/shared-skills/skills/lcx-report-bug/agents/openai.yaml +1 -1
  110. package/packages/shared-skills/skills/visual-qa/SKILL.md +5 -5
  111. package/packages/shared-skills/skills/visual-qa/references/agent-browser-setup.md +5 -4
  112. package/packages/shared-skills/skills/visual-qa/scripts/cli.test.ts +7 -2
  113. package/packages/shared-skills/skills/visual-qa/scripts/skill-prompt-contract.test.ts +10 -2
  114. package/packages/shared-skills/skills/visual-qa/scripts/visual-qa.mjs +530 -0
  115. package/dist/cli/sparkshell-appserver-websocket.d.ts +0 -4
  116. package/dist/cli/sparkshell-appserver.d.ts +0 -16
  117. package/dist/cli/sparkshell-condense.d.ts +0 -10
  118. package/dist/cli/sparkshell-parse.d.ts +0 -24
  119. package/dist/cli/sparkshell-session-context.d.ts +0 -20
  120. package/dist/cli/sparkshell-spark.d.ts +0 -23
  121. package/dist/cli/sparkshell.d.ts +0 -33
  122. package/dist/shared/sparkshell-awareness.d.ts +0 -5
  123. package/packages/omo-codex/plugin/components/rules/src/sparkshell-awareness.ts +0 -106
  124. package/packages/omo-codex/plugin/components/rules/test/sparkshell-awareness.test.ts +0 -355
@@ -35,6 +35,7 @@ import {
35
35
  setMemberStatus,
36
36
  setMemberWorktree,
37
37
  teamExists,
38
+ withTeamLock,
38
39
  writeGuideAtomic,
39
40
  writeTeamAtomic,
40
41
  } from "./team-state.mjs";
@@ -82,57 +83,71 @@ async function persist(team, dir) {
82
83
  await writeGuideAtomic(team, buildGuide(team), dir);
83
84
  }
84
85
 
86
+ async function mutateTeam(cwd, sessionId, command, fn) {
87
+ const dir = await assertSafeTeamDir(cwd, sessionId);
88
+ return withTeamLock(dir, command, async () => {
89
+ const team = await readTeam(dir);
90
+ return fn(team, dir);
91
+ });
92
+ }
93
+
85
94
  const handlers = {
86
95
  async init(cwd, flags) {
87
96
  const teamName = requireFlag(flags, "name");
88
97
  const sessionName = requireFlag(flags, "session-name");
89
98
  const sessionId = typeof flags.session === "string" ? flags.session : `team-${randomUUID().slice(0, 8)}`;
90
99
  const dir = await ensureTeamDir(cwd, sessionId);
91
- if (await teamExists(dir)) {
92
- process.stdout.write(`exists: ${dir} (left untouched; re-init is a safe no-op)\n`);
93
- return;
94
- }
95
- const team = buildTeam({
96
- teamName,
97
- sessionName,
98
- sessionId,
99
- dir,
100
- worktreeEnabled: flags.worktree === true,
101
- baseBranch: typeof flags["base-branch"] === "string" ? flags["base-branch"] : "dev",
100
+ await withTeamLock(dir, "init", async () => {
101
+ if (await teamExists(dir)) {
102
+ process.stdout.write(`exists: ${dir} (left untouched; re-init is a safe no-op)\n`);
103
+ return;
104
+ }
105
+ const team = buildTeam({
106
+ teamName,
107
+ sessionName,
108
+ sessionId,
109
+ dir,
110
+ worktreeEnabled: flags.worktree === true,
111
+ baseBranch: typeof flags["base-branch"] === "string" ? flags["base-branch"] : "dev",
112
+ });
113
+ await persist(team, dir);
114
+ process.stdout.write(`created: ${dir}\n`);
115
+ process.stdout.write(`team.json + guide.md written; artifacts/ ready. session id: ${sessionId}\n`);
116
+ process.stdout.write(`next: add-member --team ${sessionId} --id A --name "<short role>" --focus "<part/ownership/perspective>" --lens area|ownership|perspective --deliverable "<...>"\n`);
102
117
  });
103
- await persist(team, dir);
104
- process.stdout.write(`created: ${dir}\n`);
105
- process.stdout.write(`team.json + guide.md written; artifacts/ ready. session id: ${sessionId}\n`);
106
- process.stdout.write(`next: add-member --team ${sessionId} --id A --name "<short role>" --focus "<part/ownership/perspective>" --lens area|ownership|perspective --deliverable "<...>"\n`);
107
118
  },
108
119
 
109
120
  async "add-member"(cwd, flags) {
110
121
  const sessionId = requireFlag(flags, "team");
111
- const { dir, team } = await loadTeam(cwd, sessionId);
112
122
  const memberId = requireFlag(flags, "id").trim();
113
- addMember(team, {
123
+ const memberInput = {
114
124
  id: memberId,
115
125
  name: typeof flags.name === "string" ? flags.name : null,
116
126
  focus: requireFlag(flags, "focus"),
117
127
  lens: requireFlag(flags, "lens"),
118
128
  deliverable: typeof flags.deliverable === "string" ? flags.deliverable : "",
119
129
  branch: typeof flags.branch === "string" ? flags.branch : null,
130
+ };
131
+ const { team, member } = await mutateTeam(cwd, sessionId, "add-member", async (team, dir) => {
132
+ addMember(team, memberInput);
133
+ await persist(team, dir);
134
+ return { team, member: team.members.find((m) => m.id === memberId) };
120
135
  });
121
- await persist(team, dir);
122
- const member = team.members.find((m) => m.id === memberId);
123
136
  process.stdout.write(`added member ${memberId} to team ${sessionId}.\n\nSend this as the new thread's first message (title the thread "${member.threadTitle}"):\n---\n${buildMemberPrompt(team, memberId)}\n---\n`);
124
137
  },
125
138
 
126
139
  async "bind-thread"(cwd, flags) {
127
140
  const sessionId = requireFlag(flags, "team");
128
- const { dir, team } = await loadTeam(cwd, sessionId);
129
- bindThread(team, {
141
+ const input = {
130
142
  id: requireFlag(flags, "id"),
131
143
  threadId: requireFlag(flags, "thread"),
132
144
  cwd: typeof flags.cwd === "string" ? flags.cwd : null,
133
145
  worktreePath: typeof flags["worktree-path"] === "string" ? flags["worktree-path"] : null,
146
+ };
147
+ await mutateTeam(cwd, sessionId, "bind-thread", async (team, dir) => {
148
+ bindThread(team, input);
149
+ await persist(team, dir);
134
150
  });
135
- await persist(team, dir);
136
151
  process.stdout.write(`bound member ${flags.id} to thread ${flags.thread}.\n`);
137
152
  },
138
153
 
@@ -144,25 +159,29 @@ const handlers = {
144
159
 
145
160
  async "set-status"(cwd, flags) {
146
161
  const sessionId = requireFlag(flags, "team");
147
- const { dir, team } = await loadTeam(cwd, sessionId);
148
- setMemberStatus(team, {
162
+ const input = {
149
163
  id: requireFlag(flags, "id"),
150
164
  status: requireFlag(flags, "status"),
151
165
  note: typeof flags.note === "string" ? flags.note : "",
166
+ };
167
+ await mutateTeam(cwd, sessionId, "set-status", async (team, dir) => {
168
+ setMemberStatus(team, input);
169
+ await persist(team, dir);
152
170
  });
153
- await persist(team, dir);
154
171
  process.stdout.write(`member ${flags.id} -> ${flags.status}\n`);
155
172
  },
156
173
 
157
174
  async "worktree-add"(cwd, flags) {
158
175
  const sessionId = requireFlag(flags, "team");
159
- const { dir, team } = await loadTeam(cwd, sessionId);
160
- const member = memberOrThrow(team, requireFlag(flags, "id"));
161
- const result = addMemberWorktree(cwd, team, member, {
162
- baseBranch: typeof flags["base-branch"] === "string" ? flags["base-branch"] : null,
176
+ const memberId = requireFlag(flags, "id");
177
+ const baseBranch = typeof flags["base-branch"] === "string" ? flags["base-branch"] : null;
178
+ const { member, result } = await mutateTeam(cwd, sessionId, "worktree-add", async (team, dir) => {
179
+ const member = memberOrThrow(team, memberId);
180
+ const result = addMemberWorktree(cwd, team, member, { baseBranch });
181
+ setMemberWorktree(team, { id: member.id, path: result.path, branch: result.branch });
182
+ await persist(team, dir);
183
+ return { member, result };
163
184
  });
164
- setMemberWorktree(team, { id: member.id, path: result.path, branch: result.branch });
165
- await persist(team, dir);
166
185
  const note = result.created ? "" : " (already exists)";
167
186
  const thread = member.threadId
168
187
  ? `\nMember thread: ${codexThreadLink(member.threadId)}\nTell that member to: cd "${result.path}"`
@@ -174,11 +193,15 @@ const handlers = {
174
193
 
175
194
  async "worktree-remove"(cwd, flags) {
176
195
  const sessionId = requireFlag(flags, "team");
177
- const { dir, team } = await loadTeam(cwd, sessionId);
178
- const member = memberOrThrow(team, requireFlag(flags, "id"));
179
- removeMemberWorktree(cwd, team, member, { force: flags.force === true });
180
- clearMemberWorktree(team, { id: member.id });
181
- await persist(team, dir);
196
+ const memberId = requireFlag(flags, "id");
197
+ const force = flags.force === true;
198
+ const member = await mutateTeam(cwd, sessionId, "worktree-remove", async (team, dir) => {
199
+ const member = memberOrThrow(team, memberId);
200
+ removeMemberWorktree(cwd, team, member, { force });
201
+ clearMemberWorktree(team, { id: member.id });
202
+ await persist(team, dir);
203
+ return member;
204
+ });
182
205
  process.stdout.write(`removed worktree for member ${member.id}\n`);
183
206
  },
184
207
 
@@ -208,25 +231,30 @@ const handlers = {
208
231
 
209
232
  async archive(cwd, flags) {
210
233
  const sessionId = requireFlag(flags, "team");
211
- const { dir, team } = await loadTeam(cwd, sessionId);
212
- archive(team, {
234
+ const input = {
213
235
  id: typeof flags.id === "string" ? flags.id : null,
214
236
  note: typeof flags.note === "string" ? flags.note : "",
237
+ };
238
+ await mutateTeam(cwd, sessionId, "archive", async (team, dir) => {
239
+ archive(team, input);
240
+ await persist(team, dir);
215
241
  });
216
- await persist(team, dir);
217
242
  process.stdout.write(flags.id ? `archived member ${flags.id}\n` : `archived team ${sessionId} and closed all members\n`);
218
243
  },
219
244
 
220
245
  async delete(cwd, flags) {
221
246
  const sessionId = requireFlag(flags, "team");
222
- const { dir, team } = await loadTeam(cwd, sessionId);
223
- const active = team.members.filter((m) => m.status !== "archived");
224
- if (flags.force !== true && (team.status !== "archived" || active.length > 0)) {
225
- throw new Error(
226
- `refused: team "${sessionId}" is not archived or still has ${active.length} active member(s). Archive first, or pass --force to delete anyway.`,
227
- );
228
- }
229
- await rm(dir, { recursive: true, force: true });
247
+ const dir = await assertSafeTeamDir(cwd, sessionId);
248
+ await withTeamLock(dir, "delete", async () => {
249
+ const team = await readTeam(dir);
250
+ const active = team.members.filter((m) => m.status !== "archived");
251
+ if (flags.force !== true && (team.status !== "archived" || active.length > 0)) {
252
+ throw new Error(
253
+ `refused: team "${sessionId}" is not archived or still has ${active.length} active member(s). Archive first, or pass --force to delete anyway.`,
254
+ );
255
+ }
256
+ await rm(dir, { recursive: true, force: true });
257
+ });
230
258
  process.stdout.write(`deleted team state: ${dir}\n`);
231
259
  },
232
260
 
@@ -247,9 +275,11 @@ const handlers = {
247
275
 
248
276
  async guide(cwd, flags) {
249
277
  const sessionId = requireFlag(flags, "team");
250
- const { dir, team } = await loadTeam(cwd, sessionId);
251
- await writeGuideAtomic(team, buildGuide(team), dir);
252
- process.stdout.write(`${team.paths.guide}\n`);
278
+ const guidePath = await mutateTeam(cwd, sessionId, "guide", async (team, dir) => {
279
+ await writeGuideAtomic(team, buildGuide(team), dir);
280
+ return team.paths.guide;
281
+ });
282
+ process.stdout.write(`${guidePath}\n`);
253
283
  },
254
284
  };
255
285
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-telemetry",
3
- "version": "4.14.0",
3
+ "version": "4.14.1",
4
4
  "description": "Codex plugin component that emits omo-codex anonymous daily-active telemetry on SessionStart.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -46,7 +46,7 @@ If asked "where is auth?", explain the auth flow you found.]
46
46
  </results>
47
47
 
48
48
  # Tool strategy (parallel, flood the first wave)
49
- - Use `omo sparkshell <command> [args...]` first for repo-wide inspection, CLI smoke tests, git/history checks, and bounded command output. Pass ordinary commands as executable and arguments in separate argv tokens, for example `omo sparkshell rg --files`; not `omo sparkshell 'rg --files'`, because the quoted string is treated as one executable name. Raw `rg`/`grep`/`cat`/`git` are fallbacks when Sparkshell is unavailable or too narrow. Use `omo sparkshell --shell '<command>'` only for shell syntax such as metacharacters, pipelines, redirects, command substitution, or variable expansion. Use `omo sparkshell --tmux-pane <pane-id> --tail-lines 400` only to inspect an existing tmux pane, never to launch ordinary commands.
49
+ - Repo-wide inspection, CLI smoke tests, git/history checks, and bounded command output -> use native shell commands directly: `rg`, `rg --files`, `cat`, and `git`. Narrow huge output before reading it.
50
50
  - Symbol questions -> `lsp_goto_definition`, `lsp_find_references`, `lsp_symbols`, `lsp_diagnostics`.
51
51
  - Structural shapes -> the `ast-grep` skill helper or `sg` CLI with `$VAR` / `$$$` metavars.
52
52
  - Text / strings / comments / logs -> `rg` (grep).
@@ -154,8 +154,8 @@ artifact path the moment it happens. Update `## Now` and
154
154
  is your durable memory and it OUTLIVES the context window. After any
155
155
  compaction or context loss (a `Context compacted` notice, a summarized
156
156
  history, or you no longer see your own earlier steps), STOP and re-read
157
- the WHOLE notepad FIRST `omo sparkshell cat "$NOTE"`, or read the path
158
- directly — before any other action, then resume from `## Now`. Recover
157
+ the WHOLE notepad FIRST before any other action, then resume from
158
+ `## Now`. Recover
159
159
  state from the notepad; do not re-plan from scratch or re-run completed
160
160
  steps.
161
161
 
@@ -189,16 +189,8 @@ serialize only when one output strictly feeds the next.
189
189
  inactive/uninitialized, or cold-start unavailable, keep moving with
190
190
  Read/Grep/Glob/LSP and the ast-grep skill.
191
191
  - Repo-wide inspection, CLI smoke tests, git/history, bounded command
192
- output → use `omo sparkshell <command> [args...]` first. Pass ordinary
193
- commands as executable and arguments in separate argv tokens, for
194
- example `omo sparkshell rg --files`; not `omo sparkshell 'rg --files'`,
195
- because the quoted string is treated as one executable name. Raw
196
- `rg`/`grep`/`cat`/`git` are fallbacks when Sparkshell is unavailable
197
- or too narrow. `--shell` is only for shell syntax such as
198
- metacharacters, pipelines, redirects, command substitution, or
199
- variable expansion; `--tmux-pane` is only for inspecting an existing
200
- pane, never for launching ordinary commands. Sparkshell is your
201
- default lens.
192
+ output → use native shell commands directly: `rg`, `rg --files`,
193
+ `cat`, and `git`. Narrow huge output before reading it.
202
194
  - Symbols — definitions, references, rename impact, diagnostics →
203
195
  `lsp_goto_definition`, `lsp_find_references`, `lsp_symbols`,
204
196
  `lsp_diagnostics`. Use the LSP, not text search, for anything
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-ultrawork",
3
- "version": "4.14.0",
3
+ "version": "4.14.1",
4
4
  "description": "Codex plugin that injects the ultrawork orchestration directive and ships LazyCodex planning, review, QA, and gate agent roles.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -8,32 +8,6 @@ import {
8
8
  requireScripts,
9
9
  } from "../../test-support/package-smoke-fixture.js";
10
10
 
11
- function normalizeGuidance(value: string): string {
12
- return value.toLowerCase().replace(/\s+/g, " ").trim();
13
- }
14
-
15
- function expectSparkshellToolStrategyContract(value: string): void {
16
- const guidance = normalizeGuidance(value);
17
-
18
- expect(value).toContain("`omo sparkshell <command> [args...]`");
19
- expect(guidance).toMatch(/`omo sparkshell <command> \[args\.\.\.\]`[^.]*\bfirst\b/);
20
- expect(guidance).toMatch(/\brepo-wide inspection\b/);
21
- expect(guidance).toMatch(/\bcli smoke tests\b/);
22
- expect(guidance).toMatch(/\bgit\/history\b/);
23
- expect(guidance).toMatch(/\bbounded command output\b/);
24
- expect(guidance).toMatch(/\braw\b[^.]*`rg`\/`grep`\/`cat`\/`git`[^.]*\bfallbacks?\b/);
25
- expect(guidance).toMatch(/\bsparkshell is unavailable\b/);
26
- expect(guidance).toMatch(/\btoo narrow\b/);
27
- expect(value).toContain("`omo sparkshell rg --files`");
28
- expect(guidance).toMatch(/\bseparate argv tokens\b/);
29
- expect(value).toContain("not `omo sparkshell 'rg --files'`");
30
- expect(guidance).toMatch(/\bone executable name\b/);
31
- expect(guidance).toMatch(/--shell[^.]*\bmetacharacters\b[^.]*\bpipelines\b/);
32
- expect(guidance).toMatch(/--tmux-pane[^.]*\bonly\b[^.]*\binspect(?:ing)?\b[^.]*\bexisting (?:tmux )?pane\b/);
33
- expect(guidance).toMatch(/--tmux-pane[^.]*\bnever\b[^.]*\blaunch(?:ing)? ordinary commands\b/);
34
- expect(guidance).not.toMatch(/\bprefer\b[^.]*\bbefore raw shell commands\b/);
35
- }
36
-
37
11
  describe("codex ultrawork package metadata", () => {
38
12
  it("#given package metadata #when inspected #then hook ships as bundled CLI", () => {
39
13
  // given
@@ -75,23 +49,23 @@ describe("codex ultrawork package metadata", () => {
75
49
  expect(guidance).toContain("structural");
76
50
  });
77
51
 
78
- it("#given explorer guidance #when inspected #then starts codebase inspection with Sparkshell", () => {
52
+ it("#given explorer guidance #when inspected #then starts codebase inspection with native search", () => {
79
53
  // given
80
54
  const explorer = readTextFile("agents/explorer.toml");
81
55
  const directive = readTextFile("directive.md");
82
56
 
83
57
  // when
84
58
  const guidance = explorer.toLowerCase();
85
- const sparkshellIndex = guidance.indexOf("omo sparkshell <command>");
59
+ const repoInspectionIndex = guidance.indexOf("repo-wide inspection");
86
60
  const lspIndex = guidance.indexOf("lsp_goto_definition");
87
61
  const structuralIndex = guidance.indexOf("ast-grep");
88
62
 
89
63
  // then
90
- expect(sparkshellIndex).toBeGreaterThanOrEqual(0);
91
- expect(lspIndex).toBeGreaterThan(sparkshellIndex);
92
- expect(structuralIndex).toBeGreaterThan(sparkshellIndex);
93
- expectSparkshellToolStrategyContract(explorer);
94
- expectSparkshellToolStrategyContract(directive);
64
+ expect(repoInspectionIndex).toBeGreaterThanOrEqual(0);
65
+ expect(lspIndex).toBeGreaterThan(repoInspectionIndex);
66
+ expect(structuralIndex).toBeGreaterThan(repoInspectionIndex);
67
+ expect(explorer).toContain("`rg`, `rg --files`, `cat`, and `git`");
68
+ expect(directive).toContain("`rg`, `rg --files`,");
95
69
  });
96
70
 
97
71
  it("#given librarian guidance #when inspected #then names the packaged research MCP surfaces", () => {
@@ -154,8 +154,8 @@ artifact path the moment it happens. Update `## Now` and
154
154
  is your durable memory and it OUTLIVES the context window. After any
155
155
  compaction or context loss (a `Context compacted` notice, a summarized
156
156
  history, or you no longer see your own earlier steps), STOP and re-read
157
- the WHOLE notepad FIRST `omo sparkshell cat "$NOTE"`, or read the path
158
- directly — before any other action, then resume from `## Now`. Recover
157
+ the WHOLE notepad FIRST before any other action, then resume from
158
+ `## Now`. Recover
159
159
  state from the notepad; do not re-plan from scratch or re-run completed
160
160
  steps.
161
161
 
@@ -189,16 +189,8 @@ serialize only when one output strictly feeds the next.
189
189
  inactive/uninitialized, or cold-start unavailable, keep moving with
190
190
  Read/Grep/Glob/LSP and the ast-grep skill.
191
191
  - Repo-wide inspection, CLI smoke tests, git/history, bounded command
192
- output → use `omo sparkshell <command> [args...]` first. Pass ordinary
193
- commands as executable and arguments in separate argv tokens, for
194
- example `omo sparkshell rg --files`; not `omo sparkshell 'rg --files'`,
195
- because the quoted string is treated as one executable name. Raw
196
- `rg`/`grep`/`cat`/`git` are fallbacks when Sparkshell is unavailable
197
- or too narrow. `--shell` is only for shell syntax such as
198
- metacharacters, pipelines, redirects, command substitution, or
199
- variable expansion; `--tmux-pane` is only for inspecting an existing
200
- pane, never for launching ordinary commands. Sparkshell is your
201
- default lens.
192
+ output → use native shell commands directly: `rg`, `rg --files`,
193
+ `cat`, and `git`. Narrow huge output before reading it.
202
194
  - Symbols — definitions, references, rename impact, diagnostics →
203
195
  `lsp_goto_definition`, `lsp_find_references`, `lsp_symbols`,
204
196
  `lsp_diagnostics`. Use the LSP, not text search, for anything
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/codex-ulw-loop",
3
- "version": "4.14.0",
3
+ "version": "4.14.1",
4
4
  "description": "Codex plugin: durable repo-native multi-goal orchestration with embedded success criteria and observable evidence audit.",
5
5
  "type": "module",
6
6
  "packageManager": "npm@11.12.1",
@@ -23,7 +23,9 @@
23
23
  "typescript"
24
24
  ],
25
25
  "bin": {
26
- "omo-ulw-loop": "./dist/cli.js"
26
+ "omo-ulw-loop": "./dist/cli.js",
27
+ "ulw": "./dist/cli.js",
28
+ "ulw-loop": "./dist/cli.js"
27
29
  },
28
30
  "files": [
29
31
  "dist",
@@ -20,7 +20,7 @@ This skill is intentionally compact. The full workflow lives in `references/full
20
20
  ## Non-Negotiables
21
21
 
22
22
  - Use the ulw-loop CLI state under `.omo/ulw-loop`; do not hand-edit goal state.
23
- - After any compaction or context loss, re-read brief + goals + ledger FIRST (`omo sparkshell cat .omo/ulw-loop/ledger.jsonl` or read directly) plus `omo ulw-loop status --json`, then resume; never re-plan from scratch.
23
+ - After any compaction or context loss, re-read brief + goals + ledger FIRST plus `omo ulw-loop status --json`, then resume; never re-plan from scratch.
24
24
  - If `omo ulw-loop create-goals` says the existing aggregate is already complete, start unrelated new work with a fresh `--session-id <new-id>` instead of steering or forcing the completed default state. Use `--force` only to intentionally overwrite completed evidence.
25
25
  - Every success criterion needs observable evidence from a real surface: a channel (tmux, HTTP, browser, computer-use) or, for CLI- or data-shaped criteria, an auxiliary surface (CLI stdout, DB diff, parsed config dump).
26
26
  - Record evidence through the CLI only after cleanup receipts are available.
@@ -65,7 +65,7 @@ Codex subagent reliability:
65
65
  - `.omo/ulw-loop/goals.json`: goals with embedded `successCriteria` per goal.
66
66
  - `.omo/ulw-loop/ledger.jsonl`: append-only audit trail.
67
67
  - Read artifacts before resuming, steering, or checkpointing.
68
- - After compaction or context loss, re-read brief + goals + ledger FIRST via `omo sparkshell cat .omo/ulw-loop/ledger.jsonl` (or direct paths), then `omo ulw-loop status --json`. Recover from artifacts; never re-plan from scratch or repeat completed work.
68
+ - After compaction or context loss, re-read brief + goals + ledger FIRST, then `omo ulw-loop status --json`. Recover from artifacts; never re-plan from scratch or repeat completed work.
69
69
  - Never invent state outside `.omo/ulw-loop` artifacts or `omo ulw-loop status --json`.
70
70
 
71
71
  ## Bootstrap
@@ -194,9 +194,9 @@ omo ulw-loop checkpoint --goal-id <id> --status complete --evidence "<e2e eviden
194
194
  `--quality-gate-json` shape:
195
195
  ```json
196
196
  {
197
- "codeReview":{"by":"lazycodex-code-reviewer","recommendation":"APPROVE","codeQualityStatus":"CLEAR","reportPath":"packages/omo-codex/plugin/components/ulw-loop/test/fixtures/artifacts/code-review.md","evidence":"Diff review passed.","blockers":[]},
198
- "manualQa":{"by":"lazycodex-qa-executor","status":"passed","evidence":"CLI and data surfaces passed.","surfaceEvidence":[{"id":"surface-cli-pass","criterionRef":"C1","surface":"cli","invocation":"omo ulw-loop checkpoint --quality-gate-json sample-quality-gate.json --json","verdict":"passed","artifactRefs":["artifact-cli-pass"]},{"id":"surface-data-pass","criterionRef":"C2","surface":"data","invocation":"diff -u before-ledger.json after-ledger.json","verdict":"passed","artifactRefs":["artifact-data-diff"]}],"adversarialCases":[{"id":"adv-malformed-input","criterionRef":"C3","scenario":"malformed gate input omits manual QA evidence","expectedBehavior":"validator rejects ULW_LOOP_QUALITY_GATE_INVALID","verdict":"passed","artifactRefs":["artifact-cli-reject"]}],"artifactRefs":[{"id":"artifact-cli-pass","kind":"cli-transcript","description":"CLI pass artifact.","path":"packages/omo-codex/plugin/components/ulw-loop/test/fixtures/artifacts/cli-pass.txt"},{"id":"artifact-cli-reject","kind":"log","description":"Reject log artifact.","path":"packages/omo-codex/plugin/components/ulw-loop/test/fixtures/artifacts/rejection.txt"},{"id":"artifact-data-diff","kind":"data-diff","description":"Data diff artifact.","path":"packages/omo-codex/plugin/components/ulw-loop/test/fixtures/artifacts/data-diff.txt"}]},
199
- "gateReview":{"by":"lazycodex-gate-reviewer","recommendation":"APPROVE","reportPath":"packages/omo-codex/plugin/components/ulw-loop/test/fixtures/artifacts/gate-review.md","evidence":"Gate review passed.","blockers":[]},
197
+ "codeReview":{"by":"lazycodex-code-reviewer","recommendation":"APPROVE","codeQualityStatus":"CLEAR","reportPath":"test/fixtures/artifacts/code-review.md","evidence":"Diff review passed.","blockers":[]},
198
+ "manualQa":{"by":"lazycodex-qa-executor","status":"passed","evidence":"CLI and data surfaces passed.","surfaceEvidence":[{"id":"surface-cli-pass","criterionRef":"C1","surface":"cli","invocation":"omo ulw-loop checkpoint --quality-gate-json sample-quality-gate.json --json","verdict":"passed","artifactRefs":["artifact-cli-pass"]},{"id":"surface-data-pass","criterionRef":"C2","surface":"data","invocation":"diff -u before-ledger.json after-ledger.json","verdict":"passed","artifactRefs":["artifact-data-diff"]}],"adversarialCases":[{"id":"adv-malformed-input","criterionRef":"C3","scenario":"malformed gate input omits manual QA evidence","expectedBehavior":"validator rejects ULW_LOOP_QUALITY_GATE_INVALID","verdict":"passed","artifactRefs":["artifact-cli-reject"]}],"artifactRefs":[{"id":"artifact-cli-pass","kind":"cli-transcript","description":"CLI pass artifact.","path":"test/fixtures/artifacts/cli-pass.txt"},{"id":"artifact-cli-reject","kind":"log","description":"Reject log artifact.","path":"test/fixtures/artifacts/rejection.txt"},{"id":"artifact-data-diff","kind":"data-diff","description":"Data diff artifact.","path":"test/fixtures/artifacts/data-diff.txt"}]},
199
+ "gateReview":{"by":"lazycodex-gate-reviewer","recommendation":"APPROVE","reportPath":"test/fixtures/artifacts/gate-review.md","evidence":"Gate review passed.","blockers":[]},
200
200
  "iteration":{"fullRerun":true,"status":"passed","rerunCommands":["bunx vitest run packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-doc.test.ts"],"evidence":"Focused rerun passed."},
201
201
  "criteriaCoverage":{"totalCriteria":3,"passCount":3,"originalIntent":"User wanted artifact-backed completion.","desiredOutcome":"Behavior ships with review and QA evidence.","userOutcomeReview":"Result matches brief and goals.","adversarialClassesCovered":["malformed_input","stale_state"]}
202
202
  }
@@ -3,7 +3,7 @@
3
3
  "by": "lazycodex-code-reviewer",
4
4
  "recommendation": "APPROVE",
5
5
  "codeQualityStatus": "CLEAR",
6
- "reportPath": "packages/omo-codex/plugin/components/ulw-loop/test/fixtures/artifacts/code-review.md",
6
+ "reportPath": "test/fixtures/artifacts/code-review.md",
7
7
  "evidence": "Reviewed changed validator, type contract, and tests; no blocking code-quality issues remain.",
8
8
  "blockers": []
9
9
  },
@@ -68,38 +68,38 @@
68
68
  "id": "artifact-cli-pass",
69
69
  "kind": "cli-transcript",
70
70
  "description": "CLI transcript for valid quality gate acceptance.",
71
- "path": "packages/omo-codex/plugin/components/ulw-loop/test/fixtures/artifacts/cli-pass.txt"
71
+ "path": "test/fixtures/artifacts/cli-pass.txt"
72
72
  },
73
73
  {
74
74
  "id": "artifact-cli-reject",
75
75
  "kind": "log",
76
76
  "description": "Log proving malformed quality gate rejection.",
77
- "path": "packages/omo-codex/plugin/components/ulw-loop/test/fixtures/artifacts/rejection.txt"
77
+ "path": "test/fixtures/artifacts/rejection.txt"
78
78
  },
79
79
  {
80
80
  "id": "artifact-http-dump",
81
81
  "kind": "http-dump",
82
82
  "description": "HTTP dump for quality gate endpoint validation.",
83
- "path": "packages/omo-codex/plugin/components/ulw-loop/test/fixtures/artifacts/http-dump.txt"
83
+ "path": "test/fixtures/artifacts/http-dump.txt"
84
84
  },
85
85
  {
86
86
  "id": "artifact-browser-shot",
87
87
  "kind": "screenshot",
88
88
  "description": "Browser screenshot captured for the quality gate scenario.",
89
- "path": "packages/omo-codex/plugin/components/ulw-loop/test/fixtures/artifacts/browser-screenshot.txt"
89
+ "path": "test/fixtures/artifacts/browser-screenshot.txt"
90
90
  },
91
91
  {
92
92
  "id": "artifact-data-diff",
93
93
  "kind": "data-diff",
94
94
  "description": "Data diff proving stale-state compatibility.",
95
- "path": "packages/omo-codex/plugin/components/ulw-loop/test/fixtures/artifacts/data-diff.txt"
95
+ "path": "test/fixtures/artifacts/data-diff.txt"
96
96
  }
97
97
  ]
98
98
  },
99
99
  "gateReview": {
100
100
  "by": "lazycodex-gate-reviewer",
101
101
  "recommendation": "APPROVE",
102
- "reportPath": "packages/omo-codex/plugin/components/ulw-loop/test/fixtures/artifacts/gate-review.md",
102
+ "reportPath": "test/fixtures/artifacts/gate-review.md",
103
103
  "evidence": "Rechecked reviewer reports, manual QA artifacts, and criteria coverage; gate is approved.",
104
104
  "blockers": []
105
105
  },
@@ -60,10 +60,12 @@ describe("package.json", () => {
60
60
  expect((pkg["engines"] as Record<string, unknown>)["node"]).toBe(">=20.0.0");
61
61
  });
62
62
 
63
- it("#given package metadata #when bin is inspected #then exposes the omo-ulw-loop binary pointing at dist/cli.js", async () => {
63
+ it("#given package metadata #when bin is inspected #then exposes the ULW loop binaries pointing at dist/cli.js", async () => {
64
64
  const pkg = await readJson("package.json") as Record<string, unknown>;
65
65
  const bin = pkg["bin"] as Record<string, string>;
66
66
  expect(bin["omo-ulw-loop"]).toBe("./dist/cli.js");
67
+ expect(bin["ulw"]).toBe("./dist/cli.js");
68
+ expect(bin["ulw-loop"]).toBe("./dist/cli.js");
67
69
  });
68
70
 
69
71
  it("ships the expected files for npm publish", async () => {
@@ -8,8 +8,8 @@ import { describe, expect, it } from "vitest";
8
8
  import { validateQualityGate } from "../src/quality-gate.js";
9
9
 
10
10
  const FULL_WORKFLOW_URL = new URL("../skills/ulw-loop/references/full-workflow.md", import.meta.url);
11
- const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../../../../../..");
12
- const FS_OPTS = { repoRoot: REPO_ROOT, fs: { existsSync, statSync } } as const;
11
+ const COMPONENT_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
12
+ const FS_OPTS = { repoRoot: COMPONENT_ROOT, fs: { existsSync, statSync } } as const;
13
13
 
14
14
  function extractQualityGateSample(workflow: string): unknown {
15
15
  const sectionStart = workflow.indexOf("`--quality-gate-json` shape:");
@@ -1,5 +1,6 @@
1
1
  import { existsSync, statSync } from "node:fs";
2
2
  import { readFile } from "node:fs/promises";
3
+ import { dirname, resolve } from "node:path";
3
4
  import { fileURLToPath } from "node:url";
4
5
 
5
6
  import { describe, expect, it } from "vitest";
@@ -12,7 +13,7 @@ const VALID_GATE = {
12
13
  by: "lazycodex-code-reviewer",
13
14
  recommendation: "APPROVE",
14
15
  codeQualityStatus: "CLEAR",
15
- reportPath: "packages/omo-codex/plugin/components/ulw-loop/test/fixtures/artifacts/code-review.md",
16
+ reportPath: "test/fixtures/artifacts/code-review.md",
16
17
  evidence: "Reviewed diff and focused tests; no blocking code-quality issues remain.",
17
18
  blockers: [],
18
19
  },
@@ -45,20 +46,20 @@ const VALID_GATE = {
45
46
  id: "artifact-cli-pass",
46
47
  kind: "cli-transcript",
47
48
  description: "CLI transcript for valid quality gate acceptance.",
48
- path: "packages/omo-codex/plugin/components/ulw-loop/test/fixtures/artifacts/cli-pass.txt",
49
+ path: "test/fixtures/artifacts/cli-pass.txt",
49
50
  },
50
51
  {
51
52
  id: "artifact-cli-reject",
52
53
  kind: "log",
53
54
  description: "Log proving malformed quality gate rejection.",
54
- path: "packages/omo-codex/plugin/components/ulw-loop/test/fixtures/artifacts/rejection.txt",
55
+ path: "test/fixtures/artifacts/rejection.txt",
55
56
  },
56
57
  ],
57
58
  },
58
59
  gateReview: {
59
60
  by: "lazycodex-gate-reviewer",
60
61
  recommendation: "APPROVE",
61
- reportPath: "packages/omo-codex/plugin/components/ulw-loop/test/fixtures/artifacts/gate-review.md",
62
+ reportPath: "test/fixtures/artifacts/gate-review.md",
62
63
  evidence: "Rechecked reviewer reports and manual QA artifacts; gate is approved.",
63
64
  blockers: [],
64
65
  },
@@ -77,8 +78,8 @@ const VALID_GATE = {
77
78
  adversarialClassesCovered: ["malformed_input", "stale_state"],
78
79
  },
79
80
  } as const;
80
- const REPO_ROOT = fileURLToPath(new URL("../../../../../..", import.meta.url));
81
- const FS_OPTS = { repoRoot: REPO_ROOT, fs: { existsSync, statSync } } as const;
81
+ const COMPONENT_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
82
+ const FS_OPTS = { repoRoot: COMPONENT_ROOT, fs: { existsSync, statSync } } as const;
82
83
 
83
84
  function makeGate(overrides: Record<string, unknown> = {}): Record<string, unknown> {
84
85
  return { ...VALID_GATE, ...overrides };