oh-my-opencode 4.11.1 → 4.12.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/scripts/lib/app-server-client.mjs +132 -82
- package/.agents/skills/codex-qa/scripts/lib/app-server-client.test.js +48 -0
- package/.agents/skills/opencode-qa/scripts/serve-wake-split-probe.sh +361 -84
- package/.agents/skills/work-with-pr/SKILL.md +19 -5
- package/.opencode/skills/work-with-pr/SKILL.md +19 -5
- package/dist/agents/types.d.ts +7 -6
- package/dist/cli/index.js +460 -309
- package/dist/cli-node/index.js +460 -309
- package/dist/features/background-agent/parent-wake-flush-runner.d.ts +1 -0
- package/dist/features/background-agent/parent-wake-history-state.d.ts +1 -0
- package/dist/features/background-agent/parent-wake-session-history.d.ts +1 -47
- package/dist/features/background-agent/parent-wake-session-message.d.ts +47 -0
- package/dist/index.js +593 -310
- package/dist/plugin/tool-execute-before.d.ts +2 -0
- package/dist/plugin-handlers/plugin-components-loader.d.ts +1 -0
- package/dist/skills/ultraresearch/SKILL.md +11 -2
- package/dist/skills/ulw-plan/SKILL.md +2 -1
- package/dist/skills/ulw-plan/references/full-workflow.md +1 -1
- package/dist/skills/ulw-plan/references/intent-clear.md +2 -2
- package/dist/tools/background-task/constants.d.ts +1 -1
- package/dist/tools/delegate-task/sync-session-poller.d.ts +1 -2
- package/dist/tools/delegate-task/sync-session-turns.d.ts +3 -0
- package/dist/tui.js +25 -10
- package/package.json +13 -13
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +24 -2
- package/packages/omo-codex/plugin/components/bootstrap/dist/cli.js +25 -9
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +336 -173
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +284 -33
- package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/src/cli.ts +11 -0
- package/packages/omo-codex/plugin/components/codegraph/src/hook-types.ts +12 -0
- package/packages/omo-codex/plugin/components/codegraph/src/hook.ts +34 -0
- package/packages/omo-codex/plugin/components/codegraph/src/serve.ts +47 -5
- package/packages/omo-codex/plugin/components/codegraph/src/session-start-worker.ts +7 -5
- package/packages/omo-codex/plugin/components/codegraph/test/hook.test.ts +58 -96
- package/packages/omo-codex/plugin/components/codegraph/test/provisioned-node-guard.test.ts +94 -0
- package/packages/omo-codex/plugin/components/codegraph/test/serve-node-support.test.ts +33 -0
- package/packages/omo-codex/plugin/components/codegraph/test/serve-provision.test.ts +48 -0
- package/packages/omo-codex/plugin/components/codegraph/test/serve.test.ts +1 -0
- package/packages/omo-codex/plugin/components/codegraph/test/session-start-node-support.test.ts +142 -0
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/test/package-smoke.test.ts +2 -71
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/test/package-smoke.test.ts +26 -104
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/test/package-smoke.test.ts +12 -92
- package/packages/omo-codex/plugin/components/start-work-continuation/dist/cli.js +157 -234
- package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -2
- package/packages/omo-codex/plugin/components/start-work-continuation/src/boulder-reader.ts +227 -15
- package/packages/omo-codex/plugin/components/start-work-continuation/test/boulder-reader.test.ts +62 -2
- package/packages/omo-codex/plugin/components/start-work-continuation/test/cli.test.ts +34 -3
- package/packages/omo-codex/plugin/components/teammode/dist/cli.js +94 -0
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +17 -0
- package/packages/omo-codex/plugin/components/teammode/package.json +26 -0
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/SKILL.md +139 -0
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-guide.mjs +111 -0
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team-state.mjs +250 -0
- package/packages/omo-codex/plugin/components/teammode/skills/teammode/scripts/team.mjs +220 -0
- package/packages/omo-codex/plugin/components/teammode/src/cli.ts +12 -0
- package/packages/omo-codex/plugin/components/teammode/src/codex-hook.ts +125 -0
- package/packages/omo-codex/plugin/components/teammode/test/thread-title-hook.test.ts +116 -0
- package/packages/omo-codex/plugin/components/teammode/tsconfig.json +25 -0
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/test-support/package-smoke-fixture.ts +158 -0
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/SKILL.md +2 -1
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/full-workflow.md +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/skills/ulw-plan/references/intent-clear.md +2 -2
- package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +76 -49
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/test/package-smoke.test.ts +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +17 -0
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +17 -0
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +17 -0
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +17 -0
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +17 -0
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +17 -0
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +17 -0
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +17 -0
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +17 -0
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +17 -0
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +17 -0
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +17 -0
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +16 -0
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +16 -0
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +16 -0
- package/packages/omo-codex/plugin/hooks/stop-checking-start-work-continuation.json +16 -0
- package/packages/omo-codex/plugin/hooks/subagent-stop-checking-start-work-continuation.json +16 -0
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +17 -0
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +16 -0
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +16 -0
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +16 -0
- package/packages/omo-codex/plugin/package-lock.json +30 -22
- package/packages/omo-codex/plugin/package.json +2 -1
- package/packages/omo-codex/plugin/scripts/hook-status-message.mjs +15 -11
- package/packages/omo-codex/plugin/scripts/sync-hook-status-messages.mjs +15 -4
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +37 -0
- package/packages/omo-codex/plugin/skills/ast-grep/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/comment-checker/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/debugging/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/frontend/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/git-master/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/init-deep/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/lcx-contribute-bug-fix/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/lcx-doctor/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/lcx-report-bug/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/lsp/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/lsp-setup/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/programming/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/refactor/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/remove-ai-slops/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/review-work/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/rules/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/start-work/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/teammode/SKILL.md +139 -0
- package/packages/omo-codex/plugin/skills/teammode/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/teammode/scripts/team-guide.mjs +111 -0
- package/packages/omo-codex/plugin/skills/teammode/scripts/team-state.mjs +250 -0
- package/packages/omo-codex/plugin/skills/teammode/scripts/team.mjs +220 -0
- package/packages/omo-codex/plugin/skills/ultraresearch/SKILL.md +11 -2
- package/packages/omo-codex/plugin/skills/ultraresearch/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/skills/ulw-loop/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/ulw-plan/SKILL.md +2 -1
- package/packages/omo-codex/plugin/skills/ulw-plan/agents/openai.yaml +1 -1
- package/packages/omo-codex/plugin/skills/ulw-plan/references/full-workflow.md +1 -1
- package/packages/omo-codex/plugin/skills/ulw-plan/references/intent-clear.md +2 -2
- package/packages/omo-codex/plugin/skills/visual-qa/agents/openai.yaml +2 -0
- package/packages/omo-codex/plugin/test/aggregate-hooks.test.mjs +70 -33
- package/packages/omo-codex/plugin/test/aggregate-manifest.test.mjs +6 -2
- package/packages/omo-codex/plugin/test/aggregate-plugin-fixture.mjs +14 -1
- package/packages/omo-codex/plugin/test/bootstrap-binlinks.test.mjs +1 -1
- package/packages/omo-codex/plugin/test/bootstrap-hooks.test.mjs +14 -6
- package/packages/omo-codex/plugin/test/bootstrap-ps-guard.test.mjs +7 -3
- package/packages/omo-codex/plugin/test/component-bundled-cli.test.mjs +4 -3
- package/packages/omo-codex/plugin/test/component-hook-contract-cases.mjs +24 -0
- package/packages/omo-codex/plugin/test/display-metadata.test.mjs +83 -0
- package/packages/omo-codex/plugin/test/hook-status-message.test.mjs +31 -19
- package/packages/omo-codex/plugin/test/lcx-bug-skills.test.mjs +3 -3
- package/packages/omo-codex/plugin/test/sync-hook-status-messages.test.mjs +6 -6
- package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +2 -0
- package/packages/omo-codex/plugin/test/sync-skills.test.mjs +9 -4
- package/packages/omo-codex/plugin/test/teammode-safety-fixture.mjs +88 -0
- package/packages/omo-codex/plugin/test/teammode-safety.test.mjs +240 -0
- package/packages/omo-codex/plugin/test/ultraresearch-skill-contract.test.mjs +48 -0
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +398 -206
- package/packages/omo-codex/scripts/install-hook-targets.test.mjs +93 -0
- package/packages/omo-codex/scripts/install-lazycodex-version-stamp.test.mjs +2 -2
- package/packages/omo-codex/scripts/install-local-bun-global-update.test.mjs +63 -0
- package/packages/omo-codex/scripts/install-local-entrypoint.test.mjs +109 -1
- package/packages/omo-codex/scripts/install-local.mjs +1 -0
- package/packages/shared-skills/skills/ultraresearch/SKILL.md +11 -2
- package/packages/shared-skills/skills/ulw-plan/SKILL.md +2 -1
- package/packages/shared-skills/skills/ulw-plan/references/full-workflow.md +1 -1
- package/packages/shared-skills/skills/ulw-plan/references/intent-clear.md +2 -2
- package/packages/omo-codex/plugin/hooks/hooks.json +0 -214
|
@@ -25,6 +25,7 @@ const AGGREGATE_EXPECTED_LABELS = new Map([
|
|
|
25
25
|
["hooks/hooks.json:PreToolUse:1:0", "Enforcing Unlimited Goal Budget"],
|
|
26
26
|
["hooks/hooks.json:PostToolUse:0:0", "Checking Comments"],
|
|
27
27
|
["hooks/hooks.json:PostToolUse:0:1", "Checking LSP Diagnostics"],
|
|
28
|
+
["hooks/hooks.json:PostToolUse:0:2", "Checking CodeGraph Init Guidance"],
|
|
28
29
|
["hooks/hooks.json:PostToolUse:1:0", "Matching Project Rules"],
|
|
29
30
|
["hooks/hooks.json:PostCompact:0:0", "Resetting Git Bash MCP Reminder"],
|
|
30
31
|
["hooks/hooks.json:PostCompact:1:0", "Resetting Project Rule Cache"],
|
|
@@ -68,6 +69,19 @@ async function readPluginVersion() {
|
|
|
68
69
|
return (await readJson(".codex-plugin/plugin.json")).version;
|
|
69
70
|
}
|
|
70
71
|
|
|
72
|
+
async function readAggregateHookManifests() {
|
|
73
|
+
const manifest = await readJson(".codex-plugin/plugin.json");
|
|
74
|
+
const hookPaths = Array.isArray(manifest.hooks) ? manifest.hooks : [manifest.hooks];
|
|
75
|
+
return Promise.all(
|
|
76
|
+
hookPaths
|
|
77
|
+
.filter((hookPath) => typeof hookPath === "string")
|
|
78
|
+
.map(async (hookPath) => {
|
|
79
|
+
const source = hookPath.replace(/^\.\//, "");
|
|
80
|
+
return { source, version: await readPluginVersion(), hooks: await readJson(source) };
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
71
85
|
async function readComponentVersion(componentName) {
|
|
72
86
|
return (await readJson(join("components", componentName, "package.json"))).version;
|
|
73
87
|
}
|
|
@@ -114,7 +128,7 @@ function collectCommandHooks(hooks, source, version) {
|
|
|
114
128
|
return commandHooks;
|
|
115
129
|
}
|
|
116
130
|
|
|
117
|
-
test("#given hook status label #when formatting #then prefixes
|
|
131
|
+
test("#given hook status label #when formatting #then prefixes OmO display namespace", async () => {
|
|
118
132
|
// given
|
|
119
133
|
const version = (await readRepoJson("package.json")).version;
|
|
120
134
|
const label = "Checking Comments";
|
|
@@ -123,10 +137,10 @@ test("#given hook status label #when formatting #then prefixes LazyCodex with cu
|
|
|
123
137
|
const message = formatLazyCodexHookStatusMessage(version, label);
|
|
124
138
|
|
|
125
139
|
// then
|
|
126
|
-
assert.equal(message,
|
|
140
|
+
assert.equal(message, "(OmO) Checking Comments");
|
|
127
141
|
});
|
|
128
142
|
|
|
129
|
-
test("#given hook status label with blank version #when formatting #then prefixes
|
|
143
|
+
test("#given hook status label with blank version #when formatting #then still prefixes OmO display namespace", () => {
|
|
130
144
|
// given
|
|
131
145
|
const version = " ";
|
|
132
146
|
const label = "Checking Comments";
|
|
@@ -135,7 +149,7 @@ test("#given hook status label with blank version #when formatting #then prefixe
|
|
|
135
149
|
const message = formatLazyCodexHookStatusMessage(version, label);
|
|
136
150
|
|
|
137
151
|
// then
|
|
138
|
-
assert.equal(message, "
|
|
152
|
+
assert.equal(message, "(OmO) Checking Comments");
|
|
139
153
|
});
|
|
140
154
|
|
|
141
155
|
test("#given loose legacy status label #when normalizing #then removes OMO wording and title-cases label", async () => {
|
|
@@ -149,7 +163,7 @@ test("#given loose legacy status label #when normalizing #then removes OMO wordi
|
|
|
149
163
|
|
|
150
164
|
// then
|
|
151
165
|
assert.equal(normalized, "Checking Comments");
|
|
152
|
-
assert.equal(message,
|
|
166
|
+
assert.equal(message, "(OmO) Checking Comments");
|
|
153
167
|
});
|
|
154
168
|
|
|
155
169
|
test("#given LazyCodex appears inside hook label #when normalizing #then product casing is preserved", async () => {
|
|
@@ -163,7 +177,7 @@ test("#given LazyCodex appears inside hook label #when normalizing #then product
|
|
|
163
177
|
|
|
164
178
|
// then
|
|
165
179
|
assert.equal(normalized, "Verifying LazyCodex Executor Evidence");
|
|
166
|
-
assert.equal(message,
|
|
180
|
+
assert.equal(message, "(OmO) Verifying LazyCodex Executor Evidence");
|
|
167
181
|
});
|
|
168
182
|
|
|
169
183
|
test("#given MCP appears inside hook label #when normalizing #then protocol casing is preserved", () => {
|
|
@@ -176,31 +190,29 @@ test("#given MCP appears inside hook label #when normalizing #then protocol casi
|
|
|
176
190
|
|
|
177
191
|
// then
|
|
178
192
|
assert.equal(normalized, "Recommending Git Bash MCP");
|
|
179
|
-
assert.equal(message, "
|
|
193
|
+
assert.equal(message, "(OmO) Recommending Git Bash MCP");
|
|
180
194
|
});
|
|
181
|
-
test("#given aggregate comment-checker hook #when status is inspected #then it uses
|
|
195
|
+
test("#given aggregate comment-checker hook #when status is inspected #then it uses OmO comments label", async () => {
|
|
182
196
|
// given
|
|
183
|
-
const
|
|
184
|
-
const aggregateHooks = await readJson("hooks/hooks.json");
|
|
197
|
+
const aggregateManifests = await readAggregateHookManifests();
|
|
185
198
|
|
|
186
199
|
// when
|
|
187
|
-
const hooks = collectCommandHooks(
|
|
188
|
-
const commentCheckerHook = hooks.find((hook) => hook.
|
|
200
|
+
const hooks = aggregateManifests.flatMap((manifest) => collectCommandHooks(manifest.hooks, manifest.source, manifest.version));
|
|
201
|
+
const commentCheckerHook = hooks.find((hook) => hook.command.includes("components/comment-checker/dist/cli.js"));
|
|
189
202
|
|
|
190
203
|
// then
|
|
191
|
-
assert.equal(commentCheckerHook?.statusMessage, formatLazyCodexHookStatusMessage(
|
|
192
|
-
assert.doesNotMatch(JSON.stringify(
|
|
204
|
+
assert.equal(commentCheckerHook?.statusMessage, formatLazyCodexHookStatusMessage("", "Checking Comments"));
|
|
205
|
+
assert.doesNotMatch(JSON.stringify(aggregateManifests), /checking\s+OMO\s+comments/i);
|
|
193
206
|
});
|
|
194
207
|
|
|
195
|
-
test("#given aggregate and component hooks #when status messages are inspected #then all use the
|
|
208
|
+
test("#given aggregate and component hooks #when status messages are inspected #then all use the OmO formatter", async () => {
|
|
196
209
|
// given
|
|
197
|
-
const
|
|
198
|
-
const aggregateHooks = await readJson("hooks/hooks.json");
|
|
210
|
+
const aggregateManifests = await readAggregateHookManifests();
|
|
199
211
|
const componentManifests = await readComponentHookManifests();
|
|
200
212
|
|
|
201
213
|
// when
|
|
202
214
|
const commandHooks = [
|
|
203
|
-
...collectCommandHooks(
|
|
215
|
+
...aggregateManifests.flatMap((manifest) => collectCommandHooks(manifest.hooks, manifest.source, manifest.version)),
|
|
204
216
|
...componentManifests.flatMap((manifest) => collectCommandHooks(manifest.hooks, manifest.source, manifest.version)),
|
|
205
217
|
];
|
|
206
218
|
const expectedLabels = new Map([...AGGREGATE_EXPECTED_LABELS, ...COMPONENT_EXPECTED_LABELS]);
|
|
@@ -219,6 +231,6 @@ test("#given aggregate and component hooks #when status messages are inspected #
|
|
|
219
231
|
const actualLabels = new Set(commandHooks.map((hook) => parseLazyCodexHookStatusMessage(hook.statusMessage)?.label));
|
|
220
232
|
assert.deepEqual([...expectedLabels.values()].filter((label) => !actualLabels.has(label)), []);
|
|
221
233
|
for (const hook of commandHooks) {
|
|
222
|
-
assert.
|
|
234
|
+
assert.match(hook.statusMessage, /^\(OmO\) /);
|
|
223
235
|
}
|
|
224
236
|
});
|
|
@@ -20,7 +20,7 @@ test("#given synced lcx-report-bug skill #when inspected #then it files LazyCode
|
|
|
20
20
|
assert.match(skill, /gh pr create --repo openai\/codex/);
|
|
21
21
|
assert.doesNotMatch(skill, /gh pr create --repo "\$TARGET_REPO"/);
|
|
22
22
|
assert.doesNotMatch(skill, /gh pr create --repo code-yeongyu\/lazycodex/);
|
|
23
|
-
assert.match(interfaceMetadata, /display_name: "lcx-report-bug
|
|
23
|
+
assert.match(interfaceMetadata, /display_name: "\(OmO\) lcx-report-bug"/);
|
|
24
24
|
assert.match(interfaceMetadata, /- "lazycodex bug"/);
|
|
25
25
|
assert.match(interfaceMetadata, /- "openai codex bug"/);
|
|
26
26
|
});
|
|
@@ -40,7 +40,7 @@ test("#given synced lcx-contribute-bug-fix skill #when inspected #then it delive
|
|
|
40
40
|
assert.match(skill, /gh pr create --repo openai\/codex/);
|
|
41
41
|
assert.doesNotMatch(skill, /gh pr create --repo "\$TARGET_REPO"/);
|
|
42
42
|
assert.doesNotMatch(skill, /gh pr create --repo code-yeongyu\/lazycodex/);
|
|
43
|
-
assert.match(interfaceMetadata, /display_name: "lcx-contribute-bug-fix
|
|
43
|
+
assert.match(interfaceMetadata, /display_name: "\(OmO\) lcx-contribute-bug-fix"/);
|
|
44
44
|
assert.match(interfaceMetadata, /- "contribute a bug fix"/);
|
|
45
45
|
assert.match(interfaceMetadata, /- "fix bug pr"/);
|
|
46
46
|
});
|
|
@@ -55,7 +55,7 @@ test("#given synced lcx-doctor skill #when inspected #then it diagnoses installs
|
|
|
55
55
|
|
|
56
56
|
// then
|
|
57
57
|
assert.match(skill, /^---\r?\nname: lcx-doctor\r?\n/m);
|
|
58
|
-
assert.match(interfaceMetadata, /display_name: "
|
|
58
|
+
assert.match(interfaceMetadata, /display_name: "\(OmO\) lcx-doctor"/);
|
|
59
59
|
assert.match(interfaceMetadata, /- "lazycodex doctor"/);
|
|
60
60
|
assert.match(interfaceMetadata, /- "lazycodex health check"/);
|
|
61
61
|
});
|
|
@@ -88,10 +88,10 @@ test("#given source package versions and component without hooks #when hook stat
|
|
|
88
88
|
const aggregateHooks = await readJson(join(root, "hooks", "hooks.json"));
|
|
89
89
|
const componentHooks = await readJson(join(root, "components", "comment-checker", "hooks", "hooks.json"));
|
|
90
90
|
const lspHooks = await readJson(join(root, "components", "lsp", "hooks", "hooks.json"));
|
|
91
|
-
assert.equal(aggregateHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "
|
|
92
|
-
assert.equal(aggregateHooks.hooks.PostToolUse[0].hooks[1].statusMessage, "
|
|
93
|
-
assert.equal(componentHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "
|
|
94
|
-
assert.equal(lspHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "
|
|
91
|
+
assert.equal(aggregateHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "(OmO) Checking Comments");
|
|
92
|
+
assert.equal(aggregateHooks.hooks.PostToolUse[0].hooks[1].statusMessage, "(OmO) Checking LSP Diagnostics");
|
|
93
|
+
assert.equal(componentHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "(OmO) Checking Comments");
|
|
94
|
+
assert.equal(lspHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "(OmO) Checking LSP Diagnostics");
|
|
95
95
|
});
|
|
96
96
|
|
|
97
97
|
test("#given release version override #when hook status messages sync #then aggregate hooks use release version", async () => {
|
|
@@ -139,6 +139,6 @@ test("#given release version override #when hook status messages sync #then aggr
|
|
|
139
139
|
// then
|
|
140
140
|
const aggregateHooks = await readJson(join(root, "hooks", "hooks.json"));
|
|
141
141
|
const componentHooks = await readJson(join(root, "components", "comment-checker", "hooks", "hooks.json"));
|
|
142
|
-
assert.equal(aggregateHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "
|
|
143
|
-
assert.equal(componentHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "
|
|
142
|
+
assert.equal(aggregateHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "(OmO) Checking Comments");
|
|
143
|
+
assert.equal(componentHooks.hooks.PostToolUse[0].hooks[0].statusMessage, "(OmO) Checking Comments");
|
|
144
144
|
});
|
|
@@ -22,6 +22,7 @@ export const expectedSkills = [
|
|
|
22
22
|
"review-work",
|
|
23
23
|
"rules",
|
|
24
24
|
"start-work",
|
|
25
|
+
"teammode",
|
|
25
26
|
"ultraresearch",
|
|
26
27
|
"ulw-loop",
|
|
27
28
|
"ulw-plan",
|
|
@@ -32,6 +33,7 @@ export const componentSkillSources = [
|
|
|
32
33
|
["comment-checker", "components/comment-checker/skills/comment-checker"],
|
|
33
34
|
["lsp", "components/lsp/skills/lsp"],
|
|
34
35
|
["rules", "components/rules/skills/rules"],
|
|
36
|
+
["teammode", "components/teammode/skills/teammode"],
|
|
35
37
|
["ulw-loop", "components/ulw-loop/skills/ulw-loop"],
|
|
36
38
|
["ulw-plan", "components/ultrawork/skills/ulw-plan"],
|
|
37
39
|
];
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
const root = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
18
18
|
const repoRoot = join(root, "..", "..", "..");
|
|
19
19
|
const opencodeOnlyToolPattern = /\b(?:call_omo_agent|background_output|team_[a-z_]+|task)\s*\(/;
|
|
20
|
+
const generatedSkillMetadataFiles = new Set(["agents/openai.yaml"]);
|
|
20
21
|
|
|
21
22
|
async function readPackagedSkillFile(...segments) {
|
|
22
23
|
const path = join(root, "skills", ...segments);
|
|
@@ -24,6 +25,10 @@ async function readPackagedSkillFile(...segments) {
|
|
|
24
25
|
return { path, content };
|
|
25
26
|
}
|
|
26
27
|
|
|
28
|
+
function excludeGeneratedSkillMetadata(files) {
|
|
29
|
+
return files.filter((file) => !generatedSkillMetadataFiles.has(file.replaceAll("\\", "/")));
|
|
30
|
+
}
|
|
31
|
+
|
|
27
32
|
test("#given synced aggregate Codex skills #when inspected #then component and shared skills are present", async () => {
|
|
28
33
|
// given
|
|
29
34
|
const skillsRoot = join(root, "skills");
|
|
@@ -121,8 +126,8 @@ test("#given component skill sources #when aggregate Codex component skills are
|
|
|
121
126
|
for (const [skillName, sourcePath] of componentSkillSources) {
|
|
122
127
|
const sourceDir = join(root, sourcePath);
|
|
123
128
|
const aggregateDir = join(aggregateSkillsRoot, skillName);
|
|
124
|
-
const sourceFiles = await listSkillFiles(sourceDir);
|
|
125
|
-
const aggregateFiles = await listSkillFiles(aggregateDir);
|
|
129
|
+
const sourceFiles = excludeGeneratedSkillMetadata(await listSkillFiles(sourceDir));
|
|
130
|
+
const aggregateFiles = excludeGeneratedSkillMetadata(await listSkillFiles(aggregateDir));
|
|
126
131
|
assert.deepEqual(aggregateFiles, sourceFiles, `${skillName} resource set drifted from its component skill source`);
|
|
127
132
|
for (const relativePath of sourceFiles) {
|
|
128
133
|
const sourceContent = await readFile(join(sourceDir, relativePath), "utf8");
|
|
@@ -146,7 +151,7 @@ test("#given synced ulw-loop skill #when Codex hint metadata is inspected #then
|
|
|
146
151
|
|
|
147
152
|
// then
|
|
148
153
|
assert.match(skill, /^---\r?\nname: ulw-loop\r?\n/m);
|
|
149
|
-
assert.match(interfaceMetadata, /display_name: "
|
|
154
|
+
assert.match(interfaceMetadata, /display_name: "\(OmO\) ulw-loop"/);
|
|
150
155
|
assert.doesNotMatch(interfaceMetadata, /ulw-loop \/ ulw-loop/);
|
|
151
156
|
assert.match(interfaceMetadata, /short_description: "Goal-like ultrawork loop for systematic decomposition"/);
|
|
152
157
|
assert.match(interfaceMetadata, /default_prompt: "Use \$ulw-loop/);
|
|
@@ -179,7 +184,7 @@ test("#given synced git-master skill #when inspected #then commits and git histo
|
|
|
179
184
|
assert.match(skill, /Choose the Git tool by the question/);
|
|
180
185
|
assert.match(skill, /git log -S "text"/);
|
|
181
186
|
assert.match(skill, /git blame -L start,end -- file/);
|
|
182
|
-
assert.match(interfaceMetadata, /display_name: "
|
|
187
|
+
assert.match(interfaceMetadata, /display_name: "\(OmO\) git-master"/);
|
|
183
188
|
assert.match(interfaceMetadata, /- "git commit"/);
|
|
184
189
|
assert.match(interfaceMetadata, /- "history search"/);
|
|
185
190
|
});
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
|
|
7
|
+
import { root } from "./aggregate-plugin-fixture.mjs";
|
|
8
|
+
|
|
9
|
+
const teamScript = join(root, "components", "teammode", "skills", "teammode", "scripts", "team.mjs");
|
|
10
|
+
|
|
11
|
+
export function createTeamRoot(prefix) {
|
|
12
|
+
return mkdtempSync(join(tmpdir(), prefix));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function cleanupTeamRoot(tempRoot) {
|
|
16
|
+
rmSync(tempRoot, { recursive: true, force: true });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function teamDir(tempRoot, sessionId) {
|
|
20
|
+
return join(tempRoot, ".omo", "teams", sessionId);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function teamJsonPath(tempRoot, sessionId) {
|
|
24
|
+
return join(teamDir(tempRoot, sessionId), "team.json");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function readTeamJson(tempRoot, sessionId) {
|
|
28
|
+
return JSON.parse(readFileSync(teamJsonPath(tempRoot, sessionId), "utf8"));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function runTeam(cwd, ...args) {
|
|
32
|
+
const result = runTeamRaw(cwd, ...args);
|
|
33
|
+
assert.equal(result.status, 0, `team.mjs ${args.join(" ")} failed: ${result.stderr}`);
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function runTeamRaw(cwd, ...args) {
|
|
38
|
+
return spawnSync(process.execPath, [teamScript, ...args], {
|
|
39
|
+
cwd,
|
|
40
|
+
encoding: "utf8",
|
|
41
|
+
timeout: 10_000,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function symlinkOrSkip(t, target, path, type) {
|
|
46
|
+
try {
|
|
47
|
+
symlinkSync(target, path, type);
|
|
48
|
+
return true;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (isUnavailableSymlinkError(error)) {
|
|
51
|
+
t.skip(`symlink unavailable on this filesystem: ${error.code}`);
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isUnavailableSymlinkError(error) {
|
|
59
|
+
return error?.code === "EPERM" || error?.code === "EACCES" || error?.code === "EINVAL";
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function createArchivedOutsideTeam(tempRoot, sessionId) {
|
|
63
|
+
const outsideTeams = join(tempRoot, "outside-teams");
|
|
64
|
+
const outsideTeamDir = join(outsideTeams, sessionId);
|
|
65
|
+
mkdirSync(outsideTeamDir, { recursive: true });
|
|
66
|
+
writeFileSync(
|
|
67
|
+
join(outsideTeamDir, "team.json"),
|
|
68
|
+
`${JSON.stringify(
|
|
69
|
+
{
|
|
70
|
+
schemaVersion: 2,
|
|
71
|
+
teamId: "outside-team",
|
|
72
|
+
teamName: "Outside",
|
|
73
|
+
sessionName: "Escape",
|
|
74
|
+
leader: { kind: "main-session", sessionId },
|
|
75
|
+
status: "archived",
|
|
76
|
+
members: [],
|
|
77
|
+
},
|
|
78
|
+
null,
|
|
79
|
+
2,
|
|
80
|
+
)}\n`,
|
|
81
|
+
);
|
|
82
|
+
return { outsideTeams, outsideTeamDir };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function assertOutsideTeamIntact(outsideTeamDir) {
|
|
86
|
+
assert.equal(existsSync(outsideTeamDir), true);
|
|
87
|
+
assert.equal(JSON.parse(readFileSync(join(outsideTeamDir, "team.json"), "utf8")).teamId, "outside-team");
|
|
88
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { mkdirSync, readFileSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import test from "node:test";
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
assertOutsideTeamIntact,
|
|
8
|
+
cleanupTeamRoot,
|
|
9
|
+
createArchivedOutsideTeam,
|
|
10
|
+
createTeamRoot,
|
|
11
|
+
readTeamJson,
|
|
12
|
+
runTeam,
|
|
13
|
+
runTeamRaw,
|
|
14
|
+
symlinkOrSkip,
|
|
15
|
+
teamDir,
|
|
16
|
+
teamJsonPath,
|
|
17
|
+
} from "./teammode-safety-fixture.mjs";
|
|
18
|
+
|
|
19
|
+
test("#given guide.md is a symlink #when guide is regenerated #then the outside target stays untouched", (t) => {
|
|
20
|
+
const tempRoot = createTeamRoot("omo-codex-teammode-guide-");
|
|
21
|
+
try {
|
|
22
|
+
runTeam(tempRoot, "init", "--name", "Symlink", "--session-name", "Escape", "--session", "safe-guide");
|
|
23
|
+
const guidePath = join(teamDir(tempRoot, "safe-guide"), "guide.md");
|
|
24
|
+
const outsidePath = join(tempRoot, "outside-guide-target.md");
|
|
25
|
+
writeFileSync(outsidePath, "ORIGINAL_OUTSIDE\n");
|
|
26
|
+
unlinkSync(guidePath);
|
|
27
|
+
if (!symlinkOrSkip(t, outsidePath, guidePath)) return;
|
|
28
|
+
|
|
29
|
+
const result = runTeamRaw(tempRoot, "guide", "--team", "safe-guide");
|
|
30
|
+
|
|
31
|
+
assert.notEqual(result.status, 0);
|
|
32
|
+
assert.match(result.stderr, /guide\.md is a symlink|persist target escapes/);
|
|
33
|
+
assert.equal(readFileSync(outsidePath, "utf8"), "ORIGINAL_OUTSIDE\n");
|
|
34
|
+
} finally {
|
|
35
|
+
cleanupTeamRoot(tempRoot);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("#given member A exists #when add-member receives A with trailing space #then state is not partially mutated", () => {
|
|
40
|
+
const tempRoot = createTeamRoot("omo-codex-teammode-duplicate-");
|
|
41
|
+
try {
|
|
42
|
+
runTeam(tempRoot, "init", "--name", "Duplicate", "--session-name", "Members", "--session", "safe-duplicate");
|
|
43
|
+
runTeam(
|
|
44
|
+
tempRoot,
|
|
45
|
+
"add-member",
|
|
46
|
+
"--team",
|
|
47
|
+
"safe-duplicate",
|
|
48
|
+
"--id",
|
|
49
|
+
"A",
|
|
50
|
+
"--focus",
|
|
51
|
+
"alpha",
|
|
52
|
+
"--lens",
|
|
53
|
+
"area",
|
|
54
|
+
"--deliverable",
|
|
55
|
+
"first",
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const result = runTeamRaw(
|
|
59
|
+
tempRoot,
|
|
60
|
+
"add-member",
|
|
61
|
+
"--team",
|
|
62
|
+
"safe-duplicate",
|
|
63
|
+
"--id",
|
|
64
|
+
"A ",
|
|
65
|
+
"--focus",
|
|
66
|
+
"beta",
|
|
67
|
+
"--lens",
|
|
68
|
+
"ownership",
|
|
69
|
+
"--deliverable",
|
|
70
|
+
"second",
|
|
71
|
+
);
|
|
72
|
+
const team = readTeamJson(tempRoot, "safe-duplicate");
|
|
73
|
+
|
|
74
|
+
assert.notEqual(result.status, 0);
|
|
75
|
+
assert.match(result.stderr, /member id "A" already exists/);
|
|
76
|
+
assert.deepEqual(
|
|
77
|
+
team.members.map((member) => member.id),
|
|
78
|
+
["A"],
|
|
79
|
+
);
|
|
80
|
+
} finally {
|
|
81
|
+
cleanupTeamRoot(tempRoot);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("#given member focus already exists #when add-member receives same focus with different spacing and case #then state is not partially mutated", () => {
|
|
86
|
+
const tempRoot = createTeamRoot("omo-codex-teammode-duplicate-focus-");
|
|
87
|
+
try {
|
|
88
|
+
runTeam(tempRoot, "init", "--name", "DuplicateFocus", "--session-name", "Members", "--session", "safe-duplicate-focus");
|
|
89
|
+
runTeam(
|
|
90
|
+
tempRoot,
|
|
91
|
+
"add-member",
|
|
92
|
+
"--team",
|
|
93
|
+
"safe-duplicate-focus",
|
|
94
|
+
"--id",
|
|
95
|
+
"A",
|
|
96
|
+
"--focus",
|
|
97
|
+
"Plugin Hook Packaging",
|
|
98
|
+
"--lens",
|
|
99
|
+
"ownership",
|
|
100
|
+
"--deliverable",
|
|
101
|
+
"first",
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const result = runTeamRaw(
|
|
105
|
+
tempRoot,
|
|
106
|
+
"add-member",
|
|
107
|
+
"--team",
|
|
108
|
+
"safe-duplicate-focus",
|
|
109
|
+
"--id",
|
|
110
|
+
"B",
|
|
111
|
+
"--focus",
|
|
112
|
+
" plugin hook packaging ",
|
|
113
|
+
"--lens",
|
|
114
|
+
"area",
|
|
115
|
+
"--deliverable",
|
|
116
|
+
"second",
|
|
117
|
+
);
|
|
118
|
+
const team = readTeamJson(tempRoot, "safe-duplicate-focus");
|
|
119
|
+
|
|
120
|
+
assert.notEqual(result.status, 0);
|
|
121
|
+
assert.match(result.stderr, /member focus "plugin hook packaging" duplicates "Plugin Hook Packaging"/);
|
|
122
|
+
assert.deepEqual(
|
|
123
|
+
team.members.map((member) => member.id),
|
|
124
|
+
["A"],
|
|
125
|
+
);
|
|
126
|
+
} finally {
|
|
127
|
+
cleanupTeamRoot(tempRoot);
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("#given only one member exists #when bind-thread runs #then member is not activated", () => {
|
|
132
|
+
const tempRoot = createTeamRoot("omo-codex-teammode-understaffed-bind-");
|
|
133
|
+
try {
|
|
134
|
+
runTeam(tempRoot, "init", "--name", "Understaffed", "--session-name", "Members", "--session", "safe-understaffed");
|
|
135
|
+
runTeam(
|
|
136
|
+
tempRoot,
|
|
137
|
+
"add-member",
|
|
138
|
+
"--team",
|
|
139
|
+
"safe-understaffed",
|
|
140
|
+
"--id",
|
|
141
|
+
"A",
|
|
142
|
+
"--focus",
|
|
143
|
+
"installer",
|
|
144
|
+
"--lens",
|
|
145
|
+
"area",
|
|
146
|
+
"--deliverable",
|
|
147
|
+
"first",
|
|
148
|
+
);
|
|
149
|
+
|
|
150
|
+
const result = runTeamRaw(tempRoot, "bind-thread", "--team", "safe-understaffed", "--id", "A", "--thread", "thread-a");
|
|
151
|
+
let team = readTeamJson(tempRoot, "safe-understaffed");
|
|
152
|
+
|
|
153
|
+
assert.notEqual(result.status, 0);
|
|
154
|
+
assert.match(result.stderr, /at least 2 distinct members/);
|
|
155
|
+
assert.equal(team.members[0].threadId, null);
|
|
156
|
+
assert.equal(team.members[0].status, "pending");
|
|
157
|
+
|
|
158
|
+
runTeam(
|
|
159
|
+
tempRoot,
|
|
160
|
+
"add-member",
|
|
161
|
+
"--team",
|
|
162
|
+
"safe-understaffed",
|
|
163
|
+
"--id",
|
|
164
|
+
"B",
|
|
165
|
+
"--focus",
|
|
166
|
+
"runtime qa",
|
|
167
|
+
"--lens",
|
|
168
|
+
"perspective",
|
|
169
|
+
"--deliverable",
|
|
170
|
+
"second",
|
|
171
|
+
);
|
|
172
|
+
runTeam(tempRoot, "bind-thread", "--team", "safe-understaffed", "--id", "A", "--thread", "thread-a");
|
|
173
|
+
team = readTeamJson(tempRoot, "safe-understaffed");
|
|
174
|
+
|
|
175
|
+
assert.equal(team.members[0].threadId, "thread-a");
|
|
176
|
+
assert.equal(team.members[0].status, "active");
|
|
177
|
+
} finally {
|
|
178
|
+
cleanupTeamRoot(tempRoot);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
test("#given persisted paths are mutated outside trusted team dir #when guide is regenerated #then outside file stays untouched", () => {
|
|
183
|
+
const tempRoot = createTeamRoot("omo-codex-teammode-mutated-paths-");
|
|
184
|
+
try {
|
|
185
|
+
runTeam(tempRoot, "init", "--name", "Mutated", "--session-name", "Paths", "--session", "safe-mutated");
|
|
186
|
+
const teamPath = teamJsonPath(tempRoot, "safe-mutated");
|
|
187
|
+
const outsideDir = join(tempRoot, "outside");
|
|
188
|
+
const outsideGuide = join(outsideDir, "guide.md");
|
|
189
|
+
mkdirSync(outsideDir);
|
|
190
|
+
writeFileSync(outsideGuide, "ORIGINAL_OUTSIDE\n");
|
|
191
|
+
const team = JSON.parse(readFileSync(teamPath, "utf8"));
|
|
192
|
+
team.paths.dir = outsideDir;
|
|
193
|
+
team.paths.guide = outsideGuide;
|
|
194
|
+
writeFileSync(teamPath, `${JSON.stringify(team, null, 2)}\n`);
|
|
195
|
+
|
|
196
|
+
const result = runTeamRaw(tempRoot, "guide", "--team", "safe-mutated");
|
|
197
|
+
|
|
198
|
+
assert.notEqual(result.status, 0);
|
|
199
|
+
assert.match(result.stderr, /persisted team dir does not match trusted team dir/);
|
|
200
|
+
assert.equal(readFileSync(outsideGuide, "utf8"), "ORIGINAL_OUTSIDE\n");
|
|
201
|
+
} finally {
|
|
202
|
+
cleanupTeamRoot(tempRoot);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("#given team dir is swapped to a symlink #when status reads team state #then command refuses the symlink", (t) => {
|
|
207
|
+
const tempRoot = createTeamRoot("omo-codex-teammode-dir-symlink-");
|
|
208
|
+
try {
|
|
209
|
+
runTeam(tempRoot, "init", "--name", "SymlinkDir", "--session-name", "Paths", "--session", "safe-dir");
|
|
210
|
+
const targetTeamDir = teamDir(tempRoot, "safe-dir");
|
|
211
|
+
const outsideDir = join(tempRoot, "outside-team-dir");
|
|
212
|
+
mkdirSync(outsideDir);
|
|
213
|
+
rmSync(targetTeamDir, { recursive: true, force: true });
|
|
214
|
+
if (!symlinkOrSkip(t, outsideDir, targetTeamDir, "dir")) return;
|
|
215
|
+
|
|
216
|
+
const result = runTeamRaw(tempRoot, "status", "--team", "safe-dir");
|
|
217
|
+
|
|
218
|
+
assert.notEqual(result.status, 0);
|
|
219
|
+
assert.match(result.stderr, /path component is a symlink|team dir is a symlink/);
|
|
220
|
+
} finally {
|
|
221
|
+
cleanupTeamRoot(tempRoot);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("#given teams root is a symlink #when delete runs #then outside team state stays untouched", (t) => {
|
|
226
|
+
const tempRoot = createTeamRoot("omo-codex-teammode-delete-root-symlink-");
|
|
227
|
+
try {
|
|
228
|
+
const { outsideTeams, outsideTeamDir } = createArchivedOutsideTeam(tempRoot, "escape");
|
|
229
|
+
mkdirSync(join(tempRoot, ".omo"), { recursive: true });
|
|
230
|
+
if (!symlinkOrSkip(t, outsideTeams, join(tempRoot, ".omo", "teams"), "dir")) return;
|
|
231
|
+
|
|
232
|
+
const result = runTeamRaw(tempRoot, "delete", "--team", "escape", "--force");
|
|
233
|
+
|
|
234
|
+
assert.notEqual(result.status, 0);
|
|
235
|
+
assert.match(result.stderr, /path component is a symlink/);
|
|
236
|
+
assertOutsideTeamIntact(outsideTeamDir);
|
|
237
|
+
} finally {
|
|
238
|
+
cleanupTeamRoot(tempRoot);
|
|
239
|
+
}
|
|
240
|
+
});
|
|
@@ -124,3 +124,51 @@ test("#given ultraresearch worker sizing #when spawn guidance is inspected #then
|
|
|
124
124
|
);
|
|
125
125
|
}
|
|
126
126
|
});
|
|
127
|
+
|
|
128
|
+
test("#given ultraresearch execution substrate #when team usage is inspected #then it prefers a cooperating team with harness-native team tools", async () => {
|
|
129
|
+
for (const copy of await readUltraresearchCopies()) {
|
|
130
|
+
assert.match(
|
|
131
|
+
copy.content,
|
|
132
|
+
/cooperating team/i,
|
|
133
|
+
`${copy.label}: body must encourage running the swarm as a cooperating team`,
|
|
134
|
+
);
|
|
135
|
+
assert.match(copy.content, /\bteammode\b/i, `${copy.label}: body must name the Codex teammode skill`);
|
|
136
|
+
assert.match(copy.content, /team_mode/i, `${copy.label}: body must name the OpenCode team_mode path`);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("#given ultraresearch team composition #when member slicing is inspected #then members map to part/ownership/perspective, never job titles", async () => {
|
|
141
|
+
for (const copy of await readUltraresearchCopies()) {
|
|
142
|
+
assert.match(
|
|
143
|
+
copy.content,
|
|
144
|
+
/by part, ownership, or perspective/i,
|
|
145
|
+
`${copy.label}: body must compose members by part, ownership, or perspective`,
|
|
146
|
+
);
|
|
147
|
+
assert.match(
|
|
148
|
+
copy.content,
|
|
149
|
+
/never a job title|not (?:a |by )?job title/i,
|
|
150
|
+
`${copy.label}: body must forbid vague job-title members`,
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("#given ultraresearch team communication #when the raise law is inspected #then members broadcast every lead immediately rather than hoarding", async () => {
|
|
156
|
+
for (const copy of await readUltraresearchCopies()) {
|
|
157
|
+
assert.match(
|
|
158
|
+
copy.content,
|
|
159
|
+
/raise law|broadcast every lead/i,
|
|
160
|
+
`${copy.label}: body must state the raise/broadcast law`,
|
|
161
|
+
);
|
|
162
|
+
assert.match(copy.content, /over-communicate/i, `${copy.label}: body must demand over-communication`);
|
|
163
|
+
assert.match(
|
|
164
|
+
copy.content,
|
|
165
|
+
/the (?:moment|instant) it (?:surfaces|appears|lands)/i,
|
|
166
|
+
`${copy.label}: body must require raising leads the moment they surface`,
|
|
167
|
+
);
|
|
168
|
+
assert.match(
|
|
169
|
+
copy.content,
|
|
170
|
+
/hoard/i,
|
|
171
|
+
`${copy.label}: body must reject hoarding leads for a final dump`,
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
});
|