oh-my-opencode 4.14.0 → 4.14.2

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 (140) 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 +223 -1447
  5. package/dist/cli-node/index.js +223 -1447
  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/frontend/ATTRIBUTION.md +7 -1
  10. package/dist/skills/frontend/SKILL.md +4 -3
  11. package/dist/skills/frontend/references/design/README.md +4 -3
  12. package/dist/skills/frontend/references/design/_INDEX.md +5 -3
  13. package/dist/skills/frontend/references/design/aside.md +209 -0
  14. package/dist/skills/lcx-contribute-bug-fix/SKILL.md +44 -13
  15. package/dist/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
  16. package/dist/skills/lcx-doctor/SKILL.md +48 -13
  17. package/dist/skills/lcx-doctor/agents/openai.yaml +1 -1
  18. package/dist/skills/lcx-report-bug/SKILL.md +44 -13
  19. package/dist/skills/lcx-report-bug/agents/openai.yaml +1 -1
  20. package/dist/skills/visual-qa/SKILL.md +5 -5
  21. package/dist/skills/visual-qa/references/agent-browser-setup.md +5 -4
  22. package/dist/skills/visual-qa/scripts/cli.test.ts +7 -2
  23. package/dist/skills/visual-qa/scripts/skill-prompt-contract.test.ts +10 -2
  24. package/dist/skills/visual-qa/scripts/visual-qa.mjs +530 -0
  25. package/dist/tools/delegate-task/skill-resolver.d.ts +1 -0
  26. package/dist/tools/delegate-task/types.d.ts +2 -0
  27. package/dist/tools/skill/types.d.ts +1 -0
  28. package/package.json +26 -13
  29. package/packages/git-bash-mcp/dist/cli.js +2 -0
  30. package/packages/lsp-daemon/dist/cli.js +15 -4
  31. package/packages/lsp-daemon/dist/ensure-daemon.d.ts +1 -0
  32. package/packages/lsp-daemon/dist/ensure-daemon.js +9 -3
  33. package/packages/lsp-daemon/dist/index.js +15 -4
  34. package/packages/lsp-daemon/dist/paths.d.ts +1 -0
  35. package/packages/lsp-daemon/dist/paths.js +6 -1
  36. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  37. package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +4 -7
  38. package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
  39. package/packages/omo-codex/plugin/components/bootstrap/src/setup.ts +1 -1
  40. package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +123 -10
  41. package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +123 -10
  42. package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
  43. package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +126 -9
  44. package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +141 -0
  45. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  46. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  47. package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
  48. package/packages/omo-codex/plugin/components/lsp/dist/cli.js +90 -25
  49. package/packages/omo-codex/plugin/components/lsp/dist/codex-hook.js +2 -0
  50. package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.d.ts +2 -0
  51. package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.js +56 -0
  52. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  53. package/packages/omo-codex/plugin/components/lsp/src/cli.ts +2 -5
  54. package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +2 -0
  55. package/packages/omo-codex/plugin/components/lsp/src/daemon-cli-path.ts +65 -0
  56. package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +6 -1
  57. package/packages/omo-codex/plugin/components/rules/dist/cli.js +8 -106
  58. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  59. package/packages/omo-codex/plugin/components/rules/src/static-injection.ts +4 -17
  60. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
  61. package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
  62. package/packages/omo-codex/plugin/components/teammode/skills/teammode/SKILL.md +17 -5
  63. package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-state.mjs +63 -1
  64. package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team.mjs +80 -50
  65. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  66. package/packages/omo-codex/plugin/components/ultrawork/agents/explorer.toml +1 -1
  67. package/packages/omo-codex/plugin/components/ultrawork/directive.md +4 -12
  68. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  69. package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +7 -33
  70. package/packages/omo-codex/plugin/components/ulw-loop/directive.md +4 -12
  71. package/packages/omo-codex/plugin/components/ulw-loop/package.json +4 -2
  72. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +1 -1
  73. package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +4 -4
  74. package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/sample-quality-gate.json +7 -7
  75. package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +3 -1
  76. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-doc.test.ts +2 -2
  77. package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate.test.ts +7 -6
  78. package/packages/omo-codex/plugin/package-lock.json +16 -56
  79. package/packages/omo-codex/plugin/package.json +1 -1
  80. package/packages/omo-codex/plugin/skills/frontend/ATTRIBUTION.md +7 -1
  81. package/packages/omo-codex/plugin/skills/frontend/SKILL.md +4 -3
  82. package/packages/omo-codex/plugin/skills/frontend/references/design/README.md +4 -3
  83. package/packages/omo-codex/plugin/skills/frontend/references/design/_INDEX.md +5 -3
  84. package/packages/omo-codex/plugin/skills/frontend/references/design/aside.md +209 -0
  85. package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/SKILL.md +44 -13
  86. package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
  87. package/packages/omo-codex/plugin/skills/lcx-doctor/SKILL.md +48 -13
  88. package/packages/omo-codex/plugin/skills/lcx-doctor/agents/openai.yaml +1 -1
  89. package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +44 -13
  90. package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +1 -1
  91. package/packages/omo-codex/plugin/skills/teammode/SKILL.md +17 -5
  92. package/packages/omo-codex/plugin/skills/teammode/scripts/team-state.mjs +63 -1
  93. package/packages/omo-codex/plugin/skills/teammode/scripts/team.mjs +80 -50
  94. package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +1 -1
  95. package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +4 -4
  96. package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +5 -5
  97. package/packages/omo-codex/plugin/skills/visual-qa/references/agent-browser-setup.md +5 -4
  98. package/packages/omo-codex/plugin/skills/visual-qa/scripts/visual-qa.mjs +530 -0
  99. package/packages/omo-codex/plugin/test/aggregate-skills.test.mjs +23 -0
  100. package/packages/omo-codex/plugin/test/bootstrap-binlinks.test.mjs +38 -2
  101. package/packages/omo-codex/plugin/test/component-bin-names.test.mjs +12 -11
  102. package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +99 -1
  103. package/packages/omo-codex/plugin/test/lcx-bug-skills.test.mjs +62 -8
  104. package/packages/omo-codex/plugin/test/node-install-surface.test.mjs +1 -0
  105. package/packages/omo-codex/plugin/test/teammode-archive-ambiguity.test.mjs +102 -0
  106. package/packages/omo-codex/plugin/test/teammode-safety-fixture.mjs +41 -1
  107. package/packages/omo-codex/plugin/test/teammode-safety.test.mjs +93 -0
  108. package/packages/omo-codex/scripts/install-bin-links.test.mjs +6 -0
  109. package/packages/omo-codex/scripts/install-cache-copy.test.mjs +28 -0
  110. package/packages/omo-codex/scripts/install-config-autonomous-features.test.mjs +43 -3
  111. package/packages/omo-codex/scripts/install-delegated-command.test.mjs +93 -0
  112. package/packages/omo-codex/scripts/install-dist/install-local.mjs +169 -41
  113. package/packages/omo-codex/scripts/install-local-entrypoint.test.mjs +21 -2
  114. package/packages/omo-codex/scripts/install-local.test.mjs +0 -1
  115. package/packages/shared-skills/skills/frontend/ATTRIBUTION.md +7 -1
  116. package/packages/shared-skills/skills/frontend/SKILL.md +4 -3
  117. package/packages/shared-skills/skills/frontend/references/design/README.md +4 -3
  118. package/packages/shared-skills/skills/frontend/references/design/_INDEX.md +5 -3
  119. package/packages/shared-skills/skills/frontend/references/design/aside.md +209 -0
  120. package/packages/shared-skills/skills/lcx-contribute-bug-fix/SKILL.md +44 -13
  121. package/packages/shared-skills/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
  122. package/packages/shared-skills/skills/lcx-doctor/SKILL.md +48 -13
  123. package/packages/shared-skills/skills/lcx-doctor/agents/openai.yaml +1 -1
  124. package/packages/shared-skills/skills/lcx-report-bug/SKILL.md +44 -13
  125. package/packages/shared-skills/skills/lcx-report-bug/agents/openai.yaml +1 -1
  126. package/packages/shared-skills/skills/visual-qa/SKILL.md +5 -5
  127. package/packages/shared-skills/skills/visual-qa/references/agent-browser-setup.md +5 -4
  128. package/packages/shared-skills/skills/visual-qa/scripts/cli.test.ts +7 -2
  129. package/packages/shared-skills/skills/visual-qa/scripts/skill-prompt-contract.test.ts +10 -2
  130. package/packages/shared-skills/skills/visual-qa/scripts/visual-qa.mjs +530 -0
  131. package/dist/cli/sparkshell-appserver-websocket.d.ts +0 -4
  132. package/dist/cli/sparkshell-appserver.d.ts +0 -16
  133. package/dist/cli/sparkshell-condense.d.ts +0 -10
  134. package/dist/cli/sparkshell-parse.d.ts +0 -24
  135. package/dist/cli/sparkshell-session-context.d.ts +0 -20
  136. package/dist/cli/sparkshell-spark.d.ts +0 -23
  137. package/dist/cli/sparkshell.d.ts +0 -33
  138. package/dist/shared/sparkshell-awareness.d.ts +0 -5
  139. package/packages/omo-codex/plugin/components/rules/src/sparkshell-awareness.ts +0 -106
  140. 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
 
@@ -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
  }
@@ -35,7 +35,7 @@ Verify a rendered UI against intent using objective script evidence plus two par
35
35
  - Use when output must match a mock, a baseline, or a stated design intent; when you suspect a regression; when CJK (Korean/Japanese/Chinese) text may clip, misalign, or wrap awkwardly; when a claimed design system might actually be a flat image; when a terminal layout may overflow or its borders may break.
36
36
  - Skip when there is no rendered surface (pure backend or library logic with no visual or terminal output). For broad post-implementation review use review-work; this skill is the visual specialist.
37
37
 
38
- In the commands below, `$SKILL_DIR` is this skill's own directory (the folder containing this SKILL.md). The bundled script lives at `scripts/cli.ts` inside it.
38
+ In the commands below, `$SKILL_DIR` is this skill's own directory (the folder containing this SKILL.md). The bundled Node evidence CLI lives at `scripts/visual-qa.mjs` inside it; the TypeScript source in `scripts/cli.ts` is for development.
39
39
 
40
40
  ## Step 1 - Detect the surface
41
41
 
@@ -66,11 +66,11 @@ Every gate runs on captures produced AFTER the last edit to the rendered source.
66
66
  ### Web
67
67
 
68
68
  1. Capture a REFERENCE image: the user's mock/target, generated page snapshot, Figma export, source-site capture, or known-good baseline. Save as PNG. If the user provided overview text or annotations, save them next to the image and treat them as part of the reference packet.
69
- 2. Capture the ACTUAL rendered screenshot at the same viewport size. In Codex, when `browser:control-in-app-browser` is available and the page does not need an authenticated user browser session, use that Browser plugin first for navigation, page state inspection, and screenshots. If it is unavailable or lacks the needed capture action, use the project's configured browser tooling (the playwright, agent-browser, or dev-browser skill). Save as PNG. If none is configured or available, install [agent-browser](https://github.com/vercel-labs/agent-browser) (`bun add -g agent-browser && agent-browser install`) and capture with it — see `$SKILL_DIR/references/agent-browser-setup.md` for the full setup, including how to shoot a fixed-viewport screenshot.
69
+ 2. Capture the ACTUAL rendered screenshot at the same viewport size. In Codex, when `browser:control-in-app-browser` is available and the page does not need an authenticated user browser session, use that Browser plugin first for navigation, page state inspection, and screenshots. If it is unavailable or lacks the needed capture action, use the project's configured browser tooling (the playwright, agent-browser, or dev-browser skill). Save as PNG. If none is configured or available, install [agent-browser](https://github.com/vercel-labs/agent-browser) (`npm install -g agent-browser && agent-browser install`) and capture with it — see `$SKILL_DIR/references/agent-browser-setup.md` for the full setup, including how to shoot a fixed-viewport screenshot.
70
70
  3. Run the diff and keep the JSON:
71
71
 
72
72
  ```
73
- bun "$SKILL_DIR/scripts/cli.ts" image-diff <reference.png> <actual.png>
73
+ node "$SKILL_DIR/scripts/visual-qa.mjs" image-diff <reference.png> <actual.png>
74
74
  ```
75
75
 
76
76
  Key fields: `dimensionsMatch`, `diffRatio` (0..1), `similarityScore` (0..100), `alphaChannelIntact`, `hotspots[]` (grid regions ranked by `diffRatio`).
@@ -104,7 +104,7 @@ metadata with cleanup receipt.
104
104
  3. Run the check with the REAL terminal width and keep the JSON:
105
105
 
106
106
  ```
107
- bun "$SKILL_DIR/scripts/cli.ts" tui-check capture.txt --cols <N>
107
+ node "$SKILL_DIR/scripts/visual-qa.mjs" tui-check capture.txt --cols <N>
108
108
  ```
109
109
 
110
110
  Key fields: `maxWidth`, `overflowLines[]`, `borderMisaligned`, `wideCharColumns[]`, `hasAnsi`.
@@ -267,7 +267,7 @@ Run this step IN ADDITION to Steps 1-4 when the original user task has a concret
267
267
  1. Pixel-perfect design-compare subagent (visual oracle). Dispatch a focused, read-only design-compare reviewer (recommend `gpt-5.5` with medium reasoning). It must crop/zoom BOTH the reference (target / Figma export / source-site screenshot / generated page snapshot) and the ACTUAL screenshot into matching regions and read them **pixel-by-pixel** - header, nav, each card, spacing, type ramp, color tokens - not at a glance. It must also compare the overview text or annotations against the rendered content and DOM text. Anchor every claim with the bundled tool:
268
268
 
269
269
  ```
270
- bun "$SKILL_DIR/scripts/cli.ts" image-diff <reference.png> <actual.png>
270
+ node "$SKILL_DIR/scripts/visual-qa.mjs" image-diff <reference.png> <actual.png>
271
271
  ```
272
272
 
273
273
  It judges whether layout geometry, spacing, design tokens (color, type, radius, shadow), and the design itself are identical to the target, region by region. Anything off by more than rounding is a finding.
@@ -11,11 +11,12 @@ Repo: https://github.com/vercel-labs/agent-browser
11
11
  ## Install
12
12
 
13
13
  ```
14
- bun add -g agent-browser && agent-browser install
14
+ npm install -g agent-browser && agent-browser install
15
15
  ```
16
16
 
17
- `bun add -g agent-browser` installs the CLI globally; `agent-browser install` downloads the
18
- managed browser it drives. (Equivalent with npm: `npm install -g agent-browser && agent-browser install`.)
17
+ `npm install -g agent-browser` installs the CLI globally; `agent-browser install` downloads the
18
+ managed browser it drives. If Bun is already available, `bun add -g agent-browser && agent-browser install`
19
+ is equivalent, but npm is the default because LazyCodex/Codex installations do not require Bun.
19
20
 
20
21
  Confirm it is ready and discover the current flags:
21
22
 
@@ -36,7 +37,7 @@ agent-browser screenshot actual.png # add --full for full-page, --screensho
36
37
  Then feed the PNG into the diff exactly as in Step 2:
37
38
 
38
39
  ```
39
- bun "$SKILL_DIR/scripts/cli.ts" image-diff <reference.png> actual.png
40
+ node "$SKILL_DIR/scripts/visual-qa.mjs" image-diff <reference.png> actual.png
40
41
  ```
41
42
 
42
43
  Match the viewport numbers to whatever the reference/mock was captured at; mismatched