Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
46f615e
feat(cli): install the framework for every project with setup --global
alexsoyes Nov 3, 2026
ef31af8
feat(framework): make reload installs Claude, Codex and OpenCode with…
alexsoyes Nov 3, 2026
32fef3d
refactor(cli): drop the agent frontmatter strippers no profile uses a…
alexsoyes Nov 3, 2026
52a353a
feat(cli): install the framework globally for Copilot, Cursor and Kil…
alexsoyes Nov 3, 2026
1d727f2
feat(framework): make reload installs every tool with setup --global
alexsoyes Nov 3, 2026
4acfdc5
fix(framework): make reload installs from HOME and leaves Copilot plu…
alexsoyes Nov 3, 2026
4282361
fix(cli): clean --global then setup --global holds for Claude and Cop…
alexsoyes Nov 3, 2026
65882d7
test(framework): compare the reload's working directory past Windows …
alexsoyes Nov 4, 2026
96ba791
chore(framework): say how to recover from a failed make reload
alexsoyes Nov 4, 2026
1138a84
feat(framework): make check-hosts tables what each tool really loads …
alexsoyes Nov 4, 2026
829634a
fix(framework): check-hosts proves names, and fails what it cannot prove
alexsoyes Nov 5, 2026
c241e53
fix(cli): keep a project's plugins when removing the user's marketplace
alexsoyes Nov 5, 2026
b702522
fix(cli): a stopped setup --global keeps what it wrote and blocks not…
alexsoyes Nov 5, 2026
e3c2cfe
fix(cli): make a global install restore, stay out of the project and …
alexsoyes Nov 5, 2026
51f046d
refactor(cli): name what this change added global, not machine
alexsoyes Nov 5, 2026
9fbcf56
fix(cli): preserve global install ownership and recovery
blafourcade Nov 6, 2026
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
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Fork the repository, or branch from it if you have write access.
| `make setup` | Installs dependencies and git hooks, registers the marketplace, installs the plugins into Claude Code and Codex |
| `make doctor` | Checks your environment |
| `make reload` | Reloads your checkout into your tools |
| `make check-hosts` | Tables what each installed tool really loads of the plugins |
| `make check` | Runs the pre-commit checks |

## 3. The rules
Expand Down
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.DEFAULT_GOAL := help
.PHONY: help setup doctor check reload
.PHONY: help setup doctor check reload check-hosts

help: ## List targets
@grep -hE '^[a-z-]+:.*##' $(MAKEFILE_LIST) | sed 's/:.*## /\t/' | sort
Expand All @@ -15,6 +15,9 @@ doctor: ## Check the local toolchain
check: ## Run the pre-commit checks
pnpm exec lefthook run pre-commit

reload: ## Dev: reload all plugins into Claude, Codex, OpenCode, Copilot, and Cursor
reload: ## Dev: reload all plugins into Claude, Codex, OpenCode, Kilo, Copilot, and Cursor via aidd setup --global
pnpm --dir cli build
scripts/dev-sync.sh

check-hosts: ## Dev: table what each installed tool really loads of the plugins
node scripts/check-hosts.cjs
2 changes: 1 addition & 1 deletion aidd_docs/memory/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Config: `release-please-config.json`, eleven packages. Manifest: `.release-pleas

## Local reload

`make reload` syncs all plugins and generic delegation guidance to installed hosts; details in `scripts/dev-sync.sh`.
`make reload` installs every plugin into the installed hosts with `aidd setup --global` from this checkout, then syncs generic delegation guidance; details in `scripts/dev-sync.sh`.

## Monitoring

Expand Down
2 changes: 1 addition & 1 deletion aidd_docs/memory/project-brief.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ What this project is, the problem it solves, and its domain language. The non-de

| Capability | Entry |
| --- | --- |
| Install and refresh a tool's configuration | `aidd setup --ai <ids> --ide <ids>` |
| Install and refresh a tool's configuration | `aidd setup --ide <ids>`; `--global` for every project on the machine |
| Install plugins from a marketplace | `aidd plugin install`, `aidd marketplace add` |
| Build a target-native distribution | `aidd translate <source> --to <tool> --out <dir>` |
| Bootstrap and refresh project memory | `aidd-context:02-project-memory` |
Expand Down
44 changes: 29 additions & 15 deletions cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,19 @@ Check: `aidd --version`.

| Step | Run |
| --- | --- |
| Bootstrap the project | `aidd setup --ai claude --ide vscode --plugins recommended --yes` |
| Bootstrap the project | `aidd setup --ide claude,vscode --plugins recommended --yes` |
| Add a plugin | `aidd plugin install aidd-dev` |
| Check the state | `aidd doctor` |
| Restore owned files | `aidd sync` |
| Update the CLI | `aidd update` |

`setup` writes the manifest, the default marketplace, the tool configs and the plugins.
`--global` installs for the machine instead, and writes nothing under the project.

| Command | Lands in |
| --- | --- |
| `aidd setup --ide claude` | `.claude/` and `.aidd/` in this project |
| `aidd setup --global --ide opencode --plugins all` | `~/.config/opencode/` and `~/.config/aidd/`, nothing in this project |

## Commands

Expand Down Expand Up @@ -101,31 +107,39 @@ Counts the tokens of Claude Code and Codex sessions, locally. Details: [FAQ](../

## Scope

`setup`, `doctor`, `sync` and `clean` accept `--scope <project|user>`.
`setup`, `doctor`, `sync` and `clean` accept `--global`, or its former spelling `--scope user`.

| Scope | Acts on |
| --- | --- |
| `project` (default) | This project alone |
| `user` | The machine: registers the framework source and supported tools, writes nothing under the project |
| project (default) | This project alone |
| `--global` | Every project on the machine; writes nothing under the project |

`setup --scope user` needs:
`setup --global` needs:

- An explicit `--ai` list of supported tools (`--ai all` is refused).
- `--plugins none`, or no `--plugins` in a scripted run.
- No `--ide`.
- An explicit `--ide` list of supported tools, no editor (`--ide all` is refused).
- `--plugins all`, `recommended`, `none` or comma-separated names.

| Tool | `setup --scope user` |
| Tool | `setup --global --plugins …` |
| --- | --- |
| `claude`, `codex`, `copilot` | Registers the source and drives the tool's own CLI |
| `cursor` | Records the source and tool only, no native activation |
| `opencode`, `kilo` | Unsupported: use project scope |
| `claude`, `copilot` | Registers the source and enables the plugins through the tool's own CLI |
| `codex` | The same, plus the plugins' agents in `~/.codex/agents/` |
| `opencode`, `kilo` | Copies the plugins' skills and agents into `~/.config/opencode/` or `~/.config/kilo/` |
| `cursor` | Copies the plugins into `~/.cursor/plugins/local/`; Cursor must allow local plugin imports |

Files without recorded AIDD ownership are refused even when their content matches. This includes
copies from an older `make reload`, or files left by an interrupted install before ownership was
saved. Move the named files aside, then rerun the original setup or reload command.

Copilot needs a CLI whose `plugin marketplace list` accepts `--json` (verified with 1.0.98; 1.0.83 refuses it).

Native activation needs the tool's executable on `PATH`.
Without it, registration succeeds with a warning and activation stays unrun.
Install it, then run `aidd sync --scope user`.
Install it, then rerun the original `aidd setup --global` command with its `--ide` and `--plugins` selections.

```sh
aidd setup --scope user --ai claude,codex,copilot --plugins none --yes
aidd setup --global --ide claude,codex,copilot,cursor,kilo,opencode --plugins all --yes
aidd sync --global # rebuild the copied files and refresh the hosts from the framework source
aidd clean --global --force # remove what --global installed and purge the shared source; without --force it previews or asks
```

## Authentication
Expand Down Expand Up @@ -201,7 +215,7 @@ Under `$AIDD_USER_CONFIG_DIR`, else `$XDG_CONFIG_HOME/aidd`, else `~/.config/aid
| `auth.json` | The user-level credential |
| `marketplaces.json` | The user-scope registry |
| `references.json` | The projects claiming it |
| `manifest.json` | What `--scope user` owns |
| `manifest.json` | What `--global` owns |
| `cache/built/<version>/` | One built tree per CLI version |

### Settings written per tool
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,16 @@ One registration of `aidd-framework` per machine, shared by every project. Read

## `setup --scope user`

- Registers the shared local source; native machine-wide activation requires verified current host source/ref proof. Unsupported Copilot versions refuse it.
- Registers the shared local source; native machine-wide activation requires verified current host source/ref proof. Copilot proves its source through `plugin marketplace list --json` (1.0.98); a version rejecting `--json` (1.0.83) refuses it.
- Writes nothing under `projectRoot`: no content, no plugin prompt, no gitignore touch.
- `--plugins all|recommended|<names>` picks plugins from the shared source's catalog. A tool with native activation gets them through `MarketplaceSyncSettingsOptions.enablePlugins`; a tool declaring `PluginsCapability.globalFiles` (opencode's and kilo's skills and agents, codex's agents) or installing into a user plugins directory (cursor) gets them copied there as `scope: "user"` plugins, written by `UserPluginFileUpdater`, so `sync --scope user` rebuilds them and `clean --scope user` removes them. An interactive pick is refused (`UserScopePluginModeError`).
- Its manifest: `userManifestPath(userConfigDir())`, `userConfigDir()/manifest.json`, same schema and version as the project one.
- `UserManifestRepositoryAdapter` reuses `Manifest.fromJSON`/`toJSON`; its `delete()` removes that one file only.
- Each AI tool gets a manifest entry with an empty file list, so `MarketplaceSyncSettingsUseCase` has something to iterate.
- Records no `references.json` claim: absence is the state until `clean --scope user`.
- `doctor --scope user` runs only `DoctorRegistrationUseCase`.
- `sync --scope user` resolves the same manifest.
- `sync --scope user` resolves the same manifest, rebuilds its `scope: "user"` plugin files, then drives native activation.
- `--scope <project|user>` on all three, default `project`.
- Refuses an `--ide` tool (`UserScopeIdeToolsError`): IDE config is project-relative.
- Refuses an AI tool without machine-wide activation (`UserScopeUnsupportedAiToolsError`; `registry.ts`'s `supportsUserScopeActivation`, false for opencode alone).
- Refuses an AI tool without machine-wide activation (`UserScopeUnsupportedAiToolsError`; `registry.ts`'s `supportsUserScopeActivation`); every registered AI tool supports it today.
- Both refusals fire in `SetupFlow`'s constructor; `--ai all` is unusable at this scope.
2 changes: 1 addition & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"node": ">=22.12"
},
"packageManager": "pnpm@12.16.1",
"bundleBudgetKB": 722,
"bundleBudgetKB": 747,
"scripts": {
"build": "tsup && node scripts/check-bundle-size.mjs",
"build:check-size": "node scripts/check-bundle-size.mjs",
Expand Down
2 changes: 2 additions & 0 deletions cli/scripts/check-bundle-size.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ const pkg = JSON.parse(readFileSync(resolve(root, "package.json"), "utf8"));
// (+5.2 KB over its 712.9 KB skills profile); 0.3% measured headroom.
// 722 KB: 720.2 KB, Antigravity portable agents and direct-local installation
// (+2.1 KB over its 718.1 KB hooks build); 0.3% measured headroom.
// 747 KB: 732.4 KB, the machine-wide install of `setup --global` (+12.2 KB over the 720.2 KB
// above); 2.0% measured headroom.
const budgetKB = pkg.bundleBudgetKB ?? 500;
const budgetBytes = budgetKB * 1024;

Expand Down
5 changes: 3 additions & 2 deletions cli/scripts/smoke-real.sh
Original file line number Diff line number Diff line change
Expand Up @@ -167,11 +167,12 @@ PROJ_U=$(mktemp -d "$TMPROOT/proj-user.XXXXXX")
(cd "$PROJ_U" && git init -q)

# `setup --scope user` exits 1 for a tool declaring no machine-wide activation
# (`registry.ts`'s `supportsUserScopeActivation`; opencode today), before anything is
# (`registry.ts`'s `supportsUserScopeActivation`; none today), before anything is
# written — so the machine-scope phases carry their own tool list, never `ai_list`.
# kilo is listed for parity but never measured: `AI_TOOLS` does not probe for it.
user_ai_list() {
local ids=()
for t in claude codex copilot cursor; do
for t in claude codex copilot cursor kilo opencode; do
[[ -n "${PRESENT[$t]:-}" ]] && ids+=("$t")
done
(IFS=,; echo "${ids[*]}")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { join, resolve } from "node:path";
import { marketplaceCacheDir } from "../../../kernel/paths.js";
import type { FileReader } from "../../../kernel/ports/file-reader.js";
import type { Logger } from "../../../kernel/ports/logger.js";
import {
Expand Down Expand Up @@ -61,7 +60,7 @@ export class MarketplaceRefreshUseCase implements MarketplaceRefresh {
// through the result, never thrown.
private async refreshOne(projectRoot: string, m: Marketplace): Promise<RefreshEntryResult> {
try {
const cacheDir = marketplaceCacheDir(projectRoot, m.name);
const cacheDir = this.resolveMarketplace.cacheDirOf(m, projectRoot);
await this.warnIfStale(m.name, cacheDir);
this.logger?.info(`Fetching marketplace '${m.name}'...`);
const { catalog } = await this.resolveMarketplace.execute({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { marketplaceCacheDir } from "../../../kernel/paths.js";
import { fetchedMarketplaceDir } from "../../../kernel/paths.js";
import type { PluginCatalog } from "../domain/catalog.js";
import type { Marketplace } from "../domain/marketplace.js";
import type { PluginCatalogRepository } from "../domain/ports/plugin-catalog-repository.js";
Expand All @@ -24,11 +24,12 @@ export interface ResolveMarketplace {
export class ResolveMarketplaceUseCase implements ResolveMarketplace {
constructor(
private readonly fetchMarketplaceSource: FetchMarketplaceSourceUseCase,
private readonly catalogRepo: PluginCatalogRepository
private readonly catalogRepo: PluginCatalogRepository,
private readonly userConfigDir?: () => string
) {}

async execute(options: ResolveMarketplaceOptions): Promise<ResolveMarketplaceResult> {
const cacheDir = marketplaceCacheDir(options.projectRoot, options.marketplace.name);
const cacheDir = this.cacheDirOf(options.marketplace, options.projectRoot);
const localPath = await this.fetchMarketplaceSource.execute({
marketplace: options.marketplace,
cacheDir,
Expand All @@ -37,4 +38,8 @@ export class ResolveMarketplaceUseCase implements ResolveMarketplace {
const catalog = await this.catalogRepo.load(localPath);
return { marketplace: options.marketplace, localPath, catalog };
}

cacheDirOf(marketplace: Marketplace, projectRoot: string): string {
return fetchedMarketplaceDir(marketplace, projectRoot, this.userConfigDir?.());
}
}
6 changes: 4 additions & 2 deletions cli/src/contexts/framework/application/clean-use-case.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,8 @@ export class CleanUseCase {
this.hostPluginRegistries.get(toolId),
registration.hostName,
new Set([...registrations.pluginRefs, ...machineRefs]),
projectRoot
projectRoot,
toolId
);
}
}
Expand Down Expand Up @@ -604,7 +605,8 @@ export class CleanUseCase {
reader,
sourceRegistration.hostName,
new Set([...registrations.pluginRefs, ...machineRefs]),
projectRoot
projectRoot,
toolId
);
} catch (error) {
this.logger.warn(`${binary}: '${ref}' left enabled; ${String(error)}`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,8 @@ export class CleanUserScopeUseCase {
this.hostPluginRegistries.get(toolId),
registration.hostName,
owned,
projectRoot
projectRoot,
toolId
);
for (const ref of refsByHost.get(registration.hostName) ?? []) {
if (hostRefs.get(ref)?.enabled !== true)
Expand Down Expand Up @@ -419,6 +420,15 @@ export class CleanUserScopeUseCase {
await this.purgeWhitelistedPath("cache/built", "cache/built", (candidate) =>
this.fs.deleteDirectory(candidate)
);
// Other user marketplaces keep their registered sources. Plugin fetches belong to the
// machine manifest being removed, whose active dependents were checked before any purge.
const frameworkSource = `cache/marketplaces/${FRAMEWORK_MARKETPLACE_NAME}`;
for (const path of [frameworkSource, "cache/plugin-cache"]) {
await this.purgeWhitelistedPath(path, path, (candidate) =>
this.fs.deleteDirectory(candidate)
);
}
await this.purgeEmptyCacheShell("cache/marketplaces");
// The self-update check cache, written into the same `cache/` directory by any online
// command: left behind it keeps the shell below non-empty, the one occupant that made
// "leaves nothing of aidd's on the machine" false in the ordinary case.
Expand All @@ -444,17 +454,15 @@ export class CleanUserScopeUseCase {
await this.userManifestRepo.delete();
}

/** `cache/built/` and `cache/update-check.json` are this whitelist's only occupants of
* `userConfigDir()/cache/`. The shell around them is removed only once proven empty, under the
* same containment as every other candidate — never assumed from having just purged its only
* children, since a future writer under `cache/` would make that assumption stale silently. */
private async purgeEmptyCacheShell(): Promise<void> {
/** Remove a cache directory only once proven empty, with the same containment as every
* whitelist candidate: another marketplace or a future cache writer may still use it. */
private async purgeEmptyCacheShell(path = "cache"): Promise<void> {
const candidate = await resolveCacheCandidate(
this.fs,
this.logger,
this.userConfigDir(),
"cache",
"user scope: cache"
path,
`user scope: ${path}`
);
if (candidate === null) return;
let entries: string[];
Expand All @@ -466,7 +474,7 @@ export class CleanUserScopeUseCase {
}
if (entries.length > 0) return;
await this.fs.deleteDirectory(candidate);
this.logger.info(`user scope: cache purged: ${candidate}`);
this.logger.info(`user scope: ${path} purged: ${candidate}`);
}

/** Never `userConfigDir()` itself, and never on a manifest's word: every fixed whitelist entry
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export interface DoctorRegistrationOptions {
manifest: Manifest;
projectRoot: string;
allowedIds: Set<string> | null;
/** A global install declares nothing in a project's settings, so it skips that check. */
globalInstall?: boolean;
}

/** One plugin `doctor` expects a native-activation tool's own registry to carry, and the ref to
Expand Down Expand Up @@ -84,7 +86,7 @@ export class DoctorRegistrationUseCase {

async execute(options: DoctorRegistrationOptions): Promise<DoctorIssue[]> {
return [
...(await this.checkDeclaredMarketplaces(options)),
...(options.globalInstall === true ? [] : await this.checkDeclaredMarketplaces(options)),
...(await this.checkNativeRegistrations(options)),
...(await this.checkMarketplaceSources(options)),
];
Expand Down
Loading
Loading