Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/harness-picker-rows.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@taskless/cli": patch
---

The setup wizard lists every supported harness by name (Claude Code, OpenCode, Cursor, Codex, Gemini CLI, Crush, Devin, and other Agent Skills harnesses), so you no longer need to know which directory your harness reads. Harnesses found in the project are listed first and pre-checked; harnesses found on this machine come next, unchecked. Installing stays project-local. Checking Codex now also writes its permission rule, even in a project without `.codex/`. Devin is new: it installs into `.devin/` when the project has one, and `.agents/` otherwise.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-10-20
58 changes: 58 additions & 0 deletions openspec/changes/archive/2026-10-20-harness-picker-rows/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
## Context

The wizard's tool step (`wizard/steps/locations.ts`) offers one row per install directory from `SHIM_TARGETS`. The harness catalog (`TOOLS` in `install/install.ts`) is used only for detection, and its harnesses surface in the picker only as names on the `.agents/` row, and only when detected. The install plan, the manifest (`install.targets`) and the summary are all keyed by directory, and that part works: it is the picker that hides harnesses.

## Goals / Non-Goals

**Goals:**

- Every catalog harness is a visible, checkable row whatever is detected.
- A harness used on this machine but not yet configured in this project is visible near the top, unchecked.
- Checking a harness runs that harness's own setup (today, only Codex has any beyond its directory).

**Non-Goals:**

- Writing anything outside the project because of machine detection. User-level skill directories stay untouched.
- Changing the manifest, the install plan, `update`, or `--no-interactive` selection, all of which stay keyed by directory.
- Preventing a harness from loading the skill twice when it reads another harness's directory (Cursor and OpenCode read `.claude/skills`). That is the harness's choice.

## Decisions

**Rows are harnesses; the plan stays directories.** Each row's value is a harness name (or `agents` for the generic row). A pure `resolveSelection(values, projectDetected)` maps checked rows to the directories to install into (deduplicated, in `SHIM_TARGETS` order) and the checked harness names. `buildInstallPlan` and the manifest are unchanged, so two checked rows sharing `.agents/` still produce one target.

_Alternative:_ record harnesses in the manifest. Rejected: `update` and `--no-interactive` would need a second source of truth beside detection, for no behavior the user asked for.

**A harness may prefer its own directory when the project already has it.** `ToolDescriptor` gains `ownDir`, used instead of `installDir` when the harness is detected in the project. Only Devin sets it: `installDir: ".agents"`, `ownDir: ".devin"`. Devin's docs show project skills in `.devin/skills/` and also list `.agents/skills/`; a project that already has `.devin/` gets the skill there, and Taskless never creates `.devin/` in a project without it. Since Devin is detected by `.devin/`, "detected in project" and "`.devin/` exists" coincide. `SHIM_TARGETS` gains `{ dir: ".devin", label: "Devin" }` so the plan and summary know the directory.

**Machine detection is a per-harness function of home and environment.** `ToolDescriptor.machine({ home, env })` returns candidate directories; the harness is machine-detected when any is a directory. Functions rather than signal records, because each override has a different shape (`CLAUDE_CONFIG_DIR` replaces `~/.claude`; `GEMINI_CLI_HOME` replaces the home that `.gemini` sits in):

| Harness | Directory | Override |
| ----------- | ----------------------------------------- | ------------------------------------------ |
| Claude Code | `~/.claude` | `CLAUDE_CONFIG_DIR` |
| Codex | `~/.codex` | `CODEX_HOME` |
| Cursor | `~/.cursor` | `CURSOR_CONFIG_DIR` |
| OpenCode | `~/.config/opencode` | `OPENCODE_CONFIG_DIR` |
| Gemini CLI | `~/.gemini` | `GEMINI_CLI_HOME` (parent of `.gemini`) |
| Crush | `~/.config/crush`, `~/.local/share/crush` | `CRUSH_GLOBAL_CONFIG`, `CRUSH_GLOBAL_DATA` |
| Devin | `~/.config/devin` | none documented |

`home` and `env` are injected (the wizard already carries `GlobalTrustEnvironment`), so tests use a temporary directory. Windows-only locations (`%APPDATA%\devin`) are not checked; a miss there only leaves the row in "Other harnesses".

**Claude Code has no `.agents/` row.** The issue proposed a "Claude Multi-Harness (`.agents`)" row for machines with Claude Code and a project with no Claude files. Claude Code reads `AGENTS.md` in that case but never anything under `.agents/`, so a skill there is invisible to it. Claude Code always installs into `.claude/`, and `readsAgentSkills` is unchanged.

**Sections, pre-check, hints.** `locationChoices(projectDetected, machineDetected, manifestDirectories)` stays pure and returns `{ groups, initialValues }`:

- Group order: `Detected in this project`, `Detected on this machine`, `Other harnesses`. A harness detected in both is in the first. The generic row is always in `Other harnesses`. Empty groups are omitted. Rows within a group follow catalog order.
- Pre-checked: every project-detected row; plus, for each manifest-recorded directory no pre-checked row already covers, the row that installs into it (the generic row for `.agents/`). When that leaves nothing checked, the generic row is checked, which is today's `.agents/` default.
- The section header says where a harness was detected, so the `detected`/`not detected` hints go. The only hint is `installed`, on a pre-checked row whose directory the manifest records.
- Labels name the directory the row installs into: `Codex (.agents/)`, `Devin (.devin/)` or `Devin (.agents/)`, `Other Agent Skills harnesses (.agents/)`.

`groupMultiselect` with `selectableGroups: false` renders headers that are not toggles. The empty-selection loop is unchanged.

**Codex's rule follows the selection.** `SyncPermissionsOptions.codexDetected` becomes `codex`: in the wizard, whether the Codex row was checked; in `--no-interactive`, whether Codex is detected in the project. `harnessesFor` adds Codex whenever `codex` is true, since checking Codex always selects `.agents/`. Once written, `.codex/rules/taskless.rules` makes `.codex/` exist, so later `--no-interactive` runs keep the rule.

## Risks / Trade-offs

- [The picker is longer: eight rows instead of four] → Sections put the relevant rows first; the remaining rows are below.
- [An override env var points at a file, not a directory] → Treated as not detected; it only affects which section the row is in.
- [A user checks Codex in a project without `.codex/`, so `.codex/rules/` is created] → This is the requested behavior; the summary already lists the file before confirming.
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
## Why

A Codex user on 0.13 did not find Codex in the tool list and picked `.agents` only because they happened to know Codex reads it; OpsIn said the same earlier ("I didn't see it by default so I didn't think it's available"). Users know which harness they run, not which skill directory it reads. `80f3934a` merged the Codex row into one `.agents/` row labelled with detected harnesses, which falls back to "Agent Skills" when nothing is detected and names no harness at all (#553).

## What Changes

- The wizard's tool step offers **one row per harness**: Claude Code, Codex, Cursor, OpenCode, Gemini CLI, Crush, Devin, plus a generic "Other Agent Skills harnesses" row for `.agents/`. Several rows can share a directory; the install plan still has one target per directory.
- The rows are grouped with `groupMultiselect` into **Detected in this project**, **Detected on this machine**, and **Other harnesses**. Project-detected rows are pre-checked; machine-detected rows are listed unchecked; a harness found in both places appears once, in the project section.
- **Machine detection** checks each harness's documented home-directory location, honouring its documented environment override. It changes only which section a row appears in. Installing stays project-local: nothing is written outside the project because of it, and `update`, the manifest and the version pin behave as before.
- **Devin** joins the catalog: detected by `.devin/` in the project and `~/.config/devin/` on the machine. Its docs place project skills in `.devin/skills`, and it also reads `.agents/skills`, so it installs into `.devin/` when the project has `.devin/` and into `.agents/` otherwise.
- **Claude Code always installs into `.claude/`.** Claude Code never reads `.agents/` (code.claude.com/docs/en/memory: "Not read: … anything under a `.agents/` directory"), so the multi-harness Claude row the issue floated is not offered.
- **Checking the Codex row writes Codex's permission rule.** Today `.codex/rules/taskless.rules` is written only when `.agents/` is picked and `.codex/` already exists. `--no-interactive` keeps using project detection, which the written rules file itself satisfies on later runs.
- `agentsRowLabel` and the "one row per directory" reasoning at `SHIM_TARGETS` are removed; the comment records why a row per harness is what makes a harness discoverable.

## Capabilities

### New Capabilities

None.

### Modified Capabilities

- `cli-init`: the location step offers a row per harness in three sections; machine-level detection; Devin detection and install directory; Codex's permission rule follows the Codex row.

## Impact

- `packages/cli/src/install/install.ts`: harness catalog gains home signals and Devin; `SHIM_TARGETS` gains `.devin`; comment at the old line 179 rewritten.
- `packages/cli/src/wizard/steps/locations.ts`: rows, sections, pre-check and hints; `promptLocations` returns directories and checked harnesses.
- `packages/cli/src/wizard/index.ts`, `packages/cli/src/commands/init.ts`, `packages/cli/src/install/permissions.ts`: Codex rule driven by the selection.
- Tests: `test/wizard-steps.test.ts`, `test/install.test.ts`, permissions tests.
- Delivery shape: single PR. The change is one reviewable diff, safe on its own, with a `patch` changeset (pre-1.0, no consumer must react).
Loading
Loading