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
29 changes: 29 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,24 @@ source layout, CLI surface, and state schema are stabilizing but may still chang

### Added

- **Factory Droid hooks.** `apply` writes command hooks to `~/.factory/hooks.json`
and `.factory/hooks.json`. `timeout` is seconds, the unit Droid documents.
Events Droid does not have, such as `PostCompact`, are skipped, so the shared
`hooks/<event>.toml` other agents use is left alone. Matchers are written
as-is and Droid uses its own tool names (`Execute`, `Edit`, …), so a matcher
written for Claude Code's names matches nothing there.
- **`import factory` reads Factory hooks.** It reads `hooks.json`, or the `hooks`
key of `settings.json` when `hooks.json` does not exist yet, which is where
Droid reads them from too. An event with a field agentsync cannot represent
(`commandRegex`, an explicit `timeout` of `0`) is left uncaptured with a
warning instead of being captured without it.
- **The first apply keeps your `settings.json` hooks running.** Once
`hooks.json` exists, Droid stops reading the `hooks` key in `settings.json`.
So the apply that creates `hooks.json` copies those hooks in first, and only
the events agentsync renders replace what was copied. If one of those
replaces handlers you had in `settings.json`, apply now warns and names the
event, so a hook does not quietly stop firing. `settings.json` itself is
never edited.
- **Hook handlers can now carry a `timeout`.** Add `timeout = 30` to an entry in
`hooks/<event>.toml` and the handler is allowed 30 seconds across Claude Code,
Codex CLI, Cursor, Gemini CLI and Grok Build. Before this, `timeout` was a
Expand Down Expand Up @@ -453,6 +471,17 @@ source layout, CLI surface, and state schema are stabilizing but may still chang

### Changed

- **Factory skills move to `~/.agents/skills/`.** Droid scans that shared
directory as well as its own `.factory/skills/`, so writing both registered
every skill twice whenever Codex or another `.agents/skills` agent was also
enabled. The next apply after upgrading removes the skill copies agentsync
wrote under `.factory/skills/`, backing up any you edited first, and reports
them as removed files. Skills you created there yourself are left alone.
- **Factory user memory now renders to `~/.factory/AGENTS.md`.** Factory had
only a project memory file before. If you already keep a hand-written
`~/.factory/AGENTS.md`, the next apply backs it up to
`~/.agentsync/.state/backups/` and replaces it with the managed file; move
anything you want to keep into `~/.agentsync/memory/AGENTS.md`.
- **`secret edit` no longer exits the process from a signal handler
goroutine** ([#235](https://github.com/spxrogers/agentsync/issues/235)).
SIGINT/SIGTERM while the decrypted vault was on disk removed the temp file and
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,9 @@ The ten **deep adapters** (rich, agent-specific, often bidirectional):
| **Grok Build** | ✓ adapter | MCP (◐, TOML), instructions, complete skills, commands (◐), and command hooks (◐) at user and project scopes. [Supported paths and limits](docs/grok.md). Subagent/LSP projection and plugin import deferred. |

Plus a **breadth tier** of 22 more via one data-driven generic adapter — **memory**
for all, **MCP** where the agent reads a JSON server-map (15 of 22), and **Agent
Skills** where the agent natively scans a `SKILL.md` directory (18 of 22): `amp`,
for all, **MCP** where the agent reads a JSON server-map (15 of 22), **Agent
Skills** where the agent natively scans a `SKILL.md` directory (18 of 22), and
command hooks for Factory only: `amp`,
`goose`, `qwen`, `warp`, `jules`, `junie`, `openhands`, `amazonq`, `zed`,
`kilocode`, `kiro`, `trae`, `jetbrains`, `firebase`, `antigravity`, `augmentcode`,
`copilot`, `copilot-cli`, `crush`, `factory`, `pi`, `mistral`. Each is a *verified*
Expand Down
10 changes: 6 additions & 4 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ published `Adapter` interface (`KeyMergeStrategy() string`) and is deferred to
them, a single data-driven *generic* adapter (`internal/adapter/generic`) serves a
long tail of agents from a verified `Spec` table — memory, (where expressible)
MCP, and (where the agent scans a `SKILL.md` directory) Agent Skills, every other
component reported as a skip. Both kinds implement the same
component reported as a skip except Factory command hooks (see the capability matrix). Both kinds implement the same
`Adapter` interface and register identically, so the rest of the pipeline (plan,
classify, write, capture, state) treats them uniformly. The set of valid agent
names is derived from the deep package list **plus** `generic.Specs()` (see
Expand Down Expand Up @@ -673,8 +673,9 @@ type HookEventNamer interface {
}
```

- **`HookIngestGuard`** (claude, gemini, cursor, codex, grok — every hook-rendering
adapter) re-reads the destination and returns the hook events ingest
- **`HookIngestGuard`** (claude, gemini, cursor, codex, grok, and Factory — every
hook-rendering adapter; the other breadth-tier agents do not implement it)
re-reads the destination and returns the hook events ingest
*semantically* refused: unmodeled fields on well-formed entries, plus
non-command handlers where the adapter's render cannot round-trip them
(claude, gemini, cursor, grok); codex re-renders non-command types verbatim, so
Expand All @@ -696,7 +697,8 @@ type HookEventNamer interface {
adapter uses in its owned `/hooks/<name>` pointers (`PreToolUse` →
`BeforeTool` / `preToolUse`). Canonical hooks are **shared** across agents,
so a retirement disowns every agent's state key for the event at that scope
— and a renaming agent's key is only findable under its native spelling.
— a renaming agent's key under its native spelling, and Factory's key at
`/<event>` because `hooks.json` is the event map rather than a `hooks` object.
An adapter that renames without declaring would silently escape the disown
and the next apply's orphan cleanup would delete the event from its native
config; the registry-wide guard
Expand Down
47 changes: 44 additions & 3 deletions docs/capability-matrix.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ nothing is dropped silently.
| **Roo Code** | ✅ Adapter (some components projected) | MCP, memory, and slash commands — clean filesystem `.roo/` paths (rulesync and ruler converged on these). **MCP → `.roo/mcp.json`** (project-level, `mcpServers` with explicit `type: streamable-http`/`sse` for remote + `url`/`headers`; merge-by-server-name preserves foreign servers). Roo's *global* MCP lives in VS Code globalStorage (OS/editor-specific), which agentsync intentionally does **not** target — so user-scope MCP is reported as a skip. **Memory → `.roo/rules/agentsync.md`** (plain-markdown always-applied rule) and **commands → `.roo/commands/<name>.md`** (markdown + frontmatter — Roo keeps **both** `description` *and* `argument-hint`; only `allowed-tools` drops), both at **user and project scope** (`~/.roo/` + `<repo>/.roo/`). **Skills, hooks, and LSP** have no Roo concept, and Roo's "custom modes" are not per-file **subagents**, so all four are skipped. No `PluginIngester`; it still *receives* plugin-projected components on `apply`. |
| **Cline** | ✅ Adapter (scope-asymmetric) | MCP, memory, and slash commands. **MCP → `~/.cline/mcp.json`** at **user scope** — the Cline CLI's clean config (`mcpServers`, transport inferred: stdio command/args/env, remote `url` + `headers`). Cline has no project MCP file, and its VS Code-extension MCP lives in OS/editor-specific globalStorage no config-sync tool writes, so project-scope MCP is reported as a skip. **Memory → `.clinerules/agentsync.md`** (plain markdown — Cline concatenates `.clinerules/`) and **commands → `.clinerules/workflows/<name>.md`** (plain markdown workflows invoked as `/<name>.md`; command frontmatter drops), both at **project scope** (Cline's global rules and workflows live in `~/Documents/Cline/`, a non-XDG app path agentsync deliberately does not target). **Skills, subagents, hooks, and LSP** have no Cline concept and are skipped. No `PluginIngester`; it still *receives* plugin-projected components on `apply`. |
| **Grok Build** | ✅ Dedicated adapter | User/project instructions, skills with bundled files, legacy Markdown commands, TOML MCP, and JSON command hooks. Import captures the managed hook file only; unsupported handler fields are refused. See [Grok support](grok.md) for paths, trust requirements, verified upstream contracts, and limitations. |
| **Breadth tier (22 agents)** | ✅ Generic adapter (memory + MCP + skills) | A long tail of agents supported by one data-driven [generic adapter](#breadth-tier) — **memory** (rules file) for all, **MCP** where the agent reads a JSON server-map agentsync can express (15 of 22), and **Agent Skills** where the agent natively scans a `SKILL.md` directory (18 of 22). Each is a *verified* spec, not a hand-written package; see the [Breadth tier](#breadth-tier) table for per-agent coverage. They flow through the normal apply/import pipeline (drift, secrets, capture), unlike a one-way rules dump. |
| **Breadth tier (22 agents)** | ✅ Generic adapter (memory + MCP + skills; Factory hooks) | A long tail of agents supported by one data-driven [generic adapter](#breadth-tier) — **memory** (rules file) for all, **MCP** where the agent reads a JSON server-map agentsync can express (15 of 22), and **Agent Skills** where the agent natively scans a `SKILL.md` directory (18 of 22). **Factory** is the one row with command hooks. Each is a *verified* spec, not a hand-written package; see the [Breadth tier](#breadth-tier) table for per-agent coverage. They flow through the normal apply/import pipeline (drift, secrets, capture), unlike a one-way rules dump. |

## Plugin import/apply: the shared invariant

Expand Down Expand Up @@ -159,7 +159,10 @@ the deep adapters use (`claude.SkillFileOps`) — bundled files and executable b
survive byte-for-byte. Most agents read the cross-vendor **`.agents/skills/`**
convention (the same directory Codex targets, so the render pipeline dedupes the
byte-identical ops rather than fighting over the path); a few scan only their own
`.<agent>/skills/` (Qwen, Junie, Kiro, Factory, Copilot's `.github/skills/`). The
`.<agent>/skills/` (Qwen, Junie, Kiro, Copilot's `.github/skills/`). Factory
scans the shared `.agents/skills/` directory as a compatibility source and does
not get a second `.factory/skills/` copy. Droid overrides a cross-source name
clash rather than rejecting it; one copy is still the right render. The
**four without skills** are the ones that don't natively scan a `SKILL.md`
directory: **Jules** and **Firebase Studio** *publish* skills for *other* agents,
**Amazon Q** consumes skills only through an MCP server (a different component), and
Expand Down Expand Up @@ -193,10 +196,48 @@ guessed.
| **copilot** | `.github/copilot-instructions.md` | ✓ `.vscode/mcp.json` (`servers` key, `type`) | ✓ `.github/skills/` · `~/.copilot/skills/` |
| **copilot-cli** | `AGENTS.md` | ✓ `~/.copilot/mcp-config.json` (`type`, stdio="local") | ✓ `.agents/skills/` (+ user) |
| **crush** | `AGENTS.md` | ✓ crush.json `mcp` key (+ user) | ✓ `.agents/skills/` · `~/.config/crush/skills/` |
| **factory** | `AGENTS.md` | ✓ `.factory/mcp.json` · `~/.factory/mcp.json` (`type`) | ✓ `.factory/skills/` (+ user) |
| **factory** | `AGENTS.md` · `~/.factory/AGENTS.md` | ✓ `.factory/mcp.json` · `~/.factory/mcp.json` (`type`) | ✓ `.agents/skills/` (+ user) |
| **pi** | `AGENTS.md` · `~/.pi/agent/AGENTS.md` | ✓ `~/.pi/agent/mcp.json` | ✓ `.agents/skills/` (+ user) |
| **mistral** | `AGENTS.md` | ✗ TOML `.vibe/config.toml` | ✓ `.agents/skills/` · `~/.vibe/skills/` |

**Factory hooks** are the one breadth-tier exception to "no hooks". Droid's
`~/.factory/hooks.json` and `.factory/hooks.json` are an event map at the file
root (command hooks only; `timeout` is seconds, the same unit as the canonical
field). `PostCompact` is not a Droid event and is skipped, not refused, so the
shared `hooks/PostCompact.toml` other agents use is left alone. A group field
Droid supports and agentsync does not (`commandRegex`) refuses that whole
event rather than being stripped on the next apply. If `hooks.json` is absent,
import reads the `hooks` object in the matching `settings.json`. The first apply
that creates `hooks.json` copies that object in as the merge base, then overlays
only the events agentsync is rendering, so a `commandRegex` group or any other
unmodeled sibling keeps running after Droid stops reading `settings.json`. Those
copied events are not recorded as owned, so a later apply does not orphan-delete
them. An event agentsync **does** render replaces the copied array for that
event, so a `settings.json` handler under the same event stops running; apply
warns, naming the event, unless the copied handlers are the same ones canonical
renders (the usual `import` then `apply` case). `settings.json` is never edited.
The legacy `.factory/hooks/hooks.json` path still loads beside `hooks.json`
until Droid's next save archives it to `hooks/hooks.migrated.json`; apply warns
and does not move it. Import retirement disowns Factory's `/<event>` state
pointer, not only the `/hooks/<event>` spelling the nested adapters use.

**Factory matchers are written verbatim, and Droid's tool names are its own.**
A canonical `matcher` reaches `hooks.json` unchanged. Droid's matchers are
case-sensitive, accept regexes, and name Droid's tools: `Execute`, `Read`,
`Edit`, `Create`, `ApplyPatch`, `LS`, `Glob`, `Grep`, `Task`, `FetchUrl`,
`WebSearch`, and `mcp__<server>__<tool>`
([hooks reference](https://docs.factory.ai/reference/hooks-reference)). A
matcher written for Claude Code's names (`Bash`, `Write`, …) is valid JSON but
matches nothing in Droid. agentsync does not translate tool names for any
agent, so a hook meant for Droid needs a Droid matcher, `*`, or no matcher.

**Upgrading moves two Factory paths.** Skills now render to `.agents/skills/`
instead of `.factory/skills/`; the next apply removes the copies agentsync wrote
under `.factory/skills/` (backing up any you edited first) and leaves skills
you created there yourself alone. User memory now renders to
`~/.factory/AGENTS.md`; an existing hand-written file there is backed up, then
replaced by the managed file, like any other first apply over a foreign file.

**Deliberate exclusions:** **Aider** (no native MCP; memory only via an
`.aider.conf.yml` `read:` pointer — needs a content+config-pointer adapter, out of
the generic tier) and **Firebender** (single-sourced JSON-manifest model) are not
Expand Down
3 changes: 2 additions & 1 deletion docs/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,8 @@ many agents from a table of verified `Spec`s (`specs.go`) rather than a package
each. Covers **memory** (a rules/instructions file, plain markdown), **MCP** where
the agent reads a JSON server-map agentsync can express, and **Agent Skills**
(`SKILL.md` directories) where the agent natively scans a skills directory — every
other component is reported as a skip. A `Spec` declares per-scope memory/MCP/skills
other component is reported as a skip, except Factory command hooks (`hooks.json`;
only that registered wrapper implements `HookIngestGuard`). A `Spec` declares per-scope memory/MCP/skills
paths plus MCP "dialect" knobs that capture the tail's variance (top-level key
`mcpServers`/`servers`/`mcp`/`context_servers`/the flat namespaced `amp.mcpServers`;
transport field `type`/`transport`/inferred; stdio value `stdio`/`local`; remote
Expand Down
4 changes: 2 additions & 2 deletions docs/user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -982,8 +982,8 @@ Claude, OpenCode, Codex, Cursor, Gemini CLI, Continue, Windsurf, Roo Code, Cline
Beyond these ten deep adapters, a **breadth tier** of 22 more agents (amp, goose,
qwen, warp, zed, kiro, junie, factory, copilot, crush, …) is supported via one
data-driven generic adapter — memory for all, MCP where the agent reads a JSON
server-map, and Agent Skills (`SKILL.md` directories) where the agent natively scans
a skills directory. Run `agentsync agent list --all` to see them all; see the
server-map, Agent Skills (`SKILL.md` directories) where the agent natively scans
a skills directory, and command hooks for Factory only. Run `agentsync agent list --all` to see them all; see the
[capability matrix → Breadth tier](capability-matrix.md#breadth-tier) for per-agent
coverage.

Expand Down
Loading
Loading