diff --git a/openspec/changes/agent-codex-fix-cue-aliases-and-skills-tests-2026-06-09-10-24/.openspec.yaml b/openspec/changes/agent-codex-fix-cue-aliases-and-skills-tests-2026-06-09-10-24/.openspec.yaml new file mode 100644 index 0000000..5735446 --- /dev/null +++ b/openspec/changes/agent-codex-fix-cue-aliases-and-skills-tests-2026-06-09-10-24/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-06-09 diff --git a/openspec/changes/agent-codex-fix-cue-aliases-and-skills-tests-2026-06-09-10-24/notes.md b/openspec/changes/agent-codex-fix-cue-aliases-and-skills-tests-2026-06-09-10-24/notes.md new file mode 100644 index 0000000..bd34ba6 --- /dev/null +++ b/openspec/changes/agent-codex-fix-cue-aliases-and-skills-tests-2026-06-09-10-24/notes.md @@ -0,0 +1,19 @@ +# agent-codex-fix-cue-aliases-and-skills-tests-2026-06-09-10-24 (minimal / T1) + +Branch: `agent/codex/fix-cue-aliases-and-skills-tests-2026-06-09-10-24` + +Keep Claude account aliases launching through Cue while separating Soul skill +profile from Cue profile. Make skill-profile tests self-contained with a temp +Soul fixture so they pass on hosts without `~/Documents/soul`. + +## Handoff + +- Handoff: change=`agent-codex-fix-cue-aliases-and-skills-tests-2026-06-09-10-24`; branch=`agent/codex/fix-cue-aliases-and-skills-tests-2026-06-09-10-24`; scope=`src/commands/parallel.ts, src/tests/json-parity.test.ts, src/tests/skills-profile.test.ts`; action=`finish via PR`. +- Verification: `npm run build`; `node dist/tests/skills-profile.test.js`; `node dist/tests/json-parity.test.js`; `npm test` (195/195 passing); live smoke `claude-account1 --version`, `claude-account2 --version`. +- Live config: refreshed `/home/deadpool/.bashrc` aliases; `claude-account1` and `claude-account2` now expand to `__authmux_claude_account '' 'base' 'core'`. + +## Cleanup + +- [ ] Run: `gx branch finish --branch agent/codex/fix-cue-aliases-and-skills-tests-2026-06-09-10-24 --base main --via-pr --wait-for-merge --cleanup` +- [ ] Record PR URL + `MERGED` state in the completion handoff. +- [ ] Confirm sandbox worktree is gone (`git worktree list`, `git branch -a`). diff --git a/src/commands/parallel.ts b/src/commands/parallel.ts index 3746aa9..7de14b2 100644 --- a/src/commands/parallel.ts +++ b/src/commands/parallel.ts @@ -200,19 +200,21 @@ export default class ClaudeParallel extends Command { "# Claude Code parallel accounts (managed by agent-auth)", "__authmux_claude_account() {", " local name=\"$1\"", - ` local profile="\${2:-${DEFAULT_CUE_PROFILE}}"`, - " shift 2", + " local skill_profile=\"${2:-base}\"", + ` local cue_profile="\${3:-${DEFAULT_CUE_PROFILE}}"`, + " shift 3", " local dir=\"$HOME/.claude-accounts/$name\"", - " command authmux skills activate \"$profile\" --agent claude --target \"$dir/skills\" >/dev/null 2>&1 || true", + " command authmux skills activate \"$skill_profile\" --agent claude --target \"$dir/skills\" >/dev/null 2>&1 || true", " if command -v cue >/dev/null 2>&1 && [ -z \"${AUTHMUX_SKIP_CUE_LAUNCH:-}\" ]; then", - " CLAUDE_CONFIG_DIR=\"$dir\" cue launch claude --cue-profile \"$profile\" \"$@\"", + " CLAUDE_CONFIG_DIR=\"$dir\" cue launch claude --cue-profile \"$cue_profile\" \"$@\"", " else", " CLAUDE_CONFIG_DIR=\"$dir\" command claude \"$@\"", " fi", "}", ...profiles.map((p) => { - const profile = readCueProfile(p) ?? readSkillProfile(p) ?? DEFAULT_CUE_PROFILE; - return `alias claude-${p}="__authmux_claude_account ${shellQuote(p)} ${shellQuote(profile)}"`; + const skillProfile = readSkillProfile(p) ?? "base"; + const cueProfile = readCueProfile(p) ?? DEFAULT_CUE_PROFILE; + return `alias claude-${p}="__authmux_claude_account ${shellQuote(p)} ${shellQuote(skillProfile)} ${shellQuote(cueProfile)}"`; }), ]; return lines.join("\n"); diff --git a/src/tests/json-parity.test.ts b/src/tests/json-parity.test.ts index e428b3c..23d9ed5 100644 --- a/src/tests/json-parity.test.ts +++ b/src/tests/json-parity.test.ts @@ -291,11 +291,15 @@ test("parallel aliases pass explicit cue profile per Claude account", async () = assert.match(parsedAliases.data.aliases, /__authmux_claude_account\(\)/); assert.match( parsedAliases.data.aliases, - /cue launch claude --cue-profile "\$profile"/, + /authmux skills activate "\$skill_profile" --agent claude/, ); assert.match( parsedAliases.data.aliases, - /alias claude-account2="__authmux_claude_account 'account2' 'frontend'"/, + /cue launch claude --cue-profile "\$cue_profile"/, + ); + assert.match( + parsedAliases.data.aliases, + /alias claude-account2="__authmux_claude_account 'account2' 'frontend' 'frontend'"/, ); }); }); diff --git a/src/tests/skills-profile.test.ts b/src/tests/skills-profile.test.ts index 238da32..f3ae3a9 100644 --- a/src/tests/skills-profile.test.ts +++ b/src/tests/skills-profile.test.ts @@ -12,6 +12,58 @@ import { resolveDefaultSkillTarget, } from "../lib/skills/profile"; +async function withTempSoulHome(fn: (soulHome: string) => Promise | T): Promise { + const soulHome = await fsp.mkdtemp(path.join(os.tmpdir(), "authmux-soul-")); + const profilesRoot = path.join(soulHome, "skills", "profiles"); + const scriptsRoot = path.join(soulHome, "skills", "scripts"); + const activatorPath = path.join(scriptsRoot, "activate-profile.sh"); + const previous = { + AUTHMUX_SOUL_HOME: process.env.AUTHMUX_SOUL_HOME, + SOUL_HOME: process.env.SOUL_HOME, + AUTHMUX_SOUL_SKILL_ACTIVATOR: process.env.AUTHMUX_SOUL_SKILL_ACTIVATOR, + }; + + await fsp.mkdir(profilesRoot, { recursive: true }); + await fsp.mkdir(scriptsRoot, { recursive: true }); + await Promise.all([ + fsp.writeFile(path.join(profilesRoot, "base.json"), "{}\n", "utf8"), + fsp.writeFile(path.join(profilesRoot, "all.json"), "{}\n", "utf8"), + fsp.writeFile( + activatorPath, + [ + "#!/usr/bin/env bash", + "set -euo pipefail", + "target=\"\"", + "while [[ $# -gt 0 ]]; do", + " case \"$1\" in", + " --target) target=\"$2\"; shift 2 ;;", + " *) shift ;;", + " esac", + "done", + "printf 'target=%s skills=10\\n' \"$target\"", + ].join("\n"), + "utf8", + ), + ]); + await fsp.chmod(activatorPath, 0o755); + + process.env.AUTHMUX_SOUL_HOME = soulHome; + delete process.env.SOUL_HOME; + delete process.env.AUTHMUX_SOUL_SKILL_ACTIVATOR; + + try { + return await fn(soulHome); + } finally { + if (previous.AUTHMUX_SOUL_HOME === undefined) delete process.env.AUTHMUX_SOUL_HOME; + else process.env.AUTHMUX_SOUL_HOME = previous.AUTHMUX_SOUL_HOME; + if (previous.SOUL_HOME === undefined) delete process.env.SOUL_HOME; + else process.env.SOUL_HOME = previous.SOUL_HOME; + if (previous.AUTHMUX_SOUL_SKILL_ACTIVATOR === undefined) delete process.env.AUTHMUX_SOUL_SKILL_ACTIVATOR; + else process.env.AUTHMUX_SOUL_SKILL_ACTIVATOR = previous.AUTHMUX_SOUL_SKILL_ACTIVATOR; + await fsp.rm(soulHome, { recursive: true, force: true }); + } +} + test("normalizeSkillProfileName accepts simple profile names", () => { assert.equal(normalizeSkillProfileName("frontend"), "frontend"); assert.equal(normalizeSkillProfileName("medusa-v2"), "medusa-v2"); @@ -21,27 +73,31 @@ test("normalizeSkillProfileName rejects path-like names", () => { assert.throws(() => normalizeSkillProfileName("../all"), /Invalid skill profile/); }); -test("listAvailableSkillProfiles reads Soul profile JSON files", () => { - const profiles = listAvailableSkillProfiles(); - assert.ok(profiles.includes("base")); - assert.ok(profiles.includes("all")); +test("listAvailableSkillProfiles reads Soul profile JSON files", async () => { + await withTempSoulHome(() => { + const profiles = listAvailableSkillProfiles(); + assert.ok(profiles.includes("base")); + assert.ok(profiles.includes("all")); + }); }); test("activateSkillProfile delegates to the Soul activator", async (t) => { - const targetRoot = await fsp.mkdtemp(path.join(os.tmpdir(), "authmux-skills-profile-")); - t.after(async () => { - await fsp.rm(targetRoot, { recursive: true, force: true }); - }); + await withTempSoulHome(async () => { + const targetRoot = await fsp.mkdtemp(path.join(os.tmpdir(), "authmux-skills-profile-")); + t.after(async () => { + await fsp.rm(targetRoot, { recursive: true, force: true }); + }); - const result = activateSkillProfile({ - profile: "base", - agent: "codex", - target: path.join(targetRoot, "skills"), - }); + const result = activateSkillProfile({ + profile: "base", + agent: "codex", + target: path.join(targetRoot, "skills"), + }); - assert.equal(result.activated, true); - assert.equal(result.profile, "base"); - assert.equal(result.skillCount, 10); + assert.equal(result.activated, true); + assert.equal(result.profile, "base"); + assert.equal(result.skillCount, 10); + }); }); test("isSkillAgent narrows to known agents", () => { @@ -72,20 +128,22 @@ test("resolveDefaultSkillTarget returns undefined for codex and claude", () => { }); test("activateSkillProfile fills hermes target from env when not given", async (t) => { - const targetRoot = await fsp.mkdtemp(path.join(os.tmpdir(), "authmux-skills-hermes-")); - t.after(async () => { - await fsp.rm(targetRoot, { recursive: true, force: true }); + await withTempSoulHome(async () => { + const targetRoot = await fsp.mkdtemp(path.join(os.tmpdir(), "authmux-skills-hermes-")); + t.after(async () => { + await fsp.rm(targetRoot, { recursive: true, force: true }); + }); + const previous = process.env.AUTHMUX_HERMES_HOME; + process.env.AUTHMUX_HERMES_HOME = targetRoot; + try { + const result = activateSkillProfile({ profile: "base", agent: "hermes" }); + assert.equal(result.activated, true); + assert.equal(result.agent, "hermes"); + assert.equal(result.target, path.join(targetRoot, "skills")); + assert.equal(result.skillCount, 10); + } finally { + if (previous === undefined) delete process.env.AUTHMUX_HERMES_HOME; + else process.env.AUTHMUX_HERMES_HOME = previous; + } }); - const previous = process.env.AUTHMUX_HERMES_HOME; - process.env.AUTHMUX_HERMES_HOME = targetRoot; - try { - const result = activateSkillProfile({ profile: "base", agent: "hermes" }); - assert.equal(result.activated, true); - assert.equal(result.agent, "hermes"); - assert.equal(result.target, path.join(targetRoot, "skills")); - assert.equal(result.skillCount, 10); - } finally { - if (previous === undefined) delete process.env.AUTHMUX_HERMES_HOME; - else process.env.AUTHMUX_HERMES_HOME = previous; - } });