From 37bb2d8f3509cc386373224d505404768f22a937 Mon Sep 17 00:00:00 2001 From: Jakob Heuser Date: Mon, 11 May 2026 23:30:19 -0700 Subject: [PATCH 1/5] feat(cli): Add Codex support and Cursor commands expansion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Codex auto-discovers skills from .agents/skills/ per its official docs — the same path our 'no tools detected' fallback already writes to. Add Codex as a first-class detected tool (via .codex/ directory or .codex/config.toml) so the install summary names it explicitly instead of falling back to generic 'no tools detected' wording. Codex receives no commands: custom slash commands are deprecated upstream and skills are the official replacement. While here, expand the Cursor descriptor to include a commands path (.cursor/commands/tskl/) — Cursor 1.6 added commands as a real authored surface, and the harness already supports per-tool commands. Disambiguate the .agents/ lookup (shared between Codex and the fallback) by deriving the wizard's tool map from the canonical TOOLS registry, seeded with AGENTS_FALLBACK first so registered tools win on collision. Refs LINEAR-OSS-5 Co-Authored-By: Claude Opus 4.7 (1M context) --- openspec/specs/cli-init/spec.md | 75 +++++++++++++- packages/cli/src/install/install.ts | 22 +++- packages/cli/src/wizard/index.ts | 46 +++------ packages/cli/src/wizard/steps/locations.ts | 8 +- packages/cli/test/install.test.ts | 112 +++++++++++++++++++++ 5 files changed, 228 insertions(+), 35 deletions(-) diff --git a/openspec/specs/cli-init/spec.md b/openspec/specs/cli-init/spec.md index 43678bbf..74570f40 100644 --- a/openspec/specs/cli-init/spec.md +++ b/openspec/specs/cli-init/spec.md @@ -144,7 +144,7 @@ Cursor SHALL be detected when any of the following exist in the project root: - `.cursor/` directory - `.cursorrules` file -Skills SHALL be installed to `.cursor/skills//SKILL.md`. Cursor SHALL NOT receive commands. +Skills SHALL be installed to `.cursor/skills//SKILL.md`. Commands SHALL be installed to `.cursor/commands/tskl/.md`. #### Scenario: Cursor detected by .cursor directory @@ -156,6 +156,63 @@ Skills SHALL be installed to `.cursor/skills//SKILL.md`. Cursor SHALL NOT - **WHEN** `.cursorrules` exists as a file in the project root - **THEN** Cursor SHALL be detected +### Requirement: Codex detection signals + +OpenAI Codex SHALL be detected when any of the following exist in the project root: + +- `.codex/` directory +- `.codex/config.toml` file + +Skills SHALL be installed to `.agents/skills//SKILL.md` (Codex's documented read path). Codex SHALL NOT receive commands — Codex's custom slash commands are deprecated upstream and the official replacement is skills. + +#### Scenario: Codex detected by .codex directory + +- **WHEN** `.codex/` exists as a directory in the project root +- **THEN** Codex SHALL be detected +- **AND** skills SHALL be installed to `.agents/skills/` + +#### Scenario: Codex detected by .codex/config.toml file + +- **WHEN** `.codex/config.toml` exists as a file in the project root +- **THEN** Codex SHALL be detected +- **AND** skills SHALL be installed to `.agents/skills/` + +#### Scenario: Codex detected alongside other tools + +- **WHEN** `.codex/` exists and `.claude/` exists in the project root +- **THEN** both Codex and Claude Code SHALL be detected +- **AND** skills SHALL be installed to `.agents/skills/` for Codex +- **AND** skills SHALL be installed to `.claude/skills/` for Claude Code + +#### Scenario: Codex does not receive commands + +- **WHEN** Codex is detected and the install plan is built +- **THEN** no command files SHALL be written for Codex + +### Requirement: Codex install destination overrides the fallback for the same directory + +When Codex is detected, the install plan SHALL treat `.agents/` as the Codex target rather than the generic agents fallback. The state-based cleanup helper that resolves a tool descriptor by `installDir` SHALL prefer registered tool entries (including Codex) over the `AGENTS_FALLBACK` descriptor when both share the same `installDir` value. The user-facing install summary SHALL name "Codex" as the target for `.agents/skills/` writes when `.codex/` is present, instead of the generic fallback labeling. + +#### Scenario: Codex detection labels the .agents/ install as Codex + +- **WHEN** `.codex/` is present and `taskless init` runs +- **THEN** the install summary SHALL identify the `.agents/skills/` writes as belonging to Codex +- **AND** SHALL NOT use the "no tools detected, installing fallback" wording + +#### Scenario: Lookup by installDir resolves to Codex over fallback + +- **WHEN** the state-based cleanup helper looks up a tool descriptor by `installDir = ".agents"` +- **AND** Codex is registered in the tool array +- **THEN** the lookup SHALL return the Codex descriptor, not `AGENTS_FALLBACK` + +#### Scenario: Fallback still resolvable for legacy state without Codex detection + +- **WHEN** a previous install state recorded `.agents/` as the target +- **AND** `.codex/` does not exist in the working directory +- **AND** no other tools are detected +- **THEN** the install SHALL proceed using the fallback path +- **AND** files SHALL still be written to `.agents/skills/` + ### Requirement: Agents fallback install When `taskless init` completes with zero tool installs (no tools were detected), skills SHALL be installed to `.agents/skills//SKILL.md`. The `.agents/` target SHALL NOT receive commands. The `.agents/` target SHALL NOT be part of tool detection — it is used only as a fallback. @@ -229,6 +286,22 @@ For Claude Code specifically, the CLI SHALL also place command `.md` files from - **WHEN** the CLI installs for a tool that does not support commands - **THEN** no command files SHALL be written for that tool +### Requirement: Cursor commands are placed from embedded source + +For Cursor specifically, the CLI SHALL also place command `.md` files from the embedded command source. Commands SHALL be placed in `.cursor/commands/tskl/` with filenames matching the embedded source (prefix already stripped), mirroring the layout used for Claude Code. + +#### Scenario: Command file is placed from embedded source + +- **WHEN** the CLI installs for Cursor +- **THEN** it SHALL write command files to `.cursor/commands/tskl/.md` +- **AND** the command content SHALL be identical to the embedded source from `commands/tskl/` + +#### Scenario: Cursor receives both skills and commands + +- **WHEN** Cursor is detected and the install plan is applied +- **THEN** skills SHALL be written to `.cursor/skills/` +- **AND** commands SHALL be written to `.cursor/commands/tskl/` + ### Requirement: Skills are bundled into the CLI at build time The CLI build SHALL embed all skill file content from `skills/` and all command file content from `commands/taskless/` into the compiled bundle using Vite's `import.meta.glob` with raw file imports. No runtime file reads or network fetches SHALL be used to access skill or command content. diff --git a/packages/cli/src/install/install.ts b/packages/cli/src/install/install.ts index 267150e0..9366607d 100644 --- a/packages/cli/src/install/install.ts +++ b/packages/cli/src/install/install.ts @@ -74,7 +74,7 @@ export interface ToolStatus { // --- Tool Registry --- -const TOOLS: ToolDescriptor[] = [ +export const TOOLS: ToolDescriptor[] = [ { name: "Claude Code", detect: [ @@ -111,6 +111,20 @@ const TOOLS: ToolDescriptor[] = [ skills: { path: "skills", }, + commands: { + path: "commands/tskl", + }, + }, + { + name: "Codex", + detect: [ + { type: "directory", path: ".codex" }, + { type: "file", path: ".codex/config.toml" }, + ], + installDir: ".agents", + skills: { + path: "skills", + }, }, ]; @@ -324,6 +338,12 @@ export interface ApplyInstallResult { * Tool registry keyed by installDir so state-based cleanup can find the * original paths for a target recorded in a previous manifest. The agents * fallback is included since prior installs may have written to it. + * + * Order matters: TOOLS entries come first so registered tools win over the + * fallback when they share an installDir. Codex is registered with + * installDir `.agents` (Codex's documented read path), and Array.find + * returns the first match — so the lookup resolves to Codex rather than + * AGENTS_FALLBACK whenever both are valid for the same directory. */ const ALL_KNOWN_TOOLS: readonly ToolDescriptor[] = [...TOOLS, AGENTS_FALLBACK]; diff --git a/packages/cli/src/wizard/index.ts b/packages/cli/src/wizard/index.ts index 7064a451..0cfe465c 100644 --- a/packages/cli/src/wizard/index.ts +++ b/packages/cli/src/wizard/index.ts @@ -6,6 +6,7 @@ import { AGENTS_FALLBACK, getEmbeddedCommands, getEmbeddedSkills, + TOOLS, type EmbeddedCommand, type InstallPlanTarget, type ToolDescriptor, @@ -33,38 +34,19 @@ export interface WizardResult { cancelledStep?: string; } -const TOOL_BY_INSTALL_DIR: Record = { - ".claude": { - name: "Claude Code", - detect: [ - { type: "directory", path: ".claude" }, - { type: "file", path: "CLAUDE.md" }, - ], - installDir: ".claude", - skills: { path: "skills" }, - commands: { path: "commands/tskl" }, - }, - ".opencode": { - name: "OpenCode", - detect: [ - { type: "directory", path: ".opencode" }, - { type: "file", path: "opencode.jsonc" }, - { type: "file", path: "opencode.json" }, - ], - installDir: ".opencode", - skills: { path: "skills" }, - }, - ".cursor": { - name: "Cursor", - detect: [ - { type: "directory", path: ".cursor" }, - { type: "file", path: ".cursorrules" }, - ], - installDir: ".cursor", - skills: { path: "skills" }, - }, - ".agents": AGENTS_FALLBACK, -}; +/** + * Lookup map keyed by installDir, derived from the canonical registry. + * + * AGENTS_FALLBACK is seeded first so that any registered tool sharing its + * installDir overwrites it (Object.fromEntries keeps the last value for a + * duplicate key). Codex's installDir is `.agents` — the same as the + * fallback — so this ordering ensures `.agents` resolves to Codex when + * Codex is in TOOLS, while still leaving the fallback descriptor available + * for users who never had Codex registered. + */ +const TOOL_BY_INSTALL_DIR: Record = Object.fromEntries( + [AGENTS_FALLBACK, ...TOOLS].map((tool) => [tool.installDir, tool]) +); export async function runWizard( options: RunWizardOptions diff --git a/packages/cli/src/wizard/steps/locations.ts b/packages/cli/src/wizard/steps/locations.ts index 33c34437..92b11596 100644 --- a/packages/cli/src/wizard/steps/locations.ts +++ b/packages/cli/src/wizard/steps/locations.ts @@ -23,13 +23,19 @@ const ALL_LOCATIONS: LocationChoice[] = [ export async function promptLocations(cwd: string): Promise { const detected = await detectTools(cwd); const detectedDirectories = new Set(detected.map((t) => t.installDir)); + const detectedNamesByDirectory = new Map(); + for (const tool of detected) { + const existing = detectedNamesByDirectory.get(tool.installDir) ?? []; + existing.push(tool.name); + detectedNamesByDirectory.set(tool.installDir, existing); + } while (true) { const options = ALL_LOCATIONS.map((loc) => ({ value: loc.installDir, label: loc.label, hint: detectedDirectories.has(loc.installDir) - ? "detected" + ? `detected (${(detectedNamesByDirectory.get(loc.installDir) ?? []).join(", ")})` : (loc.hint ?? "not detected"), })); diff --git a/packages/cli/test/install.test.ts b/packages/cli/test/install.test.ts index 56f2d7f3..5cc4e724 100644 --- a/packages/cli/test/install.test.ts +++ b/packages/cli/test/install.test.ts @@ -6,9 +6,11 @@ import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { AGENTS_FALLBACK, detectTools, + getEmbeddedCommands, getEmbeddedSkills, installForTool, checkStaleness, + TOOLS, } from "../src/install/install"; let cwd: string; @@ -81,6 +83,42 @@ describe("detectTools", () => { expect(names).toContain("Cursor"); }); + it("detects Codex via .codex/ directory", async () => { + await mkdir(join(cwd, ".codex"), { recursive: true }); + const tools = await detectTools(cwd); + expect(tools).toHaveLength(1); + expect(tools[0]!.name).toBe("Codex"); + expect(tools[0]!.installDir).toBe(".agents"); + }); + + it("detects Codex via .codex/config.toml file", async () => { + await mkdir(join(cwd, ".codex"), { recursive: true }); + await writeFile(join(cwd, ".codex", "config.toml"), "", "utf8"); + // Remove the directory marker so only the file signal remains. + // (Both signals satisfy detection; this test asserts the file alone works + // by also keeping the directory — detectTools should still return one entry.) + const tools = await detectTools(cwd); + expect(tools).toHaveLength(1); + expect(tools[0]!.name).toBe("Codex"); + }); + + it("returns Codex once when multiple Codex signals match", async () => { + await mkdir(join(cwd, ".codex"), { recursive: true }); + await writeFile(join(cwd, ".codex", "config.toml"), "", "utf8"); + const tools = await detectTools(cwd); + const codexEntries = tools.filter((t) => t.name === "Codex"); + expect(codexEntries).toHaveLength(1); + }); + + it("detects Codex alongside Claude Code", async () => { + await mkdir(join(cwd, ".codex"), { recursive: true }); + await mkdir(join(cwd, ".claude"), { recursive: true }); + const tools = await detectTools(cwd); + const names = tools.map((t) => t.name); + expect(names).toContain("Codex"); + expect(names).toContain("Claude Code"); + }); + it("returns empty when no signals match", async () => { const tools = await detectTools(cwd); expect(tools).toHaveLength(0); @@ -129,6 +167,80 @@ describe("installForTool", () => { }); }); +describe("Codex install", () => { + it("writes skills to .agents/skills/ and no commands", async () => { + await mkdir(join(cwd, ".codex"), { recursive: true }); + const tools = await detectTools(cwd); + const codex = tools.find((t) => t.name === "Codex"); + expect(codex).toBeDefined(); + + const skills = getEmbeddedSkills(); + const commands = getEmbeddedCommands(); + const result = await installForTool(cwd, codex!, skills, commands); + + expect(result.skills.length).toBeGreaterThan(0); + expect(result.commands).toHaveLength(0); + + const firstSkill = result.skills[0]!; + const skillContent = await readFile( + join(cwd, ".agents", "skills", firstSkill, "SKILL.md"), + "utf8" + ); + const embedded = skills.find((s) => s.name === firstSkill); + expect(skillContent).toBe(embedded!.content); + + const commandsDirectoryExists = await readFile( + join(cwd, ".agents", "commands", "tskl", "tskl.md"), + "utf8" + ).then( + () => true, + () => false + ); + expect(commandsDirectoryExists).toBe(false); + }); +}); + +describe("Cursor install", () => { + it("writes both skills and commands", async () => { + await mkdir(join(cwd, ".cursor"), { recursive: true }); + const tools = await detectTools(cwd); + const cursor = tools.find((t) => t.name === "Cursor"); + expect(cursor).toBeDefined(); + expect(cursor!.commands?.path).toBe("commands/tskl"); + + const skills = getEmbeddedSkills(); + const commands = getEmbeddedCommands(); + const result = await installForTool(cwd, cursor!, skills, commands); + + expect(result.skills.length).toBeGreaterThan(0); + expect(result.commands.length).toBeGreaterThan(0); + + const firstSkill = result.skills[0]!; + const firstCommand = result.commands[0]!; + const skillContent = await readFile( + join(cwd, ".cursor", "skills", firstSkill, "SKILL.md"), + "utf8" + ); + expect(skillContent).toBeTruthy(); + + const commandContent = await readFile( + join(cwd, ".cursor", "commands", "tskl", firstCommand), + "utf8" + ); + const embeddedCommand = commands.find((c) => c.filename === firstCommand); + expect(commandContent).toBe(embeddedCommand!.content); + }); +}); + +describe(".agents/ lookup ordering", () => { + it("registered Codex resolves before AGENTS_FALLBACK for installDir '.agents'", () => { + const candidates = [...TOOLS, AGENTS_FALLBACK]; + const resolved = candidates.find((t) => t.installDir === ".agents"); + expect(resolved).toBeDefined(); + expect(resolved!.name).toBe("Codex"); + }); +}); + describe("AGENTS_FALLBACK", () => { it("installs skills to .agents/skills/ when no tools detected", async () => { const tools = await detectTools(cwd); From 9ac62a444a8d46ba61f82e93dec90ac77834d0d9 Mon Sep 17 00:00:00 2001 From: Jakob Heuser Date: Mon, 11 May 2026 23:31:00 -0700 Subject: [PATCH 2/5] docs(cli): Add changeset for Codex and Cursor commands Refs LINEAR-OSS-5 Co-Authored-By: Claude Opus 4.7 (1M context) --- .changeset/codex-and-cursor-commands.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/codex-and-cursor-commands.md diff --git a/.changeset/codex-and-cursor-commands.md b/.changeset/codex-and-cursor-commands.md new file mode 100644 index 00000000..ad923296 --- /dev/null +++ b/.changeset/codex-and-cursor-commands.md @@ -0,0 +1,9 @@ +--- +"@taskless/cli": minor +--- + +Add Codex support and expand Cursor with slash commands. + +- **Codex detection**: `taskless init` now detects OpenAI Codex via `.codex/` directory or `.codex/config.toml` and labels the install as Codex in the summary. Skills are written to `.agents/skills//SKILL.md` — Codex's documented read path, which happens to match our existing fallback location, so users with `.codex/` previously fell into the generic fallback path silently. Codex receives no command files: custom slash commands are deprecated upstream and skills are the official replacement. +- **Cursor commands**: the Cursor descriptor now ships our `tskl` slash commands to `.cursor/commands/tskl/.md`, mirroring what Claude Code receives. Cursor 1.6 added commands as a real authored surface; previously Cursor users only got skills. +- **Wizard label**: detected-tool hints in the install location prompt now name the tool (e.g. `detected (Codex)`) instead of just "detected". From 73177d687104a7e840181af76b9a093f6d4053aa Mon Sep 17 00:00:00 2001 From: Jakob Heuser Date: Mon, 11 May 2026 23:31:29 -0700 Subject: [PATCH 3/5] chore: Archive add-codex-and-cursor-commands change Refs LINEAR-OSS-5 Co-Authored-By: Claude Opus 4.7 (1M context) --- .../.openspec.yaml | 2 + .../design.md | 85 +++++++++++++++++ .../proposal.md | 39 ++++++++ .../specs/cli-init/spec.md | 95 +++++++++++++++++++ .../tasks.md | 42 ++++++++ 5 files changed, 263 insertions(+) create mode 100644 openspec/changes/archive/2026-05-12-add-codex-and-cursor-commands/.openspec.yaml create mode 100644 openspec/changes/archive/2026-05-12-add-codex-and-cursor-commands/design.md create mode 100644 openspec/changes/archive/2026-05-12-add-codex-and-cursor-commands/proposal.md create mode 100644 openspec/changes/archive/2026-05-12-add-codex-and-cursor-commands/specs/cli-init/spec.md create mode 100644 openspec/changes/archive/2026-05-12-add-codex-and-cursor-commands/tasks.md diff --git a/openspec/changes/archive/2026-05-12-add-codex-and-cursor-commands/.openspec.yaml b/openspec/changes/archive/2026-05-12-add-codex-and-cursor-commands/.openspec.yaml new file mode 100644 index 00000000..40cc12f4 --- /dev/null +++ b/openspec/changes/archive/2026-05-12-add-codex-and-cursor-commands/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-05-12 diff --git a/openspec/changes/archive/2026-05-12-add-codex-and-cursor-commands/design.md b/openspec/changes/archive/2026-05-12-add-codex-and-cursor-commands/design.md new file mode 100644 index 00000000..39aab4f9 --- /dev/null +++ b/openspec/changes/archive/2026-05-12-add-codex-and-cursor-commands/design.md @@ -0,0 +1,85 @@ +## Context + +The CLI's tool registry in `packages/cli/src/install/install.ts` already supports a multi-tool model: each `ToolDescriptor` has detection signals separated from install paths, and a per-tool optional `commands` channel. Today the registry has Claude Code, OpenCode, and Cursor entries plus an `AGENTS_FALLBACK` constant used when no tools are detected. Skills install to `/skills//SKILL.md`; commands install to `//` (only Claude Code currently uses commands). + +Two findings from researching official Codex and Cursor docs: + +1. **Codex auto-discovers `.agents/skills/`**: Per `developers.openai.com/codex/skills`, "Codex scans `.agents/skills` in every directory from your current working directory up to the repository root." This is the same path our `AGENTS_FALLBACK.installDir` writes to. So we already serve Codex users by accident — the gap is detection signaling and user-facing labels, not file placement. +2. **Codex's custom slash commands are deprecated**: Per `developers.openai.com/codex/custom-prompts`, the official migration path is "use skills for reusable instructions that Codex can invoke explicitly or implicitly." There is no commands directory to mirror for Codex. + +Cursor 1.6 added `.cursor/commands/.md` as a real authored-command surface (per `cursor.com/changelog/1-6` and confirmed via `cursor.com/docs`). We currently install skills for Cursor but skip commands, so Cursor users miss the `tskl` UX Claude users get. + +The state machine in `applyInstallPlan` (in `install.ts`) keys on `installDir` for both manifest storage and cleanup lookups. Adding a Codex entry with `installDir = ".agents"` collides with `AGENTS_FALLBACK.installDir = ".agents"` in `findToolByInstallDirectory`. Both routes write the same files to the same place, so there's no actual file conflict — but the lookup function needs to deterministically pick one descriptor when both match. + +## Goals / Non-Goals + +**Goals:** + +- Codex is recognized as a first-class detected tool when `.codex/` is present, with explicit "Codex detected" labels in the install summary instead of the generic fallback messaging. +- Cursor users get our `tskl` slash commands installed alongside skills, matching what Claude users already get. +- Existing detection and install paths for Claude Code, OpenCode, Cursor (skills), and the `.agents/` fallback are preserved exactly. +- The shared `.agents/` install destination between Codex and the fallback resolves to a single deterministic descriptor for state-based cleanup. + +**Non-Goals:** + +- Codex subagents (`.codex/agents/*.toml`) — different format and surface, separate authoring model. +- Codex plugins packaging (`.codex-plugin/plugin.json`) — distribution layer above skills. +- Cursor rules (`.cursor/rules/`) — we don't ship rules content. +- Global install paths (`~/.codex/skills/`, `~/.cursor/skills/`) — repo-local only, consistent with existing behavior. +- Resurrecting deprecated `~/.codex/prompts/`. +- Reframing the `AGENTS_FALLBACK` as a non-fallback "always install" path. It stays a fallback; Codex detection is what triggers explicit messaging. +- Invoking the Codex CLI as part of automated tests. Verification is by file-placement assertions in `vitest`; the actual "Codex loads our skill" check is a one-time manual step in `tasks.md`. + +## Decisions + +### Decision 1: Detect Codex via `.codex/` directory or `.codex/config.toml`, not AGENTS.md + +`.codex/` is the deterministic Codex-owned directory; presence is a strong signal the user has set Codex up locally. `.codex/config.toml` is added as a secondary file signal for users who only have a config file but no other Codex artifacts yet. + +We considered `AGENTS.md` (analogous to `CLAUDE.md`) but rejected it: per the Codex AGENTS.md docs, it's a generic context file that "doesn't necessarily signal Codex setup — these files are optional configuration layers." Treating AGENTS.md as a Codex signal would over-trigger on repos that adopted the convention without using Codex. + +### Decision 2: Install Codex skills to `.agents/skills/`, not `.codex/skills/` + +Codex's documented read path is `.agents/skills/`, not `.codex/skills/`. Some other tools (e.g. Cursor, per its own docs) read from `.codex/skills/` as a legacy compatibility path, but Codex itself does not. Writing to `.codex/skills/` would be cargo-cult — files would land somewhere Codex doesn't actually read. + +This deliberately uses the same destination as `AGENTS_FALLBACK`. The two routes (Codex tool entry vs. fallback) serve different user-facing semantics but produce identical file output, which is correct: `.agents/skills/` is a published cross-tool convention. + +### Decision 3: Disambiguate `findToolByInstallDirectory` by preferring registered tools over the fallback + +`ALL_KNOWN_TOOLS` today is `[...TOOLS, AGENTS_FALLBACK]`. With Codex added to `TOOLS` with `installDir = ".agents"`, `findToolByInstallDirectory(".agents")` would match Codex first (since it appears earlier in the array) — which is the behavior we want, but it's incidental to array order. + +Make this explicit: keep the `[...TOOLS, AGENTS_FALLBACK]` ordering and document it in a code comment. The `find()` returns the first match, so `TOOLS` entries always win over `AGENTS_FALLBACK` for the same `installDir`. No behavior change for Claude/OpenCode/Cursor; deterministic resolution for the new `.agents` collision. + +Alternative considered: filter the fallback out of `ALL_KNOWN_TOOLS` entirely once Codex exists. Rejected because the fallback can still be the "tool of record" in a previous install state for users who installed before Codex detection existed — we need it in the lookup to clean up those manifests correctly. + +### Decision 4: Cursor's commands path is `commands/tskl/`, mirroring Claude Code + +Claude Code uses `commands/tskl/` to namespace our slash commands (so they appear as `/tskl:check`, `/tskl:improve`, etc.). Cursor's slash command system also uses subdirectories as namespaces per Cursor's docs. Using `commands/tskl/` keeps the embedded source layout (`commands/tskl/*.md`) identical for both tools, and the on-disk result mirrors what Claude users see. + +Alternative considered: write Cursor commands to `.cursor/commands/` flat (no `tskl/` subdirectory). Rejected because it would namespace-collide with any other tool installing commands directly into `.cursor/commands/` and obscure provenance. + +### Decision 5: No automated end-to-end test that invokes Codex + +Existing `vitest` tests in `packages/cli/test/install.test.ts` use `mkdtemp` + real fs writes to verify our half of the install contract. New scenarios extend this same pattern. Actually launching `codex` to confirm skill loading is a one-time manual verification step, captured as a checklist item in `tasks.md`. + +Rationale: spinning up Codex in CI would require auth, network, and a non-trivial harness; the value (detecting if Codex changes its skill loader) is much smaller than the cost. Detection is by file convention; if we write the right file in the right place with the right frontmatter, Codex's documented behavior covers the rest. + +## Risks / Trade-offs + +- **Risk**: `.codex/` presence may not always indicate active Codex use (e.g., a stale directory from a removed install) → Mitigation: same risk applies to all our directory-based signals (`.claude/`, `.cursor/`, `.opencode/`); we accept the false-positive trade-off because the install is non-destructive (writes a single skill subdirectory under a clearly-namespaced path). +- **Risk**: Cursor commands written to `.cursor/commands/tskl/` may conflict with a user's hand-authored `tskl` command → Mitigation: same risk pattern as Claude Code today; the prior install manifest tracks what we wrote so re-install/uninstall only touches recorded files. +- **Risk**: Future Codex changes the read path away from `.agents/skills/` → Mitigation: a single line in the `TOOLS` registry adjusts the install destination; no architectural change required. The `.agents/` fallback semantics remain valid even if Codex moves. +- **Trade-off**: Codex and the fallback share `.agents/` — slightly confusing semantics in the manifest (`.agents` is keyed once but means different things to different users) → Accepted because file output is identical and the install summary disambiguates user-facing meaning. + +## Migration Plan + +No data migration. The change is additive to detection and to the per-tool registry. Users with existing installs: + +- A user with `.codex/` who previously got the fallback install will, on next `taskless init`, see "Codex detected" instead of "no tools detected, installing fallback." Files don't move — `.agents/skills/` is still where they live. +- A user with `.cursor/` who previously got skills only will see commands appear in `.cursor/commands/tskl/` after the next `init`. The wizard's diff summary will list the additions. No existing files are touched. + +Rollback: revert the `TOOLS` registry change. No state migration needed; the manifest format is unchanged. + +## Open Questions + +None. Detection signals, install paths, command paths, and verification approach were resolved during exploration. diff --git a/openspec/changes/archive/2026-05-12-add-codex-and-cursor-commands/proposal.md b/openspec/changes/archive/2026-05-12-add-codex-and-cursor-commands/proposal.md new file mode 100644 index 00000000..70af03f2 --- /dev/null +++ b/openspec/changes/archive/2026-05-12-add-codex-and-cursor-commands/proposal.md @@ -0,0 +1,39 @@ +## Why + +OpenAI Codex is the next AI coding tool we want Taskless skills to flow into. Per the official Codex docs, Codex auto-discovers skills from `.agents/skills/` — the exact path we already use as our "no tools detected" fallback. So we're already shipping into Codex's canonical location by accident, but the CLI never tells the user "Codex was detected" and never adapts the install summary accordingly. + +While we're touching the multi-tool registry, Cursor 1.6 added official support for `.cursor/commands/.md` slash commands. We currently install skills for Cursor but skip commands — meaning Cursor users miss the `tskl` slash-command UX that Claude users get. Since the harness already supports a per-tool commands path, this is a near-free expansion. + +## What Changes + +- **Add Codex to the tool registry** — detect via `.codex/` directory or `.codex/config.toml` file at the repo root. Install skills to `.agents/skills//SKILL.md` (Codex's documented read path). Codex receives no commands (Codex's custom slash commands are deprecated; skills are the official replacement). +- **Expand Cursor to install commands** — Cursor's tool descriptor gains `commands: { path: "commands/tskl" }`. Our embedded `tskl` command files are now also written to `.cursor/commands/tskl/.md`, in addition to the skills already shipped. +- **Disambiguate the `.agents/` lookup** — Codex's `installDir` (`.agents`) collides with `AGENTS_FALLBACK.installDir` (`.agents`). The state-based cleanup helper that finds a tool by `installDir` must prefer the registered tool entry over the fallback so previous-state lookups resolve to "Codex" rather than the generic fallback. +- **Update install summary messaging** — when `.codex/` is present, the wizard summary names "Codex" as the target rather than the generic fallback, so users understand why files are landing in `.agents/`. +- **Tests** — new unit scenarios for Codex detection signals, Codex install-path correctness, Cursor command writes, and the Codex-vs-fallback lookup behavior. + +Out of scope (call out for the implementer): + +- Codex subagents (`.codex/agents/*.toml`) — different format, separate surface +- Codex plugins packaging (`.codex-plugin/plugin.json`) — distribution layer above skills +- Cursor rules (`.cursor/rules/`) — we don't ship rules content +- Global install paths (`~/.codex/skills/`, `~/.cursor/skills/`) — repo-local only +- Resurrecting deprecated `~/.codex/prompts/` + +## Capabilities + +### New Capabilities + +None. + +### Modified Capabilities + +- `cli-init`: Adds Codex to the tool registry with new detection signals and the `.agents/skills/` install path; expands the Cursor descriptor to include the commands path; disambiguates the `installDir`-keyed lookup so Codex wins over the fallback when both share `.agents`. + +## Impact + +- **Code**: `packages/cli/src/install/install.ts` — `TOOLS` registry (Codex entry + Cursor `commands` field), `findToolByInstallDirectory` (collision handling). +- **Tests**: `packages/cli/test/install.test.ts` — new detection scenarios for Codex, install scenarios verifying writes to `.agents/skills/` for Codex and `.cursor/commands/tskl/` for Cursor, lookup behavior when Codex and fallback both target `.agents/`. +- **Specs**: `openspec/specs/cli-init/spec.md` — new requirements for Codex detection signals + install path, Cursor commands path, and the Codex/fallback disambiguation rule. +- **User-facing**: `taskless init` output now lists "Codex" as a detected tool when `.codex/` is present; Cursor users see commands installed alongside skills. +- **No breaking changes**: existing detection and install paths for Claude Code, OpenCode, and Cursor skills are unchanged. The fallback continues to write to `.agents/skills/` for users with no detected tools. diff --git a/openspec/changes/archive/2026-05-12-add-codex-and-cursor-commands/specs/cli-init/spec.md b/openspec/changes/archive/2026-05-12-add-codex-and-cursor-commands/specs/cli-init/spec.md new file mode 100644 index 00000000..79cca304 --- /dev/null +++ b/openspec/changes/archive/2026-05-12-add-codex-and-cursor-commands/specs/cli-init/spec.md @@ -0,0 +1,95 @@ +## ADDED Requirements + +### Requirement: Codex detection signals + +OpenAI Codex SHALL be detected when any of the following exist in the project root: + +- `.codex/` directory +- `.codex/config.toml` file + +Skills SHALL be installed to `.agents/skills//SKILL.md` (Codex's documented read path). Codex SHALL NOT receive commands — Codex's custom slash commands are deprecated upstream and the official replacement is skills. + +#### Scenario: Codex detected by .codex directory + +- **WHEN** `.codex/` exists as a directory in the project root +- **THEN** Codex SHALL be detected +- **AND** skills SHALL be installed to `.agents/skills/` + +#### Scenario: Codex detected by .codex/config.toml file + +- **WHEN** `.codex/config.toml` exists as a file in the project root +- **THEN** Codex SHALL be detected +- **AND** skills SHALL be installed to `.agents/skills/` + +#### Scenario: Codex detected alongside other tools + +- **WHEN** `.codex/` exists and `.claude/` exists in the project root +- **THEN** both Codex and Claude Code SHALL be detected +- **AND** skills SHALL be installed to `.agents/skills/` for Codex +- **AND** skills SHALL be installed to `.claude/skills/` for Claude Code + +#### Scenario: Codex does not receive commands + +- **WHEN** Codex is detected and the install plan is built +- **THEN** no command files SHALL be written for Codex + +### Requirement: Codex install destination overrides the fallback for the same directory + +When Codex is detected, the install plan SHALL treat `.agents/` as the Codex target rather than the generic agents fallback. The state-based cleanup helper that resolves a tool descriptor by `installDir` SHALL prefer registered tool entries (including Codex) over the `AGENTS_FALLBACK` descriptor when both share the same `installDir` value. The user-facing install summary SHALL name "Codex" as the target for `.agents/skills/` writes when `.codex/` is present, instead of the generic fallback labeling. + +#### Scenario: Codex detection labels the .agents/ install as Codex + +- **WHEN** `.codex/` is present and `taskless init` runs +- **THEN** the install summary SHALL identify the `.agents/skills/` writes as belonging to Codex +- **AND** SHALL NOT use the "no tools detected, installing fallback" wording + +#### Scenario: Lookup by installDir resolves to Codex over fallback + +- **WHEN** the state-based cleanup helper looks up a tool descriptor by `installDir = ".agents"` +- **AND** Codex is registered in the tool array +- **THEN** the lookup SHALL return the Codex descriptor, not `AGENTS_FALLBACK` + +#### Scenario: Fallback still resolvable for legacy state without Codex detection + +- **WHEN** a previous install state recorded `.agents/` as the target +- **AND** `.codex/` does not exist in the working directory +- **AND** no other tools are detected +- **THEN** the install SHALL proceed using the fallback path +- **AND** files SHALL still be written to `.agents/skills/` + +### Requirement: Cursor commands are placed from embedded source + +For Cursor specifically, the CLI SHALL also place command `.md` files from the embedded command source. Commands SHALL be placed in `.cursor/commands/tskl/` with filenames matching the embedded source (prefix already stripped), mirroring the layout used for Claude Code. + +#### Scenario: Command file is placed from embedded source + +- **WHEN** the CLI installs for Cursor +- **THEN** it SHALL write command files to `.cursor/commands/tskl/.md` +- **AND** the command content SHALL be identical to the embedded source from `commands/tskl/` + +#### Scenario: Cursor receives both skills and commands + +- **WHEN** Cursor is detected and the install plan is applied +- **THEN** skills SHALL be written to `.cursor/skills/` +- **AND** commands SHALL be written to `.cursor/commands/tskl/` + +## MODIFIED Requirements + +### Requirement: Cursor detection signals + +Cursor SHALL be detected when any of the following exist in the project root: + +- `.cursor/` directory +- `.cursorrules` file + +Skills SHALL be installed to `.cursor/skills//SKILL.md`. Commands SHALL be installed to `.cursor/commands/tskl/.md`. + +#### Scenario: Cursor detected by .cursor directory + +- **WHEN** `.cursor/` exists as a directory in the project root +- **THEN** Cursor SHALL be detected + +#### Scenario: Cursor detected by .cursorrules file + +- **WHEN** `.cursorrules` exists as a file in the project root +- **THEN** Cursor SHALL be detected diff --git a/openspec/changes/archive/2026-05-12-add-codex-and-cursor-commands/tasks.md b/openspec/changes/archive/2026-05-12-add-codex-and-cursor-commands/tasks.md new file mode 100644 index 00000000..b895b0b4 --- /dev/null +++ b/openspec/changes/archive/2026-05-12-add-codex-and-cursor-commands/tasks.md @@ -0,0 +1,42 @@ +## 1. Tool registry updates + +- [x] 1.1 Add a `Codex` entry to the `TOOLS` array in `packages/cli/src/install/install.ts` with `detect: [{ type: "directory", path: ".codex" }, { type: "file", path: ".codex/config.toml" }]`, `installDir: ".agents"`, `skills: { path: "skills" }`, and no `commands` field. +- [x] 1.2 Add a `commands: { path: "commands/tskl" }` field to the existing Cursor entry in the `TOOLS` array. +- [x] 1.3 Add a short code comment above `ALL_KNOWN_TOOLS` (or `findToolByInstallDirectory`) documenting that registered tools take precedence over `AGENTS_FALLBACK` when both share an `installDir`, and that this is why Codex's `.agents` resolution wins over the fallback. + +## 2. Wizard / install summary labeling + +- [x] 2.1 Verify the existing wizard summary already names the tool by its registry `name` field — if so, no change needed (Codex will appear as "Codex" automatically once registered). Confirm by reading `packages/cli/src/wizard/steps/summary.ts` and the install summary printer. +- [x] 2.2 If the summary still uses generic "no tools detected, installing fallback" wording when Codex is the only detected tool, update the wording so detection of Codex produces "Codex detected" messaging and the fallback message only appears when no tools (including Codex) are detected. + +## 3. Tests — Codex detection + +- [x] 3.1 In `packages/cli/test/install.test.ts`, add `detectTools` scenarios for: `.codex/` directory present → returns Codex; `.codex/config.toml` file present → returns Codex; both signals present → still returns one Codex entry; `.codex/` plus `.claude/` → returns both. +- [x] 3.2 Add an `installForTool` (or `applyInstallPlan`) scenario that runs the install for Codex against a temp dir and asserts: `.agents/skills//SKILL.md` is written with content matching the embedded source, and no `.agents/commands/` directory or files are created. + +## 4. Tests — Cursor commands + +- [x] 4.1 Add a Cursor install scenario that asserts both `.cursor/skills//SKILL.md` and `.cursor/commands/tskl/.md` are written, with content matching the embedded source for each. +- [x] 4.2 Update any existing Cursor install test that asserted "no command files written for Cursor" — those expectations are now invalid; flip them to assert command files ARE written. + +## 5. Tests — disambiguation + +- [x] 5.1 Add a unit test for `findToolByInstallDirectory(".agents")` that confirms it returns the Codex descriptor (not `AGENTS_FALLBACK`) once Codex is registered. +- [x] 5.2 Add a regression scenario: a temp dir with no detected tools and no `.codex/` runs `applyInstallPlan` → fallback path is used, files land in `.agents/skills/`, manifest records `.agents` as the target. + +## 6. Spec sync + +- [x] 6.1 After implementation passes tests, run `pnpm openspec sync --change add-codex-and-cursor-commands` (or follow the project's spec-sync recipe) so `openspec/specs/cli-init/spec.md` reflects the new Codex requirements and the modified Cursor requirement. + +## 7. Quality checks + +- [x] 7.1 Run `pnpm typecheck` and fix any type errors. +- [x] 7.2 Run `pnpm lint` and fix any lint errors. +- [x] 7.3 Run `pnpm test --filter @taskless/cli` and ensure all tests pass. + +## 8. End-to-end verification (manual, one-time) + +- [x] 8.1 Build the CLI locally (`pnpm --filter @taskless/cli build`). +- [x] 8.2 In a temp directory with `.codex/` present, run `pnpm cli init --no-interactive` and confirm files land in `.agents/skills/taskless/SKILL.md` and the install summary names Codex as the target. +- [x] 8.3 If a Codex CLI install is available locally, launch `codex` in that temp directory and confirm the `taskless` skill is discoverable (e.g., `/skills` lists it, or invoking it by name works). _Skipped during automated apply — Codex CLI not present in this environment. Detection + file placement verified per spec; the documented Codex read path (`.agents/skills/`) is what we wrote to._ +- [x] 8.4 In a separate temp directory with `.cursor/` present, run `pnpm cli init --no-interactive` and confirm `.cursor/commands/tskl/.md` files exist alongside `.cursor/skills/`. From f2c57255d19ba7cca0eda0939da78321a6aee029 Mon Sep 17 00:00:00 2001 From: Jakob Heuser Date: Tue, 12 May 2026 10:39:23 -0700 Subject: [PATCH 4/5] fix(cli): Address PR #19 review feedback Updates from copilot review: - Correct misleading "Claude Code only" comment on command placement; commands are written for any tool descriptor that defines a path. - Rewrite self-contradictory comment in the Codex config.toml detection test to describe the actual assertion. - Replace stale `commands/taskless/` and `.claude/commands/taskless/` references in the cli-init spec with `commands/tskl/` and `.claude/commands/tskl/` so docs match the implementation. - Skip the AGENTS_FALLBACK append in `checkStaleness` when a detected tool already uses `.agents`; prevents duplicate Codex/fallback rows. - Assert the Codex `commands/tskl/` directory does not exist (via readdir) instead of checking a single hard-coded filename. Co-Authored-By: Claude Opus 4.7 (1M context) --- openspec/specs/cli-init/spec.md | 10 +++++----- packages/cli/src/install/install.ts | 13 ++++++++++--- packages/cli/test/install.test.ts | 27 +++++++++++++++------------ 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/openspec/specs/cli-init/spec.md b/openspec/specs/cli-init/spec.md index 74570f40..f22eafba 100644 --- a/openspec/specs/cli-init/spec.md +++ b/openspec/specs/cli-init/spec.md @@ -273,13 +273,13 @@ For each detected tool that supports skills, the CLI SHALL write SKILL.md files ### Requirement: Claude Code commands are placed from embedded source -For Claude Code specifically, the CLI SHALL also place command `.md` files from the embedded command source. Commands SHALL be placed in `.claude/commands/taskless/` with filenames matching the embedded source (prefix already stripped). +For Claude Code specifically, the CLI SHALL also place command `.md` files from the embedded command source. Commands SHALL be placed in `.claude/commands/tskl/` with filenames matching the embedded source (prefix already stripped). #### Scenario: Command file is placed from embedded source - **WHEN** the CLI installs for Claude Code -- **THEN** it SHALL write command files to `.claude/commands/taskless/.md` -- **AND** the command content SHALL be identical to the embedded source from `commands/taskless/` +- **THEN** it SHALL write command files to `.claude/commands/tskl/.md` +- **AND** the command content SHALL be identical to the embedded source from `commands/tskl/` #### Scenario: Command files are only placed for Claude Code @@ -304,7 +304,7 @@ For Cursor specifically, the CLI SHALL also place command `.md` files from the e ### Requirement: Skills are bundled into the CLI at build time -The CLI build SHALL embed all skill file content from `skills/` and all command file content from `commands/taskless/` into the compiled bundle using Vite's `import.meta.glob` with raw file imports. No runtime file reads or network fetches SHALL be used to access skill or command content. +The CLI build SHALL embed all skill file content from `skills/` and all command file content from `commands/tskl/` into the compiled bundle using Vite's `import.meta.glob` with raw file imports. No runtime file reads or network fetches SHALL be used to access skill or command content. #### Scenario: Embedded skills are available at runtime @@ -324,7 +324,7 @@ The CLI build SHALL embed all skill file content from `skills/` and all command #### Scenario: Build includes all commands from source directory - **WHEN** `pnpm build` is run in `packages/cli/` -- **THEN** every `.md` file under `commands/taskless/` SHALL be embedded in the output bundle +- **THEN** every `.md` file under `commands/tskl/` SHALL be embedded in the output bundle ### Requirement: Init respects the global working directory flag diff --git a/packages/cli/src/install/install.ts b/packages/cli/src/install/install.ts index 9366607d..33d2b4aa 100644 --- a/packages/cli/src/install/install.ts +++ b/packages/cli/src/install/install.ts @@ -282,7 +282,7 @@ export async function installForTool( installedSkills.push(skill.name); } - // Place commands (Claude Code only) + // Place commands for any tool descriptor that defines a commands path if (tool.commands) { const commandDirectory = join(cwd, tool.installDir, tool.commands.path); await mkdir(commandDirectory, { recursive: true }); @@ -513,11 +513,18 @@ export async function checkStaleness(cwd: string): Promise { const embedded = getEmbeddedSkills(); const tools = await detectTools(cwd); - // Include .agents/ fallback if the directory exists (from a previous fallback install) + // Include .agents/ fallback if the directory exists (from a previous + // fallback install) AND no detected tool already uses that installDir. + // Codex registers installDir `.agents`, so without this guard a Codex + // repo would surface duplicate/contradictory statuses for the same + // directory (once under Codex, once under AGENTS_FALLBACK). + const fallbackAlreadyCovered = tools.some( + (t) => t.installDir === AGENTS_FALLBACK.installDir + ); const fallbackExists = await stat(join(cwd, AGENTS_FALLBACK.installDir)) .then((s) => s.isDirectory()) .catch(() => false); - if (fallbackExists) { + if (fallbackExists && !fallbackAlreadyCovered) { tools.push(AGENTS_FALLBACK); } diff --git a/packages/cli/test/install.test.ts b/packages/cli/test/install.test.ts index 5cc4e724..9b80df40 100644 --- a/packages/cli/test/install.test.ts +++ b/packages/cli/test/install.test.ts @@ -1,4 +1,11 @@ -import { mkdir, mkdtemp, rm, readFile, writeFile } from "node:fs/promises"; +import { + mkdir, + mkdtemp, + readdir, + rm, + readFile, + writeFile, +} from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; @@ -94,9 +101,9 @@ describe("detectTools", () => { it("detects Codex via .codex/config.toml file", async () => { await mkdir(join(cwd, ".codex"), { recursive: true }); await writeFile(join(cwd, ".codex", "config.toml"), "", "utf8"); - // Remove the directory marker so only the file signal remains. - // (Both signals satisfy detection; this test asserts the file alone works - // by also keeping the directory — detectTools should still return one entry.) + // Both detection signals are present (directory + file); this asserts + // that having config.toml in place still yields a single Codex detection + // (no duplicates from multiple matching signals). const tools = await detectTools(cwd); expect(tools).toHaveLength(1); expect(tools[0]!.name).toBe("Codex"); @@ -189,14 +196,10 @@ describe("Codex install", () => { const embedded = skills.find((s) => s.name === firstSkill); expect(skillContent).toBe(embedded!.content); - const commandsDirectoryExists = await readFile( - join(cwd, ".agents", "commands", "tskl", "tskl.md"), - "utf8" - ).then( - () => true, - () => false - ); - expect(commandsDirectoryExists).toBe(false); + const commandsDirectoryEntries = await readdir( + join(cwd, ".agents", "commands", "tskl") + ).catch(() => null); + expect(commandsDirectoryEntries).toBeNull(); }); }); From c11aa1f68c260682576a0292c5b877ed493cfd97 Mon Sep 17 00:00:00 2001 From: Jakob Heuser Date: Tue, 12 May 2026 10:55:14 -0700 Subject: [PATCH 5/5] docs(cli): Align stale rules->rule references in docstrings and specs The CLI subcommand was renamed from `rules` to `rule` in v0.7.0, but several JSDoc comments and active openspec docs still referenced the plural form. Align them with the implementation: - Schema docstrings in `src/schemas/rules-*.ts`, `src/schemas/ast-grep-rule.ts`, and `src/rules/verify-examples.ts` now reference `taskless rule `. - Active specs (`cli-check`, `cli-help`, `cli`, `cli-rules`, `cli-taskless-bootstrap`) updated to use the singular form. The intentional back-compat references documenting the rename (`cli-rules/spec.md` lines 13, 22, 28, 175) are preserved. No behavior change. Archived openspec changes and CHANGELOG entries are left as historical record. Co-Authored-By: Claude Opus 4.7 (1M context) --- openspec/specs/cli-check/spec.md | 2 +- openspec/specs/cli-help/spec.md | 4 ++-- openspec/specs/cli-rules/spec.md | 4 ++-- openspec/specs/cli-taskless-bootstrap/spec.md | 4 ++-- openspec/specs/cli/spec.md | 8 ++++---- packages/cli/src/rules/verify-examples.ts | 2 +- packages/cli/src/schemas/ast-grep-rule.ts | 2 +- packages/cli/src/schemas/rules-create.ts | 6 +++--- packages/cli/src/schemas/rules-improve.ts | 6 +++--- packages/cli/src/schemas/rules-meta.ts | 4 ++-- packages/cli/src/schemas/rules-verify.ts | 2 +- 11 files changed, 22 insertions(+), 22 deletions(-) diff --git a/openspec/specs/cli-check/spec.md b/openspec/specs/cli-check/spec.md index 599e3975..70003d7d 100644 --- a/openspec/specs/cli-check/spec.md +++ b/openspec/specs/cli-check/spec.md @@ -18,7 +18,7 @@ The `check` command SHALL NOT require `.taskless/taskless.json` to exist. The co #### Scenario: Check exits cleanly with no .taskless/ directory - **WHEN** a user runs `taskless check` in a directory without a `.taskless/` directory -- **THEN** the CLI SHALL print a message: "No rules configured. Create one with `taskless rules create`." +- **THEN** the CLI SHALL print a message: "No rules configured. Create one with `taskless rule create`." - **AND** the CLI SHALL exit with code 0 #### Scenario: Check exits cleanly with empty rules directory diff --git a/openspec/specs/cli-help/spec.md b/openspec/specs/cli-help/spec.md index 0ac482de..a4a3924a 100644 --- a/openspec/specs/cli-help/spec.md +++ b/openspec/specs/cli-help/spec.md @@ -62,8 +62,8 @@ Help text files SHALL be located at `packages/cli/src/help/` as plain `.txt` fil #### Scenario: Help file naming convention -- **WHEN** a help file is created for the `rules create` subcommand -- **THEN** the file SHALL be named `rules-create.txt` in `packages/cli/src/help/` +- **WHEN** a help file is created for the `rule create` subcommand +- **THEN** the file SHALL be named `rule-create.txt` in `packages/cli/src/help/` ### Requirement: Help text files follow a consistent format diff --git a/openspec/specs/cli-rules/spec.md b/openspec/specs/cli-rules/spec.md index 301a588c..e6794a6b 100644 --- a/openspec/specs/cli-rules/spec.md +++ b/openspec/specs/cli-rules/spec.md @@ -60,7 +60,7 @@ The API calls for rule generation (`POST /cli/api/request` and `GET /cli/api/req #### Scenario: Stub implementation returns an error -- **WHEN** `rules create` is run against the stub network layer +- **WHEN** `rule create` is run against the stub network layer - **THEN** the stub SHALL return an error indicating rule generation is not yet available #### Scenario: Interface is swappable @@ -166,7 +166,7 @@ The generated JSON Schema file SHALL be importable by the CLI bundle via Vite. T #### Scenario: Schema imported in verify command -- **WHEN** the `rules verify` command needs the ast-grep schema +- **WHEN** the `rule verify` command needs the ast-grep schema - **THEN** it SHALL import the schema from `../generated/ast-grep-rule-schema.json` - **AND** the schema object SHALL be available synchronously at runtime diff --git a/openspec/specs/cli-taskless-bootstrap/spec.md b/openspec/specs/cli-taskless-bootstrap/spec.md index 06e2b38c..40306c4f 100644 --- a/openspec/specs/cli-taskless-bootstrap/spec.md +++ b/openspec/specs/cli-taskless-bootstrap/spec.md @@ -82,7 +82,7 @@ These utilities SHALL NOT hardcode Taskless-specific entries — the migrations ### Requirement: Bootstrap is called from all write paths -The `ensureTasklessDirectory()` function SHALL be called from: `writeRuleFile()`, `writeRuleTestFile()`, `generateSgConfig()`, and the `rules verify` command. This ensures `.taskless/` is always properly initialized and up-to-date before any file writes. +The `ensureTasklessDirectory()` function SHALL be called from: `writeRuleFile()`, `writeRuleTestFile()`, `generateSgConfig()`, and the `rule verify` command. This ensures `.taskless/` is always properly initialized and up-to-date before any file writes. #### Scenario: Rule file write triggers bootstrap @@ -91,7 +91,7 @@ The `ensureTasklessDirectory()` function SHALL be called from: `writeRuleFile()` #### Scenario: Verify command triggers bootstrap -- **WHEN** `taskless rules verify` runs and needs to generate `sgconfig.yml` +- **WHEN** `taskless rule verify` runs and needs to generate `sgconfig.yml` - **THEN** `ensureTasklessDirectory()` SHALL run as part of the `generateSgConfig()` call ### Requirement: Migration 2 initializes an empty install object diff --git a/openspec/specs/cli/spec.md b/openspec/specs/cli/spec.md index 5e9e5521..2a931073 100644 --- a/openspec/specs/cli/spec.md +++ b/openspec/specs/cli/spec.md @@ -144,10 +144,10 @@ The CLI entry point SHALL use citty to define a main command with subcommand sup - **WHEN** a user runs `taskless auth` - **THEN** the CLI SHALL route to the auth subcommand group -#### Scenario: Rules subcommand group is registered +#### Scenario: Rule subcommand group is registered -- **WHEN** a user runs `taskless rules` -- **THEN** the CLI SHALL route to the rules subcommand group +- **WHEN** a user runs `taskless rule` +- **THEN** the CLI SHALL route to the rule subcommand group #### Scenario: Help subcommand is registered @@ -160,7 +160,7 @@ The CLI SHALL proactively create and maintain a `.taskless/.gitignore` file that #### Scenario: .gitignore is created when .taskless/ is first written to -- **WHEN** the CLI creates any file in `.taskless/` (e.g., during `auth login`, `rules create`, or `check`) +- **WHEN** the CLI creates any file in `.taskless/` (e.g., during `auth login`, `rule create`, or `check`) - **AND** `.taskless/.gitignore` does not exist - **THEN** the CLI SHALL create `.taskless/.gitignore` containing `.env.local.json` and `sgconfig.yml` diff --git a/packages/cli/src/rules/verify-examples.ts b/packages/cli/src/rules/verify-examples.ts index 9a949b6f..045423e8 100644 --- a/packages/cli/src/rules/verify-examples.ts +++ b/packages/cli/src/rules/verify-examples.ts @@ -1,5 +1,5 @@ /** - * Curated annotated examples for `rules verify --schema` output. + * Curated annotated examples for `rule verify --schema` output. * These teach agents how to write valid ast-grep rules. */ export const RULE_EXAMPLES = [ diff --git a/packages/cli/src/schemas/ast-grep-rule.ts b/packages/cli/src/schemas/ast-grep-rule.ts index 296d4a5c..bd0f58aa 100644 --- a/packages/cli/src/schemas/ast-grep-rule.ts +++ b/packages/cli/src/schemas/ast-grep-rule.ts @@ -8,7 +8,7 @@ import astGrepSchema from "../generated/ast-grep-rule-schema.json"; * validation coverage matching the upstream spec. * * The raw JSON Schema is also embedded for agent consumption via - * `rules verify --schema`. + * `rule verify --schema`. */ // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument export const astGrepRuleSchema = z.fromJSONSchema(astGrepSchema as any); diff --git a/packages/cli/src/schemas/rules-create.ts b/packages/cli/src/schemas/rules-create.ts index 7e97cc91..237862a1 100644 --- a/packages/cli/src/schemas/rules-create.ts +++ b/packages/cli/src/schemas/rules-create.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -/** Input schema for `taskless rules create --from` JSON file */ +/** Input schema for `taskless rule create --from` JSON file */ export const inputSchema = z.object({ prompt: z .string() @@ -17,7 +17,7 @@ export const inputSchema = z.object({ .describe("Examples of incorrect code that should fail the rule"), }); -/** Output schema for `taskless rules create --json` on success */ +/** Output schema for `taskless rule create --json` on success */ export const outputSchema = z.object({ success: z.literal(true), ruleId: z.string().describe("UUID of the generated rule job"), @@ -25,7 +25,7 @@ export const outputSchema = z.object({ files: z.array(z.string()).describe("File paths that were written"), }); -/** Error schema for `taskless rules create --json` on failure */ +/** Error schema for `taskless rule create --json` on failure */ export const errorSchema = z.object({ error: z.string().describe("Error message"), }); diff --git a/packages/cli/src/schemas/rules-improve.ts b/packages/cli/src/schemas/rules-improve.ts index a1ca3d5e..26c6e458 100644 --- a/packages/cli/src/schemas/rules-improve.ts +++ b/packages/cli/src/schemas/rules-improve.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -/** Input schema for `taskless rules improve --from` JSON file */ +/** Input schema for `taskless rule improve --from` JSON file */ export const inputSchema = z.object({ ruleId: z .string() @@ -23,7 +23,7 @@ export const inputSchema = z.object({ .describe("Reference files to include as context"), }); -/** Output schema for `taskless rules improve --json` on success */ +/** Output schema for `taskless rule improve --json` on success */ export const outputSchema = z.object({ success: z.literal(true), requestId: z.string().describe("The request ID for polling status"), @@ -31,7 +31,7 @@ export const outputSchema = z.object({ files: z.array(z.string()).describe("File paths that were written"), }); -/** Error schema for `taskless rules improve --json` on failure */ +/** Error schema for `taskless rule improve --json` on failure */ export const errorSchema = z.object({ error: z.string().describe("Error message"), }); diff --git a/packages/cli/src/schemas/rules-meta.ts b/packages/cli/src/schemas/rules-meta.ts index de38ba41..2d2f5f5b 100644 --- a/packages/cli/src/schemas/rules-meta.ts +++ b/packages/cli/src/schemas/rules-meta.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -/** Output schema for `taskless rules meta --json` on success */ +/** Output schema for `taskless rule meta --json` on success */ export const outputSchema = z.object({ id: z.string().describe("Rule ID"), ticketId: z.string().describe("Ticket ID that produced this rule"), @@ -12,7 +12,7 @@ export const outputSchema = z.object({ schemaVersion: z.string().describe("Sidecar schema version"), }); -/** Error schema for `taskless rules meta --json` on failure */ +/** Error schema for `taskless rule meta --json` on failure */ export const errorSchema = z.object({ error: z.string().describe("Error message"), }); diff --git a/packages/cli/src/schemas/rules-verify.ts b/packages/cli/src/schemas/rules-verify.ts index a68f1688..39187aab 100644 --- a/packages/cli/src/schemas/rules-verify.ts +++ b/packages/cli/src/schemas/rules-verify.ts @@ -29,7 +29,7 @@ export const schemaOutputSchema = z.object({ .describe("Curated annotated rule examples"), }); -// --- Verify mode output (rules verify --json) --- +// --- Verify mode output (rule verify --json) --- const layerResultSchema = z.object({ valid: z.boolean(),