Files
2026-07-13 12:20:01 +08:00

128 lines
6.9 KiB
TypeScript

import { existsSync, readFileSync } from "fs"
import path from "path"
import { describe, expect, test } from "bun:test"
const SKILL_DIR = path.join(
process.cwd(),
"skills/ce-worktree",
)
const SKILL_BODY = readFileSync(path.join(SKILL_DIR, "SKILL.md"), "utf8")
describe("ce-worktree SKILL.md", () => {
// Issue #946: ce-worktree was re-architected from a script-based creator into
// a portable isolation guardrail. There must be no bundled script (it was the
// root cause of the #943/#764 path-resolution bug class) and no dependence on
// ${CLAUDE_SKILL_DIR}, so the skill works verbatim on every target.
test("ships no bundled script and no ${CLAUDE_SKILL_DIR} dependence", () => {
expect(
existsSync(path.join(SKILL_DIR, "scripts")),
"ce-worktree must not bundle a scripts/ directory — it is now a portable inline-git guardrail (issue #946). A bundled script reintroduces the cross-platform path-resolution bug class (#943/#764).",
).toBe(false)
expect(
SKILL_BODY.includes("CLAUDE_SKILL_DIR"),
"ce-worktree/SKILL.md must not reference ${CLAUDE_SKILL_DIR} — the guardrail uses inline git only, so it resolves on every platform without a skill-dir variable.",
).toBe(false)
expect(
SKILL_BODY.includes("worktree-manager.sh"),
"ce-worktree/SKILL.md must not reference the removed worktree-manager.sh script.",
).toBe(false)
})
// The guardrail must be portable to every target, so it must NOT gate itself
// to Claude only. (Absence of the worktree-manager.sh allowed-tools pin is
// covered by the whole-file check above.)
test("is portable: no ce_platforms gate", () => {
const frontmatter = SKILL_BODY.match(/^---\n([\s\S]*?)\n---/)
expect(frontmatter, "ce-worktree/SKILL.md must have YAML frontmatter").not.toBeNull()
expect(
/^ce_platforms:/m.test(frontmatter![1]),
"ce-worktree/SKILL.md must not declare `ce_platforms` — the inline-git guardrail is portable to all targets (issue #946).",
).toBe(false)
})
// The core value of the skill is the isolation-discipline judgment. Guard the
// three load-bearing behaviors so they cannot silently regress.
test("detects existing isolation before creating a worktree", () => {
expect(
SKILL_BODY.includes("git rev-parse --git-common-dir"),
"ce-worktree/SKILL.md must compare against --git-common-dir to detect an existing linked worktree (Step 0).",
).toBe(true)
// Must compare RESOLVED ABSOLUTE paths, not raw `git rev-parse` output:
// from a subdirectory of a normal checkout, --git-dir is absolute while
// --git-common-dir may be relative, so a raw string compare gives a false
// "already isolated". Guard the canonicalized form so that can't regress.
expect(
SKILL_BODY.includes("--absolute-git-dir"),
"ce-worktree/SKILL.md must compare the resolved absolute git dir (`--absolute-git-dir`) so a subdirectory CWD in a normal checkout is not misread as an existing worktree.",
).toBe(true)
expect(
SKILL_BODY.includes("git rev-parse --show-superproject-working-tree"),
"ce-worktree/SKILL.md must include the submodule guard (--show-superproject-working-tree) so a submodule is not mistaken for a worktree.",
).toBe(true)
expect(
/work in place/i.test(SKILL_BODY),
"ce-worktree/SKILL.md must instruct the agent to work in place when already isolated, rather than nesting a worktree.",
).toBe(true)
})
test("prefers the harness's native worktree tool before the git fallback", () => {
expect(
/native worktree (primitive|tool)/i.test(SKILL_BODY),
"ce-worktree/SKILL.md must instruct the agent to prefer the harness's native worktree tool before falling back to git (avoids phantom state).",
).toBe(true)
})
test("documents an inline git fallback under .worktrees with gitignore safety", () => {
expect(
SKILL_BODY.includes("git worktree add"),
"ce-worktree/SKILL.md must document the inline `git worktree add` fallback.",
).toBe(true)
expect(
SKILL_BODY.includes("git check-ignore -q .worktrees/"),
"ce-worktree/SKILL.md must probe `git check-ignore -q .worktrees/` WITH the trailing slash, so an existing directory-only `.worktrees/` ignore rule is honored and the skill doesn't redundantly dirty `.gitignore` (PR #948 review).",
).toBe(true)
})
// PR #948 review: the fallback's relative `.worktrees/` and `.gitignore`
// paths resolve against the agent's CWD, which may be a subdirectory — so the
// skill must anchor at the repo root first, or it creates `src/.worktrees/...`
// and edits `src/.gitignore` instead of the repo-root ones.
test("anchors the git fallback at the repo root before using relative paths", () => {
expect(
SKILL_BODY.includes('cd "$(git rev-parse --show-toplevel)"'),
"ce-worktree/SKILL.md must `cd \"$(git rev-parse --show-toplevel)\"` in the git fallback so relative `.worktrees`/`.gitignore` paths resolve at the repo root, not a subdirectory CWD.",
).toBe(true)
expect(
/run from the repo root/i.test(SKILL_BODY),
"ce-worktree/SKILL.md Step 2 must explicitly instruct running the fallback from the repo root.",
).toBe(true)
})
// PR #948 review: `git fetch origin <branch>` exits 128 with no `origin`
// remote / a local-only base. The fetch must be non-fatal so the flow
// continues to the local-ref fallback it claims to handle.
test("treats the base-branch fetch as best-effort / non-fatal", () => {
expect(
/non-fatal|best-effort/i.test(SKILL_BODY),
"ce-worktree/SKILL.md must mark the `git fetch` step non-fatal so an absent `origin` remote falls through to the local base ref instead of aborting.",
).toBe(true)
})
// PR #948 review: on a sandbox/permission failure the requested isolation was
// not created. The skill must not silently fall back to the current checkout —
// the user chose isolation specifically to avoid touching it.
test("on a sandbox/permission failure, asks rather than silently using the current checkout", () => {
expect(
/work in the current (directory|checkout) instead/i.test(SKILL_BODY),
"ce-worktree/SKILL.md must not instruct the agent to silently work in the current checkout on a sandbox failure — that defeats the isolation contract.",
).toBe(false)
// The confirmation is blocking, so it must route through the platform's
// blocking-question tool (AGENTS.md > Cross-Platform User Interaction),
// not a vague "ask the user" that can degrade to a non-blocking prompt.
expect(
SKILL_BODY.includes("AskUserQuestion") && SKILL_BODY.includes("request_user_input"),
"ce-worktree/SKILL.md must route the sandbox-failure confirmation through the platform's blocking question tool (name AskUserQuestion / request_user_input / ask_user), per AGENTS.md > Cross-Platform User Interaction, rather than a non-blocking 'ask the user'.",
).toBe(true)
})
})