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.
- package/.agents/skills/codex-qa/references/isolation.md +1 -1
- package/README.md +1 -1
- package/dist/cli/doctor/framework/types.d.ts +2 -0
- package/dist/cli/index.js +149 -1426
- package/dist/cli-node/index.js +149 -1426
- package/dist/index.js +4255 -714
- package/dist/plugin/native-skills.d.ts +9 -0
- package/dist/plugin/system-transform.d.ts +1 -1
- package/dist/skills/lcx-contribute-bug-fix/SKILL.md +44 -13
- package/dist/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
- package/dist/skills/lcx-doctor/SKILL.md +48 -13
- package/dist/skills/lcx-doctor/agents/openai.yaml +1 -1
- package/dist/skills/lcx-report-bug/SKILL.md +44 -13
- package/dist/skills/lcx-report-bug/agents/openai.yaml +1 -1
- package/dist/skills/visual-qa/SKILL.md +5 -5
- package/dist/skills/visual-qa/references/agent-browser-setup.md +5 -4
- package/dist/skills/visual-qa/scripts/cli.test.ts +7 -2
- package/dist/skills/visual-qa/scripts/skill-prompt-contract.test.ts +10 -2
- package/dist/skills/visual-qa/scripts/visual-qa.mjs +530 -0
- package/dist/tools/delegate-task/skill-resolver.d.ts +1 -0
- package/dist/tools/delegate-task/types.d.ts +2 -0
- package/dist/tools/skill/types.d.ts +1 -0
- package/package.json +26 -13
- package/packages/git-bash-mcp/dist/cli.js +2 -0
- package/packages/lsp-daemon/dist/cli.js +15 -4
- package/packages/lsp-daemon/dist/ensure-daemon.d.ts +1 -0
- package/packages/lsp-daemon/dist/ensure-daemon.js +9 -3
- package/packages/lsp-daemon/dist/index.js +15 -4
- package/packages/lsp-daemon/dist/paths.d.ts +1 -0
- package/packages/lsp-daemon/dist/paths.js +6 -1
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +4 -7
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/src/setup.ts +1 -1
- package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +123 -10
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +123 -10
- package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/src/mcp-bridge.ts +126 -9
- package/packages/omo-codex/plugin/components/codegraph/test/serve-mcp-bridge.test.ts +141 -0
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +90 -25
- package/packages/omo-codex/plugin/components/lsp/dist/codex-hook.js +2 -0
- package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.d.ts +2 -0
- package/packages/omo-codex/plugin/components/lsp/dist/daemon-cli-path.js +56 -0
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/src/cli.ts +2 -5
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +2 -0
- package/packages/omo-codex/plugin/components/lsp/src/daemon-cli-path.ts +65 -0
- package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +6 -1
- package/packages/omo-codex/plugin/components/rules/dist/cli.js +8 -106
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/src/static-injection.ts +4 -17
- package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/SKILL.md +17 -5
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-state.mjs +63 -1
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team.mjs +80 -50
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/explorer.toml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/directive.md +4 -12
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +7 -33
- package/packages/omo-codex/plugin/components/ulw-loop/directive.md +4 -12
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +4 -2
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/sample-quality-gate.json +7 -7
- package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +3 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-doc.test.ts +2 -2
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate.test.ts +7 -6
- package/packages/omo-codex/plugin/package-lock.json +16 -56
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/SKILL.md +44 -13
- package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/lcx-doctor/SKILL.md +48 -13
- package/packages/omo-codex/plugin/skills/lcx-doctor/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +44 -13
- package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/teammode/SKILL.md +17 -5
- package/packages/omo-codex/plugin/skills/teammode/scripts/team-state.mjs +63 -1
- package/packages/omo-codex/plugin/skills/teammode/scripts/team.mjs +80 -50
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +1 -1
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +4 -4
- package/packages/omo-codex/plugin/skills/visual-qa/SKILL.md +5 -5
- package/packages/omo-codex/plugin/skills/visual-qa/references/agent-browser-setup.md +5 -4
- package/packages/omo-codex/plugin/skills/visual-qa/scripts/visual-qa.mjs +530 -0
- package/packages/omo-codex/plugin/test/bootstrap-binlinks.test.mjs +38 -2
- package/packages/omo-codex/plugin/test/component-bin-names.test.mjs +12 -11
- package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +99 -1
- package/packages/omo-codex/plugin/test/lcx-bug-skills.test.mjs +62 -8
- package/packages/omo-codex/plugin/test/node-install-surface.test.mjs +1 -0
- package/packages/omo-codex/plugin/test/teammode-archive-ambiguity.test.mjs +102 -0
- package/packages/omo-codex/plugin/test/teammode-safety-fixture.mjs +41 -1
- package/packages/omo-codex/plugin/test/teammode-safety.test.mjs +93 -0
- package/packages/omo-codex/scripts/install-bin-links.test.mjs +6 -0
- package/packages/omo-codex/scripts/install-cache-copy.test.mjs +28 -0
- package/packages/omo-codex/scripts/install-config-autonomous-features.test.mjs +43 -3
- package/packages/omo-codex/scripts/install-delegated-command.test.mjs +93 -0
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +103 -28
- package/packages/omo-codex/scripts/install-local-entrypoint.test.mjs +21 -2
- package/packages/omo-codex/scripts/install-local.test.mjs +0 -1
- package/packages/shared-skills/skills/lcx-contribute-bug-fix/SKILL.md +44 -13
- package/packages/shared-skills/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
- package/packages/shared-skills/skills/lcx-doctor/SKILL.md +48 -13
- package/packages/shared-skills/skills/lcx-doctor/agents/openai.yaml +1 -1
- package/packages/shared-skills/skills/lcx-report-bug/SKILL.md +44 -13
- package/packages/shared-skills/skills/lcx-report-bug/agents/openai.yaml +1 -1
- package/packages/shared-skills/skills/visual-qa/SKILL.md +5 -5
- package/packages/shared-skills/skills/visual-qa/references/agent-browser-setup.md +5 -4
- package/packages/shared-skills/skills/visual-qa/scripts/cli.test.ts +7 -2
- package/packages/shared-skills/skills/visual-qa/scripts/skill-prompt-contract.test.ts +10 -2
- package/packages/shared-skills/skills/visual-qa/scripts/visual-qa.mjs +530 -0
- package/dist/cli/sparkshell-appserver-websocket.d.ts +0 -4
- package/dist/cli/sparkshell-appserver.d.ts +0 -16
- package/dist/cli/sparkshell-condense.d.ts +0 -10
- package/dist/cli/sparkshell-parse.d.ts +0 -24
- package/dist/cli/sparkshell-session-context.d.ts +0 -20
- package/dist/cli/sparkshell-spark.d.ts +0 -23
- package/dist/cli/sparkshell.d.ts +0 -33
- package/dist/shared/sparkshell-awareness.d.ts +0 -5
- package/packages/omo-codex/plugin/components/rules/src/sparkshell-awareness.ts +0 -106
- 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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
160
|
-
const
|
|
161
|
-
const result =
|
|
162
|
-
|
|
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
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
|
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
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
|
251
|
-
|
|
252
|
-
|
|
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
|
|
|
@@ -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
|
-
-
|
|
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
|
|
158
|
-
|
|
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
|
|
193
|
-
|
|
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.
|
|
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
|
|
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
|
|
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(
|
|
91
|
-
expect(lspIndex).toBeGreaterThan(
|
|
92
|
-
expect(structuralIndex).toBeGreaterThan(
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
158
|
-
|
|
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
|
|
193
|
-
|
|
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.
|
|
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
|
|
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.
|
package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md
CHANGED
|
@@ -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
|
|
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":"
|
|
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":"
|
|
199
|
-
"gateReview":{"by":"lazycodex-gate-reviewer","recommendation":"APPROVE","reportPath":"
|
|
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
|
}
|
package/packages/omo-codex/plugin/components/ulw-loop/test/fixtures/sample-quality-gate.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"by": "lazycodex-code-reviewer",
|
|
4
4
|
"recommendation": "APPROVE",
|
|
5
5
|
"codeQualityStatus": "CLEAR",
|
|
6
|
-
"reportPath": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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
|
|
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
|
|
12
|
-
const FS_OPTS = { repoRoot:
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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: "
|
|
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
|
|
81
|
-
const FS_OPTS = { repoRoot:
|
|
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 };
|