Repository navigation
Conversation
Persists the agreed command-surface redesign (explicit tool/subtype dispatch + broadcast groups, sourceDir wildcard, breaking 1.0.0) with user personas and journeys, as the implementation reference.
resolveSourceDir() exposes which precedence tier (override/explicit/ wildcard/default) resolved a tool's source directory, needed for broadcast-command dry-run output; getSourceDir() becomes a thin wrapper over it. cli-groups.ts centralizes the CLI-group-name-to- adapter-tool mapping and defines the broadcast subtype groups (skills/agents/rules/commands/md/prompts) used by upcoming top-level commands.
registerToolGroup() generates ais <tool>'s full command set (subtype subgroups, install/add-all/import, hidden add/remove stubs) from adapterRegistry instead of ~30 hand-written, drifting blocks. Also fixes import's subtype auto-detection to match on adapter.mode/ fileSuffixes instead of bare fs.pathExists, which mis-detected nested targetDirs (e.g. cline-commands inside cline-rules' directory). As a side effect, the five adapters that had no real CLI command (cline-commands, cline-agents, windsurf-commands, opencode-rules, codex-agents) are now fully wired. Flat `<tool> add`/`<tool> remove` no longer guess a default subtype; they're hidden stubs that redirect to the explicit subtype command.
…stry TOOL_CLI_GROUPS/UNWIRED_ADAPTER_NAMES existed only to describe how the old hand-written per-tool command blocks diverged from what adapterRegistry knew about; registerToolGroup removed that divergence, so the hand-maintained table is gone too. Completion now includes the five adapters that previously had no real CLI command, and no longer suggests the removed flat <tool> add/remove shortcuts.
buildTemplateSourceDirConfig used to write one explicit sourceDir line per adapter, so a fresh repo started as a 29-line hand-enumerated map even though the wildcard resolver has supported "*" fallback all along. Subtypes whose content is tool-agnostic (skills/agents/rules/ commands/prompts) now collapse into a single "*" entry when ≥2 tools share them; per-tool file subtypes (md, instructions, workflows, ...) stay explicit since their content differs by tool. createDirs follows the generated config instead of enumerating adapters separately.
Adds add/remove/list for each BROADCAST_GROUPS entry (skills, agents, rules, commands, md, prompts), so one skill can be pushed to several tools in a single command instead of repeating <tool> skills add per tool. --tools/--all is mandatory (no default-to-everything), unknown tool names get a did-you-mean suggestion, and a tool with no adapter in the group but a known equivalent concept (e.g. copilot's "instructions" for the rules group) gets pointed at the exact replacement command via CONCEPT_HINTS. --dry-run's origin column surfaces which sourceDir precedence tier resolved each tool's source directory (resolveSourceDir), and --strict turns "repo has nothing at that path" from a warning into a failure.
BREAKING CHANGE: top-level `ais add`/`ais remove` (guessed-tool dispatch) and `ais user install` are removed with no replacement shim. Use the explicit `ais <tool> <subtype> add/remove` or the `ais <subtype> add/remove --tools <list>` broadcast commands instead; `ais user install` is now `ais install -g`. `status`'s JSON output no longer has a `project.inferredMode` field. Deletes the inference layer that caused most of the silent-failure bugs fixed in the registry-driven dispatch work: inferDefaultMode, requireExplicitMode, resolveSingleAdapterForMode, getToolsForInstallMode, TOOLS_WITH_DEFAULT_ADD_SUBTYPE, getRegisteredTools, resolveCopilotAliasFromConfig, and findAdapterForAlias (plus its private helpers and dedicated tests) — all dead once the commands that called them are gone. `ais install` no longer inspects project state to guess which tools to install for; it just installs every adapter's already-configured entries (each adapter already no-ops cleanly when it has none) and gains `-g/--global` (folding in the deleted `ais user install`) and `--json`. `ais add-all --tools` now hard-errors on an unknown tool name instead of warning and skipping, matching the broadcast groups' severity, and resolves CLI-group aliases (agy/droid) through cli-groups.ts instead of a second hand-maintained table. Top-level `--help` collapses the 30 registry-driven tool groups into one index line so the core commands and broadcast groups aren't buried.
Documents the breaking changes from the mode-inference removal, registry-driven tool/broadcast commands, and install/help simplification.
…ll noise - repoHasEntry/detectImportAdapter's hybrid branch was a bare pathExists on the un-suffixed path, missing every cursor-rules entry stored as "<name>.mdc"/"<name>.md" — broadcast add/dry-run and `ais <tool> import` now check hybridFileSuffixes the same way createMultiSuffixResolver does. Also fixes file-mode lookups when the caller already passed a suffixed name. - Stub redirects (`ais <tool> add`) no longer suggest a nonexistent broadcast command for subtypes with no group (e.g. copilot's "instructions"). - `ais install` (project scope) no longer prints one "No X Y found" line per adapter (~80 lines); it aggregates into a single summary line. - Shell completion no longer offers the deleted `add`/`remove`/`user` top-level commands, and now covers the 6 new `ais <subtype>` broadcast groups (add/remove/list, plus entry-name completion after add). - RESERVED_TOP_LEVEL_WORDS now includes git/completion/_complete. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ort name passthrough - repoHasEntry now delegates to adapter.resolveSource when present (the same function real add() calls via GitRepoSource) instead of a hand-rolled mode/suffix reimplementation. Fixes three related preview/dry-run drift bugs: false "will-add" for ~30 file-mode adapters that have no resolveSource and only match an exact un-suffixed name; false "skip: not-in-repo" for file-mode adapters using createMultiSuffixResolver (e.g. cline-rules directory entries); and dry-run ignoring sourceFile/ sourceDir repo config overrides (claude-md, codebuddy-md). - detectImportAdapter now returns the resolved on-disk name alongside the adapter, and `ais <tool> import <name>` passes that through instead of the user's raw input — detection matching a suffixed file (e.g. "foo" -> "foo.mdc") used to still hand the unsuffixed name to importEntry, which has no suffix resolution of its own and would fail with a confusing "File ... does not exist" after printing "Detected rules: foo". - buildAddPreviewRow's "already configured" check now also matches suffixed config keys (manager.add() stores file/hybrid entries under their suffix-resolved name), so re-running dry-run after a real add reports "skip: already-configured" instead of "will-add" forever. - Broadcast groups' remove/list commands get real `rm`/`ls` aliases — completion already advertised them (added in the previous commit) but Commander had no matching alias registered. - `ais init --only/--exclude` now accepts CLI group names (e.g. "agy"), not just the adapter registry's internal tool field, matching --tools elsewhere. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…resolveSource errors isEntryConfigured and runList's config-section lookup keyed by adapter.tool/subtype, which is wrong for agents-md (tool: 'agents-md', configPath: ['agentsMd', 'file']) — its manifest entries are written under configPath, so already-configured detection and `ais md list` always missed it. repoHasEntry also collapsed every resolveSource rejection into a bare "not found", including errors that mean the entry exists but needs disambiguation (copilot's duplicate-suffix error) or is invalid (agents-md's non-AGENTS.md validation error) — masking an actionable message behind a misleading "not in repository". Non-"not found" errors now propagate through to dry-run rows and real add's output. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Wires the existing (but previously unused) DotfileManager.status()/diff() into a new read-only `ais doctor` command that reports ok/missing/conflict/ stale per entry, with --user and --json, exiting non-zero for CI use. Actually exercising status()/diff() end-to-end for the first time surfaced three real bugs, all fixed here with regression tests: - GitSource.ensureCloned() ran a bare `git pull` for any repo with a .git dir, failing on a purely local (never-pushed) rules repo. - diff()'s stale check compared a relative readlink() result against an absolute path as a raw string, misclassifying every healthy relative symlink as stale. - status()/diff() resolved the on-disk filename from the raw manifest key, ignoring suffixes a hybrid/file-mode adapter appends at link time — so entries added via the documented string-shorthand config format (`"react": "<url>"`) on e.g. cursor rules showed as missing even when correctly linked. Also adds user-scope support (UserRulesSyncManifest) so `ais doctor --user` can check ~/.config/ai-rules-sync/user.json entries too. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Brings docs (EN+ZH), README, and README banners in line with the new explicit-only command surface: ais doctor, broadcast command groups, the generated per-tool command shape, and updated migration guidance. Verified with `npm run docs:build` (no dead links). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… and false-success bugs
Broadcast add/remove now accept a comma-separated entry list ("ais skills
add a,b,c --tools ..."), and a new "ais <subtype> add-all" discovers and
adds every entry a repo has for that subtype across the targeted tools.
Dogfooding these against a real user-level setup surfaced two pre-existing
bugs, unrelated to the new feature itself:
- User/global-scope remove deleted whatever real file or directory
happened to sit at the target path when it wasn't an ais-created symlink
(unlinkEntry in sync-engine.ts had no isSymbolicLink() guard, unlike the
project-scope path). Now refuses and reports instead of deleting.
- User/global-scope add silently reported success and wrote a user-config
dependency even when DotfileManager.add() had correctly refused to
overwrite a real file and returned linked: false. handleAdd's user
branch, and broadcast add/add-all, never checked that flag. Now all
three surface skip: conflict (or a non-zero exit for the single-tool
command) instead of claiming success for a link that never happened.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e, fix alias-blindness in add-all/search - claude-agents was mode: 'directory' but real Claude Code subagents are single .md files, so add/add-all/search never found any real agent. - ~30 other file/hybrid-mode adapters (trae/junie/kilo/kimi/qwen/cline/codex/ augment/codebuddy/factorydroid/continue/kiro/windsurf/antigravity-cli/ opencode/pi) declared fileSuffixes but had no resolveSource, so add only matched a name typed with the literal suffix. All now use the same createMultiSuffixResolver/createSuffixAwareTargetResolver pair the working adapters already used. - add-all and search --configured checked raw config-key membership, so an entry configured under an alias was invisible to both, causing add-all to re-add (and re-link under a duplicate name) anything already aliased. New isSourceNameConfigured helper checks both the plain key and every entry's rule field. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…e .md files Same bug class as the claude-agents fix in the previous commit: cursor-agents was mode: 'directory' but real Cursor subagents are single .md files with YAML frontmatter (.cursor/agents/<name>.md), confirmed against Cursor's official docs and matching third-party writeups. Checked deepagents-agents against the same question — DeepAgents subagents are each a directory containing a fixed AGENTS.md, so mode: 'directory' is correct there and it's left unchanged. Live-verified: a real .md agent file in a scratch repo now resolves and links via `ais cursor agents add <bare-name>`. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…or file-mode adapters Found while checking a related shape question: a shared sourceDir (sourceDir['*'].agents) can hold a directory-shaped entry for one tool (deepagents: <name>/AGENTS.md) alongside a file-shaped entry for another (claude/cursor/etc: <name>.md) under the same bare name. createMultiSuffixResolver checked the un-suffixed exact name first and matched it even for adapters declared mode: 'file', so e.g. `ais claude agents add reviewer` could silently link the deepagents-shaped directory instead of the correctly-shaped .md file, with no error at all. Directory matching is now opt-in via allowDirectoryMatch, defaulting to false. Only cursor-rules (the one adapter that's genuinely mode: 'hybrid') sets it. Every other adapter now rejects a same-named directory with a clear "exists as a directory, but this tool expects a single <suffix> file" error instead of silently matching it. Live-verified: a repo with both agents/reviewer.md and agents/reviewer/ (deepagents shape) now correctly resolves reviewer.md for `ais claude agents add`; a repo with only the directory shape now fails with the new clear error instead of matching the directory or a generic "not found". Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This branch has not been deployed
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.
Summary
This completes the AIS 1.0 command redesign and hardens the global asset workflow around it.
ais <tool> <subtype> ...) and shared asset types support broadcast operations across selected tools.ais skills add review --profile personal --global.AIS_CONFIG_HOMEandXDG_CONFIG_HOMEisolate global state; newer config schemas fail explicitly, whilestatusreports unknown tools/subtypes and minimum-version requirements without deleting them.env, target-awarestatus, asset-centricsearch, remote-awaredoctor, and non-destructive repositoryremove/prunemake the active executable, config, source, and symlink state inspectable.The complete breaking-command migration and compatibility notes remain in
.changeset/wise-lions-decide.md.Review notes
Repository cleanup only removes AIS config records; it never deletes a local repository. Remote checks are opt-in for
doctor, while add commands only emit a warning for unpublished or dirty sources so local skill development remains supported.Verification
pnpm run buildpnpm exec vitest run— 565 passed, 0 failed/skipped~/.config/ai-rules-syncconfig hash unchangedpnpm run docs:buildpnpm run quality— unavailable in the current checkout becausepawlis not installed or declared (pawl: command not found)🤖 Generated with Claude Code