mirror of
https://github.com/Fission-AI/OpenSpec.git
synced 2026-10-02 05:24:34 +08:00
feat(tools): add Zed Agent support (#1659)
* feat(tools): add Zed Agent support * fix(tools): detect Zed projects
This commit is contained in:
+1
-1
@@ -109,7 +109,7 @@ openspec init [path] [options]
|
||||
|
||||
The welcome animation is also skipped when the `OPENSPEC_NO_ANIMATION` environment variable is set (any value, including empty), when `NO_COLOR` is set to a non-empty value, or when the OS reduced-motion preference is enabled (macOS Reduce Motion, GNOME animations disabled).
|
||||
|
||||
**Supported tool IDs (`--tools`)** — `windsurf` is also accepted, as an alias for `devin`: `amazon-q`, `antigravity`, `auggie`, `bob`, `claude`, `cline`, `command-code`, `codeartsagent`, `codex`, `devin`, `forgecode`, `codebuddy`, `continue`, `costrict`, `crush`, `cursor`, `factory`, `gemini`, `github-copilot`, `hermes`, `iflow`, `junie`, `kilocode`, `kimi`, `kiro`, `lingma`, `minimax-code`, `vibe`, `oh-my-pi`, `opencode`, `pi`, `qoder`, `qwen`, `roocode`, `trae`, `zcode`, `agents`
|
||||
**Supported tool IDs (`--tools`)** — `windsurf` is also accepted, as an alias for `devin`: `amazon-q`, `antigravity`, `auggie`, `bob`, `claude`, `cline`, `command-code`, `codeartsagent`, `codex`, `devin`, `forgecode`, `codebuddy`, `continue`, `costrict`, `crush`, `cursor`, `factory`, `gemini`, `github-copilot`, `hermes`, `iflow`, `junie`, `kilocode`, `kimi`, `kiro`, `lingma`, `minimax-code`, `vibe`, `oh-my-pi`, `opencode`, `pi`, `qoder`, `qwen`, `roocode`, `trae`, `zed`, `zcode`, `agents`
|
||||
|
||||
> This list mirrors `AI_TOOLS` in `src/core/config.ts`. See [Supported Tools](supported-tools.md) for each tool's skill and command paths.
|
||||
|
||||
|
||||
+1
-1
@@ -673,7 +673,7 @@ Different AI tools use slightly different command syntax. Use the format that ma
|
||||
|--------------------------|----------------|---------------|
|
||||
| `.../commands/opsx/<id>.*` | `/opsx:propose`, `/opsx:apply` | Claude Code, Gemini CLI, Crush |
|
||||
| `.../opsx-<id>.*` | `/opsx-propose`, `/opsx-apply` | Cursor, Devin Desktop, Copilot (IDE), Trae, Oh My Pi |
|
||||
| none — skills only | `/openspec-propose`, `/openspec-apply-change` | CodeArts, ForgeCode, Hermes, MiniMax Code, Mistral Vibe, shared `.agents` |
|
||||
| none — skills only | `/openspec-propose`, `/openspec-apply-change` | CodeArts, ForgeCode, Hermes, MiniMax Code, Mistral Vibe, Zed Agent, shared `.agents` |
|
||||
| none — Kimi Code | `/skill:openspec-propose` | Kimi Code |
|
||||
| none — Codex CLI | `$openspec-propose` | Codex |
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ The intent is identical everywhere. The spelling follows the file your tool load
|
||||
| `.../commands/opsx/<id>.*` | `/opsx:propose` | Claude Code, Gemini CLI, Crush |
|
||||
| `.../opsx-<id>.*` | `/opsx-propose` | Cursor, GitHub Copilot (IDE), Devin Desktop, Trae, Oh My Pi |
|
||||
| `.amazonq/prompts/opsx-<id>.md` | `@opsx-propose` | Amazon Q Developer |
|
||||
| none — skills only | `/openspec-propose` | CodeArts, ForgeCode, Hermes, Mistral Vibe, shared `.agents` |
|
||||
| none — skills only | `/openspec-propose` | CodeArts, ForgeCode, Hermes, Mistral Vibe, Zed Agent, shared `.agents` |
|
||||
| none — Kimi Code | `/skill:openspec-propose` | Kimi Code |
|
||||
| none — Codex CLI | `$openspec-propose` | Codex |
|
||||
|
||||
@@ -114,7 +114,7 @@ See [Supported Tools](supported-tools.md) for the exact paths per tool, and [Mig
|
||||
|
||||
Quick checks, fastest first:
|
||||
|
||||
1. **Type a slash in your AI chat.** Start typing `/opsx` and watch for autocomplete suggestions. If they appear, you're set. On a skills-only tool (Codex, Kimi Code, CodeArts, ForgeCode, Hermes, Mistral Vibe, or the shared `.agents` target) `/opsx` never completes even on a healthy install — try the skill name from the table above instead.
|
||||
1. **Type a slash in your AI chat.** Start typing `/opsx` and watch for autocomplete suggestions. If they appear, you're set. On a skills-only tool (Codex, Kimi Code, CodeArts, ForgeCode, Hermes, Mistral Vibe, Zed Agent, or the shared `.agents` target) `/opsx` never completes even on a healthy install — try the skill name from the table above instead.
|
||||
2. **Look for the files.** For Claude Code, check that `.claude/skills/` contains `openspec-*` folders. Other tools use their own directories ([Supported Tools](supported-tools.md) lists them).
|
||||
3. **Re-run setup.** From your project root, run `openspec update`. This regenerates the skill and command files for whatever tools you configured.
|
||||
4. **Restart your assistant.** Many tools scan for skills and commands at startup, so a fresh window can be the missing step.
|
||||
|
||||
+15
-7
@@ -33,7 +33,7 @@ way it loads the file OpenSpec wrote. Find your tool's command path in the
|
||||
| `.../opsx-<id>.*` — the filename is the command | `/opsx-<id>` | Every other tool with generated command files, except Amazon Q and Devin |
|
||||
| `.devin/workflows/opsx-<id>.md` — read by only one of Devin's two agents | `/opsx-<id>` on Devin Desktop, `/openspec-<skill>` on Devin Local | Devin Desktop\*\*\*\* |
|
||||
| `.amazonq/prompts/opsx-<id>.md` — a prompt, not a command | `@opsx-<id>` | Amazon Q Developer |
|
||||
| none — skills only | `/openspec-<skill>` | CodeArts, ForgeCode, Hermes, MiniMax Code, Mistral Vibe, shared `.agents` |
|
||||
| none — skills only | `/openspec-<skill>` | CodeArts, ForgeCode, Hermes, MiniMax Code, Mistral Vibe, Zed Agent, shared `.agents` |
|
||||
| none — Kimi Code | `/skill:openspec-<skill>` | Kimi Code |
|
||||
| none — Codex CLI | `$openspec-<skill>` | Codex ([`/openspec-<skill>` is not recognized](https://github.com/openai/codex/issues/11817)) |
|
||||
|
||||
@@ -100,6 +100,7 @@ to read the hint.
|
||||
| [Rovo Dev CLI](https://support.atlassian.com/rovo/docs/use-rovo-dev-cli/) (`rovodev`) | `.rovodev/skills/openspec-*/SKILL.md` | Not generated. Rovo has no slash-command surface — it matches skills automatically or by prompt (e.g. "use the openspec-propose skill"); `/skills` only manages them. Generated content references skills by name, never as `/openspec-*` commands. |
|
||||
| [Zoo Code](https://github.com/Zoo-Code-Org/Zoo-Code) (`roocode`) | `.roo/skills/openspec-*/SKILL.md` | `.roo/commands/opsx-<id>.md` |
|
||||
| Trae (`trae`) | `.trae/skills/openspec-*/SKILL.md` | `.trae/commands/opsx-<id>.md` |
|
||||
| [Zed Agent](https://zed.dev/docs/ai/skills) (`zed`) | `.agents/skills/openspec-*/SKILL.md` | Not generated (skills-only; use `/openspec-*` or `@openspec-*`) |
|
||||
| ZCode (`zcode`) | `.zcode/skills/openspec-*/SKILL.md` | `.zcode/commands/opsx/<id>.md` |
|
||||
| Shared `.agents` skills (`agents`) | `.agents/skills/openspec-*/SKILL.md` | Not generated (no command adapter; use skill-based `/openspec-*` invocations) |
|
||||
|
||||
@@ -145,8 +146,8 @@ shared root many agent tools read, instead of a tool-specific directory.
|
||||
| Your tool isn't listed yet but reads `.agents/skills` | `agents` |
|
||||
|
||||
Selecting it alongside a tool-specific ID is fine; each normally writes to its
|
||||
own root. Codex is the exception because it uses the same canonical `.agents`
|
||||
root. If both `codex` and `agents` are selected, OpenSpec keeps one
|
||||
own root. Codex and Zed Agent are the exceptions because they use the same canonical
|
||||
`.agents` root. If Codex is selected with Zed or `agents`, OpenSpec keeps one
|
||||
Codex-led tree. Its handoffs name both `$openspec-*` for Codex and
|
||||
`/openspec-*` for other agents, so `--tools all` and existing multi-agent
|
||||
setups keep working without two writers overwriting the same files.
|
||||
@@ -168,10 +169,17 @@ Two things to know:
|
||||
If your root `AGENTS.md` still carries OpenSpec marker blocks from an older
|
||||
version, `openspec update` strips them — see the [Migration Guide](migration-guide.md).
|
||||
|
||||
Because `.agents/skills/` is shared, it is worth knowing what OpenSpec claims there:
|
||||
Zed support here is for the built-in Zed Agent. Zed External Agents and Terminal
|
||||
Threads use their own integrations. Agent Skills require
|
||||
[Zed v1.4.2](https://github.com/zed-industries/zed/releases/tag/v1.4.2) or newer.
|
||||
Project-local skills are unavailable in an untrusted worktree until you
|
||||
[grant trust](https://zed.dev/docs/worktree-trust).
|
||||
|
||||
Because `.agents/skills/` is shared by Codex, Zed Agent, and the vendor-neutral target,
|
||||
it is worth knowing what OpenSpec claims there:
|
||||
it writes, refreshes, and removes only the `openspec-*` skill directories for your
|
||||
selected workflows, plus an `.openspec-target` marker that records whether Codex
|
||||
or the vendor-neutral target rendered that shared tree. Anything else in that
|
||||
selected workflows, plus an `.openspec-target` marker that records whether Codex,
|
||||
Zed Agent, or the vendor-neutral target rendered that shared tree. Anything else in that
|
||||
directory is left alone. Treat the `openspec-*` names and marker as OpenSpec's —
|
||||
edits inside them are replaced on the next `openspec update`, the same as for
|
||||
every other tool.
|
||||
@@ -206,7 +214,7 @@ openspec init --tools none
|
||||
openspec init --profile core
|
||||
```
|
||||
|
||||
**Available tool IDs (`--tools`)** — `windsurf` is also accepted, as an alias for `devin`: `amazon-q`, `antigravity`, `auggie`, `bob`, `claude`, `cline`, `command-code`, `codeartsagent`, `codex`, `devin`, `forgecode`, `codebuddy`, `continue`, `costrict`, `crush`, `cursor`, `factory`, `gemini`, `github-copilot`, `hermes`, `iflow`, `junie`, `kilocode`, `kimi`, `kiro`, `lingma`, `minimax-code`, `vibe`, `oh-my-pi`, `opencode`, `pi`, `qoder`, `qwen`, `roocode`, `trae`, `zcode`, `agents`
|
||||
**Available tool IDs (`--tools`)** — `windsurf` is also accepted, as an alias for `devin`: `amazon-q`, `antigravity`, `auggie`, `bob`, `claude`, `cline`, `command-code`, `codeartsagent`, `codex`, `devin`, `forgecode`, `codebuddy`, `continue`, `costrict`, `crush`, `cursor`, `factory`, `gemini`, `github-copilot`, `hermes`, `iflow`, `junie`, `kilocode`, `kimi`, `kiro`, `lingma`, `minimax-code`, `vibe`, `oh-my-pi`, `opencode`, `pi`, `qoder`, `qwen`, `roocode`, `trae`, `zed`, `zcode`, `agents`
|
||||
|
||||
## Workflow-Dependent Installation
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ If `/opsx:propose` (or your tool's equivalent) doesn't appear or doesn't do anyt
|
||||
|
||||
5. **Check you initialized this project.** Skills are written per project. If you cloned a repo or switched folders, run `openspec init` (or `openspec update`) there.
|
||||
|
||||
6. **Confirm your tool supports command files.** Codex, CodeArts, ForgeCode, Hermes, Kimi Code, Mistral Vibe and the shared `.agents` target don't get generated `opsx-*` command files; they use skill-based invocations instead, so `/opsx` will never autocomplete for them. Type `$openspec-propose` in Codex, `/skill:openspec-propose` in Kimi Code, and `/openspec-propose` in the rest. The shared `.agents` target is vendor-neutral, so `/openspec-propose` is the common form rather than a guaranteed one — if your assistant does not answer to it, check its own docs for how it invokes a skill. Amazon Q does get command files, but loads them into its prompt library rather than its slash menu — type `@opsx-propose` there, not `/opsx`. Every tool's form is listed in [How To Invoke](supported-tools.md#how-to-invoke).
|
||||
6. **Confirm your tool supports command files.** Codex, CodeArts, ForgeCode, Hermes, Kimi Code, Mistral Vibe, Zed Agent, and the shared `.agents` target don't get generated `opsx-*` command files; they use skill-based invocations instead, so `/opsx` will never autocomplete for them. Type `$openspec-propose` in Codex, `/skill:openspec-propose` in Kimi Code, and `/openspec-propose` in the rest. The shared `.agents` target is vendor-neutral, so `/openspec-propose` is the common form rather than a guaranteed one — if your assistant does not answer to it, check its own docs for how it invokes a skill. Amazon Q does get command files, but loads them into its prompt library rather than its slash menu — type `@opsx-propose` there, not `/opsx`. Every tool's form is listed in [How To Invoke](supported-tools.md#how-to-invoke).
|
||||
|
||||
## Working with changes
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ export const AI_TOOLS: AIToolOption[] = [
|
||||
{ name: 'Rovo Dev CLI', value: 'rovodev', available: true, successLabel: 'Rovo Dev CLI', skillsDir: '.rovodev', detectionPaths: ['.rovodev/skills', '.rovodev'] },
|
||||
{ name: 'Zoo Code', value: 'roocode', available: true, successLabel: 'Zoo Code', skillsDir: '.roo', requiresIdeRestart: true },
|
||||
{ name: 'Trae', value: 'trae', available: true, successLabel: 'Trae', skillsDir: '.trae', requiresIdeRestart: true },
|
||||
{ name: 'Zed Agent', value: 'zed', available: true, successLabel: 'Zed Agent', skillsDir: '.agents', detectionPaths: ['.zed', '.agents/skills'] },
|
||||
{ name: 'ZCode', value: 'zcode', available: true, successLabel: 'ZCode', skillsDir: '.zcode' },
|
||||
// Vendor-neutral target for assistants that read the shared `.agents` root.
|
||||
// Detection keys off `.agents/skills` rather than the bare root: frameworks use
|
||||
|
||||
+25
-4
@@ -751,13 +751,34 @@ export class InitCommand {
|
||||
): ValidatedInitTool[] {
|
||||
const validatedTools: ValidatedInitTool[] = [];
|
||||
|
||||
const reconciledToolIds = toolIds.includes('codex') && toolIds.includes('agents')
|
||||
? toolIds.filter((toolId) => toolId !== 'agents')
|
||||
const sharedAgentsTargets = ['codex', 'zed', 'agents'];
|
||||
const selectedSharedTargets = sharedAgentsTargets.filter((toolId) => toolIds.includes(toolId));
|
||||
// A Codex-rendered tree already serves Zed. Keep it when Zed is added later
|
||||
// so Codex users do not lose the `$openspec-*` references they require.
|
||||
const preserveConfiguredCodex = selectedSharedTargets.includes('zed') &&
|
||||
toolStates.get('codex')?.configured;
|
||||
const sharedTargetCandidates = preserveConfiguredCodex
|
||||
? [...new Set([...selectedSharedTargets, 'codex'])]
|
||||
: selectedSharedTargets;
|
||||
const sharedTargetOwner = sharedTargetCandidates.includes('codex')
|
||||
? 'codex'
|
||||
: selectedSharedTargets.includes('zed')
|
||||
? 'zed'
|
||||
: selectedSharedTargets[0];
|
||||
const firstSharedIndex = toolIds.findIndex((id) => sharedAgentsTargets.includes(id));
|
||||
const reconciledToolIds = sharedTargetCandidates.length > 1
|
||||
? toolIds.flatMap((toolId, index) => {
|
||||
if (!sharedAgentsTargets.includes(toolId)) return [toolId];
|
||||
return index === firstSharedIndex && sharedTargetOwner ? [sharedTargetOwner] : [];
|
||||
})
|
||||
: toolIds;
|
||||
if (reconciledToolIds.length !== toolIds.length) {
|
||||
if (
|
||||
reconciledToolIds.length !== toolIds.length ||
|
||||
reconciledToolIds.some((toolId, index) => toolId !== toolIds[index])
|
||||
) {
|
||||
console.log(
|
||||
chalk.dim(
|
||||
'Codex and agents share .agents/skills; writing one tree with Codex and generic skill references.'
|
||||
`Codex, Zed, and agents share .agents/skills; writing one tree for ${sharedTargetOwner}.`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -185,6 +185,35 @@ describe('openspec CLI e2e basics', () => {
|
||||
expect(await fileExists(skillPath)).toBe(true);
|
||||
});
|
||||
|
||||
it('initializes with --tools zed option', async () => {
|
||||
const projectDir = await prepareFixture('tmp-init');
|
||||
const emptyProjectDir = path.join(projectDir, '..', 'empty-project');
|
||||
await fs.mkdir(emptyProjectDir, { recursive: true });
|
||||
|
||||
const result = await runCLI(['init', '--tools', 'zed'], { cwd: emptyProjectDir });
|
||||
expect(result.exitCode).toBe(0);
|
||||
expect(result.stdout).toContain('OpenSpec Setup Complete');
|
||||
expect(result.stdout).toContain('Zed Agent');
|
||||
expect(result.stdout).not.toContain('Restart your IDE');
|
||||
|
||||
const skillPath = path.join(emptyProjectDir, '.agents', 'skills', 'openspec-explore', 'SKILL.md');
|
||||
expect(await fileExists(skillPath)).toBe(true);
|
||||
expect(await fs.readFile(
|
||||
path.join(emptyProjectDir, '.agents', 'skills', '.openspec-target'),
|
||||
'utf-8'
|
||||
)).toBe('zed\n');
|
||||
|
||||
const updateResult = await runCLI(['update'], { cwd: emptyProjectDir });
|
||||
expect(updateResult.exitCode).toBe(0);
|
||||
expect(await fs.readFile(
|
||||
path.join(emptyProjectDir, '.agents', 'skills', '.openspec-target'),
|
||||
'utf-8'
|
||||
)).toBe('zed\n');
|
||||
const updatedSkill = await fs.readFile(skillPath, 'utf-8');
|
||||
expect(updatedSkill).toContain('/openspec-explore');
|
||||
expect(updatedSkill).not.toContain('$openspec-explore');
|
||||
});
|
||||
|
||||
it('initializes with --tools none option', async () => {
|
||||
const projectDir = await prepareFixture('tmp-init');
|
||||
const emptyProjectDir = path.join(projectDir, '..', 'empty-project');
|
||||
|
||||
@@ -129,6 +129,13 @@ describe('available-tools', () => {
|
||||
const toolValues = tools.map((t) => t.value);
|
||||
expect(toolValues).toContain('agents');
|
||||
expect(toolValues).not.toContain('codex');
|
||||
expect(toolValues).not.toContain('zed');
|
||||
});
|
||||
|
||||
it('should detect Zed Agent from its project configuration directory', async () => {
|
||||
await fs.mkdir(path.join(testDir, '.zed'), { recursive: true });
|
||||
|
||||
expect(getAvailableTools(testDir).map((tool) => tool.value)).toEqual(['zed']);
|
||||
});
|
||||
|
||||
it('should not detect the shared agents target from a bare .agents directory', async () => {
|
||||
@@ -157,6 +164,14 @@ describe('available-tools', () => {
|
||||
const tools = getAvailableTools(testDir);
|
||||
expect(tools.map((tool) => tool.value)).toContain('codex');
|
||||
expect(tools.map((tool) => tool.value)).not.toContain('agents');
|
||||
expect(tools.map((tool) => tool.value)).not.toContain('zed');
|
||||
});
|
||||
|
||||
it('should use the shared-root marker to detect a configured Zed Agent target', async () => {
|
||||
await fs.mkdir(path.join(testDir, '.agents', 'skills'), { recursive: true });
|
||||
await fs.writeFile(path.join(testDir, '.agents', 'skills', '.openspec-target'), 'zed\n');
|
||||
|
||||
expect(getAvailableTools(testDir).map((tool) => tool.value)).toEqual(['zed']);
|
||||
});
|
||||
|
||||
it('should preserve a global tool while reconciling a shared project root', async () => {
|
||||
|
||||
+40
-3
@@ -717,8 +717,8 @@ describe('InitCommand', () => {
|
||||
}
|
||||
);
|
||||
|
||||
it('should reconcile Codex and agents to one tree both consumers can invoke', async () => {
|
||||
const initCommand = new InitCommand({ tools: 'codex,agents', force: true });
|
||||
it('should reconcile Codex, Zed, and agents to one tree all consumers can invoke', async () => {
|
||||
const initCommand = new InitCommand({ tools: 'codex,zed,agents', force: true });
|
||||
await initCommand.execute(testDir);
|
||||
|
||||
const skillsDir = path.join(testDir, '.agents', 'skills');
|
||||
@@ -734,12 +734,27 @@ describe('InitCommand', () => {
|
||||
.flat()
|
||||
.map(String);
|
||||
expect(logCalls.some((entry) => entry.includes('Created: Codex'))).toBe(true);
|
||||
expect(logCalls.some((entry) => entry.includes('Created: Zed'))).toBe(false);
|
||||
expect(logCalls.some((entry) => entry.includes('Shared .agents skills'))).toBe(false);
|
||||
expect(
|
||||
logCalls.some((entry) => entry.includes('writing one tree with Codex and generic'))
|
||||
logCalls.some((entry) => entry.includes('writing one tree for codex'))
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('should keep a configured Codex tree compatible when Zed is added later', async () => {
|
||||
await new InitCommand({ tools: 'codex', force: true }).execute(testDir);
|
||||
await new InitCommand({ tools: 'zed', force: true }).execute(testDir);
|
||||
|
||||
const skillsDir = path.join(testDir, '.agents', 'skills');
|
||||
const proposeSkill = await fs.readFile(
|
||||
path.join(skillsDir, 'openspec-propose', 'SKILL.md'),
|
||||
'utf-8'
|
||||
);
|
||||
expect(proposeSkill).toContain('$openspec-apply-change');
|
||||
expect(proposeSkill).toContain('/openspec-apply-change');
|
||||
expect(await fs.readFile(path.join(skillsDir, '.openspec-target'), 'utf-8')).toBe('codex\n');
|
||||
});
|
||||
|
||||
it('should migrate legacy Codex skills only after init writes their replacements', async () => {
|
||||
await new InitCommand({ tools: 'codex', force: true }).execute(testDir);
|
||||
await fs.rename(path.join(testDir, '.agents'), path.join(testDir, '.codex'));
|
||||
@@ -1382,6 +1397,28 @@ describe('InitCommand - profile and detection features', () => {
|
||||
)).toBe('agents\n');
|
||||
});
|
||||
|
||||
it('should generate Zed skills in the shared .agents directory', async () => {
|
||||
const initCommand = new InitCommand({ tools: 'zed,agents', force: true });
|
||||
|
||||
await initCommand.execute(testDir);
|
||||
|
||||
const skillFile = path.join(
|
||||
testDir,
|
||||
'.agents',
|
||||
'skills',
|
||||
'openspec-apply-change',
|
||||
'SKILL.md'
|
||||
);
|
||||
expect(await fileExists(skillFile)).toBe(true);
|
||||
const skillContent = await fs.readFile(skillFile, 'utf-8');
|
||||
expect(skillContent).toContain('/openspec-archive-change');
|
||||
expect(skillContent).not.toContain('$openspec-archive-change');
|
||||
expect(await fs.readFile(
|
||||
path.join(testDir, '.agents', 'skills', '.openspec-target'),
|
||||
'utf-8'
|
||||
)).toBe('zed\n');
|
||||
});
|
||||
|
||||
it('should preserve legacy Codex prompts without replacement skills during non-interactive init', async () => {
|
||||
const promptDir = path.join(process.env.CODEX_HOME!, 'prompts');
|
||||
const legacyPrompt = path.join(promptDir, 'opsx-onboard.md');
|
||||
|
||||
Reference in New Issue
Block a user