From e54bad311fd76a0874072790a6411dfa89c1cfa8 Mon Sep 17 00:00:00 2001 From: rlaope Date: Mon, 15 Jun 2026 17:57:51 +0900 Subject: [PATCH] Verify Codex Loop skill invocation Signed-off-by: rlaope --- README.md | 1 + docs/compatibility.md | 2 +- skills/loop/SKILL.md | 3 ++- test/adapter.test.js | 62 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2aaba09..fb6658c 100644 --- a/README.md +++ b/README.md @@ -227,6 +227,7 @@ Install or load this repository as a Codex plugin source, then invoke: ```text $loop +$Loop ``` The skill tells the agent to record durable state, check repository boundaries, diff --git a/docs/compatibility.md b/docs/compatibility.md index 506d139..734c7fc 100644 --- a/docs/compatibility.md +++ b/docs/compatibility.md @@ -2,7 +2,7 @@ | Surface | MVP behavior | Notes | | --- | --- | --- | -| Codex `$loop` | Shipped first. | `$loop ` maps to the Codex skill in `skills/loop/SKILL.md`. | +| Codex `$loop` / `$Loop` | Shipped first. | `$loop ` and the display-case `$Loop ` map to the Codex skill in `skills/loop/SKILL.md`. | | Loop Agent Console TUI | Shipped local baseline. | `loop` with no arguments opens a terminal console for run selection, wiki/log reading, verification notes, lineage-preserving follow-up intent, dashboard open, and Codex resume actions when a session id is known. | | Loop CLI Codex agent | Prototype. | `loop run --agent codex "prompt"` launches `codex exec` after Loop state and safety checks. | | Loop CLI Claude Code agent | Prototype. | `loop run --agent claudecode "prompt"` launches `claude --print` after Loop state and safety checks. | diff --git a/skills/loop/SKILL.md b/skills/loop/SKILL.md index e2145ce..be689ed 100644 --- a/skills/loop/SKILL.md +++ b/skills/loop/SKILL.md @@ -1,6 +1,6 @@ --- name: loop -description: Run a Loop Engineering workflow for a coding-agent objective with durable state, budget/stop rules, isolation checks, and human verification boundaries. Use when the user invokes $loop or asks the agent to keep iterating safely until a goal is complete. +description: Run a Loop Engineering workflow for a coding-agent objective with durable state, budget/stop rules, isolation checks, and human verification boundaries. Use when the user invokes $loop, $Loop, or asks the agent to keep iterating safely until a goal is complete. --- # Loop @@ -13,6 +13,7 @@ isolate, act, verify, persist, then stop or escalate. Use this skill for: - `$loop ` +- `$Loop ` - "run a loop for this" - "keep iterating until this goal is complete" diff --git a/test/adapter.test.js b/test/adapter.test.js index 21f6f57..9e09db3 100644 --- a/test/adapter.test.js +++ b/test/adapter.test.js @@ -7,6 +7,12 @@ import { createServer as createNetServer } from "node:net"; import { join, resolve } from "node:path"; import { tmpdir } from "node:os"; +/** @param {string} command */ +function commandExists(command) { + const result = spawnSync(command, ["--version"], { encoding: "utf8" }); + return !(result.error && "code" in result.error && result.error.code === "ENOENT"); +} + test("Codex plugin manifest points at skills", async () => { const manifest = JSON.parse(await readFile(".codex-plugin/plugin.json", "utf8")); @@ -20,11 +26,67 @@ test("$loop skill describes the six Loop Engineering components", async () => { const skill = await readFile("skills/loop/SKILL.md", "utf8"); assert.match(skill, /\$loop/); + assert.match(skill, /\$Loop/); for (const component of ["Automations", "Worktrees", "Skills", "Plugins/connectors", "Sub-agents", "Memory"]) { assert.match(skill, new RegExp(component.replace("/", "\\/"))); } }); +test("Codex can install the Loop plugin and render $Loop skill context", { + skip: commandExists("codex") ? false : "codex CLI is not installed" +}, async () => { + const root = await mkdtemp(join(tmpdir(), "loop-codex-plugin-")); + const codexHome = join(root, "codex-home"); + const marketplace = join(root, "marketplace"); + const pluginRoot = join(marketplace, "plugins", "loop"); + await mkdir(join(marketplace, ".agents", "plugins"), { recursive: true }); + await mkdir(join(pluginRoot, ".codex-plugin"), { recursive: true }); + await mkdir(join(pluginRoot, "skills", "loop"), { recursive: true }); + await mkdir(codexHome, { recursive: true }); + await writeFile( + join(pluginRoot, ".codex-plugin", "plugin.json"), + await readFile(".codex-plugin/plugin.json", "utf8") + ); + await writeFile( + join(pluginRoot, "skills", "loop", "SKILL.md"), + await readFile("skills/loop/SKILL.md", "utf8") + ); + await writeFile(join(marketplace, ".agents", "plugins", "marketplace.json"), `${JSON.stringify({ + name: "loop-local-test", + interface: { displayName: "Loop local test" }, + plugins: [{ + name: "loop", + source: { source: "local", path: "./plugins/loop" }, + policy: { installation: "AVAILABLE", authentication: "ON_INSTALL" }, + category: "Productivity" + }] + }, null, 2)}\n`); + const env = { ...process.env, CODEX_HOME: codexHome }; + + const addMarketplace = spawnSync("codex", ["plugin", "marketplace", "add", marketplace], { + env, + encoding: "utf8" + }); + assert.equal(addMarketplace.status, 0, addMarketplace.stderr || addMarketplace.stdout); + const addPlugin = spawnSync("codex", ["plugin", "add", "loop@loop-local-test"], { + env, + encoding: "utf8" + }); + assert.equal(addPlugin.status, 0, addPlugin.stderr || addPlugin.stdout); + const pluginList = execFileSync("codex", ["plugin", "list"], { env, encoding: "utf8" }); + const promptInput = execFileSync( + "codex", + ["debug", "prompt-input", "$Loop 현재 대시보드 품질 루프를 검증해줘"], + { env, encoding: "utf8" } + ); + + assert.match(pluginList, /loop@loop-local-test\s+installed, enabled/); + assert.match(promptInput, /loop:loop/); + assert.match(promptInput, /Available plugins/); + assert.match(promptInput, /`Loop`/); + assert.match(promptInput, /\$Loop 현재 대시보드 품질 루프를 검증해줘/); +}); + test("CLI dry-run writes durable state without source edits", async () => { const stateDir = await mkdtemp(join(tmpdir(), "loop-cli-state-")); const output = execFileSync(