oh-my-opencode 4.5.12 → 4.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/opencode-qa/SKILL.md +194 -0
- package/.agents/skills/opencode-qa/references/cli-commands.md +188 -0
- package/.agents/skills/opencode-qa/references/db-investigation.md +197 -0
- package/.agents/skills/opencode-qa/references/events-hooks.md +110 -0
- package/.agents/skills/opencode-qa/references/sdk.md +96 -0
- package/.agents/skills/opencode-qa/references/server-api.md +200 -0
- package/.agents/skills/opencode-qa/references/testing-harness.md +218 -0
- package/.agents/skills/opencode-qa/references/tui-tmux.md +52 -0
- package/.agents/skills/opencode-qa/scripts/db-session-by-id.sh +53 -0
- package/.agents/skills/opencode-qa/scripts/db-session-by-name.sh +57 -0
- package/.agents/skills/opencode-qa/scripts/db-session-by-text.sh +158 -0
- package/.agents/skills/opencode-qa/scripts/export-roundtrip.sh +57 -0
- package/.agents/skills/opencode-qa/scripts/lib/common.sh +216 -0
- package/.agents/skills/opencode-qa/scripts/server-smoke.sh +64 -0
- package/.agents/skills/opencode-qa/scripts/sse-hook-probe.sh +106 -0
- package/.agents/skills/opencode-qa/scripts/tui-smoke.sh +89 -0
- package/README.ja.md +13 -3
- package/README.ko.md +13 -3
- package/README.md +24 -14
- package/README.ru.md +13 -3
- package/README.zh-cn.md +13 -3
- package/bin/oh-my-opencode.js +4 -3
- package/bin/oh-my-opencode.test.ts +35 -7
- package/bin/platform.d.ts +1 -1
- package/bin/platform.js +4 -4
- package/bin/platform.test.ts +31 -9
- package/bin/version-mismatch.js +47 -0
- package/bin/version-mismatch.test.ts +120 -0
- package/dist/cli/cleanup-command.d.ts +4 -0
- package/dist/cli/cleanup.d.ts +11 -0
- package/dist/cli/cli-program.d.ts +2 -1
- package/dist/cli/codex-ulw-loop.d.ts +12 -0
- package/dist/cli/doctor/checks/tui-plugin-config.d.ts +2 -0
- package/dist/cli/index.js +2189 -529
- package/dist/cli/install-codex/codex-cache.d.ts +1 -0
- package/dist/cli/install-codex/codex-cleanup-config.d.ts +6 -0
- package/dist/cli/install-codex/codex-cleanup.d.ts +21 -0
- package/dist/cli/install-codex/codex-config-permissions.d.ts +1 -0
- package/dist/cli/install-codex/codex-config-reasoning.d.ts +2 -0
- package/dist/cli/install-codex/codex-config-toml.d.ts +2 -1
- package/dist/cli/install-codex/codex-installation-detection.d.ts +36 -0
- package/dist/cli/install-codex/codex-model-catalog.d.ts +13 -0
- package/dist/cli/install-codex/codex-package-layout.d.ts +1 -0
- package/dist/cli/install-codex/codex-project-local-cleanup-best-effort.d.ts +7 -0
- package/dist/cli/install-codex/codex-project-local-cleanup.d.ts +35 -0
- package/dist/cli/install-codex/git-bash.d.ts +35 -0
- package/dist/cli/install-codex/index.d.ts +4 -0
- package/dist/cli/install-codex/toml-section-editor.d.ts +2 -0
- package/dist/cli/install-codex/types.d.ts +20 -0
- package/dist/cli/run/event-state.d.ts +1 -0
- package/dist/cli/run/poll-for-completion.d.ts +1 -0
- package/dist/cli/run/prompt-start.d.ts +7 -0
- package/dist/cli/star-request.d.ts +9 -0
- package/dist/config/schema/hooks.d.ts +0 -1
- package/dist/create-hooks.d.ts +0 -1
- package/dist/features/background-agent/concurrency.d.ts +1 -0
- package/dist/features/background-agent/process-cleanup.d.ts +6 -0
- package/dist/features/builtin-skills/skills/debugging.d.ts +2 -0
- package/dist/features/builtin-skills/skills/index.d.ts +1 -0
- package/dist/features/claude-code-session-state/state.d.ts +1 -0
- package/dist/features/opencode-skill-loader/index.d.ts +1 -0
- package/dist/features/opencode-skill-loader/opencode-config-skills-reader.d.ts +5 -0
- package/dist/features/tmux-subagent/attachable-session-status.d.ts +1 -1
- package/dist/features/tmux-subagent/session-status-parser.d.ts +1 -0
- package/dist/hooks/comment-checker/cli.d.ts +1 -0
- package/dist/hooks/index.d.ts +0 -1
- package/dist/hooks/tasks-todowrite-disabler/constants.d.ts +1 -1
- package/dist/index.js +1077 -563
- package/dist/plugin/hooks/create-core-hooks.d.ts +0 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts +1 -2
- package/dist/plugin/messages-transform.d.ts +8 -1
- package/dist/plugin/user-abort-interrupted-recovery-guard.d.ts +6 -0
- package/dist/shared/command-executor/execute-hook-command.d.ts +2 -0
- package/dist/shared/prompt-async-gate/recent-dispatches.d.ts +14 -0
- package/dist/shared/prompt-async-gate/semantic-dedupe.d.ts +7 -0
- package/dist/shared/prompt-async-gate/session-idle-dispatch.d.ts +1 -0
- package/dist/shared/prompt-async-gate/timing.d.ts +1 -0
- package/dist/shared/prompt-async-gate/types.d.ts +2 -0
- package/dist/shared/prompt-async-gate.d.ts +1 -1
- package/dist/tools/skill/description-formatter.d.ts +5 -1
- package/dist/tools/skill/types.d.ts +1 -0
- package/package.json +22 -18
- package/packages/ast-grep-mcp/dist/cli.js +53 -9
- package/packages/git-bash-mcp/dist/cli.js +367 -0
- package/packages/lsp-tools-mcp/dist/lsp/process.js +1 -1
- package/packages/omo-codex/plugin/.mcp.json +11 -0
- package/packages/omo-codex/plugin/components/comment-checker/README.md +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +29 -0
- package/packages/omo-codex/plugin/components/git-bash/package.json +23 -0
- package/packages/omo-codex/plugin/components/git-bash/src/cli.ts +33 -0
- package/packages/omo-codex/plugin/components/git-bash/src/codex-hook.ts +180 -0
- package/packages/omo-codex/plugin/components/git-bash/src/index.ts +10 -0
- package/packages/omo-codex/plugin/components/git-bash/test/codex-hook.test.ts +195 -0
- package/packages/omo-codex/plugin/components/git-bash/tsconfig.build.json +13 -0
- package/packages/omo-codex/plugin/components/git-bash/tsconfig.json +25 -0
- package/packages/omo-codex/plugin/components/lsp/README.md +1 -1
- package/packages/omo-codex/plugin/components/lsp/src/cli.ts +5 -5
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook-cli.ts +33 -0
- package/packages/omo-codex/plugin/components/lsp/src/codex-hook.ts +19 -27
- package/packages/omo-codex/plugin/components/lsp/test/codex-hook-cli.test.ts +28 -0
- package/packages/omo-codex/plugin/components/lsp/test/codex-hook-errors.test.ts +55 -0
- package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +7 -5
- package/packages/omo-codex/plugin/components/rules/README.md +1 -1
- package/packages/omo-codex/plugin/components/rules/bundled-rules/hephaestus.md +6 -4
- package/packages/omo-codex/plugin/components/rules/bundled-rules/windows-git-bash.md +10 -0
- package/packages/omo-codex/plugin/components/rules/src/post-compact-budget.ts +0 -2
- package/packages/omo-codex/plugin/components/rules/test/package-smoke.test.ts +3 -1
- package/packages/omo-codex/plugin/components/rules/test/windows-git-bash-bundled-rule.test.ts +97 -0
- package/packages/omo-codex/plugin/components/start-work-continuation/directive.md +6 -5
- package/packages/omo-codex/plugin/components/start-work-continuation/test/codex-hook.test.ts +22 -0
- package/packages/omo-codex/plugin/components/ultrawork/CHANGELOG.md +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/README.md +3 -3
- package/packages/omo-codex/plugin/components/ultrawork/agents/codex-ultrawork-reviewer.toml +4 -1
- package/packages/omo-codex/plugin/components/ultrawork/agents/librarian.toml +8 -7
- package/packages/omo-codex/plugin/components/ultrawork/agents/plan.toml +9 -8
- package/packages/omo-codex/plugin/components/ultrawork/directive.md +32 -6
- package/packages/omo-codex/plugin/components/ultrawork/test/codex-hook.test.ts +27 -4
- package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +25 -0
- package/packages/omo-codex/plugin/components/ulw-loop/README.md +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/SKILL.md +28 -205
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +231 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +12 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint.test.ts +19 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +102 -5
- package/packages/omo-codex/plugin/hooks/hooks.json +35 -2
- package/packages/omo-codex/plugin/model-catalog.json +49 -0
- package/packages/omo-codex/plugin/package-lock.json +19 -0
- package/packages/omo-codex/plugin/package.json +3 -1
- package/packages/omo-codex/plugin/scripts/auto-update.mjs +159 -0
- package/packages/omo-codex/plugin/scripts/build-bundled-mcp-runtimes.mjs +16 -1
- package/packages/omo-codex/plugin/scripts/build-components.mjs +2 -1
- package/packages/omo-codex/plugin/scripts/migrate-codex-config.mjs +269 -0
- package/packages/omo-codex/plugin/scripts/sync-hook-status-messages.mjs +89 -0
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +6 -6
- package/packages/omo-codex/plugin/skills/init-deep/SKILL.md +6 -6
- package/packages/omo-codex/plugin/skills/lcx-report-bug/SKILL.md +127 -0
- package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +9 -0
- package/packages/omo-codex/plugin/skills/refactor/SKILL.md +6 -6
- package/packages/omo-codex/plugin/skills/remove-ai-slops/SKILL.md +6 -6
- package/packages/omo-codex/plugin/skills/review-work/SKILL.md +33 -8
- package/packages/omo-codex/plugin/skills/start-work/SKILL.md +25 -5
- package/packages/omo-codex/plugin/skills/ulw-loop/SKILL.md +28 -205
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +231 -0
- package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +17 -17
- package/packages/omo-codex/plugin/test/aggregate.test.mjs +188 -20
- package/packages/omo-codex/plugin/test/auto-update.test.mjs +129 -0
- package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +58 -11
- package/packages/omo-codex/plugin/test/install-time-build-runtime.test.mjs +34 -0
- package/packages/omo-codex/plugin/test/mcp-research-servers.test.mjs +21 -0
- package/packages/omo-codex/plugin/test/migrate-codex-config.test.mjs +146 -0
- package/packages/omo-codex/plugin/test/node-install-surface.test.mjs +48 -0
- package/packages/omo-codex/plugin/test/subagent-guidance.test.mjs +76 -0
- package/packages/omo-codex/plugin/test/sync-hook-status-messages.test.mjs +67 -0
- package/packages/omo-codex/plugin/test/sync-skills.test.mjs +54 -2
- package/packages/omo-codex/scripts/install/cache.mjs +5 -3
- package/packages/omo-codex/scripts/install/cli-args.mjs +112 -0
- package/packages/omo-codex/scripts/install/config.mjs +23 -1
- package/packages/omo-codex/scripts/install/delegated-command.mjs +25 -0
- package/packages/omo-codex/scripts/install/git-bash.mjs +99 -0
- package/packages/omo-codex/scripts/install/git-bash.test.mjs +174 -0
- package/packages/omo-codex/scripts/install/legacy-bins.mjs +1 -0
- package/packages/omo-codex/scripts/install/mcp-runtime-cache.mjs +5 -1
- package/packages/omo-codex/scripts/install/model-catalog.mjs +66 -0
- package/packages/omo-codex/scripts/install/multi-agent-v2-config.mjs +7 -1
- package/packages/omo-codex/scripts/install/permissions.d.mts +1 -0
- package/packages/omo-codex/scripts/install/permissions.mjs +26 -0
- package/packages/omo-codex/scripts/install/project-local-cleanup.mjs +229 -0
- package/packages/omo-codex/scripts/install/reasoning-config.mjs +72 -0
- package/packages/omo-codex/scripts/install/source-package-build.mjs +20 -0
- package/packages/omo-codex/scripts/install/toml-editor.mjs +19 -2
- package/packages/omo-codex/scripts/install-bin-links.test.mjs +23 -0
- package/packages/omo-codex/scripts/install-cli-args.test.mjs +146 -0
- package/packages/omo-codex/scripts/install-config-autonomous.test.mjs +48 -0
- package/packages/omo-codex/scripts/install-config-reasoning.test.mjs +141 -0
- package/packages/omo-codex/scripts/install-config.test.mjs +205 -0
- package/packages/omo-codex/scripts/install-local-entrypoint.test.mjs +157 -0
- package/packages/omo-codex/scripts/install-local-git-bash-preflight.test.mjs +145 -0
- package/packages/omo-codex/scripts/install-local.mjs +91 -8
- package/packages/omo-codex/scripts/install-local.test.mjs +15 -0
- package/packages/omo-codex/scripts/install-mcp-runtime.test.mjs +60 -0
- package/packages/omo-codex/scripts/install-packaged-local.test.mjs +67 -0
- package/packages/omo-codex/scripts/install-project-local-cleanup.test.mjs +277 -0
- package/packages/shared-skills/skills/lcx-report-bug/SKILL.md +127 -0
- package/packages/shared-skills/skills/lcx-report-bug/agents/openai.yaml +9 -0
- package/packages/shared-skills/skills/review-work/SKILL.md +33 -8
- package/packages/shared-skills/skills/start-work/SKILL.md +25 -5
- package/packages/shared-skills/skills/ulw-plan/SKILL.md +11 -11
- package/postinstall.mjs +36 -3
- package/dist/hooks/context-window-monitor.d.ts +0 -19
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { readdir, readFile } from "node:fs/promises";
|
|
2
|
+
import { access, readdir, readFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import test from "node:test";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
@@ -15,6 +15,7 @@ const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
|
15
15
|
const AGGREGATE_EXPECTED_LABELS = new Map([
|
|
16
16
|
["hooks/hooks.json:SessionStart:0:0", "Loading Project Rules"],
|
|
17
17
|
["hooks/hooks.json:SessionStart:1:0", "Recording Session Telemetry"],
|
|
18
|
+
["hooks/hooks.json:SessionStart:2:0", "Checking Auto Update"],
|
|
18
19
|
["hooks/hooks.json:UserPromptSubmit:0:0", "Loading Project Rules"],
|
|
19
20
|
["hooks/hooks.json:UserPromptSubmit:1:0", "Checking Ultrawork Trigger"],
|
|
20
21
|
["hooks/hooks.json:UserPromptSubmit:2:0", "Checking Ulw-Loop Steering"],
|
|
@@ -46,18 +47,49 @@ async function readJson(relativePath) {
|
|
|
46
47
|
return JSON.parse(await readFile(join(root, relativePath), "utf8"));
|
|
47
48
|
}
|
|
48
49
|
|
|
50
|
+
async function exists(relativePath) {
|
|
51
|
+
try {
|
|
52
|
+
await access(join(root, relativePath));
|
|
53
|
+
return true;
|
|
54
|
+
} catch (error) {
|
|
55
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return false;
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
49
60
|
async function readComponentHookManifests() {
|
|
50
61
|
const components = await readdir(join(root, "components"), { withFileTypes: true });
|
|
51
62
|
const manifests = [];
|
|
52
63
|
for (const entry of components) {
|
|
53
64
|
if (!entry.isDirectory()) continue;
|
|
54
65
|
const source = join("components", entry.name, "hooks", "hooks.json");
|
|
66
|
+
if (!(await exists(source))) continue;
|
|
55
67
|
const packageJson = await readJson(join("components", entry.name, "package.json"));
|
|
56
68
|
manifests.push({ source, version: packageJson.version, hooks: await readJson(source) });
|
|
57
69
|
}
|
|
58
70
|
return manifests.sort((left, right) => left.source.localeCompare(right.source));
|
|
59
71
|
}
|
|
60
72
|
|
|
73
|
+
async function readComponentVersions() {
|
|
74
|
+
const components = await readdir(join(root, "components"), { withFileTypes: true });
|
|
75
|
+
const versions = new Map();
|
|
76
|
+
for (const entry of components) {
|
|
77
|
+
if (!entry.isDirectory()) continue;
|
|
78
|
+
if (!(await exists(join("components", entry.name, "package.json")))) continue;
|
|
79
|
+
const packageJson = await readJson(join("components", entry.name, "package.json"));
|
|
80
|
+
versions.set(entry.name, packageJson.version);
|
|
81
|
+
}
|
|
82
|
+
return versions;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function hookOwnerVersion(hook, aggregateVersion, componentVersions) {
|
|
86
|
+
const command = hook.command;
|
|
87
|
+
for (const [componentName, version] of componentVersions.entries()) {
|
|
88
|
+
if (command.includes(`/components/${componentName}/dist/cli.js`)) return version;
|
|
89
|
+
}
|
|
90
|
+
return aggregateVersion;
|
|
91
|
+
}
|
|
92
|
+
|
|
61
93
|
function collectCommandHooks(hooks, source, version) {
|
|
62
94
|
const commandHooks = [];
|
|
63
95
|
const normalizedSource = source.replaceAll("\\", "/");
|
|
@@ -68,6 +100,7 @@ function collectCommandHooks(hooks, source, version) {
|
|
|
68
100
|
commandHooks.push({
|
|
69
101
|
id: `${normalizedSource}:${eventName}:${groupIndex}:${handlerIndex}`,
|
|
70
102
|
version,
|
|
103
|
+
command: handler.command,
|
|
71
104
|
statusMessage: handler.statusMessage,
|
|
72
105
|
});
|
|
73
106
|
});
|
|
@@ -88,6 +121,18 @@ test("#given hook status label #when formatting #then prefixes LazyCodex with ve
|
|
|
88
121
|
assert.equal(message, "LazyCodex(0.1.0): Checking Comments");
|
|
89
122
|
});
|
|
90
123
|
|
|
124
|
+
test("#given hook status label with blank version #when formatting #then prefixes LazyCodex with local version", () => {
|
|
125
|
+
// given
|
|
126
|
+
const version = " ";
|
|
127
|
+
const label = "Checking Comments";
|
|
128
|
+
|
|
129
|
+
// when
|
|
130
|
+
const message = formatLazyCodexHookStatusMessage(version, label);
|
|
131
|
+
|
|
132
|
+
// then
|
|
133
|
+
assert.equal(message, "LazyCodex(local): Checking Comments");
|
|
134
|
+
});
|
|
135
|
+
|
|
91
136
|
test("#given loose legacy status label #when normalizing #then removes OMO wording and title-cases label", () => {
|
|
92
137
|
// given
|
|
93
138
|
const version = "0.1.0";
|
|
@@ -104,15 +149,15 @@ test("#given loose legacy status label #when normalizing #then removes OMO wordi
|
|
|
104
149
|
|
|
105
150
|
test("#given aggregate comment-checker hook #when status is inspected #then it uses LazyCodex comments label", async () => {
|
|
106
151
|
// given
|
|
107
|
-
const aggregateVersion = (await readJson(".codex-plugin/plugin.json")).version;
|
|
108
152
|
const aggregateHooks = await readJson("hooks/hooks.json");
|
|
153
|
+
const componentVersions = await readComponentVersions();
|
|
109
154
|
|
|
110
155
|
// when
|
|
111
|
-
const hooks = collectCommandHooks(aggregateHooks, "hooks/hooks.json",
|
|
156
|
+
const hooks = collectCommandHooks(aggregateHooks, "hooks/hooks.json", "0.1.0");
|
|
112
157
|
const commentCheckerHook = hooks.find((hook) => hook.id === "hooks/hooks.json:PostToolUse:0:0");
|
|
113
158
|
|
|
114
159
|
// then
|
|
115
|
-
assert.equal(commentCheckerHook?.statusMessage, formatLazyCodexHookStatusMessage(
|
|
160
|
+
assert.equal(commentCheckerHook?.statusMessage, formatLazyCodexHookStatusMessage(componentVersions.get("comment-checker"), "Checking Comments"));
|
|
116
161
|
assert.doesNotMatch(JSON.stringify(aggregateHooks), /checking\s+OMO\s+comments/i);
|
|
117
162
|
});
|
|
118
163
|
|
|
@@ -121,18 +166,22 @@ test("#given aggregate and component hooks #when status messages are inspected #
|
|
|
121
166
|
const aggregateVersion = (await readJson(".codex-plugin/plugin.json")).version;
|
|
122
167
|
const aggregateHooks = await readJson("hooks/hooks.json");
|
|
123
168
|
const componentManifests = await readComponentHookManifests();
|
|
169
|
+
const componentVersions = await readComponentVersions();
|
|
124
170
|
|
|
125
171
|
// when
|
|
126
172
|
const commandHooks = [
|
|
127
|
-
...collectCommandHooks(aggregateHooks, "hooks/hooks.json", aggregateVersion)
|
|
173
|
+
...collectCommandHooks(aggregateHooks, "hooks/hooks.json", aggregateVersion).map((hook) => ({
|
|
174
|
+
...hook,
|
|
175
|
+
version: hookOwnerVersion(hook, aggregateVersion, componentVersions),
|
|
176
|
+
})),
|
|
128
177
|
...componentManifests.flatMap((manifest) => collectCommandHooks(manifest.hooks, manifest.source, manifest.version)),
|
|
129
178
|
];
|
|
130
179
|
const expectedLabels = new Map([...AGGREGATE_EXPECTED_LABELS, ...COMPONENT_EXPECTED_LABELS]);
|
|
131
180
|
const mismatches = commandHooks
|
|
132
181
|
.map((hook) => {
|
|
133
|
-
const label = expectedLabels.get(hook.id);
|
|
134
|
-
const expected = label === undefined ? undefined : formatLazyCodexHookStatusMessage(hook.version, label);
|
|
135
182
|
const parsed = parseLazyCodexHookStatusMessage(hook.statusMessage);
|
|
183
|
+
const label = parsed?.label;
|
|
184
|
+
const expected = label === undefined ? undefined : formatLazyCodexHookStatusMessage(hook.version, label);
|
|
136
185
|
return { ...hook, expected, parsed };
|
|
137
186
|
})
|
|
138
187
|
.filter((hook) => hook.expected === undefined || hook.statusMessage !== hook.expected || hook.parsed === null)
|
|
@@ -140,10 +189,8 @@ test("#given aggregate and component hooks #when status messages are inspected #
|
|
|
140
189
|
|
|
141
190
|
// then
|
|
142
191
|
assert.deepEqual(mismatches, []);
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
[...expectedLabels.keys()].sort(),
|
|
146
|
-
);
|
|
192
|
+
const actualLabels = new Set(commandHooks.map((hook) => parseLazyCodexHookStatusMessage(hook.statusMessage)?.label));
|
|
193
|
+
assert.deepEqual([...expectedLabels.values()].filter((label) => !actualLabels.has(label)), []);
|
|
147
194
|
for (const hook of commandHooks) {
|
|
148
195
|
assert.doesNotMatch(hook.statusMessage, /\bOMO\b/i);
|
|
149
196
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
8
|
+
|
|
9
|
+
test("#given aggregate build scripts #when inspected #then install-time build does not invoke Bun", async () => {
|
|
10
|
+
// given
|
|
11
|
+
const buildComponentsScript = await readFile(join(root, "scripts", "build-components.mjs"), "utf8");
|
|
12
|
+
const buildBundledMcpRuntimesScript = await readFile(join(root, "scripts", "build-bundled-mcp-runtimes.mjs"), "utf8");
|
|
13
|
+
|
|
14
|
+
// when
|
|
15
|
+
const installTimeBuildScripts = [buildComponentsScript, buildBundledMcpRuntimesScript].join("\n");
|
|
16
|
+
|
|
17
|
+
// then
|
|
18
|
+
assert.doesNotMatch(installTimeBuildScripts, /spawnSync\("bun"/);
|
|
19
|
+
assert.doesNotMatch(installTimeBuildScripts, /\bbun\s+run\b/);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test("#given aggregate build scripts #when inspected #then npm subprocesses resolve on Windows", async () => {
|
|
23
|
+
// given
|
|
24
|
+
const buildComponentsScript = await readFile(join(root, "scripts", "build-components.mjs"), "utf8");
|
|
25
|
+
const buildBundledMcpRuntimesScript = await readFile(join(root, "scripts", "build-bundled-mcp-runtimes.mjs"), "utf8");
|
|
26
|
+
|
|
27
|
+
// when
|
|
28
|
+
const installTimeBuildScripts = [buildComponentsScript, buildBundledMcpRuntimesScript].join("\n");
|
|
29
|
+
|
|
30
|
+
// then
|
|
31
|
+
assert.match(installTimeBuildScripts, /process\.platform === "win32"/);
|
|
32
|
+
assert.match(installTimeBuildScripts, /shell: process\.platform === "win32"/);
|
|
33
|
+
assert.doesNotMatch(installTimeBuildScripts, /npm\.cmd/);
|
|
34
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
8
|
+
|
|
9
|
+
test("#given aggregate MCP config #when inspected #then registers research and structural-search MCPs", async () => {
|
|
10
|
+
// given
|
|
11
|
+
const mcp = JSON.parse(await readFile(join(root, ".mcp.json"), "utf8"));
|
|
12
|
+
|
|
13
|
+
// when
|
|
14
|
+
const serverNames = Object.keys(mcp.mcpServers).sort();
|
|
15
|
+
|
|
16
|
+
// then
|
|
17
|
+
assert.deepEqual(serverNames, ["ast_grep", "context7", "git_bash", "grep_app", "lsp"]);
|
|
18
|
+
assert.equal(mcp.mcpServers.grep_app.url, "https://mcp.grep.app");
|
|
19
|
+
assert.equal(mcp.mcpServers.context7.url, "https://mcp.context7.com/mcp");
|
|
20
|
+
assert.deepEqual(mcp.mcpServers.ast_grep.args, ["../../ast-grep-mcp/dist/cli.js", "mcp"]);
|
|
21
|
+
});
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
|
|
7
|
+
import { ensureCodexReasoningConfig, migrateCodexConfig } from "../scripts/migrate-codex-config.mjs";
|
|
8
|
+
|
|
9
|
+
test("#given stale root reasoning config #when ensuring config #then replaces stale values without duplicate keys", () => {
|
|
10
|
+
const result = ensureCodexReasoningConfig(
|
|
11
|
+
[
|
|
12
|
+
'model = "gpt-5.2"',
|
|
13
|
+
"model_context_window = 272000",
|
|
14
|
+
'model_reasoning_effort = "low"',
|
|
15
|
+
'plan_mode_reasoning_effort = "medium"',
|
|
16
|
+
"",
|
|
17
|
+
"[features]",
|
|
18
|
+
"plugins = true",
|
|
19
|
+
"",
|
|
20
|
+
].join("\n"),
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
assert.equal(result.match(/^model\s*=/gm)?.length, 1);
|
|
24
|
+
assert.equal(result.match(/^model_context_window\s*=/gm)?.length, 1);
|
|
25
|
+
assert.equal(result.match(/^model_reasoning_effort\s*=/gm)?.length, 1);
|
|
26
|
+
assert.equal(result.match(/^plan_mode_reasoning_effort\s*=/gm)?.length, 1);
|
|
27
|
+
assert.match(result, /model = "gpt-5\.5"/);
|
|
28
|
+
assert.match(result, /model_context_window = 400000/);
|
|
29
|
+
assert.match(result, /model_reasoning_effort = "high"/);
|
|
30
|
+
assert.match(result, /plan_mode_reasoning_effort = "xhigh"/);
|
|
31
|
+
assert.doesNotMatch(result, /gpt-5\.2/);
|
|
32
|
+
assert.match(result, /\[features\]/);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("#given global and project-local stale Codex configs #when migrating #then both configs are forced to current defaults", async () => {
|
|
36
|
+
const root = await mkdtemp(join(tmpdir(), "lazycodex-config-migration-"));
|
|
37
|
+
const codexHome = join(root, "codex-home");
|
|
38
|
+
const project = join(root, "project", "nested");
|
|
39
|
+
const projectConfig = join(root, "project", ".codex", "config.toml");
|
|
40
|
+
await mkdir(codexHome, { recursive: true });
|
|
41
|
+
await mkdir(dirname(projectConfig), { recursive: true });
|
|
42
|
+
await writeFile(join(codexHome, "config.toml"), 'model = "gpt-5.2"\n');
|
|
43
|
+
await writeFile(projectConfig, 'model = "gpt-5.2"\nmodel_context_window = 272000\n');
|
|
44
|
+
|
|
45
|
+
const result = await migrateCodexConfig({
|
|
46
|
+
env: { CODEX_HOME: codexHome, LAZYCODEX_MODEL_CATALOG_STATE_PATH: join(root, "model-state.json") },
|
|
47
|
+
cwd: project,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
assert.deepEqual(result.changed.sort(), [join(codexHome, "config.toml"), projectConfig].sort());
|
|
51
|
+
assert.match(await readFile(join(codexHome, "config.toml"), "utf8"), /model = "gpt-5\.5"/);
|
|
52
|
+
assert.match(await readFile(projectConfig, "utf8"), /model_context_window = 400000/);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("#given user-customized Codex model config #when migrating #then user values are preserved", async () => {
|
|
56
|
+
const root = await mkdtemp(join(tmpdir(), "lazycodex-config-custom-"));
|
|
57
|
+
const codexHome = join(root, "codex-home");
|
|
58
|
+
await mkdir(codexHome, { recursive: true });
|
|
59
|
+
await writeFile(
|
|
60
|
+
join(codexHome, "config.toml"),
|
|
61
|
+
[
|
|
62
|
+
'model = "gpt-5.4"',
|
|
63
|
+
"model_context_window = 123456",
|
|
64
|
+
'model_reasoning_effort = "medium"',
|
|
65
|
+
'plan_mode_reasoning_effort = "medium"',
|
|
66
|
+
"",
|
|
67
|
+
].join("\n"),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const result = await migrateCodexConfig({
|
|
71
|
+
env: { CODEX_HOME: codexHome, LAZYCODEX_MODEL_CATALOG_STATE_PATH: join(root, "model-state.json") },
|
|
72
|
+
cwd: root,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const content = await readFile(join(codexHome, "config.toml"), "utf8");
|
|
76
|
+
assert.deepEqual(result.changed, []);
|
|
77
|
+
assert.match(content, /model = "gpt-5\.4"/);
|
|
78
|
+
assert.match(content, /model_context_window = 123456/);
|
|
79
|
+
assert.match(content, /model_reasoning_effort = "medium"/);
|
|
80
|
+
assert.match(content, /plan_mode_reasoning_effort = "medium"/);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("#given managed catalog state #when catalog version advances #then only previously managed config is updated", async () => {
|
|
84
|
+
const root = await mkdtemp(join(tmpdir(), "lazycodex-config-catalog-state-"));
|
|
85
|
+
const codexHome = join(root, "codex-home");
|
|
86
|
+
const catalogPath = join(root, "catalog.json");
|
|
87
|
+
const statePath = join(root, "model-state.json");
|
|
88
|
+
await mkdir(codexHome, { recursive: true });
|
|
89
|
+
await writeFile(
|
|
90
|
+
catalogPath,
|
|
91
|
+
JSON.stringify(
|
|
92
|
+
{
|
|
93
|
+
version: "test.v1",
|
|
94
|
+
current: {
|
|
95
|
+
model: "gpt-5.4",
|
|
96
|
+
model_context_window: 1000000,
|
|
97
|
+
model_reasoning_effort: "high",
|
|
98
|
+
plan_mode_reasoning_effort: "xhigh",
|
|
99
|
+
},
|
|
100
|
+
managedProfiles: [],
|
|
101
|
+
},
|
|
102
|
+
null,
|
|
103
|
+
2,
|
|
104
|
+
),
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
const first = await migrateCodexConfig({
|
|
108
|
+
env: {
|
|
109
|
+
CODEX_HOME: codexHome,
|
|
110
|
+
LAZYCODEX_MODEL_CATALOG_PATH: catalogPath,
|
|
111
|
+
LAZYCODEX_MODEL_CATALOG_STATE_PATH: statePath,
|
|
112
|
+
},
|
|
113
|
+
cwd: root,
|
|
114
|
+
});
|
|
115
|
+
await writeFile(
|
|
116
|
+
catalogPath,
|
|
117
|
+
JSON.stringify(
|
|
118
|
+
{
|
|
119
|
+
version: "test.v2",
|
|
120
|
+
current: {
|
|
121
|
+
model: "gpt-5.5",
|
|
122
|
+
model_context_window: 400000,
|
|
123
|
+
model_reasoning_effort: "high",
|
|
124
|
+
plan_mode_reasoning_effort: "xhigh",
|
|
125
|
+
},
|
|
126
|
+
managedProfiles: [],
|
|
127
|
+
},
|
|
128
|
+
null,
|
|
129
|
+
2,
|
|
130
|
+
),
|
|
131
|
+
);
|
|
132
|
+
const second = await migrateCodexConfig({
|
|
133
|
+
env: {
|
|
134
|
+
CODEX_HOME: codexHome,
|
|
135
|
+
LAZYCODEX_MODEL_CATALOG_PATH: catalogPath,
|
|
136
|
+
LAZYCODEX_MODEL_CATALOG_STATE_PATH: statePath,
|
|
137
|
+
},
|
|
138
|
+
cwd: root,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const content = await readFile(join(codexHome, "config.toml"), "utf8");
|
|
142
|
+
assert.deepEqual(first.changed, [join(codexHome, "config.toml")]);
|
|
143
|
+
assert.deepEqual(second.changed, [join(codexHome, "config.toml")]);
|
|
144
|
+
assert.match(content, /model = "gpt-5\.5"/);
|
|
145
|
+
assert.match(content, /model_context_window = 400000/);
|
|
146
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const pluginRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
8
|
+
const repoRoot = join(pluginRoot, "..", "..", "..");
|
|
9
|
+
|
|
10
|
+
test("#given Codex Light install docs #when inspected #then lazycodex is npm-first and Bun-free", async () => {
|
|
11
|
+
// given
|
|
12
|
+
const files = [
|
|
13
|
+
join(repoRoot, "README.md"),
|
|
14
|
+
join(repoRoot, "docs", "guide", "installation.md"),
|
|
15
|
+
join(repoRoot, "packages", "omo-codex", "README.md"),
|
|
16
|
+
join(repoRoot, "packages", "omo-codex", "MARKETPLACE.md"),
|
|
17
|
+
join(pluginRoot, "components", "ultrawork", "README.md"),
|
|
18
|
+
join(pluginRoot, "components", "ulw-loop", "README.md"),
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
// when
|
|
22
|
+
const docs = await Promise.all(files.map(async (path) => [path, await readFile(path, "utf8")]));
|
|
23
|
+
|
|
24
|
+
// then
|
|
25
|
+
for (const [path, text] of docs) {
|
|
26
|
+
assert.match(text, /\bnpx lazycodex-ai install\b/, `${path} should document the Node/npm install command`);
|
|
27
|
+
assert.doesNotMatch(text, /\bbunx lazycodex-ai\b/, `${path} should not require Bun for lazycodex`);
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test("#given cleanup troubleshooting docs #when inspected #then project-local cleanup and command delegation are documented", async () => {
|
|
32
|
+
// given
|
|
33
|
+
const files = [
|
|
34
|
+
join(repoRoot, "README.md"),
|
|
35
|
+
join(repoRoot, "docs", "guide", "installation.md"),
|
|
36
|
+
join(repoRoot, "packages", "omo-codex", "README.md"),
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
// when
|
|
40
|
+
const docs = await Promise.all(files.map(async (path) => [path, await readFile(path, "utf8")]));
|
|
41
|
+
|
|
42
|
+
// then
|
|
43
|
+
for (const [path, text] of docs) {
|
|
44
|
+
assert.match(text, /\bnpx lazycodex-ai cleanup\b/, `${path} should document the LazyCodex cleanup command`);
|
|
45
|
+
assert.match(text, /project-local .*\.codex\/config\.toml/i, `${path} should mention project-local config repair`);
|
|
46
|
+
assert.match(text, /\.codex.*\.omx|\.omx.*\.codex/s, `${path} should distinguish project-local .codex and .omx artifacts`);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
|
|
7
|
+
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
8
|
+
|
|
9
|
+
const SKILLS = [
|
|
10
|
+
"review-work",
|
|
11
|
+
"start-work",
|
|
12
|
+
"ulw-loop",
|
|
13
|
+
];
|
|
14
|
+
|
|
15
|
+
const AGENT_FILES = [
|
|
16
|
+
"components/ultrawork/agents/codex-ultrawork-reviewer.toml",
|
|
17
|
+
"components/ultrawork/agents/plan.toml",
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
test("#given orchestration skills #when inspected #then Codex subagent delegation is hardened", async () => {
|
|
21
|
+
// given
|
|
22
|
+
const skillPaths = SKILLS.map((skillName) => join("skills", skillName, "SKILL.md"));
|
|
23
|
+
|
|
24
|
+
// when
|
|
25
|
+
const missing = [];
|
|
26
|
+
for (const skillPath of skillPaths) {
|
|
27
|
+
const text = await readFile(join(root, skillPath), "utf8");
|
|
28
|
+
if (
|
|
29
|
+
!/TASK:/.test(text) ||
|
|
30
|
+
!/fork_turns:\s*"none"/.test(text) ||
|
|
31
|
+
!/wait_agent.*signal, not proof/s.test(text) ||
|
|
32
|
+
!/one targeted followup/.test(text) ||
|
|
33
|
+
!/respawn.*smaller/s.test(text) ||
|
|
34
|
+
!/model.*reasoning_effort.*default agent/s.test(text) ||
|
|
35
|
+
!/Plan and reviewer agents may run for a long time/.test(text) ||
|
|
36
|
+
!/short wait_agent cycles/.test(text) ||
|
|
37
|
+
!/single long blocking wait/.test(text)
|
|
38
|
+
) {
|
|
39
|
+
missing.push(skillPath);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// then
|
|
44
|
+
assert.deepEqual(missing, []);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("#given ultrawork directive #when inspected #then reviewer fallback keeps an agent role", async () => {
|
|
48
|
+
// given
|
|
49
|
+
const directivePath = "components/ultrawork/directive.md";
|
|
50
|
+
|
|
51
|
+
// when
|
|
52
|
+
const text = await readFile(join(root, directivePath), "utf8");
|
|
53
|
+
|
|
54
|
+
// then
|
|
55
|
+
assert.doesNotMatch(text, /any `gpt-5\.2`\s+xhigh reviewer/);
|
|
56
|
+
assert.match(text, /codex-ultrawork-reviewer/);
|
|
57
|
+
assert.match(text, /agent_type.*worker/s);
|
|
58
|
+
assert.match(text, /model.*reasoning_effort.*default agent/s);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test("#given ultrawork agents #when inspected #then inter-agent commentary is treated as assignments", async () => {
|
|
62
|
+
// given
|
|
63
|
+
const agentPaths = AGENT_FILES;
|
|
64
|
+
|
|
65
|
+
// when
|
|
66
|
+
const missing = [];
|
|
67
|
+
for (const agentPath of agentPaths) {
|
|
68
|
+
const text = await readFile(join(root, agentPath), "utf8");
|
|
69
|
+
if (!/TASK:|active review assignment/.test(text) || !/context|commentary/.test(text)) {
|
|
70
|
+
missing.push(agentPath);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// then
|
|
75
|
+
assert.deepEqual(missing, []);
|
|
76
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdtemp, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import test from "node:test";
|
|
6
|
+
|
|
7
|
+
import { syncHookStatusMessages } from "../scripts/sync-hook-status-messages.mjs";
|
|
8
|
+
|
|
9
|
+
async function writeJson(path, value) {
|
|
10
|
+
await writeFile(path, `${JSON.stringify(value, null, "\t")}\n`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function readJson(path) {
|
|
14
|
+
return JSON.parse(await readFile(path, "utf8"));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
test("#given a component without hooks #when hook status messages sync #then build-time version sync skips it", async () => {
|
|
18
|
+
// given
|
|
19
|
+
const root = await mkdtemp(join(tmpdir(), "omo-codex-hook-status-"));
|
|
20
|
+
await mkdir(join(root, ".codex-plugin"), { recursive: true });
|
|
21
|
+
await mkdir(join(root, "hooks"), { recursive: true });
|
|
22
|
+
await mkdir(join(root, "components", "comment-checker", "hooks"), { recursive: true });
|
|
23
|
+
await mkdir(join(root, "components", "git-bash"), { recursive: true });
|
|
24
|
+
await mkdir(join(root, "components", "stale-build-output", "dist"), { recursive: true });
|
|
25
|
+
await writeJson(join(root, ".codex-plugin", "plugin.json"), { version: "0.1.0" });
|
|
26
|
+
await writeJson(join(root, "components", "comment-checker", "package.json"), { version: "0.1.1" });
|
|
27
|
+
await writeJson(join(root, "components", "git-bash", "package.json"), { version: "0.3.0" });
|
|
28
|
+
await writeJson(join(root, "hooks", "hooks.json"), {
|
|
29
|
+
hooks: {
|
|
30
|
+
PostToolUse: [
|
|
31
|
+
{
|
|
32
|
+
hooks: [
|
|
33
|
+
{
|
|
34
|
+
type: "command",
|
|
35
|
+
command: 'node "${PLUGIN_ROOT}/components/comment-checker/dist/cli.js" hook post-tool-use',
|
|
36
|
+
statusMessage: "LazyCodex(0.1.0): Checking Comments",
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
await writeJson(join(root, "components", "comment-checker", "hooks", "hooks.json"), {
|
|
44
|
+
hooks: {
|
|
45
|
+
PostToolUse: [
|
|
46
|
+
{
|
|
47
|
+
hooks: [
|
|
48
|
+
{
|
|
49
|
+
type: "command",
|
|
50
|
+
command: 'node "${PLUGIN_ROOT}/dist/cli.js" hook post-tool-use',
|
|
51
|
+
statusMessage: "LazyCodex(0.1.0): Checking Comments",
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// when
|
|
60
|
+
await syncHookStatusMessages(root);
|
|
61
|
+
|
|
62
|
+
// then
|
|
63
|
+
const aggregateHooks = await readJson(join(root, "hooks", "hooks.json"));
|
|
64
|
+
const componentHooks = await readJson(join(root, "components", "comment-checker", "hooks", "hooks.json"));
|
|
65
|
+
assert.equal(aggregateHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "LazyCodex(0.1.1): Checking Comments");
|
|
66
|
+
assert.equal(componentHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "LazyCodex(0.1.1): Checking Comments");
|
|
67
|
+
});
|
|
@@ -7,12 +7,14 @@ import { sharedSkillsRootPath } from "@oh-my-opencode/shared-skills";
|
|
|
7
7
|
|
|
8
8
|
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
9
9
|
const repoRoot = join(root, "..", "..", "..");
|
|
10
|
+
const CONTEXT_PRESSURE_SKILL_BUDGET_BYTES = 25_000;
|
|
10
11
|
|
|
11
12
|
const expectedSkills = [
|
|
12
13
|
"comment-checker",
|
|
13
14
|
"debugging",
|
|
14
15
|
"frontend-ui-ux",
|
|
15
16
|
"init-deep",
|
|
17
|
+
"lcx-report-bug",
|
|
16
18
|
"lsp",
|
|
17
19
|
"programming",
|
|
18
20
|
"refactor",
|
|
@@ -32,6 +34,7 @@ const componentSkillSources = [
|
|
|
32
34
|
];
|
|
33
35
|
|
|
34
36
|
const codexCompatibilityEndMarkers = [
|
|
37
|
+
"Codex full-history forks inherit the parent agent type, model, and reasoning effort, so role-specific spawns with `agent_type` must use a non-full-history fork mode such as `fork_turns=\"none\"`. Include any required conversation context, files, diffs, constraints, and requested skill names directly in the spawned agent's `message`. If a code block below conflicts with this section, this section wins.\n\n",
|
|
35
38
|
"When translating `load_skills=[...]`, include the requested skill names in the spawned agent's `message`. If a code block below conflicts with this section, this section wins.\n\n",
|
|
36
39
|
"When translating `load_skills=[...]`, name the skills inside the spawned agent's `message`. If a code block below conflicts with this section, this section wins.\n\n",
|
|
37
40
|
];
|
|
@@ -152,10 +155,34 @@ test("#given synced ulw-loop skill #when Codex hint metadata is inspected #then
|
|
|
152
155
|
assert.match(interfaceMetadata, /- "ulw-loop"/);
|
|
153
156
|
});
|
|
154
157
|
|
|
158
|
+
test("#given synced lcx-report-bug skill #when inspected #then it files LazyCodex bug issues from proven debugging evidence", async () => {
|
|
159
|
+
// given
|
|
160
|
+
const skillRoot = join(root, "skills", "lcx-report-bug");
|
|
161
|
+
|
|
162
|
+
// when
|
|
163
|
+
const skill = await readFile(join(skillRoot, "SKILL.md"), "utf8");
|
|
164
|
+
const interfaceMetadata = await readFile(join(skillRoot, "agents", "openai.yaml"), "utf8");
|
|
165
|
+
|
|
166
|
+
// then
|
|
167
|
+
assert.match(skill, /^---\r?\nname: lcx-report-bug\r?\n/m);
|
|
168
|
+
assert.match(skill, /code-yeongyu\/lazycodex/);
|
|
169
|
+
assert.match(skill, /\$omo:debugging/);
|
|
170
|
+
assert.match(skill, /gh issue create --repo code-yeongyu\/lazycodex/);
|
|
171
|
+
assert.match(skill, /Browser use fallback/);
|
|
172
|
+
assert.match(skill, /Computer use fallback/);
|
|
173
|
+
assert.match(skill, /## Issue Body Template/);
|
|
174
|
+
assert.match(interfaceMetadata, /display_name: "lcx-report-bug \(omo\)"/);
|
|
175
|
+
assert.match(interfaceMetadata, /- "lazycodex bug"/);
|
|
176
|
+
});
|
|
177
|
+
|
|
155
178
|
test("#given synced ulw-loop skill #when worker guidance is inspected #then context-hygiene guidance matches the source", async () => {
|
|
156
179
|
// given
|
|
157
|
-
const sourceSkill = await readFile(
|
|
180
|
+
const sourceSkill = await readFile(
|
|
181
|
+
join(root, "components", "ulw-loop", "skills", "ulw-loop", "references", "full-workflow.md"),
|
|
182
|
+
"utf8",
|
|
183
|
+
);
|
|
158
184
|
const syncedSkill = await readFile(join(root, "skills", "ulw-loop", "SKILL.md"), "utf8");
|
|
185
|
+
const syncedWorkflow = await readFile(join(root, "skills", "ulw-loop", "references", "full-workflow.md"), "utf8");
|
|
159
186
|
const requiredPatterns = [
|
|
160
187
|
["list_agents polling guard", /list_agents/],
|
|
161
188
|
["status polling warning", /polling or status tool/],
|
|
@@ -164,13 +191,38 @@ test("#given synced ulw-loop skill #when worker guidance is inspected #then cont
|
|
|
164
191
|
["wait_agent completion path", /wait_agent.*completion/],
|
|
165
192
|
["targeted followups", /targeted followups only when needed/],
|
|
166
193
|
["close_agent cleanup", /close_agent.*after integrating each result/],
|
|
194
|
+
["long-running plan/reviewer background guidance", /Plan and reviewer agents may run for a long time/],
|
|
195
|
+
["bounded plan/reviewer polling", /short wait_agent cycles/],
|
|
196
|
+
["single long wait guard", /single long blocking wait/],
|
|
167
197
|
];
|
|
168
198
|
|
|
169
199
|
// when / then
|
|
170
200
|
for (const [label, pattern] of requiredPatterns) {
|
|
171
201
|
assert.match(sourceSkill, pattern, `source skill missing ${label}`);
|
|
172
|
-
assert.match(
|
|
202
|
+
assert.match(syncedWorkflow, pattern, `synced workflow missing ${label}`);
|
|
203
|
+
}
|
|
204
|
+
assert.match(syncedSkill, /references\/full-workflow\.md/);
|
|
205
|
+
assert.match(syncedSkill, /wait_agent/);
|
|
206
|
+
assert.match(syncedSkill, /close_agent/);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("#given context-pressure-prone skills #when bundled for Codex #then the eagerly loaded payload stays budgeted", async () => {
|
|
210
|
+
// given
|
|
211
|
+
const skillsRoot = join(root, "skills");
|
|
212
|
+
const skillNames = ["debugging", "ulw-loop"];
|
|
213
|
+
|
|
214
|
+
// when
|
|
215
|
+
let totalBytes = 0;
|
|
216
|
+
for (const skillName of skillNames) {
|
|
217
|
+
const content = await readFile(join(skillsRoot, skillName, "SKILL.md"), "utf8");
|
|
218
|
+
totalBytes += Buffer.byteLength(content, "utf8");
|
|
173
219
|
}
|
|
220
|
+
|
|
221
|
+
// then
|
|
222
|
+
assert.ok(
|
|
223
|
+
totalBytes <= CONTEXT_PRESSURE_SKILL_BUDGET_BYTES,
|
|
224
|
+
`debugging + ulw-loop eager payload is ${totalBytes} bytes, above ${CONTEXT_PRESSURE_SKILL_BUDGET_BYTES}`,
|
|
225
|
+
);
|
|
174
226
|
});
|
|
175
227
|
|
|
176
228
|
test("#given synced aggregate Codex skills #when they contain OpenCode orchestration examples #then Codex tool compatibility guidance is injected", async () => {
|
|
@@ -6,9 +6,11 @@ import { exists, isRecord } from "./utils.mjs";
|
|
|
6
6
|
import { COMMAND_SHIM_MARKER } from "./command-shim.mjs";
|
|
7
7
|
import { removeLegacyCodexComponentBins } from "./legacy-bins.mjs";
|
|
8
8
|
|
|
9
|
-
export async function installCachedPlugin({ codexHome, marketplaceName, name, runCommand, sourcePath, version }) {
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
export async function installCachedPlugin({ buildSource = true, codexHome, marketplaceName, name, runCommand, sourcePath, version }) {
|
|
10
|
+
if (buildSource) {
|
|
11
|
+
await maybeRunNpmInstall(sourcePath, runCommand);
|
|
12
|
+
await maybeRunNpmBuild(sourcePath, runCommand);
|
|
13
|
+
}
|
|
12
14
|
|
|
13
15
|
const targetPath = join(codexHome, "plugins", "cache", marketplaceName, name, version);
|
|
14
16
|
await replaceDirectory(sourcePath, targetPath, shouldCopyPluginPath);
|