From ec75b183c8cf5fdc56627a00000f62c400fffb80 Mon Sep 17 00:00:00 2001 From: D-AI Test Date: Sun, 6 Sep 2026 20:06:39 +1200 Subject: [PATCH] fix: bind installed D-AI runtime explicitly --- skills/custom/d-ai/SKILL.md | 1 + skills/custom/d-ai/scripts/invoke.ps1 | 92 ++++++++- .../d-ai/scripts/set-runtime-binding.ps1 | 87 +++++++++ tests/integration/codex-skill-entry.test.ts | 182 +++++++++++++++--- 4 files changed, 333 insertions(+), 29 deletions(-) create mode 100644 skills/custom/d-ai/scripts/set-runtime-binding.ps1 diff --git a/skills/custom/d-ai/SKILL.md b/skills/custom/d-ai/SKILL.md index de560c6..30c21bf 100644 --- a/skills/custom/d-ai/SKILL.md +++ b/skills/custom/d-ai/SKILL.md @@ -33,6 +33,7 @@ An explicit `@D-AI` command overrides the natural-language default. In particula - `--task ` selects a durable task in a fresh Codex process. - `--workspace ` selects the target workspace; otherwise use the current workspace. 3. Run this Skill's `scripts/invoke.ps1` with `-CommandText`, `-WorkspacePath`, and optional `-TaskId`; natural-language text is passed unchanged when it is the default entry. + The installed Skill root must contain a machine-local `.runtime-root` file pointing to a validated D-AI-Hub runtime checkout. Establish or switch that binding with `scripts/set-runtime-binding.ps1 -SkillRoot -RuntimeRoot `; a missing or invalid binding fails closed. 4. Report the returned status, message, and evidence without converting `BLOCKED` or `NO` into completion. For `@D-AI status` and `@D-AI close`, omit `--task` on the normal path. The runtime discovers the unique active durable task for the current workspace. If there are zero matches, multiple matches, or an ownership/workspace conflict, keep the result `BLOCKED` and follow the returned retry guidance. diff --git a/skills/custom/d-ai/scripts/invoke.ps1 b/skills/custom/d-ai/scripts/invoke.ps1 index 14026e3..08b7f33 100644 --- a/skills/custom/d-ai/scripts/invoke.ps1 +++ b/skills/custom/d-ai/scripts/invoke.ps1 @@ -11,15 +11,93 @@ param( ) $ErrorActionPreference = 'Stop' + +function Write-Blocked([string]$Message) { + [Console]::Out.WriteLine((([ordered]@{ + status = 'blocked' + taskId = 'unassigned' + environment = 'codex' + stage = 'bootstrap' + message = $Message + } | ConvertTo-Json -Compress))) +} + +function Assert-FullyQualifiedPath([string]$Path, [string]$Label) { + if ([string]::IsNullOrWhiteSpace($Path)) { + throw "$Label is empty" + } + if ($Path -notmatch '^(?:[A-Za-z]:[\\/]|\\\\)') { + throw "$Label must be a fully qualified absolute path" + } +} + +function Resolve-Directory([string]$Path, [string]$Label) { + Assert-FullyQualifiedPath $Path $Label + $resolved = Resolve-Path -LiteralPath $Path -ErrorAction Stop + $item = Get-Item -LiteralPath $resolved.Path -Force + if (-not $item.PSIsContainer) { + throw "$Label is not a directory: $Path" + } + return $resolved.Path +} + +function Assert-InstalledSkillRoot([string]$Candidate) { + Assert-FullyQualifiedPath $Candidate 'Installed D-AI Skill root' + $skillManifestPath = Join-Path $Candidate 'SKILL.md' + if (-not (Test-Path -LiteralPath $skillManifestPath -PathType Leaf)) { + throw 'Installed D-AI Skill root is invalid: SKILL.md is missing' + } + $skillManifest = Get-Content -LiteralPath $skillManifestPath -Raw + if ($skillManifest -notmatch '(?m)^name:\s*d-ai\s*$') { + throw 'Installed D-AI Skill root is invalid: SKILL.md is not the d-ai Skill' + } + foreach ($scriptName in @('invoke.ps1', 'set-runtime-binding.ps1')) { + if (-not (Test-Path -LiteralPath (Join-Path $Candidate "scripts\\$scriptName") -PathType Leaf)) { + throw "Installed D-AI Skill root is invalid: scripts/$scriptName is missing" + } + } +} + +function Test-RuntimeRoot([string]$Candidate, [ref]$FailureReason) { + try { + $packagePath = Join-Path $Candidate 'package.json' + if (-not (Test-Path -LiteralPath $packagePath -PathType Leaf)) { + throw 'package.json is missing' + } + $package = Get-Content -LiteralPath $packagePath -Raw | ConvertFrom-Json + if ([string]::IsNullOrWhiteSpace([string]$package.scripts.'d-ai')) { + throw 'package.json does not define the d-ai npm script' + } + if (-not (Test-Path -LiteralPath (Join-Path $Candidate 'src\entry\codex-cli.ts') -PathType Leaf)) { + throw 'canonical D-AI Codex entry is missing' + } + return $true + } catch { + $FailureReason.Value = $_.Exception.Message + return $false + } +} + $skillRoot = Split-Path -Parent $PSScriptRoot -$skillEntry = Get-Item -LiteralPath $skillRoot -Force -$canonicalSkillRoot = if ($null -ne $skillEntry.Target -and $skillEntry.Target.Count -gt 0) { - (Resolve-Path -LiteralPath @($skillEntry.Target)[0]).Path -} else { - (Resolve-Path -LiteralPath $skillRoot).Path +$repositoryRoot = $null +try { + Assert-InstalledSkillRoot $skillRoot + $bindingPath = Join-Path $skillRoot '.runtime-root' + if (-not (Test-Path -LiteralPath $bindingPath -PathType Leaf)) { + throw "Installed D-AI runtime binding is missing: $bindingPath" + } + $bindingValue = (Get-Content -LiteralPath $bindingPath -Raw).Trim() + $repositoryRoot = Resolve-Directory $bindingValue 'Installed D-AI runtime binding' + $failureReason = '' + if (-not (Test-RuntimeRoot $repositoryRoot ([ref]$failureReason))) { + throw "Installed D-AI runtime binding is invalid: $failureReason" + } + $npm = (Get-Command npm.cmd -ErrorAction Stop).Source +} catch { + Write-Blocked $_.Exception.Message + exit 2 } -$repositoryRoot = (Resolve-Path -LiteralPath (Join-Path $canonicalSkillRoot '..\..\..')).Path -$npm = (Get-Command npm.cmd -ErrorAction Stop).Source + $arguments = @( '--silent', '--prefix', diff --git a/skills/custom/d-ai/scripts/set-runtime-binding.ps1 b/skills/custom/d-ai/scripts/set-runtime-binding.ps1 new file mode 100644 index 0000000..7c74a78 --- /dev/null +++ b/skills/custom/d-ai/scripts/set-runtime-binding.ps1 @@ -0,0 +1,87 @@ +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [string]$SkillRoot, + + [Parameter(Mandatory = $true)] + [string]$RuntimeRoot +) + +$ErrorActionPreference = 'Stop' + +function Assert-FullyQualifiedPath([string]$Path, [string]$Label) { + if ([string]::IsNullOrWhiteSpace($Path)) { + throw "$Label is empty" + } + if ($Path -notmatch '^(?:[A-Za-z]:[\\/]|\\\\)') { + throw "$Label must be a fully qualified absolute path" + } +} + +function Resolve-Directory([string]$Path, [string]$Label) { + Assert-FullyQualifiedPath $Path $Label + $resolved = Resolve-Path -LiteralPath $Path -ErrorAction Stop + $item = Get-Item -LiteralPath $resolved.Path -Force + if (-not $item.PSIsContainer) { + throw "$Label is not a directory: $Path" + } + return $resolved.Path +} + +function Assert-RuntimeRoot([string]$Candidate) { + $packagePath = Join-Path $Candidate 'package.json' + if (-not (Test-Path -LiteralPath $packagePath -PathType Leaf)) { + throw "Runtime root is invalid: package.json is missing" + } + $package = Get-Content -LiteralPath $packagePath -Raw | ConvertFrom-Json + if ([string]::IsNullOrWhiteSpace([string]$package.scripts.'d-ai')) { + throw "Runtime root is invalid: package.json does not define the d-ai npm script" + } + if (-not (Test-Path -LiteralPath (Join-Path $Candidate 'src\entry\codex-cli.ts') -PathType Leaf)) { + throw 'Runtime root is invalid: canonical D-AI Codex entry is missing' + } +} + +function Assert-InstalledSkillRoot([string]$Candidate) { + Assert-FullyQualifiedPath $Candidate 'Installed D-AI Skill root' + $skillManifestPath = Join-Path $Candidate 'SKILL.md' + if (-not (Test-Path -LiteralPath $skillManifestPath -PathType Leaf)) { + throw 'Installed D-AI Skill root is invalid: SKILL.md is missing' + } + $skillManifest = Get-Content -LiteralPath $skillManifestPath -Raw + if ($skillManifest -notmatch '(?m)^name:\s*d-ai\s*$') { + throw 'Installed D-AI Skill root is invalid: SKILL.md is not the d-ai Skill' + } + foreach ($scriptName in @('invoke.ps1', 'set-runtime-binding.ps1')) { + if (-not (Test-Path -LiteralPath (Join-Path $Candidate "scripts\\$scriptName") -PathType Leaf)) { + throw "Installed D-AI Skill root is invalid: scripts/$scriptName is missing" + } + } +} + +$skillItem = Get-Item -LiteralPath $SkillRoot -Force +Assert-FullyQualifiedPath $SkillRoot 'Installed D-AI Skill root' +if (-not $skillItem.PSIsContainer) { + throw "Installed D-AI Skill root is not a directory: $SkillRoot" +} +$resolvedSkillRoot = $skillItem.FullName +Assert-InstalledSkillRoot $resolvedSkillRoot +$resolvedRuntimeRoot = Resolve-Directory $RuntimeRoot 'D-AI runtime root' +Assert-RuntimeRoot $resolvedRuntimeRoot +$bindingPath = Join-Path $resolvedSkillRoot '.runtime-root' +$existing = if (Test-Path -LiteralPath $bindingPath -PathType Leaf) { (Get-Content -LiteralPath $bindingPath -Raw).Trim() } else { $null } +if ($existing -eq $resolvedRuntimeRoot) { + [Console]::Out.WriteLine((([ordered]@{ status = 'unchanged'; bindingPath = $bindingPath; runtimeRoot = $resolvedRuntimeRoot } | ConvertTo-Json -Compress))) + exit 0 +} + +$temporaryPath = "$bindingPath.$([guid]::NewGuid().ToString('N')).tmp" +try { + [System.IO.File]::WriteAllText($temporaryPath, "$resolvedRuntimeRoot`r`n", [System.Text.UTF8Encoding]::new($false)) + Move-Item -LiteralPath $temporaryPath -Destination $bindingPath -Force +} finally { + if (Test-Path -LiteralPath $temporaryPath) { + Remove-Item -LiteralPath $temporaryPath -Force + } +} +[Console]::Out.WriteLine((([ordered]@{ status = if ($null -eq $existing) { 'created' } else { 'updated' }; bindingPath = $bindingPath; runtimeRoot = $resolvedRuntimeRoot } | ConvertTo-Json -Compress))) diff --git a/tests/integration/codex-skill-entry.test.ts b/tests/integration/codex-skill-entry.test.ts index 316f776..5ab6148 100644 --- a/tests/integration/codex-skill-entry.test.ts +++ b/tests/integration/codex-skill-entry.test.ts @@ -1,11 +1,14 @@ import { spawn } from "node:child_process"; -import { mkdtemp, mkdir, rm, symlink, writeFile } from "node:fs/promises"; +import { access, copyFile, mkdtemp, mkdir, readFile, rm, symlink, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; -import { join } from "node:path"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; import { runCommand } from "../../src/adapters/command-runner.js"; import { FileDurableContextStore } from "../../src/state/file-durable-context-store.js"; import { describe, expect, it } from "vitest"; +const repositoryRoot = dirname(dirname(dirname(fileURLToPath(import.meta.url)))); + interface ProcessResult { readonly exitCode: number | null; readonly stdout: string; @@ -13,6 +16,15 @@ interface ProcessResult { } function runPowerShell(scriptPath: string, workspacePath: string, commandText: string): Promise { + return runPowerShellArguments(scriptPath, workspacePath, [ + "-WorkspacePath", + workspacePath, + "-CommandText", + commandText, + ]); +} + +function runPowerShellArguments(scriptPath: string, cwdPath: string, argumentsList: readonly string[], pathPrefix?: string): Promise { return new Promise((resolve, reject) => { const child = spawn("powershell.exe", [ "-NoProfile", @@ -20,11 +32,12 @@ function runPowerShell(scriptPath: string, workspacePath: string, commandText: s "Bypass", "-File", scriptPath, - "-WorkspacePath", - workspacePath, - "-CommandText", - commandText, - ], { cwd: workspacePath, windowsHide: true }); + ...argumentsList, + ], { + cwd: cwdPath, + windowsHide: true, + ...(pathPrefix === undefined ? {} : { env: { ...process.env, PATH: `${pathPrefix};${process.env.PATH ?? ""}` } }), + }); let stdout = ""; let stderr = ""; child.stdout.setEncoding("utf8").on("data", (chunk: string) => { stdout += chunk; }); @@ -34,21 +47,152 @@ function runPowerShell(scriptPath: string, workspacePath: string, commandText: s }); } +async function pathExists(path: string): Promise { + try { + await access(path); + return true; + } catch { + return false; + } +} + +async function createInvocationMarker(root: string): Promise<{ readonly binPath: string; readonly markerPath: string }> { + const binPath = join(root, "fake-npm-bin"); + const markerPath = join(root, "npm-invoked.marker"); + await mkdir(binPath); + await writeFile(join(binPath, "npm.cmd"), `@echo off\r\n> "${markerPath}" echo invoked\r\nexit /b 99\r\n`, "utf8"); + return { binPath, markerPath }; +} + +async function createBoundInstalledSkill(root: string, runtimeRoot = process.cwd()): Promise { + const installedSkillPath = join(root, "installed-skill"); + const canonicalSkillPath = join(repositoryRoot, "skills", "custom", "d-ai"); + await mkdir(join(installedSkillPath, "scripts"), { recursive: true }); + await copyFile(join(canonicalSkillPath, "SKILL.md"), join(installedSkillPath, "SKILL.md")); + await copyFile(join(canonicalSkillPath, "scripts", "invoke.ps1"), join(installedSkillPath, "scripts", "invoke.ps1")); + await copyFile(join(canonicalSkillPath, "scripts", "set-runtime-binding.ps1"), join(installedSkillPath, "scripts", "set-runtime-binding.ps1")); + await writeFile(join(installedSkillPath, ".runtime-root"), `${runtimeRoot}\n`, "utf8"); + return installedSkillPath; +} + +async function createInstalledSkill(root: string): Promise { + const installedSkillPath = join(root, "installed-skill"); + const canonicalSkillPath = join(repositoryRoot, "skills", "custom", "d-ai"); + await mkdir(join(installedSkillPath, "scripts"), { recursive: true }); + await copyFile(join(canonicalSkillPath, "SKILL.md"), join(installedSkillPath, "SKILL.md")); + await copyFile(join(canonicalSkillPath, "scripts", "invoke.ps1"), join(installedSkillPath, "scripts", "invoke.ps1")); + await copyFile(join(canonicalSkillPath, "scripts", "set-runtime-binding.ps1"), join(installedSkillPath, "scripts", "set-runtime-binding.ps1")); + return installedSkillPath; +} + async function runGit(workspacePath: string, argumentsList: readonly string[]): Promise { await runCommand({ command: "git", arguments: argumentsList, cwd: workspacePath }); } describe.skipIf(process.platform !== "win32")("D-AI Codex Skill PowerShell product boundary", { timeout: 20_000 }, () => { + it("uses the explicit binding from D-AI-Hub, Quote Float-like, and unrelated CWDs", async () => { + const root = await mkdtemp(join(tmpdir(), "d-ai-codex-skill-cwds-")); + const installedSkillPath = await createBoundInstalledSkill(root); + const dAiHubCwd = join(root, "D-AI-Hub"); + const quoteFloatCwd = join(root, "Quote Float"); + const unrelatedCwd = join(root, "unrelated"); + try { + await Promise.all([mkdir(dAiHubCwd), mkdir(quoteFloatCwd), mkdir(unrelatedCwd)]); + const cwdWorkspacePairs: readonly (readonly [string, string])[] = [[dAiHubCwd, dAiHubCwd], [quoteFloatCwd, quoteFloatCwd], [unrelatedCwd, unrelatedCwd]]; + for (const [cwd, workspacePath] of cwdWorkspacePairs) { + const args = ["-WorkspacePath", workspacePath, "-CommandText", "@D-AI close"]; + const result = await runPowerShellArguments(join(installedSkillPath, "scripts", "invoke.ps1"), cwd, args); + expect(result.exitCode, `${result.stderr}\n${result.stdout}`).toBe(2); + const response = JSON.parse(result.stdout) as Record; + expect(response.message).toMatch(/No active D-AI task matches this workspace/i); + expect(await pathExists(join(workspacePath, ".d-ai"))).toBe(false); + } + } finally { + await rm(root, { recursive: true, force: true }); + } + }); + + it("fails closed before runtime invocation for missing, stale, invalid, relative, and drive-relative bindings", async () => { + const root = await mkdtemp(join(tmpdir(), "d-ai-codex-skill-binding-failure-")); + const installedSkillPath = await createInstalledSkill(root); + const workspacePath = join(root, "workspace"); + const invalidRuntimePath = join(root, "invalid-runtime"); + const { binPath, markerPath } = await createInvocationMarker(root); + try { + await mkdir(workspacePath); + await mkdir(invalidRuntimePath); + const drive = root.slice(0, 2); + for (const binding of [null, join(root, "missing-runtime"), invalidRuntimePath, ".", `${drive}relative-runtime`]) { + if (binding === null) { + await rm(join(installedSkillPath, ".runtime-root"), { force: true }); + } else { + await writeFile(join(installedSkillPath, ".runtime-root"), `${binding}\n`, "utf8"); + } + const result = await runPowerShellArguments(join(installedSkillPath, "scripts", "invoke.ps1"), workspacePath, ["-WorkspacePath", workspacePath, "-CommandText", "@D-AI status"], binPath); + expect(result.exitCode, `${result.stderr}\n${result.stdout}`).toBe(2); + const response = JSON.parse(result.stdout) as Record; + expect(response).toMatchObject({ taskId: "unassigned", environment: "codex", status: "blocked", stage: "bootstrap" }); + expect(response.message).toMatch(/binding|runtime root/i); + expect(await pathExists(markerPath)).toBe(false); + expect(await pathExists(join(workspacePath, ".d-ai"))).toBe(false); + expect(await new FileDurableContextStore(join(workspacePath, ".d-ai")).discoverActiveTasks(workspacePath)).toHaveLength(0); + } + } finally { + await rm(root, { recursive: true, force: true }); + } + }, 60_000); + + it("creates, idempotently preserves, and updates a validated runtime binding", async () => { + const root = await mkdtemp(join(tmpdir(), "d-ai-codex-skill-binding-tool-")); + const installedSkillPath = await createInstalledSkill(root); + const runtimeRoot = process.cwd(); + const secondRuntimeRoot = join(root, "second-runtime"); + try { + await mkdir(join(secondRuntimeRoot, "src", "entry"), { recursive: true }); + await copyFile(join(runtimeRoot, "package.json"), join(secondRuntimeRoot, "package.json")); + await copyFile(join(runtimeRoot, "src", "entry", "codex-cli.ts"), join(secondRuntimeRoot, "src", "entry", "codex-cli.ts")); + const setScript = join(installedSkillPath, "scripts", "set-runtime-binding.ps1"); + const invoke = (target: string) => runPowerShellArguments(setScript, root, ["-SkillRoot", installedSkillPath, "-RuntimeRoot", target]); + + const created = await invoke(runtimeRoot); + expect(created.exitCode, `${created.stderr}\n${created.stdout}`).toBe(0); + expect(JSON.parse(created.stdout)).toMatchObject({ status: "created", runtimeRoot }); + await expect(readFile(join(installedSkillPath, ".runtime-root"), "utf8")).resolves.toBe(`${runtimeRoot}\r\n`); + + const unchanged = await invoke(runtimeRoot); + expect(unchanged.exitCode, `${unchanged.stderr}\n${unchanged.stdout}`).toBe(0); + expect(JSON.parse(unchanged.stdout)).toMatchObject({ status: "unchanged", runtimeRoot }); + + const updated = await invoke(secondRuntimeRoot); + expect(updated.exitCode, `${updated.stderr}\n${updated.stdout}`).toBe(0); + expect(JSON.parse(updated.stdout)).toMatchObject({ status: "updated", runtimeRoot: secondRuntimeRoot }); + await expect(readFile(join(installedSkillPath, ".runtime-root"), "utf8")).resolves.toBe(`${secondRuntimeRoot}\r\n`); + + for (const mistypedSkillRoot of [join(root, "Quote Float"), join(root, "user-workspace")]) { + await mkdir(mistypedSkillRoot); + const result = await runPowerShellArguments(setScript, root, ["-SkillRoot", mistypedSkillRoot, "-RuntimeRoot", secondRuntimeRoot]); + expect(result.exitCode, `${result.stderr}\n${result.stdout}`).toBe(1); + expect(result.stderr).toMatch(/Installed D-AI Skill root|SKILL\.md/i); + expect(await pathExists(join(mistypedSkillRoot, ".runtime-root"))).toBe(false); + } + + const invalid = join(root, "invalid-runtime"); + await mkdir(invalid); + const rejected = await invoke(invalid); + expect(rejected.exitCode).toBe(1); + expect(rejected.stderr).toMatch(/package\.json|Runtime root is invalid/i); + await expect(readFile(join(installedSkillPath, ".runtime-root"), "utf8")).resolves.toBe(`${secondRuntimeRoot}\r\n`); + } finally { + await rm(root, { recursive: true, force: true }); + } + }); + it("discovers the Skill and sends a raw close command into the configured runtime from an unrelated workspace", async () => { const root = await mkdtemp(join(tmpdir(), "d-ai-codex-skill-e2e-")); - const discoveryRoot = join(root, "user-skills"); const workspacePath = join(root, "unrelated-workspace"); - const canonicalSkillPath = join(process.cwd(), "skills", "custom", "d-ai"); try { - await mkdir(discoveryRoot); await mkdir(workspacePath); - await symlink(canonicalSkillPath, join(discoveryRoot, "d-ai"), "junction"); - const entryPath = join(discoveryRoot, "d-ai", "scripts", "invoke.ps1"); + const entryPath = join(await createBoundInstalledSkill(root), "scripts", "invoke.ps1"); const result = await runPowerShell(entryPath, workspacePath, "@D-AI close"); @@ -63,17 +207,13 @@ describe.skipIf(process.platform !== "win32")("D-AI Codex Skill PowerShell produ it("returns BLOCKED when the configured Codex workspace is not a Git repository", async () => { const root = await mkdtemp(join(tmpdir(), "d-ai-codex-skill-connector-")); - const discoveryRoot = join(root, "user-skills"); const workspacePath = join(root, "unrelated-workspace"); - const canonicalSkillPath = join(process.cwd(), "skills", "custom", "d-ai"); - const executionSkillPath = join(process.cwd(), "tests", "fixtures", "skills", "typescript-execution"); + const executionSkillPath = join(repositoryRoot, "tests", "fixtures", "skills", "typescript-execution"); try { - await mkdir(discoveryRoot); await mkdir(workspacePath); await mkdir(join(workspacePath, ".agents", "skills"), { recursive: true }); - await symlink(canonicalSkillPath, join(discoveryRoot, "d-ai"), "junction"); await symlink(executionSkillPath, join(workspacePath, ".agents", "skills", "typescript-execution"), "junction"); - const entryPath = join(discoveryRoot, "d-ai", "scripts", "invoke.ps1"); + const entryPath = join(await createBoundInstalledSkill(root), "scripts", "invoke.ps1"); const result = await runPowerShell(entryPath, workspacePath, "@D-AI implement typescript"); @@ -89,7 +229,6 @@ describe.skipIf(process.platform !== "win32")("D-AI Codex Skill PowerShell produ it("completes a bounded verify intent through the public Skill and persists a recovery point", async () => { const root = await mkdtemp(join(tmpdir(), "d-ai-codex-skill-real-execution-")); const workspacePath = join(root, "workspace"); - const canonicalSkillPath = join(process.cwd(), "skills", "custom", "d-ai"); const verificationSkillPath = join(workspacePath, ".agents", "skills", "verify-local"); try { await mkdir(verificationSkillPath, { recursive: true }); @@ -103,7 +242,7 @@ describe.skipIf(process.platform !== "win32")("D-AI Codex Skill PowerShell produ await runGit(workspacePath, ["branch", "-m", "verify/review"]); await runGit(workspacePath, ["remote", "add", "origin", "https://github.com/acme/d-ai.git"]); - const entryPath = join(canonicalSkillPath, "scripts", "invoke.ps1"); + const entryPath = join(await createBoundInstalledSkill(root), "scripts", "invoke.ps1"); const result = await runPowerShell(entryPath, workspacePath, "@D-AI verify local workspace"); expect(result.exitCode, `${result.stderr}\n${result.stdout}`).toBe(0); @@ -128,7 +267,6 @@ describe.skipIf(process.platform !== "win32")("D-AI Codex Skill PowerShell produ it("blocks unsupported remotes at the public Skill execution boundary", async () => { const root = await mkdtemp(join(tmpdir(), "d-ai-codex-skill-unsupported-remote-")); const workspacePath = join(root, "workspace"); - const canonicalSkillPath = join(process.cwd(), "skills", "custom", "d-ai"); const verificationSkillPath = join(workspacePath, ".agents", "skills", "verify-local"); const bareRemotePath = join(root, "remote.git"); try { @@ -143,7 +281,7 @@ describe.skipIf(process.platform !== "win32")("D-AI Codex Skill PowerShell produ await mkdir(bareRemotePath); await runGit(bareRemotePath, ["init", "--bare"]); await runGit(workspacePath, ["remote", "add", "origin", bareRemotePath]); - const entryPath = join(canonicalSkillPath, "scripts", "invoke.ps1"); + const entryPath = join(await createBoundInstalledSkill(root), "scripts", "invoke.ps1"); const result = await runPowerShell(entryPath, workspacePath, "@D-AI verify local workspace"); expect(result.exitCode, `${result.stderr}\n${result.stdout}`).toBe(2);