feat(cli): Canonical install store with per-tool reference stubs#21
Merged
Conversation
Add the OpenSpec change for moving skill/command installation to a single canonical .taskless/ store with thin reference stubs in each tool directory, replacing per-tool full copies. Also allow the WebSearch tool in .claude/settings.json, used while researching cross-tool skill discovery behavior for this change. Refs OSS-8 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduce an InstallMode (canonical | reference) recorded per target in .taskless/taskless.json install state. A canonical target stores full skill/command content; a reference target stores thin stubs. readInstallState normalizes a missing mode to canonical, so manifests written before this field round-trip unchanged. computeInstallDiff entries now carry the effective mode so removal logic can respect it without a second state lookup. No write or cleanup logic acts on the mode yet — this lands the schema ahead of the install-engine changes that depend on it. Refs OSS-8 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add install/canonical.ts: writeCanonicalSkill/writeCanonicalCommand write full content verbatim into the .taskless/ store; buildSkillStub/ buildCommandStub produce thin reference files that carry name and description frontmatter and delegate to the canonical path. Command stubs preserve the canonical argument-hint and pass $ARGUMENTS through. stubFrontmatterDrifted lets update leave a still-matching stub untouched instead of regenerating it. These helpers are standalone — the install plan wires them in next. Refs OSS-8 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the routed .agents/ design with a uniform model: every selected tool directory (.claude/.cursor/.opencode/.agents) is a peer target and receives its own reference stub. No directory is special-cased or routed. The wizard location step is reframed as a fixed tool-selection multiselect. The migration converts existing full per-tool copies into stubs rather than removing them. Refs OSS-8 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the per-tool full-copy install model with a resolved install-plan: one canonical .taskless/ target holding full content, plus one reference stub target per selected tool directory. applyInstallPlan branches on each target's mode — canonical targets get verbatim content, reference targets get a drift-checked stub that is never overwritten with full content. Removals are scoped to each diff entry's own directory, so no target's cleanup can reach the canonical store; the destructive rm -rf glob is gone. The wizard's location step is reframed as a fixed tool-selection multiselect. Refs OSS-8 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mark every reference stub with a metadata.type: shim frontmatter marker. applyInstallPlan now rewrites a reference file unless it is already a current, non-drifted shim stub — converging full copies left by older installs, symlinked entries, and drifted stubs on the next init/update. This replaces the planned .taskless/ convergence migration: a migration would import install.ts (an import cycle through state.ts) and would run on every ensureTasklessDirectory call, including taskless check. The manifest mode field is additive, so no schema migration is needed. Also add a migration version-matrix test that seeds .taskless/ at each prior schema version and asserts a clean forward-migration. Refs OSS-8 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Update the init/update help recipes, the CLI README, and the .taskless/README.md Files section to describe the canonical .taskless/ store plus per-tool reference stubs. Add a changeset for the release. Refs OSS-8 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stub frontmatter now records metadata.version alongside the metadata.type: shim marker, copied from the canonical content. The drift check compares version too, so a canonical version bump regenerates every stub on the next update — keeping the stub's version reference accurate rather than frozen at install time. Refs OSS-8 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extract the pure detection-to-choices logic from promptLocations into a testable locationChoices() function, and unit-test it: every shim target is offered, the canonical .taskless/ store is never selectable, detected tools are pre-checked, and .agents/ is the default when nothing is detected. The interactive multiselect itself still needs a TTY and stays covered only by the mocked wizard-integration tests; this closes the gap on the detection mapping that feeds it. Refs OSS-8 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sync the cli-canonical-install delta into openspec/specs/cli-init/spec.md (5 added, 9 modified requirements) so the main spec reflects the shipped canonical-store-plus-stub install model, and move the change to openspec/changes/archive/2026-05-17-cli-canonical-install/. Refs OSS-8 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…uirement The "Re-install computes a diff against the previous manifest" requirement lacked a SHALL/MUST keyword, failing openspec spec validation. Reword to make the diff computation and confirmation normative. Pre-existing error, surfaced while syncing OSS-8. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
Replaces the per-tool full-copy install model with a single canonical store (.taskless/skills/, .taskless/commands/) plus thin reference stubs in each enabled tool directory. This fixes a destructive-cleanup bug where Codex's .agents/ target's rm -rf could wipe content other targets pointed into, and ends the N-identical-copies drift problem.
Changes:
- Introduces canonical store under
.taskless/with newcanonical.ts(writes + stub builders + shim detection) and adds a per-targetmode(canonical|reference) to the manifest (legacy entries default tocanonical). - Refactors
applyInstallPlanto be mode-aware and self-healing: rewrites any reference file that isn't a current shim stub (full copies, symlinks, or drifted stubs), and scopes manifest-driven cleanup to each target's own directory so it can never reach.taskless/. - Reframes the wizard's location step as a tool-selection multiselect over a fixed catalog (
SHIM_TARGETS), removesAGENTS_FALLBACKplumbing andinstallForTool, and updates init/update non-interactive flow and help text accordingly.
Reviewed changes
Copilot reviewed 26 out of 26 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| packages/cli/src/install/install.ts | Adds canonical/reference plan model, mode-aware writes, self-healing reference rewrite, removes glob cleanup and AGENTS_FALLBACK |
| packages/cli/src/install/canonical.ts | New module: canonical write helpers, skill/command stub builders, shim detection, drift checks |
| packages/cli/src/install/state.ts | Adds InstallMode, plumbs mode through state read/write and diff |
| packages/cli/src/filesystem/migrate.ts | Adds optional mode field to manifest target type |
| packages/cli/src/filesystem/migrations/0001-init.ts | Adds README entries for skills/ and commands/ canonical dirs |
| packages/cli/src/wizard/steps/locations.ts | Refactors to pure locationChoices over SHIM_TARGETS; new prompt wording |
| packages/cli/src/wizard/index.ts | Switches to buildInstallPlan / planToStateTargets; drops TOOL_BY_INSTALL_DIR |
| packages/cli/src/commands/init.ts | Non-interactive flow uses new plan model + per-target summary with mode-aware noun |
| packages/cli/src/help/init.txt, update.txt | Doc updates for canonical-plus-stub model |
| packages/cli/README.md, .taskless/README.md | User-facing docs for new layout |
| packages/cli/test/* (apply-install-plan, install, install-state, migrate-install, canonical-store, wizard-steps) | Rewrites/adds tests for canonical writes, stubs, drift, symlink/full-copy convergence, mode round-trip, migration matrix |
| .changeset/canonical-install.md | Minor changeset documenting the new install model |
| openspec/changes/archive/2026-05-17-cli-canonical-install/* + openspec/specs/cli-init/spec.md | Archived change proposal/design/tasks and synced spec |
| .claude/settings.json | Adds WebSearch permission (unrelated) |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
… output
init-no-interactive.test.ts and cli.test.ts exec the built CLI bundle
and asserted the old per-tool summary line ("<Tool>: installed"). The
canonical-install model prints "<Tool> (<dir>/): wrote N skill ..."
instead. These were missed locally because the tests ran against a
stale dist/ build; CI's fresh build caught them.
Refs OSS-8
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Replace the per-tool full-copy install model with a single canonical store plus thin reference stubs.
taskless init/updatenow writes the skill and command content exactly once — to.taskless/skills/<name>/SKILL.mdand.taskless/commands/tskl/<name>.md— and drops a small delegating stub into each enabled tool directory (.claude/,.cursor/,.opencode/,.agents/).Why
The old model wrote a full identical copy of every skill into each detected tool's directory. N tools meant N copies that drift and churn PR diffs. A customer standardizing on a shared skill hit the deeper flaw: the model conflated where content lives with where tools read it. Codex's install directory is
.agents, so the Codex target'srm -rfcleanup destroyed the directory other targets pointed into — symlinks brokeupdate, and wrapper files were clobbered with full copies.Putting the canonical content in
.taskless/— a directory no tool target ever installs into or cleans up — makes that whole class of bug structurally impossible. Each tool directory holds only a featherweight stub, so there is one source of truth and no drift.What changed
.taskless/skills/and.taskless/commands/.name/descriptionfrontmatter, ametadata: { type: shim, version }marker, and a body that delegates to the canonical file..claude/and.cursor/also get atsklcommand stub.mode(canonical|reference) in.taskless/taskless.json— additive and backward-compatible (absent reads ascanonical).applyInstallPlan: rewrites any reference file that is not a current shim stub — a full copy from an older install, a symlink, or a drifted stub — converging stale layouts on the nextinit/update. The destructiverm -rfglob cleanup is gone.Approach notes
applyInstallPlanrather than a.taskless/schema migration. A migration would form an import cycle throughinstall.ts/state.ts/migrate.tsand would run on everyensureTasklessDirectorycall (includingtaskless check).Review focus
packages/cli/src/install/install.ts— the install-plan model andapplyInstallPlan.packages/cli/src/install/canonical.ts— canonical writes, stub builders, drift/shim detection.Full design and the synced
cli-initspec are inopenspec/changes/archive/2026-05-17-cli-canonical-install/.A redundant-
info/checkStalenessfollow-up is filed as OSS-9.Refs OSS-8