From c3a4f87b8dacedfd82be659ba6e4588418555901 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 05:54:56 +0000 Subject: [PATCH 1/7] chore: version packages --- .changeset/fix-interactive-install-spinner-hang.md | 7 ------- CHANGELOG.md | 8 ++++++++ package.json | 2 +- 3 files changed, 9 insertions(+), 8 deletions(-) delete mode 100644 .changeset/fix-interactive-install-spinner-hang.md diff --git a/.changeset/fix-interactive-install-spinner-hang.md b/.changeset/fix-interactive-install-spinner-hang.md deleted file mode 100644 index 18cf7b33..00000000 --- a/.changeset/fix-interactive-install-spinner-hang.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -'agentsmesh': patch ---- - -Fix interactive prompts hanging behind the status spinner. `install`, `uninstall`, and `refresh` held the spinner across the whole run, and its redraw timer overwrote the interactive prompts underneath (skill-pack select, broken-link, invalid-resource confirm, uninstall drift, and the refresh consent prompt) — leaving the command waiting on invisible input (refresh silently timed out after 5 minutes and skipped the pack). The spinner now yields the terminal to the prompt flow whenever a run may prompt (real TTY, no `--force`/`--dry-run`), mirroring `init`. - -Also hardened the prompt primitives: `confirm()` now reuses the shared `readLine` helper so a closed or erroring stdin declines gracefully instead of hanging (Ctrl-D) or crashing; `readLine` resolves to empty (decline) on a stream `error` instead of throwing. And `install --force --dry-run` now previews the same resource set a real `--force` install would write (invalid resources were incorrectly dropped from the preview). diff --git a/CHANGELOG.md b/CHANGELOG.md index 253584ae..f5c01cca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.30.2 + +### Patch Changes + +- f6e8096: Fix interactive prompts hanging behind the status spinner. `install`, `uninstall`, and `refresh` held the spinner across the whole run, and its redraw timer overwrote the interactive prompts underneath (skill-pack select, broken-link, invalid-resource confirm, uninstall drift, and the refresh consent prompt) — leaving the command waiting on invisible input (refresh silently timed out after 5 minutes and skipped the pack). The spinner now yields the terminal to the prompt flow whenever a run may prompt (real TTY, no `--force`/`--dry-run`), mirroring `init`. + + Also hardened the prompt primitives: `confirm()` now reuses the shared `readLine` helper so a closed or erroring stdin declines gracefully instead of hanging (Ctrl-D) or crashing; `readLine` resolves to empty (decline) on a stream `error` instead of throwing. And `install --force --dry-run` now previews the same resource set a real `--force` install would write (invalid resources were incorrectly dropped from the preview). + ## 0.30.1 ### Patch Changes diff --git a/package.json b/package.json index 7e890133..88f416a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "agentsmesh", - "version": "0.30.1", + "version": "0.30.2", "description": "One canonical source for AI coding agent rules, commands, skills, MCP, hooks, and permissions — synced across every major AI coding tool.", "type": "module", "main": "./dist/index.js", From 8085c8965341dcad3ff90fab4bbaec0a90c8346f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 20 Jul 2026 19:07:11 +0000 Subject: [PATCH 2/7] chore: version packages --- .changeset/aider-capability-audit.md | 13 - .../amazon-q-hooks-permissions-embedded.md | 15 - .changeset/amp-commands-hooks-downgrade.md | 12 - .../antigravity-capability-path-audit.md | 16 - .changeset/augment-code-global-hooks.md | 9 - .changeset/capability-ledger-audit.md | 5 - .changeset/capability-ledger-text-format.md | 5 - .changeset/check-generated-output-drift.md | 5 - .../cline-cli-reference-capability-audit.md | 17 - ...i-permissions-hooks-mcp-additionalrules.md | 10 - .changeset/continue-ignore-native.md | 10 - .../copilot-global-mcp-hooks-permissions.md | 11 - .changeset/crush-capability-audit.md | 10 - .changeset/crush-global-settings-merge.md | 5 - .../cursor-permissions-native-cli-config.md | 10 - .changeset/deepagents-cli-capability-audit.md | 12 - .changeset/engine-merge-audit-fixes.md | 9 - ...ory-droid-hooks-path-permissions-native.md | 11 - ...gemini-cli-hooks-native-extended-events.md | 20 - .changeset/goose-capability-audit.md | 11 - .changeset/jules-capability-audit.md | 19 - .changeset/junie-capability-audit-fixes.md | 41 --- .../kilo-code-global-config-unification.md | 11 - .changeset/kiro-capability-audit.md | 12 - ...ncode-additionalrules-agents-broken-fix.md | 12 - .changeset/pi-agent-capability-audit.md | 10 - .../qwen-code-additionalrules-commands-fix.md | 11 - .../replit-agent-partial-generator-stubs.md | 15 - .changeset/roo-code-capability-audit.md | 13 - .../rovodev-prompts-mcp-permissions-fix.md | 14 - .../schema-test-partial-generator-stubs.md | 25 -- .changeset/trae-capability-audit.md | 13 - .changeset/warp-global-mcp.md | 17 - .changeset/windsurf-capability-audit.md | 37 -- .changeset/zed-capability-audit.md | 34 -- CHANGELOG.md | 345 ++++++++++++++++++ package.json | 2 +- 37 files changed, 346 insertions(+), 501 deletions(-) delete mode 100644 .changeset/aider-capability-audit.md delete mode 100644 .changeset/amazon-q-hooks-permissions-embedded.md delete mode 100644 .changeset/amp-commands-hooks-downgrade.md delete mode 100644 .changeset/antigravity-capability-path-audit.md delete mode 100644 .changeset/augment-code-global-hooks.md delete mode 100644 .changeset/capability-ledger-audit.md delete mode 100644 .changeset/capability-ledger-text-format.md delete mode 100644 .changeset/check-generated-output-drift.md delete mode 100644 .changeset/cline-cli-reference-capability-audit.md delete mode 100644 .changeset/codex-cli-permissions-hooks-mcp-additionalrules.md delete mode 100644 .changeset/continue-ignore-native.md delete mode 100644 .changeset/copilot-global-mcp-hooks-permissions.md delete mode 100644 .changeset/crush-capability-audit.md delete mode 100644 .changeset/crush-global-settings-merge.md delete mode 100644 .changeset/cursor-permissions-native-cli-config.md delete mode 100644 .changeset/deepagents-cli-capability-audit.md delete mode 100644 .changeset/engine-merge-audit-fixes.md delete mode 100644 .changeset/factory-droid-hooks-path-permissions-native.md delete mode 100644 .changeset/gemini-cli-hooks-native-extended-events.md delete mode 100644 .changeset/goose-capability-audit.md delete mode 100644 .changeset/jules-capability-audit.md delete mode 100644 .changeset/junie-capability-audit-fixes.md delete mode 100644 .changeset/kilo-code-global-config-unification.md delete mode 100644 .changeset/kiro-capability-audit.md delete mode 100644 .changeset/opencode-additionalrules-agents-broken-fix.md delete mode 100644 .changeset/pi-agent-capability-audit.md delete mode 100644 .changeset/qwen-code-additionalrules-commands-fix.md delete mode 100644 .changeset/replit-agent-partial-generator-stubs.md delete mode 100644 .changeset/roo-code-capability-audit.md delete mode 100644 .changeset/rovodev-prompts-mcp-permissions-fix.md delete mode 100644 .changeset/schema-test-partial-generator-stubs.md delete mode 100644 .changeset/trae-capability-audit.md delete mode 100644 .changeset/warp-global-mcp.md delete mode 100644 .changeset/windsurf-capability-audit.md delete mode 100644 .changeset/zed-capability-audit.md diff --git a/.changeset/aider-capability-audit.md b/.changeset/aider-capability-audit.md deleted file mode 100644 index 74249b66..00000000 --- a/.changeset/aider-capability-audit.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"agentsmesh": minor ---- - -Aider: raise MCP, Hooks, and Permissions from none to partial. - -- **MCP (project, none → partial)**: Aider has no project-local MCP config file surface — MCP tool use is handled by Aider's own `--mcp` CLI flag or the `mcp` section of `~/.aider.conf.yml` (global user config, not a project file). `lintMcp` warns when canonical MCP servers are present but cannot be projected to the project scope. `generateMcp` returns `[]` (no-op stub satisfying the descriptor schema contract). - -- **Hooks (project, none → partial)**: Aider has no file-based lifecycle hook system for projects. Lifecycle behavior is controlled via scripting (e.g. `--script` mode) rather than a writable config surface. `lintHooks` warns when canonical hooks are present but cannot be projected. `generateHooks` returns `[]`. - -- **Permissions (project, none → partial)**: Aider has no writable permissions file in the project — tool allow/deny is configured via the `--allowed-cmds`/`--no-auto-accept-architect` CLI flags or user-global config, not a project-scope file. `lintPermissions` warns when canonical permissions are present but cannot be projected. `generatePermissions` returns `[]`. - -All three features remain `none` at global scope (Aider's `~/.aider.conf.yml` user config exists but carries no MCP-server list, hook-event list, or structured permission blocks that map faithfully from canonical form). diff --git a/.changeset/amazon-q-hooks-permissions-embedded.md b/.changeset/amazon-q-hooks-permissions-embedded.md deleted file mode 100644 index cbc3f0b6..00000000 --- a/.changeset/amazon-q-hooks-permissions-embedded.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -"agentsmesh": minor ---- - -Amazon Q: raise Hooks and Permissions from partial to embedded; add missing ledger cells for rules/project, rules/global, and mcp/global. - -- **Hooks (project + global, none → partial → embedded)**: Amazon Q agent JSON files at `.amazonq/cli-agents/*.json` support a top-level `hooks` key with triggers `agentSpawn`, `userPromptSubmit`, `preToolUse`, `postToolUse`, and `stop`. Verified at https://aws.github.io/amazon-q-developer-cli/agent-format.html. Canonical `PreToolUse`, `PostToolUse`, and `UserPromptSubmit` entries are now embedded into each generated agent JSON under the corresponding Amazon Q trigger names. `Notification`, `SubagentStart`, and `SubagentStop` have no Amazon Q equivalent — `lintHooks` warns about those events only (not about the mappable ones). `generateHooks()` is a registered no-op so the engine's dispatch path finds a generator. - -- **Permissions (project + global, none → partial → embedded)**: Agent JSON files support `allowedTools` (array of tool names) and `toolsSettings` (per-tool restrictions). Canonical `permissions.allow` maps directly to `allowedTools` and is now merged with per-agent tools (deduplicated) in each generated agent JSON. `deny` and `ask` have no Amazon Q equivalent — `lintPermissions` warns about those only when they are non-empty. `generatePermissions()` is a registered no-op. - -- **Round-trip (importer)**: `amazonQAgentMapper` now preserves the `hooks` key from imported agent JSON into canonical agent frontmatter, completing the generate → import → generate round-trip. - -- **Ledger cells (rules/project, rules/global, mcp/global)**: These three cells were absent from `capability-ledger.json` despite the descriptor declaring `rules=native` for both scopes and `mcp=native` for global scope. Added with correct paths and format metadata. - -- **Hooks/permissions ledger cells**: Updated `maxAchievable` from `partial` to `embedded` for hooks and permissions in both project and global scopes. diff --git a/.changeset/amp-commands-hooks-downgrade.md b/.changeset/amp-commands-hooks-downgrade.md deleted file mode 100644 index 3b7884a9..00000000 --- a/.changeset/amp-commands-hooks-downgrade.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"agentsmesh": minor ---- - -Amp: downgrade Commands to embedded, Hooks to partial, Permissions to partial; add Agents (embedded) and Ignore (partial). - -- **Commands (project + global, native → embedded)**: Amp has no declarative slash-command file format — per https://ampcode.com/manual, commands only exist via `amp.registerCommand(...)` inside a TypeScript plugin. `generateCommands()` projects each command as `.agents/skills//SKILL.md` (the same embedding `generateSkills` uses natively), so the generated output is unchanged; only the declared capability level is corrected from `native` to `embedded`. -- **Hooks (project + global, native → partial)**: `buildAmpScopedSettings()` used to write an undocumented `"amp.hooks"` key into `.amp/settings.json`. Re-verified directly against ampcode.com/manual — the word "hooks" never appears anywhere in the manual's content. Amp's only hook-like mechanism is the plugin-based `amp.on(...)` event API (code, not a declarative settings file), which qualifies as a partial surface (the product supports via unstable/code surface). `buildAmpScopedSettings()` no longer emits `amp.hooks`, and a `lintHooks` warning fires when canonical hooks exist but cannot be projected. -- **Agents (project + global, none → embedded)**: Agents are now projected as skills via `generateAgents()` (each agent emits `.agents/skills/am-agent-/SKILL.md`), raising the ceiling from `none` to `embedded`. -- **Ignore (project + global, none → partial)**: Amp has no dedicated ignore file and relies on `.gitignore`. Canonical ignore patterns are not projected; `lintIgnore` warns users. The ceiling is `partial` (the product supports the concept via an adjacent mechanism, but agentsmesh cannot write it). -- **Permissions (project + global, native → partial)**: `amp.permissions` is a LEGACY key per https://ampcode.com/manual/appendix/legacy-permissions-rules.txt. Its documented schema is an array of rule objects (`[{ tool, matches, action, context }]`), which is incompatible with the canonical `{allow,deny,ask}` string-array structure. agentsmesh cannot emit a valid `amp.permissions` value; `buildAmpScopedSettings()` no longer emits the key, `mergeAmpSettings()` no longer special-cases it, and a new `lintPermissions` warning fires when canonical permissions are present. The ceiling is `partial` (the legacy surface exists but cannot be driven from canonical config). -- Updated `src/targets/catalog/capability-ledger.json` entries for `amp/commands`, `amp/hooks`, `amp/agents`, `amp/ignore`, and `amp/permissions` across project + global scopes. diff --git a/.changeset/antigravity-capability-path-audit.md b/.changeset/antigravity-capability-path-audit.md deleted file mode 100644 index e5a77b8c..00000000 --- a/.changeset/antigravity-capability-path-audit.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -"agentsmesh": minor ---- - -Antigravity: promote commands to native (both scopes) and correct global workflows path. - -- **Commands (project + global, partial → native)**: `generateCommands()` produces `.agents/workflows/.md` files and the importer reads them back from the same directory. The old `partial` declaration was incorrect — full round-trip has always been present. Global scope commands round-trip via `ANTIGRAVITY_GLOBAL_WORKFLOWS_DIR`. - -- **Global workflows path corrected**: `ANTIGRAVITY_GLOBAL_WORKFLOWS_DIR` was changed on this branch from `.gemini/antigravity/workflows` (master) to `.gemini/config/workflows` — but no primary source confirms `.gemini/config/workflows` as a valid Antigravity global workflows location. Multiple sources (GitHub Issue #16058 on google-gemini/gemini-cli and the antigravity-minimal-setup community repo) document the correct path as `~/.gemini/antigravity/global_workflows/`. The constant is now set to `.gemini/antigravity/global_workflows`. The MCP path (`.gemini/config/mcp_config.json`) and skills path (`.gemini/config/skills/`) are unaffected — both are confirmed by primary sources (Google Codelabs). - -- **Global paths summary (final state)**: - - Rules (global): `~/.gemini/GEMINI.md` (aggregate, all rules embedded) - - Skills (global): `~/.gemini/config/skills/` - - Commands/workflows (global): `~/.gemini/antigravity/global_workflows/` - - MCP (global): `~/.gemini/config/mcp_config.json` - - Hooks (global): `~/.gemini/config/hooks.json` diff --git a/.changeset/augment-code-global-hooks.md b/.changeset/augment-code-global-hooks.md deleted file mode 100644 index 3439387c..00000000 --- a/.changeset/augment-code-global-hooks.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"agentsmesh": minor ---- - -Augment Code: raise global Hooks from none to native. - -- **Hooks (global, none → native)**: `~/.augment/settings.json` supports the same `hooks` key as the project-scope `.augment/settings.json` — the `buildSettingsContent` helper already serialises canonical hooks into AugmentCode's native format (`{ event: [{ matcher, hooks: [{ type, command, timeout }] }] }`), and `importAugmentSettings` already reads them back. The global capability was previously declared `none` even though generation and import were already wired. Only the `globalCapabilities.hooks` declaration needed to change from `none` to `native`; no code changes were required. - -- **Project hooks (native — unchanged)**: project-scope hooks were already `native` and are unaffected. diff --git a/.changeset/capability-ledger-audit.md b/.changeset/capability-ledger-audit.md deleted file mode 100644 index 6604eb4d..00000000 --- a/.changeset/capability-ledger-audit.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"agentsmesh": minor ---- - -Add a capability provenance ledger (`src/targets/catalog/capability-ledger.json`) plus deterministic `pnpm capabilities:audit` / `capabilities:seed` and a CI conformance test that validates each target's generated files against a recorded path/extension/structure fingerprint. Reworks the `update-target-capabilities` skill to an audit-driven flow. diff --git a/.changeset/capability-ledger-text-format.md b/.changeset/capability-ledger-text-format.md deleted file mode 100644 index c54720c5..00000000 --- a/.changeset/capability-ledger-text-format.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'agentsmesh': patch ---- - -Capability ledger: add a `text` format for plain-text surfaces (shell hook scripts, Starlark permission rules, gitignore-style files) so conformance checks them by path/extension instead of mislabeling them as markdown. diff --git a/.changeset/check-generated-output-drift.md b/.changeset/check-generated-output-drift.md deleted file mode 100644 index b2d10bf2..00000000 --- a/.changeset/check-generated-output-drift.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -"agentsmesh": minor ---- - -`agentsmesh check` now detects drift in generated outputs. `agentsmesh generate` records a checksum for every generated file in a new `outputs` map inside `.agentsmesh/.lock` (full runs replace the map; `--targets`/`--features` runs merge per-path), and `check` re-hashes those files, failing with exit code 1 when a generated output was hand-edited or deleted. JSON output gains `outputsModified`, `outputsRemoved`, and `outputsChecked` alongside the existing canonical-drift fields, so the two drift kinds are reported separately. A new `check --no-outputs` flag skips output verification (for setups that gitignore generated outputs in CI). Locks written by earlier versions skip output verification with a hint until the next `generate` upgrades them. The MCP `check` tool performs the same output verification. Checksums are BOM- and line-ending-normalized, so CRLF-only editor rewrites do not register as drift. diff --git a/.changeset/cline-cli-reference-capability-audit.md b/.changeset/cline-cli-reference-capability-audit.md deleted file mode 100644 index b7b26a3a..00000000 --- a/.changeset/cline-cli-reference-capability-audit.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -"agentsmesh": minor ---- - -Cline: rebase on the standalone CLI's documented paths (docs.cline.bot/cli/cli-reference) instead of the VS Code extension's IDE-era layout, raise Permissions, and downgrade unsupported global surfaces. - -- **Rules (project + global, native — fixed)**: project rules move from `.clinerules/{slug}.md` to `.cline/rules/{slug}.md`; global rules move from `~/Documents/Cline/Rules/` to `~/.cline/data/settings/rules/`. The legacy flat-file `.clinerules` convention (no directory) is dropped — it is undocumented for the CLI; only the directory form is imported. Root-rule detection order is unchanged: `_root.md`, then `AGENTS.md`, then the first alphabetically-sorted rule file. -- **Hooks (project + global, native — fixed)**: hooks move from `.clinerules/hooks/*.sh` to `.cline/hooks/*.sh`. Project and global scope now resolve to the identical relative path (project root vs. `$HOME`), matching the CLI's documented `~/.cline/hooks` default (also configurable via `--hooks-dir`/`CLINE_HOOKS_DIR`) — the importer reads a single directory instead of merging two. -- **MCP (project, native — fixed)**: the settings file moves from `.cline/cline_mcp_settings.json` to `.cline/mcp.json`. Both the old filename and `.cline/mcp_settings.json` are still accepted on import for backward compatibility. -- **MCP (global): native → none**. No global MCP config path is documented anywhere in the CLI reference's `~/.cline/data/settings/` tree; `.cline/mcp.json` is project-only. -- **Skills (global, native — fixed)**: global skills move from the project-identical `.cline/skills/` to the documented `~/.cline/data/settings/skills/`. -- **Agents (project, native — fixed)**: generator now writes a single combined `.cline/agents.yaml` (the CLI-documented surface: "Agent definitions") instead of per-agent `.cline/agents/.md` files (the undocumented per-file format used by earlier agentsmesh versions). The YAML file has a top-level `agents:` list; each entry uses a round-trippable `name`/`description`/`model`/`tools`/`prompt` shape plus the same `x-agentsmesh-*` extension keys used by other native-agent targets. The importer now reads `.cline/agents.yaml` as the primary format and falls back to the legacy `.cline/agents/.md` directory for backward compatibility. Because `.cline/agents.yaml` is a combined file, `agentPath()` returns `null` at project scope (no per-name destination for cross-reference rewriting) — the same pattern used by roo-code's `.roomodes`. -- **Agents (global): native → none**. No `agents.yaml` or `agents/` directory is documented anywhere under `~/.cline/data/settings/` — only `providers.json`, `rules/`, and `skills/`. -- **Ignore (global): native → none**. docs.cline.bot/customization/clineignore documents `.clineignore` as strictly per-workspace-root (each monorepo workspace root can have its own); no global/home-directory ignore file exists. -- **Permissions (project + global): none → partial**. Cline has no dedicated writable permissions file in either scope — approval is controlled via the `--auto-approve` CLI flag, the `CLINE_COMMAND_PERMISSIONS` environment variable (JSON allow/deny command-glob policy), or the extension UI's Auto Approve/YOLO Mode. A no-op generator stub plus a lint warning point users at these mechanisms directly. - -Commands (workflows) are unaffected by this change and remain at the pre-existing `.clinerules/workflows/` (project) and `~/Documents/Cline/Workflows/` (global) paths — the CLI reference does not document a workflows/commands surface, so this IDE-era path is left as-is. diff --git a/.changeset/codex-cli-permissions-hooks-mcp-additionalrules.md b/.changeset/codex-cli-permissions-hooks-mcp-additionalrules.md deleted file mode 100644 index 75c29c90..00000000 --- a/.changeset/codex-cli-permissions-hooks-mcp-additionalrules.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"agentsmesh": minor ---- - -Codex CLI: raise Permissions to native and fix broken AdditionalRules/Hooks/MCP. - -- **Permissions (project + global): none → native**. Canonical `allow`/`ask`/`deny` command patterns now project onto `.codex/rules/agentsmesh-permissions.rules`, Codex's real Starlark `prefix_rule(pattern, decision, justification)` execution-policy DSL (allow → `allow`, ask → `prompt`, deny → `forbidden`), per https://developers.openai.com/codex/rules. `Bash([:*])` entries get a real, enforceable `prefix_rule`; every entry (including non-Bash ones like `Read`/`WebFetch`, which have no Codex command-execution equivalent) is also recorded as a `# agentsmesh-permission : ` marker comment so import recovers the exact canonical string losslessly. Uses a dedicated filename distinct from both per-rule `{slug}.rules` execution files and Codex's own `default.rules` TUI-write destination, so nothing double-writes or collides. `.codex/config.toml`'s `sandbox_mode`/`approval_policy`/`[permissions.]` schema is real but has no faithful, round-trippable mapping from canonical allow/deny/ask command patterns, so it is intentionally not used here. -- **AdditionalRules (project, native — fixed)**: scoped/non-root advisory rules now generate real nested `/AGENTS.md` (or `AGENTS.override.md`) files that Codex's documented root-to-cwd directory walk actually loads, instead of `.codex/instructions/{slug}.md` (not part of Codex's instruction-loading hierarchy) plus a link-only index in the root `AGENTS.md`. `{dir}` is the first glob's directory prefix, falling back to the rule's slug when no glob yields one (no globs, root-wildcard globs, traversal/absolute/brace-ambiguous globs). Multiple rules resolving to the same directory are joined into one file. The old `.codex/instructions/` mirror is still imported for backward compatibility with repos generated by older agentsmesh versions. -- **Hooks (project + global, native — fixed)**: canonical `Notification` hooks are no longer written to `hooks.json` — Codex's real lifecycle events are `SessionStart`, `SubagentStart`, `PreToolUse`, `PermissionRequest`, `PostToolUse`, `PreCompact`, `PostCompact`, `UserPromptSubmit`, `SubagentStop`, `Stop` (https://learn.chatgpt.com/docs/hooks); there is no `Notification` event, so it silently never fired. A new lint warning flags any unsupported event. The shared `buildWrappedCommandHooks` helper (also used by factory-droid and goose) gained an optional `supportedEvents` filter, defaulting to unfiltered so those targets are unaffected. -- **MCP (project, native — fixed)**: `.codex/config.toml`'s `[mcp_servers.]` now supports Codex's remote/Streamable HTTP transport (`url`, `bearer_token_env_var`, `http_headers`, per https://developers.openai.com/codex/mcp) on both the generator and importer side. Previously the importer required `command` and silently dropped every URL-based server, and the generator only ever emitted stdio servers. A `bearer_token_env_var` round-trips through the canonical `Authorization: Bearer ${VAR}` header convention. The stale "codex-cli only generates stdio MCP servers" lint warning is replaced with one that only fires when a remote server has env vars (which codex-cli still can't project). diff --git a/.changeset/continue-ignore-native.md b/.changeset/continue-ignore-native.md deleted file mode 100644 index 77818d84..00000000 --- a/.changeset/continue-ignore-native.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"agentsmesh": minor ---- - -Continue: raise ignore from partial to native for both project and global scopes — generate and import `.continueignore` and `~/.continue/.continueignore`. - -- **Ignore (project, partial → native)**: agentsmesh now generates `.continueignore` at the project root from canonical ignore patterns (gitignore format, one pattern per line). Import reads `.continueignore` back into `.agentsmesh/ignore`. Verified against the official Continue docs: "If you'd like to exclude additional files, you can add them to a `.continueignore` file, which follows the exact same rules as `.gitignore`." (docs.continue.dev/reference/deprecated-codebase) -- **Ignore (global, partial → native)**: agentsmesh now generates `~/.continue/.continueignore` from canonical ignore patterns in global mode. Import reads it back into `.agentsmesh/ignore`. Verified: "Continue also supports a global `.continueignore` file that will be respected for all workspaces, which can be created at `~/.continue/.continueignore`." (docs.continue.dev/reference/deprecated-codebase) -- The `lintIgnore` warning that told users to configure ignore manually is removed — generation handles it natively. -- The global importer now correctly propagates the `scope` parameter to `runDescriptorImport` (was hardcoded to `'project'`), enabling scope-correct path resolution for all descriptor-driven imports. diff --git a/.changeset/copilot-global-mcp-hooks-permissions.md b/.changeset/copilot-global-mcp-hooks-permissions.md deleted file mode 100644 index ef58e998..00000000 --- a/.changeset/copilot-global-mcp-hooks-permissions.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"agentsmesh": minor ---- - -GitHub Copilot: raise global MCP/Hooks to native, permissions to partial, fix the project-scope hooks matcher bug, and drop the phantom global commands surface. - -- **MCP (global): none → native**. `~/.copilot/mcp-config.json` (`mcpServers` key — distinct from the project-scope `.vscode/mcp.json` `servers` key) now round-trips canonical MCP servers via generator + importer, wired through `globalSupport.scopeExtras` (per https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers). The prior ledger cell for this cell pointed at the OS-specific VS Code user-profile `mcp.json` (`~/.config/Code/User/mcp.json`, `servers` key) — that models the VS Code extension's own config, not this target's `~/.copilot`-based global scope (every other global path here — `copilot-instructions.md`, `agents/`, `skills/` — lives under `~/.copilot`), so the ledger cell is corrected to Copilot CLI's own path/key. -- **Hooks (global): none → native**. `~/.copilot/hooks/agentsmesh.json` (+ wrapper scripts under `~/.copilot/hooks/scripts/`) uses the exact same `{version, hooks}` schema as project scope (https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/use-hooks), wired independently via `scopeExtras` so the project-shaped generator never leaks into global paths. -- **Permissions (global): none → partial**. `~/.copilot/permissions-config.json` records saved tool/directory approvals, but per https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-config-dir-reference it has no deny rules, ask rules, default modes, URL rules, tool filtering, or repository-local shared policy — capped at `partial` with a lint warning rather than a generator that would overclaim control. -- **Commands (global): native → none**. Global commands were generated to `.copilot/prompts/{name}.prompt.md`, but no real Copilot surface reads that path: Copilot CLI has no prompt-file/slash-command mechanism at all (maintainer, github/copilot-cli#618, closed: "we do not plan on supporting prompt files"; confirmed absent from the official `~/.copilot` config-dir reference), and VS Code Copilot Chat's own user-level prompt files live in the OS-specific VS Code profile folder, not `~/.copilot`. The phantom path is no longer generated or imported in global scope; project-scope commands (`.github/prompts/*.prompt.md`, a real VS Code Copilot Chat workspace surface) are unaffected. -- **Hooks (project, native — fixed)**: `generateHooks` now emits the real top-level `matcher` regex field (https://docs.github.com/en/copilot/reference/hooks-configuration) instead of a non-functional `comment: "Matcher: ..."` field, so hooks actually filter by tool instead of firing unconditionally for every tool. The canonical `'*'` wildcard sentinel is omitted (Copilot compiles `matcher` as `^(?:PATTERN)$`, and `"*"` alone is an invalid regex that would cause the whole entry to be skipped). The importer prefers the real field, falling back to the legacy comment for previously-generated files. (Re-verification note: the research pass also flagged canonical `Notification` hooks as unsupported by Copilot — that does not hold up against the current docs, which document `notification` as a real, supported hook event; no change was made there.) diff --git a/.changeset/crush-capability-audit.md b/.changeset/crush-capability-audit.md deleted file mode 100644 index 09929c02..00000000 --- a/.changeset/crush-capability-audit.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"agentsmesh": minor ---- - -Crush: raise Commands to embedded, Permissions to native (project + global); fix generator key, add permissions round-trip importer. - -- **Commands (project + global): none → embedded**. Canonical commands are projected as skill bundles under `.crush/skills/am-command-/SKILL.md` (with `x-agentsmesh-kind: command` frontmatter) via `serializeCommandSkill`. Crush has no native slash-command file format; embedded projection via `supportsConversion` is the correct level. Import recovers commands through `importEmbeddedSkills`. -- **Permissions (project + global): partial/none → native**. Canonical `allow` list maps to `permissions.allowed_tools` and `deny` list maps to `options.disabled_tools` in `crush.json` — both confirmed against the official crush schema.json (`charmbracelet/crush`) and `internal/config/config.go` (`Permissions.AllowedTools`, `Options.DisabledTools`). The generator previously wrote `permissions.denied_tools` (a non-existent field); this is corrected to `options.disabled_tools`. A new `parseCrushPermissions` function in the importer reads both fields back into canonical `permissions.yaml`, completing the native round-trip for both project scope (`crush.json`) and global scope (`~/.config/crush/crush.json`). -- **Generator fix**: `generatePermissions` now correctly emits deny-list entries under `options.disabled_tools` instead of the non-existent `permissions.denied_tools` key. -- **Lint comment updated**: file-level comment in `lint.ts` updated to reflect native support and the correct field names. diff --git a/.changeset/crush-global-settings-merge.md b/.changeset/crush-global-settings-merge.md deleted file mode 100644 index 0dc31278..00000000 --- a/.changeset/crush-global-settings-merge.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'agentsmesh': patch ---- - -Crush: fix global-scope settings merge — `mergeGeneratedOutputContent` now also matches the global `~/.config/crush/crush.json` path, so mcp, hooks, and permissions merge into one file instead of overwriting each other in global mode. diff --git a/.changeset/cursor-permissions-native-cli-config.md b/.changeset/cursor-permissions-native-cli-config.md deleted file mode 100644 index 60ed5f92..00000000 --- a/.changeset/cursor-permissions-native-cli-config.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"agentsmesh": minor ---- - -Cursor: raise permissions to native in both scopes, fix global path to `~/.cursor/cli-config.json`. - -- **Permissions (project, native)**: canonical `allow`/`deny` entries are now written to `.cursor/cli.json` (project root) and imported back, completing the round-trip. The output format is `{ "permissions": { "allow": [...], "deny": [...] } }` per the official Cursor CLI docs. -- **Permissions (global, native — path fix)**: global scope now correctly writes to `.cursor/cli-config.json` (resolved as `~/.cursor/cli-config.json`), distinct from the project-scope `.cursor/cli.json`. The previous implementation used `.cursor/cli.json` for both scopes, landing permissions at `~/.cursor/cli.json` — an undocumented path that Cursor does not read. Source: https://cursor.com/docs/cli/reference/permissions. -- **lintPermissions removed**: the stale `'Cursor permissions are partial; tool-level allow/deny may lose fidelity.'` warning no longer fires. Cursor permissions are fully round-trippable at the `native` level; emitting a partial warning was factually incorrect after the capability upgrade. -- **Importer (global scope)**: `importSettings` now reads from `.cursor/cli-config.json` when called in global scope, matching the generate path. diff --git a/.changeset/deepagents-cli-capability-audit.md b/.changeset/deepagents-cli-capability-audit.md deleted file mode 100644 index 4c24fc91..00000000 --- a/.changeset/deepagents-cli-capability-audit.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"agentsmesh": minor ---- - -Deep Agents CLI: fix per-agent global paths, raise Agents to native and Commands to embedded, downgrade project Hooks to none. - -- **Hooks (project, native → none)**: docs.langchain.com/oss/javascript/deepagents/code/hooks documents only the global `~/.deepagents/hooks.json` — there is no project-level hooks surface at all. `generateHooks()` is removed from the project generator; project `capabilities.hooks` is now `none`, with a `lintHooks` warning when canonical hooks exist but can't be projected. -- **Hooks (global, native, broken-fix)**: the generator wrote the Claude-Code-style `{EventName: [{matcher, hooks:[...]}]}` shape, which Deep Agents Code never reads — the real schema is a flat `{"hooks":[{"command":[...], "events":[...]}]}` array with its own dotted event vocabulary (`session.start`, `task.complete`, …). Added `hooks-format.ts` (canonical ⇄ Deep Agents event mapping, limited to the 5 events with an honest 1:1 match — `SessionStart`/`SessionEnd`/`UserPromptSubmit`/`Stop`/`PreCompact`) and moved generation to `globalSupport.scopeExtras` (gated on `scope === 'global'`) so it never leaks into project scope. Added a matching importer (`importDeepagentsCliGlobalHooks`) — hooks previously had no round-trip at all. -- **Agents (project + global, none → native)**: `.deepagents/agents/{name}/AGENTS.md` (project) and `~/.deepagents/{agent}/agents/{name}/AGENTS.md` (global) are a dedicated on-disk subagent surface, distinct from skills — previously agentsmesh only projected agents as skills (`am-agent-*`). Added a native generator/importer (`agent-format.ts`) emitting the documented frontmatter (`name`, `description`, optional `model`) instead of the richer Claude-Code-style fields no Deep Agents subagent reads. -- **Commands (project + global, none → embedded)**: no dedicated command file format exists; commands are (and already were) projected as skills via the existing `am-command-*` mechanism. Only the declared capability level was wrong — output is unchanged. -- **Global paths (broken-fix)**: global rules/skills/agents were generated to the exact same flat paths as project scope (`.deepagents/AGENTS.md`, `.deepagents/skills/`) instead of the documented per-agent-instance directory `~/.deepagents/{agent}/...` (default instance name `"agent"`). Fixed via `DEEPAGENTS_CLI_DEFAULT_AGENT_NAME` threaded through the global constants — global root/skills/agents now resolve under `.deepagents/agent/...`. MCP (`.mcp.json`) and hooks (`hooks.json`) are unaffected — both are flat, unscoped globals per the docs. -- Updated `src/targets/catalog/capability-ledger.json`: added confirmed cells for `hooks/global`, `rules/global`, `skills/global`, `agents/project`, `agents/global`, `commands/project`, `commands/global`; `hooks/project` marked `rejected`. diff --git a/.changeset/engine-merge-audit-fixes.md b/.changeset/engine-merge-audit-fixes.md deleted file mode 100644 index 00418ece..00000000 --- a/.changeset/engine-merge-audit-fixes.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -"agentsmesh": patch ---- - -Fix capability-ledger engine: preserve researched maxAchievable ceilings and full fingerprints during merge, report over-declared cells independently from unverified. - -- `scripts/merge-capability-ledger.ts`: fix two data-loss bugs — (1) confirmed/rejected cells now keep their researched `maxAchievable` ceiling instead of being overwritten by the descriptor level; (2) fingerprint preservation now checks all three arrays (topLevelKeys, requiredFrontmatter, keyChecks), not just topLevelKeys, so manually-added keyChecks and requiredFrontmatter entries are no longer silently wiped. -- `src/core/capabilities/merge.ts`: extract merge logic into a pure, unit-tested module (`mergeCell`, `hasNonEmptyFingerprint`). `pnpm capabilities:merge` is now a registered script. -- `src/core/capabilities/audit.ts`: report `over-declared` independently from `unverified` — a cell with `verifiedAt=null` and a descriptor that exceeds its `maxAchievable` now appears in the stale bucket with both reasons rather than masking the over-declared signal. diff --git a/.changeset/factory-droid-hooks-path-permissions-native.md b/.changeset/factory-droid-hooks-path-permissions-native.md deleted file mode 100644 index ea9df090..00000000 --- a/.changeset/factory-droid-hooks-path-permissions-native.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"agentsmesh": minor ---- - -Factory Droid: fix hooks output path regression and raise permissions to native. - -- **Hooks (project + global, native — path corrected)**: The primary hooks surface is `.factory/hooks.json` (project) and `~/.factory/hooks.json` (global) per the official Factory Droid reference docs (`docs.factory.ai/reference/hooks-reference`). The branch had regressed this to `.factory/settings.json` (only a documented fallback when `hooks.json` is absent), which would cause generated hooks to be silently ignored whenever a real `hooks.json` exists. The generator, importer, constants, and managed-outputs arrays are restored to target `.factory/hooks.json` as the primary surface. - -- **Permissions (project + global): partial → native**. `commandAllowlist` and `commandDenylist` are documented top-level keys of `.factory/settings.json` at both project and global scope (plain JSON, not GUI-only or cloud-managed). A new `generatePermissions` generator writes canonical `allow` → `commandAllowlist` and `deny` → `commandDenylist`; a new `importFactoryDroidPermissions` helper reads them back into `.agentsmesh/permissions.yaml`. The misleading lint warning (which directed users to manually edit the file we now generate) is removed. - -- **`.factory/settings.json`** is now the dedicated permissions file; **`.factory/hooks.json`** is the dedicated hooks file. Both are tracked in `managedOutputs.files`. diff --git a/.changeset/gemini-cli-hooks-native-extended-events.md b/.changeset/gemini-cli-hooks-native-extended-events.md deleted file mode 100644 index 0731e5c0..00000000 --- a/.changeset/gemini-cli-hooks-native-extended-events.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -"agentsmesh": minor ---- - -Gemini CLI: upgrade hooks from partial to native and extend canonical↔Gemini hook event mapping to all supported events. - -- **Hooks (project + global): partial → native**. Gemini CLI reads hooks natively from `.gemini/settings.json` (project) and `~/.gemini/settings.json` (global), confirmed against upstream `settingsSchema.ts`. Both scopes are file-writable with full round-trip support. - -- **Extended hook event mapping**: the generator and importer previously mapped only 3 events (PreToolUse↔BeforeTool, PostToolUse↔AfterTool, Notification↔Notification). Gemini CLI's schema defines 11 hook events; 6 now have canonical equivalents and are fully wired: - - | Canonical (agentsmesh) | Gemini CLI | - |---|---| - | `PreToolUse` | `BeforeTool` | - | `PostToolUse` | `AfterTool` | - | `Notification` | `Notification` | - | `SubagentStart` | `BeforeAgent` *(new)* | - | `SubagentStop` | `AfterAgent` *(new)* | - | `SessionStart` | `SessionStart` *(new)* | - - The 5 Gemini-only events (`SessionEnd`, `PreCompress`, `BeforeModel`, `AfterModel`, `BeforeToolSelection`) have no canonical equivalent and are silently dropped on import, as before for any unmapped event. The lint `supported` list is updated to match; `SubagentStart`, `SubagentStop`, and `SessionStart` no longer produce spurious unsupported-event warnings. diff --git a/.changeset/goose-capability-audit.md b/.changeset/goose-capability-audit.md deleted file mode 100644 index 9366ac8b..00000000 --- a/.changeset/goose-capability-audit.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"agentsmesh": minor ---- - -Goose: raise commands/agents to embedded, mcp/permissions to partial, fix lintMcp scope-gate. - -- **commands (project + global): none → embedded**. Canonical commands are projected as Goose skills under `.agents/skills//SKILL.md` with `name`/`description` frontmatter, discoverable by Goose's skills system at both project and global (`~/.agents/skills/`) scope. -- **agents (project + global): none → embedded**. Canonical agents are projected as skills under `.agents/skills//SKILL.md`, consistent with the commands projection path. -- **mcp (project): none → partial**. Goose has no per-project MCP config file; all MCP extensions live in `~/.config/goose/config.yaml` (global, native). A lint warning is emitted when canonical MCP servers are present at project scope, directing users to configure extensions globally via `goose configure`. -- **permissions (project): none → partial**. Goose tool permissions live exclusively in `~/.config/goose/permission.yaml` (global, native). A lint warning is emitted when canonical permissions are present at project scope. -- **lintMcp scope-gate (bug fix)**. `lintMcp` previously accepted no `options` argument and always emitted a "project-level MCP is not projected" warning, even at global scope where MCP is native. The function now matches the `lintPermissions` pattern: it accepts `options?: unknown`, reads `scope` via narrowing, and returns `[]` at global scope. diff --git a/.changeset/jules-capability-audit.md b/.changeset/jules-capability-audit.md deleted file mode 100644 index 5c901fb5..00000000 --- a/.changeset/jules-capability-audit.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -"agentsmesh": minor ---- - -Jules: raise Commands, MCP, Hooks, Ignore, and Permissions from none to partial; fix engine ignore-dispatch gap. - -- **Commands (project, none → partial)**: Jules has no slash-command or prompt-file mechanism. Tasks are submitted via GitHub issues or the web UI. No commands config file surface exists. `lintCommands` warns when canonical commands are present but cannot be projected. - -- **MCP (project, none → partial)**: No MCP configuration surface is mentioned anywhere in Jules documentation. Jules is cloud-hosted with no writable file surface for MCP. `lintMcp` warns when canonical MCP servers are present but cannot be projected. - -- **Hooks (project, none → partial)**: Jules has no lifecycle hook system. It is async and cloud-based with no local hook execution mechanism. `lintHooks` warns when canonical hooks are present but cannot be projected. - -- **Ignore (project, none → partial)**: Jules has no dedicated ignore file (no `.julesignore` or similar surface). It is a cloud agent with no local ignore mechanism. `lintIgnore` warns when canonical ignore patterns are present but cannot be projected. - -- **Permissions (project, none → partial)**: Jules has no permissions configuration file. Permission-like controls exist only via GitHub PR review workflows (GUI), not a writable file. `lintPermissions` warns when canonical permissions are present but cannot be projected. - -- **Engine fix — ignore dispatch gap (linter.ts)**: `descriptor.lint?.ignore` was never dispatched by the engine. The `lintSilentFeatureDrops` guard only fires for `capability.level === 'none'`, so promoting ignore from `none` to `partial` silently dropped the only warning path. The engine now dispatches `descriptor.lint?.ignore` when the `ignore` feature is enabled, matching how commands, mcp, permissions, and hooks are dispatched. This fix applies to all targets with `ignore: 'partial'` and a `lint.ignore` hook. - -All primary-doc claims verified against https://jules.google/docs. diff --git a/.changeset/junie-capability-audit-fixes.md b/.changeset/junie-capability-audit-fixes.md deleted file mode 100644 index 73c11585..00000000 --- a/.changeset/junie-capability-audit-fixes.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -"agentsmesh": minor ---- - -fix(junie): correct allowlist.json schema, raise hooks/global to embedded, raise permissions/project to partial - -**Breaking fix — allowlist.json schema correction (permissions/global)** - -`generatePermissions` previously emitted `rules` as a flat array of -`{type, name, behavior}` objects. The real `~/.junie/allowlist.json` schema -requires `rules` to be an object with four categorized sub-keys -(`fileEditing`, `executables`, `mcpTools`, `readOutsideProject`), each -containing a `rules` array of `{prefix|pattern, action}` items — no `type`, -`name`, or `behavior` field exists anywhere in the real schema. The old output -was silently ignored by Junie, making all permission rules non-functional. -Canonical allow/deny/ask entries are now mapped to the `executables` category -using `prefix` (literal) or `pattern` (glob) fields with `action: allow|ask` -(Junie has no deny action; deny is mapped to ask as the safe equivalent). - -**hooks/global raised: partial → embedded** - -`~/.junie/config.json` is a writable multi-feature file with a top-level -`hooks` key that Junie auto-loads. Hooks are now folded into this file via -`emitScopedSettings`. A `mergeGeneratedOutputContent` hook preserves -pre-existing keys (model, provider, brave, mcp-locations, etc.) on -regeneration. The lint warning for project-scope hooks (which require -`--config-location` and are ignored from the default project config file for -safety) is preserved. - -**permissions/project raised: none → partial** - -`.junie/config.json` at project scope exposes a `brave` boolean (auto-approve -mode). This is a coarse project-level permission control. A `lintPermissions` -warning is now emitted when granular allow/deny/ask rules are configured, -explaining that only the `brave` flag is available at project scope. - -**generator.ts split** - -Global-scope config emitters (`generatePermissions`, `emitJunieScopedSettings`, -`mergeJunieConfig`) moved to a new `global-config.ts` module to keep both -files under 200 lines. diff --git a/.changeset/kilo-code-global-config-unification.md b/.changeset/kilo-code-global-config-unification.md deleted file mode 100644 index 8e0891eb..00000000 --- a/.changeset/kilo-code-global-config-unification.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"agentsmesh": minor ---- - -Kilo Code: fix broken global-scope capabilities, downgrade global Ignore, raise Hooks to partial. - -- **Rules / AdditionalRules / Commands / Agents (global, native — fixed)**: global-scope paths now point at the documented `~/.config/kilo/` unified config directory instead of the old `~/.kilo/` mirror. Per https://kilo.ai/docs/getting-started/settings, kilo reads global config from `~/.config/kilo/kilo.jsonc` (and its sibling `~/.config/kilo/AGENTS.md`), not `~/.kilo/`. Root rule → `~/.config/kilo/AGENTS.md` (https://kilo.ai/docs/customize/custom-instructions); commands → `~/.config/kilo/commands/*.md` (https://kilo.ai/docs/customize/workflows); agents → `~/.config/kilo/agents/*.md` (https://kilo.ai/docs/customize/custom-subagents — note `custom-modes.md` inconsistently shows a singular `agent/` in three spots, but the dedicated `custom-subagents.md` page states plural `agents/` in its Configuration Precedence list, Method 2 directory list, and legacy-migration note; treated as authoritative). Additional (non-root) rules are now also registered under the `instructions` key of the shared `kilo.jsonc` (https://kilo.ai/docs/customize/custom-rules) — a bare `.kilo/rules/`-style directory is not auto-loaded at global scope, only `AGENTS.md` is. Global Skills were re-verified and are unchanged: `~/.kilo/skills/` is the correct, currently-documented location (https://kilo.ai/docs/customize/skills), separate from the `~/.config/kilo/` migration. -- **MCP (global, native — fixed)**: MCP servers now fold into the `mcp` key of the shared `~/.config/kilo/kilo.jsonc` (https://kilo.ai/docs/automate/mcp/using-in-kilo-code) using kilo's own schema (`{ type: "local"|"remote", command: [...], environment, url, headers }`), instead of a standalone `~/.kilo/mcp.json` with the unrelated `mcpServers` wrapper kilo does not read at global scope. A new `importGlobalKiloMcp()` reads it back. `mergeKiloConfig` now overlays `instructions` and `mcp` alongside the existing `permission` key so permissions, rules, and MCP writes to the same file compose correctly across one generate run (`pending?.content ?? existing` base, per this repo's settings-merge-discipline rule). -- **Ignore (global): native → none**. `~/.kilocodeignore` is no longer generated or imported. Per https://kilo.ai/docs/customize/context/kilocodeignore, `.kilocodeignore` is documented as a workspace-root-only file (patterns evaluated relative to the workspace root, auto-migrated into project-scope `permission` deny-rules) — there is no global ignore file or `kilo.jsonc` key. -- **Hooks (project + global, none → partial)**: Kilo Code hooks are supported via auto-loaded plugin files at `.kilo/plugin/*.{ts,js}` (and their global equivalents), not via a writable config surface. agentsmesh cannot generate plugin code from canonical hook definitions. `lintHooks` warns when canonical hooks are present and directs users to author plugin files manually. No hook file is generated at either scope. -- Updated `src/targets/catalog/capability-ledger.json`: added confirmed `kilo-code` global cells for `rules`, `additionalRules`, `commands`, `agents`, `skills`, and `mcp`; added a rejected `kilo-code/ignore/global` cell recording the downgrade rationale. diff --git a/.changeset/kiro-capability-audit.md b/.changeset/kiro-capability-audit.md deleted file mode 100644 index 75f5cdf2..00000000 --- a/.changeset/kiro-capability-audit.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"agentsmesh": minor ---- - -kiro: migrate hook schema to v1, raise hooks/global and permissions to partial - -- Hook JSON schema migrated from deprecated beta format (`version:"1"`, `when`/`then`, `askAgent`/`shellCommand`) to the current Kiro v1.0.0 format (`version:"v1"`, top-level `hooks` array, `trigger`/`action` with `agent`/`command` types). Old-format hooks are no longer active in Kiro IDE v1.0.0+. -- Hook file extension renamed from `.kiro.hook` to `.json` (Kiro v1.0.0 requires `.json`). -- `globalCapabilities.hooks` raised from `none` to `partial` (global ~/.kiro/hooks/ path does not exist; hooks are workspace-only). -- `globalCapabilities.permissions` raised from `none` to `partial` (Kiro v3 CLI exposes `~/.kiro/settings/permissions.yaml`; agentsmesh does not yet generate it). -- `capabilities.permissions` (project scope) raised from `none` to `partial` (workspace permissions live at `~/.kiro/workspace-roots//permissions.yaml`, outside the repo). -- `lintPermissions` added: emits a warning when canonical permissions are non-empty, directing users to configure permissions manually. diff --git a/.changeset/opencode-additionalrules-agents-broken-fix.md b/.changeset/opencode-additionalrules-agents-broken-fix.md deleted file mode 100644 index 393194de..00000000 --- a/.changeset/opencode-additionalrules-agents-broken-fix.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"agentsmesh": minor ---- - -OpenCode: fix broken AdditionalRules and Agents, raise Hooks and Ignore to partial. - -- **AdditionalRules (project + global, native — fixed)**: `.opencode/rules/.md` files are now also declared in `opencode.json`'s `instructions` array (project: `.opencode/rules/*.md`; global: an absolute `~/.config/opencode/rules/*.md`). Per https://opencode.ai/docs/rules/, OpenCode does not auto-scan any rules directory — only `AGENTS.md`/`CLAUDE.md` auto-discover via directory traversal, and every other instruction file needs an explicit `instructions` entry. Previously the generated rule files were invisible to stock OpenCode. -- **Engine gate fix (core)**: `generateScopedSettingsFeature` was only invoked when at least one of `mcp`, `ignore`, `hooks`, `agents`, or `permissions` was enabled — `rules` was absent from the gate condition. With `features: ['rules']` alone (no other features), `emitOpenCodeScopedSettings` was never reached, so the `instructions` glob was never written to `opencode.json`. The gate now also fires when `hasRules` is true, making the instructions entry visible again for rules-only configs. -- **Agents (project + global, native — fixed)**: `.opencode/agents/.md` now emits a real `permission` object (e.g. `permission: { edit: deny }`) mapped from canonical `tools`/`disallowedTools`, instead of `tools`/`disallowedTools` frontmatter keys. Per https://opencode.ai/docs/agents/, OpenCode has no `disallowedTools` key at all, and `tools` is deprecated ("Prefer the agent's permission field") and takes a boolean-map shape, not a string array — both emitted keys were silently non-functional. The importer now translates an imported `permission` object back into canonical `tools`/`disallowedTools` so the restriction round-trips (categorically, not by original tool name). -- **Fix (opencode.json merge)**: `mergeOpenCodeSettings` previously delegated to the generic Claude-shaped settings merger, which only ever carried over `permissions`(plural)/`hooks` from freshly generated content — silently freezing `mcp`/`permission`/`instructions` at whatever a first `generate` wrote, on every subsequent regenerate. It now merges OpenCode's own `mcp`/`permission`/`instructions` keys directly. -- **Hooks (project, none → partial)**: OpenCode hooks are plugin-based TypeScript/JavaScript lifecycle events (`.opencode/plugins/`), not a writable config surface that agentsmesh can generate. `lintHooks` warns when canonical hooks are present, directing users to author plugins manually. No hook file is generated. -- **Ignore (project, none → partial)**: OpenCode's ignore configuration lives under the `watcher.ignore` key of `opencode.json` rather than a standalone file, and mapping canonical glob-patterns to that key has no clean round-trip. `lintIgnore` warns when canonical ignore patterns are present, directing users to configure `watcher.ignore` manually. No ignore file is generated. diff --git a/.changeset/pi-agent-capability-audit.md b/.changeset/pi-agent-capability-audit.md deleted file mode 100644 index 797a1b6e..00000000 --- a/.changeset/pi-agent-capability-audit.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"agentsmesh": minor ---- - -Correct pi-agent capability levels based on primary-source verification of the earendil-works/pi repository. - -- `mcp` (project + global): `partial` → `none`. A full recursive tree scan of the earendil-works/pi repository finds zero MCP-related source files. Pi has no native MCP config file surface — neither at project scope (`.pi/`) nor global scope (`~/.pi/agent/`). The extension system supports custom TypeScript tools and lifecycle events but not the MCP protocol. Declaring `partial` was inaccurate; `none` re-enables the silent-drop guard. The `lintMcp` stub (which falsely claimed MCP was managed via `extensions`) is removed. -- `hooks` (project + global): `none` → `partial`. Pi Agent lifecycle hooks are supported via TypeScript extensions auto-discovered from `.pi/extensions/` (project) and `~/.pi/agent/extensions/` (global). These are hand-authored code files, not a writable config surface, which justifies `partial`. A `lintHooks` warning is emitted when canonical hooks are present. -- `ignore` (project + global): `none` → `partial`. Pi has no dedicated ignore file; it relies on `.gitignore`. A `lintIgnore` warning is emitted to inform users that canonical ignore patterns are not projected. -- `permissions` (project + global): `none` → `partial`. Pi has no built-in permissions config; permissions can be implemented via extension hooks. A `lintPermissions` warning is emitted when canonical permissions are present. diff --git a/.changeset/qwen-code-additionalrules-commands-fix.md b/.changeset/qwen-code-additionalrules-commands-fix.md deleted file mode 100644 index 611d23c3..00000000 --- a/.changeset/qwen-code-additionalrules-commands-fix.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"agentsmesh": minor ---- - -Qwen Code: raise global AdditionalRules to native and fix broken rule/command frontmatter keys. - -- **AdditionalRules (global, embedded → native)**: non-root rules now generate real files under `~/.qwen/rules/.md` instead of being folded into `~/.qwen/QWEN.md`'s body. Qwen Code's `loadRules()` (`rulesDiscovery.ts`) reads `.qwen/rules/` recursively from **both** the global `~/.qwen` dir and the project dir with the identical mechanism, so there's no reason to embed. A new `QWEN_GLOBAL_RULES_DIR` constant, global layout rewrite, and importer-spec global source wire this up; the now-unused `renderQwenGlobalInstructions` embedding helper is removed. -- **AdditionalRules (project + global, native — fixed)**: the emitted frontmatter key for path-scoped rules changes from `globs:` to `paths:` in `.qwen/rules/.md`. Qwen Code's `parseRuleFile()` only recognizes `paths:` for conditional (turn-level lazy) rule injection — `globs:` was never read, so any canonical rule with path-scoping silently became an always-injected baseline rule. The importer's `frontmatterRemap` now maps the on-disk `paths:` key back to the canonical `globs` field so round-tripping still works. -- **Commands (project + global, native — fixed)**: the generator no longer emits `allowed-tools` into `.qwen/commands/.md` frontmatter. Qwen Code's `MarkdownCommandDefSchema` (`markdown-command-parser.ts`) only maps `description`, `argument-hint`, `when_to_use`, and `disable-model-invocation` — there is no tool-restriction field, so the value was silently ignored. A new `lintCommands` warning flags canonical commands with non-empty `allowedTools` for this target. - -Source: https://github.com/QwenLM/qwen-code/blob/main/packages/core/src/utils/rulesDiscovery.ts, https://github.com/QwenLM/qwen-code/blob/main/packages/cli/src/services/markdown-command-parser.ts, https://github.com/QwenLM/qwen-code/blob/main/packages/cli/src/services/FileCommandLoader.ts diff --git a/.changeset/replit-agent-partial-generator-stubs.md b/.changeset/replit-agent-partial-generator-stubs.md deleted file mode 100644 index 7eb77dd1..00000000 --- a/.changeset/replit-agent-partial-generator-stubs.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -"agentsmesh": minor ---- - -replit-agent: raise MCP, Hooks, Ignore, and Permissions from none to partial; add no-op generator stubs to satisfy schema contract. - -- **MCP (project, none → partial)**: Replit Agent MCP servers are configured exclusively via the Integrations UI pane, not via any project-local file. `lintMcp` warns when canonical MCP servers are present but cannot be projected. `generateMcp` is a no-op stub (returns `[]`) satisfying the descriptor schema contract. - -- **Hooks (project, none → partial)**: Replit Agent has no lifecycle hook file surface. Hook state transitions (Draft, Active, Queued, etc.) are internal platform states, not user-writable hooks. `lintHooks` warns when canonical hooks are present but cannot be projected. `generateHooks` is a no-op stub (returns `[]`). - -- **Ignore (project, none → partial)**: Replit Agent has no dedicated ignore file (no `.replitignore` or similar). The agent relies on `.gitignore` for version-control purposes, but no Replit-specific file-based ignore surface exists. `lintIgnore` warns when canonical ignore patterns are present but cannot be projected. `generateIgnore` is a no-op stub (returns `[]`). - -- **Permissions (project, none → partial)**: Replit Agent permissions are managed in the cloud UI with no writable file surface in the project. `lintPermissions` warns when canonical permissions are present but cannot be projected. `generatePermissions` is a no-op stub (returns `[]`). - -All primary-doc claims verified against https://docs.replit.com/references/mcp/overview, https://docs.replit.com/replitai/agent, https://docs.replit.com/references/agent/task-lifecycle, and https://docs.replit.com/replitai/replit-dot-md. diff --git a/.changeset/roo-code-capability-audit.md b/.changeset/roo-code-capability-audit.md deleted file mode 100644 index 7ff28624..00000000 --- a/.changeset/roo-code-capability-audit.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"agentsmesh": minor ---- - -Roo Code: fix broken capabilities and correct mislabeled support levels. - -- **Agents (project, native — fixed)**: `.roomodes` custom modes now include a `groups` array (mapped from canonical agent `tools`) and always a non-empty `roleDefinition`. Roo Code's `modeConfigSchema` requires both fields with no default — omitting either made `CustomModesManager.loadModesFromFile()` silently drop every mode in the file. -- **Agents (global, partial — fixed)**: the settings file now writes to `~/.roo/settings/custom_modes.yaml` (was missing the `.roo/` prefix, i.e. `~/settings/custom_modes.yaml`) and carries the same `groups`/`roleDefinition` fix. Stays `partial`: Roo Code's real read path is a non-deterministic per-OS/per-fork VS Code extension `globalStorage` directory that `--global` cannot resolve deterministically. -- **Rules (global, native — fixed)**: the root rule now stays at `~/.roo/rules/00-root.md` instead of being redirected to `~/.roo/AGENTS.md`. Roo Code's `loadRuleFiles()` reads `.roo/rules/` from both the global `~/.roo` directory and the project directory; `loadAllAgentRulesFiles()` never reads a home-directory `AGENTS.md`. The old `~/.roo/AGENTS.md` path is kept as a lowest-priority import fallback for users migrating from the old (buggy) output. -- **MCP (global): native → partial**. `~/mcp_settings.json` is still written, but Roo Code's `McpHub.getMcpSettingsFilePath()` actually resolves via `context.globalStorageUri.fsPath + '/settings/mcp_settings.json'` — a non-deterministic per-OS/per-fork path agentsmesh cannot target. A new global-scope lint warning explains this. -- **Ignore (global): native → none**. `~/.rooignore` is no longer generated or imported. `RooIgnoreController` only ever reads `.rooignore` from the open workspace (`path.join(cwd, '.rooignore')`) — there is no home-directory/global ignore concept. A new global-scope lint warning explains the drop. -- **Permissions (project): partial → native**. Canonical `allow`/`deny` command-prefix rules are now written to (and read back from) `.vscode/settings.json` under `roo-cline.allowedCommands` / `roo-cline.deniedCommands` — real, workspace-scoped VS Code settings Roo Code's `package.json` contributes without a `scope: application` restriction. The file is merged (read-modify-write), never overwritten. Scoped to command-prefix allow/deny only — canonical "ask" rules have no Roo Code equivalent and are lint-warned instead. Global scope stays `partial` (no deterministic VS Code user-settings path). -- **Import-map (global root-rule) — fixed**: `src/core/reference/import-maps/roo-code.ts` now registers `.roo/rules/00-root.md` (`ROO_CODE_GLOBAL_ROOT_RULE`) as the primary `_root.md` alias in global scope, and adds a skip guard in the global rules-dir iteration so the root file is not double-mapped to `.agentsmesh/rules/00-root.md`. The legacy `.roo/AGENTS.md` path is kept as a secondary fallback alias for users migrating from the old (buggy) output. Previously, cross-reference rewriting for the global root rule was broken: links pointing to the root resolved to `.agentsmesh/rules/00-root.md` instead of `.agentsmesh/rules/_root.md`. diff --git a/.changeset/rovodev-prompts-mcp-permissions-fix.md b/.changeset/rovodev-prompts-mcp-permissions-fix.md deleted file mode 100644 index 8b50cf98..00000000 --- a/.changeset/rovodev-prompts-mcp-permissions-fix.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -"agentsmesh": minor ---- - -Rovo Dev: raise Commands and Agents to native/embedded, add Ignore partial, fix MCP path, fix permissions schema. - -- **Commands (project + global, none → native)**: `.rovodev/prompts.yml` (repo root) and `~/.rovodev/prompts.yml` (global user prompts) are real, documented saved-prompts manifests — https://support.atlassian.com/rovo/docs/save-and-reuse-a-prompt-in-rovo-dev-cli/ documents both the repo-root/cwd tier and the `~/.rovodev/prompts.yml` "global user prompts" tier. Canonical commands now generate a `prompts.yml` manifest entry (`name`/`description`/`content_file`) plus a `.rovodev/commands/.md` content file, and both are read back on import. Commands are no longer projected as skills (`am-command-*` skill dirs) for this target. -- **Agents (project + global, none → embedded)**: Rovo Dev has no native agent file format; agents are projected as skill bundles under `.rovodev/skills/am-agent-/SKILL.md` (project) and `~/.rovodev/skills/am-agent-/SKILL.md` (global) via `supportsConversion: { agents: true }`. The old `supportsConversion: { commands: true, agents: true }` for commands is removed since commands are now native (no longer need conversion). -- **Ignore (project + global, none → partial)**: Rovo Dev has no dedicated project-level ignore file surface. `lintIgnore` warns when canonical ignore patterns are present but cannot be projected. -- **MCP (project): native → partial**. No project-level MCP config file is documented for Rovo Dev — only `~/.rovodev/mcp_config.json` (global) exists. `.rovodev/mcp.json` is no longer generated or imported at project scope; a `lintMcp` warning explains the drop to users. (Final level is `partial`, not `none` — the lint stub satisfies the schema contract.) -- **MCP (global): native — fixed path**. The generated/imported file is renamed from `~/.rovodev/mcp.json` to `~/.rovodev/mcp_config.json`, the actual documented filename (configurable via `mcp.mcpConfigPath` in `~/.rovodev/config.yml`). -- **Permissions (global): native — fixed schema**. `~/.rovodev/config.yml`'s `toolPermissions` now emits the real nested shape (`toolPermissions.tools.: allow|ask|deny`, with bash rules under `toolPermissions.tools.bash.default` / `toolPermissions.tools.bash.commands[]`) instead of a flat `{allow:[],deny:[],ask:[]}` list the CLI never reads. - -Source: https://support.atlassian.com/rovo/docs/manage-rovo-dev-cli-settings/, https://support.atlassian.com/rovo/docs/save-and-reuse-a-prompt-in-rovo-dev-cli/ diff --git a/.changeset/schema-test-partial-generator-stubs.md b/.changeset/schema-test-partial-generator-stubs.md deleted file mode 100644 index aca11e6d..00000000 --- a/.changeset/schema-test-partial-generator-stubs.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -"agentsmesh": patch ---- - -schema-test: add no-op generator stubs to satisfy descriptor schema contract across 9 targets; remove pending-validation skip from the builtin-descriptor schema test so all builtins are validated on every run. - -The descriptor schema validator requires a `generateX` function (or `emitScopedSettings` / `globalSupport.scopeExtras`) for every capability whose level is not `'none'`. Nine built-in targets declared partial/native capabilities without a matching generator, causing the schema test to skip them. This change adds minimal no-op stubs (returning `[]`) to each affected target and removes the skip set so the test now validates all 30 built-in descriptors. - -**aider**: add `generateMcp`, `generateHooks`, `generatePermissions` no-op stubs. Aider has no MCP config file, no lifecycle hook system, and no permissions config. Existing lint functions (`lintMcp`, `lintHooks`, `lintPermissions`) surface advisory warnings. - -**deepagents-cli**: add `generateIgnore`, `generatePermissions` no-op stubs. Deep Agents CLI has no dedicated ignore file (relies on `.gitignore`) and permissions are env-var-based. Existing lint functions warn. - -**factory-droid**: add `generateIgnore` no-op stub. Factory Droid relies on `.gitignore` for ignore; the existing `lintIgnore` warns when canonical ignore patterns are present. - -**goose**: add `generatePermissions` no-op stub. Goose permissions are global-only (`~/.config/goose/permission.yaml`, emitted by `scopeExtras`); project-scope has no file surface. `lintPermissions` warns at project scope. - -**jules**: add `generateCommands`, `generateMcp`, `generateHooks`, `generateIgnore`, `generatePermissions` no-op stubs. Jules is a cloud-based async agent that only reads `AGENTS.md`; none of these surfaces exist. Existing lint functions warn. - -**kiro**: add `generatePermissions` no-op stub. Kiro permissions.yaml is not yet generated by agentsmesh. `lintPermissions` warns. - -**pi-agent**: add `generateHooks`, `generateIgnore`, `generatePermissions` no-op stubs. Pi Agent hooks are extension-based (not yet generated), has no dedicated ignore file, and no permissions config. Existing lint functions warn. - -**warp**: add `generateHooks`, `generateIgnore` no-op stubs. Warp has no lifecycle hook system and no dedicated ignore file. Existing lint functions warn. - -**windsurf**: add `generatePermissions` no-op stub in `generator/permissions.ts`. Windsurf terminal permissions are managed via the user settings UI. `lintPermissions` warns. diff --git a/.changeset/trae-capability-audit.md b/.changeset/trae-capability-audit.md deleted file mode 100644 index 8b9e1ff4..00000000 --- a/.changeset/trae-capability-audit.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"agentsmesh": minor ---- - -Trae: fix agents round-trip, raise hooks to native, add full test coverage. - -- **Agents (project + global, native — fixed)**: `.trae/agents/.md` files are now imported back via the descriptor importer (`preset: 'agent'`). The `importer` block previously had no `agents` key, so `agentsmesh import` silently dropped every agent file written by `agentsmesh generate`, breaking every generate→edit→import round-trip. Project agents import from `.trae/agents/`, global agents from `.trae-cn/agents/` (Trae CN edition). A canonical `TRAE_CANONICAL_AGENTS_DIR` constant and `importer-spec.ts` module were added. - -- **Hooks (project + global, partial → native)**: Trae's official documentation (docs.trae.cn/ide_hook-configuration-reference) confirms a fully writable file-based hook system. Project hooks live at `$PROJECT/.trae/hooks.json`; global hooks at `~/.trae-cn/hooks.json` (macOS/Linux). Both use a flat JSON schema: `{ "version": 1, "hooks": { "": [{ "matcher", "type", "command", "timeout"? }] } }`. `generateHooks` now serialises canonical command-type hooks to this format; `importHooks` in `importer.ts` reads them back into `hooks.yaml`. Prompt/agent hook types are dropped on both sides for a symmetric round-trip. The previous partial-level `lintHooks` warning is removed. - -- **Test coverage added**: `generateAgents` unit tests (path, frontmatter fields, tools conditional, model conditional, empty-array short-circuit, body trim); `generateHooks` unit tests (null, empty, single event, multi-event, timeout omit, prompt-drop); `rewriteGeneratedPath` tests for agents (`.trae/agents/` → `.trae-cn/agents/`) and hooks (`.trae/hooks.json` → `.trae-cn/hooks.json`); project and global agentPath tests in `descriptor-paths.test.ts`; agents + hooks import round-trip tests in `importer.test.ts` for both project and global scope. - -- **File size**: `index.ts` (was 205 lines) refactored to delegate the importer spec to `importer-spec.ts`, bringing it to 181 lines (within the 200-line rule). diff --git a/.changeset/warp-global-mcp.md b/.changeset/warp-global-mcp.md deleted file mode 100644 index d3fad35c..00000000 --- a/.changeset/warp-global-mcp.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -"agentsmesh": minor ---- - -Warp: correct project MCP path to `.warp/.mcp.json` (Warp's own native surface) and raise five additional capabilities. - -**Path correction (project MCP):** `agentsmesh generate` now writes MCP servers to `.warp/.mcp.json` at the project root — Warp's own native project-scope config — instead of `.mcp.json`. The root `.mcp.json` is a cross-tool compatibility path Warp reads via autodiscovery (not its own primary surface). `agentsmesh import --from warp` reads `.warp/.mcp.json` accordingly. - -**Capability raises (both scopes unless noted):** - -- `commands`: none → embedded — commands are projected as Warp skill bundles under `.warp/skills/`. -- `hooks`: none → partial — Warp has no file-based lifecycle hooks; a lint warning is emitted when hooks are configured (no file is generated). -- `ignore`: none → partial — Warp has no ignore-file surface; a lint warning is emitted when ignore patterns are configured. - -**Global scope only:** - -- `mcp`: none → native — `agentsmesh generate --global` writes MCP servers to `~/.warp/.mcp.json` (standard `mcpServers` JSON), and `agentsmesh import --global` reads it back to canonical. diff --git a/.changeset/windsurf-capability-audit.md b/.changeset/windsurf-capability-audit.md deleted file mode 100644 index d2f4bfaf..00000000 --- a/.changeset/windsurf-capability-audit.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -'agentsmesh': minor ---- - -fix(windsurf): raise additionalRules global partial→embedded, add permissions partial, add unit tests - -## Changes - -**globalCapabilities.additionalRules: partial → embedded (global scope)** - -Windsurf's global additional rules are embedded into the single aggregate file -`~/.codeium/windsurf/memories/global_rules.md` (confirmed by official Devin Desktop -documentation at https://docs.devin.ai/desktop/cascade/memories). Per-rule files do -not exist at global scope. `renderWindsurfGlobalInstructions` is now wired as -`globalLayout.renderPrimaryRootInstruction` and appends non-root rules via -`appendEmbeddedRulesBlock`. Branch coverage tests added to -`tests/unit/targets/windsurf/rules-branches.test.ts`. - -**permissions: none → partial (project and global scopes)** - -`windsurf.cascadeCommandsAllowList` and `windsurf.cascadeCommandsDenyList` are real -VS Code extension settings (documented at https://docs.windsurf.com/windsurf/terminal). -The settings surface is real but does not meet the native threshold: no official -documentation specifies a writable file path or workspace-scope support for these keys — -all docs reference "Command Palette → Open Settings (UI)". Partial is the accurate level -for both scopes. `lintPermissions` is added to both the project and global descriptor -lint hooks to emit a warning when canonical permissions are present. Branch coverage tests -added to `tests/unit/targets/per-target-lint-branches-2.test.ts`. - -**Deferred: .devin/rules/ path (Devin Desktop rebrand)** - -Windsurf rebranded to Devin Desktop on June 2 2026. The new preferred workspace rules -directory is `.devin/rules/` (`.windsurf/rules/` is kept as legacy fallback per official -docs). The importer, generator, import-maps, and detection paths have not been updated to -support `.devin/rules/`. This is a deferred follow-up tracked separately; the `native` -level for `additionalRules` at project scope remains accurate because `.windsurf/rules/` -is still read by Devin Desktop. diff --git a/.changeset/zed-capability-audit.md b/.changeset/zed-capability-audit.md deleted file mode 100644 index 161c36f1..00000000 --- a/.changeset/zed-capability-audit.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -'agentsmesh': minor ---- - -fix(zed): revert hooks to none, wire global skills round-trip - -## Changes - -**hooks: partial → none (project and global)** - -Zed lifecycle hooks (agent.hooks) are a 2026 GitHub proposal (#57890, -#57943) that has never shipped. No writable hooks surface exists in any -stable or preview release. Reverted both project and global hooks capability -from 'partial' to 'none'. The lintHooks descriptor entry is removed; the -generic silent-drop-guard now issues the warning when canonical hooks are -present. The lintHooks message itself ("Zed has no lifecycle hook system") -confirmed the over-claim. - -**globalCapabilities.skills: wired generator and importer (native, confirmed)** - -Zed v1.4.0+ officially reads global skills from ~/.agents/skills/ (confirmed -at https://github.com/zed-industries/zed/blob/main/docs/src/ai/skills.md). -The native claim was correct per primary docs but the round-trip was broken: - -- Added `ZED_GLOBAL_SKILLS_DIR = '.agents/skills'` constant (home-relative, - same suffix as project because Zed uses the same dir name at both scopes). -- Added `skillDir: ZED_GLOBAL_SKILLS_DIR` to globalLayout so the reference - rewriter maps skill references correctly in global scope. -- Added `.agents/skills` to `globalLayout.managedOutputs.dirs`. -- Removed the `scope === 'project'` guard in importFromZed so global import - reads skills from .agents/skills/ (relative to the home-dir projectRoot). - -The generator already emits to `.agents/skills/*` which, in global mode -(projectRoot = home dir), correctly resolves to ~/.agents/skills/*. diff --git a/CHANGELOG.md b/CHANGELOG.md index f5c01cca..5e4be165 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,350 @@ # Changelog +## 0.31.0 + +### Minor Changes + +- 580c7ea: Aider: raise MCP, Hooks, and Permissions from none to partial. + - **MCP (project, none → partial)**: Aider has no project-local MCP config file surface — MCP tool use is handled by Aider's own `--mcp` CLI flag or the `mcp` section of `~/.aider.conf.yml` (global user config, not a project file). `lintMcp` warns when canonical MCP servers are present but cannot be projected to the project scope. `generateMcp` returns `[]` (no-op stub satisfying the descriptor schema contract). + - **Hooks (project, none → partial)**: Aider has no file-based lifecycle hook system for projects. Lifecycle behavior is controlled via scripting (e.g. `--script` mode) rather than a writable config surface. `lintHooks` warns when canonical hooks are present but cannot be projected. `generateHooks` returns `[]`. + - **Permissions (project, none → partial)**: Aider has no writable permissions file in the project — tool allow/deny is configured via the `--allowed-cmds`/`--no-auto-accept-architect` CLI flags or user-global config, not a project-scope file. `lintPermissions` warns when canonical permissions are present but cannot be projected. `generatePermissions` returns `[]`. + + All three features remain `none` at global scope (Aider's `~/.aider.conf.yml` user config exists but carries no MCP-server list, hook-event list, or structured permission blocks that map faithfully from canonical form). + +- b71fd29: Amazon Q: raise Hooks and Permissions from partial to embedded; add missing ledger cells for rules/project, rules/global, and mcp/global. + - **Hooks (project + global, none → partial → embedded)**: Amazon Q agent JSON files at `.amazonq/cli-agents/*.json` support a top-level `hooks` key with triggers `agentSpawn`, `userPromptSubmit`, `preToolUse`, `postToolUse`, and `stop`. Verified at https://aws.github.io/amazon-q-developer-cli/agent-format.html. Canonical `PreToolUse`, `PostToolUse`, and `UserPromptSubmit` entries are now embedded into each generated agent JSON under the corresponding Amazon Q trigger names. `Notification`, `SubagentStart`, and `SubagentStop` have no Amazon Q equivalent — `lintHooks` warns about those events only (not about the mappable ones). `generateHooks()` is a registered no-op so the engine's dispatch path finds a generator. + - **Permissions (project + global, none → partial → embedded)**: Agent JSON files support `allowedTools` (array of tool names) and `toolsSettings` (per-tool restrictions). Canonical `permissions.allow` maps directly to `allowedTools` and is now merged with per-agent tools (deduplicated) in each generated agent JSON. `deny` and `ask` have no Amazon Q equivalent — `lintPermissions` warns about those only when they are non-empty. `generatePermissions()` is a registered no-op. + - **Round-trip (importer)**: `amazonQAgentMapper` now preserves the `hooks` key from imported agent JSON into canonical agent frontmatter, completing the generate → import → generate round-trip. + - **Ledger cells (rules/project, rules/global, mcp/global)**: These three cells were absent from `capability-ledger.json` despite the descriptor declaring `rules=native` for both scopes and `mcp=native` for global scope. Added with correct paths and format metadata. + - **Hooks/permissions ledger cells**: Updated `maxAchievable` from `partial` to `embedded` for hooks and permissions in both project and global scopes. + +- 3462660: Amp: downgrade Commands to embedded, Hooks to partial, Permissions to partial; add Agents (embedded) and Ignore (partial). + - **Commands (project + global, native → embedded)**: Amp has no declarative slash-command file format — per https://ampcode.com/manual, commands only exist via `amp.registerCommand(...)` inside a TypeScript plugin. `generateCommands()` projects each command as `.agents/skills//SKILL.md` (the same embedding `generateSkills` uses natively), so the generated output is unchanged; only the declared capability level is corrected from `native` to `embedded`. + - **Hooks (project + global, native → partial)**: `buildAmpScopedSettings()` used to write an undocumented `"amp.hooks"` key into `.amp/settings.json`. Re-verified directly against ampcode.com/manual — the word "hooks" never appears anywhere in the manual's content. Amp's only hook-like mechanism is the plugin-based `amp.on(...)` event API (code, not a declarative settings file), which qualifies as a partial surface (the product supports via unstable/code surface). `buildAmpScopedSettings()` no longer emits `amp.hooks`, and a `lintHooks` warning fires when canonical hooks exist but cannot be projected. + - **Agents (project + global, none → embedded)**: Agents are now projected as skills via `generateAgents()` (each agent emits `.agents/skills/am-agent-/SKILL.md`), raising the ceiling from `none` to `embedded`. + - **Ignore (project + global, none → partial)**: Amp has no dedicated ignore file and relies on `.gitignore`. Canonical ignore patterns are not projected; `lintIgnore` warns users. The ceiling is `partial` (the product supports the concept via an adjacent mechanism, but agentsmesh cannot write it). + - **Permissions (project + global, native → partial)**: `amp.permissions` is a LEGACY key per https://ampcode.com/manual/appendix/legacy-permissions-rules.txt. Its documented schema is an array of rule objects (`[{ tool, matches, action, context }]`), which is incompatible with the canonical `{allow,deny,ask}` string-array structure. agentsmesh cannot emit a valid `amp.permissions` value; `buildAmpScopedSettings()` no longer emits the key, `mergeAmpSettings()` no longer special-cases it, and a new `lintPermissions` warning fires when canonical permissions are present. The ceiling is `partial` (the legacy surface exists but cannot be driven from canonical config). + - Updated `src/targets/catalog/capability-ledger.json` entries for `amp/commands`, `amp/hooks`, `amp/agents`, `amp/ignore`, and `amp/permissions` across project + global scopes. + +- a9698e3: Antigravity: promote commands to native (both scopes) and correct global workflows path. + - **Commands (project + global, partial → native)**: `generateCommands()` produces `.agents/workflows/.md` files and the importer reads them back from the same directory. The old `partial` declaration was incorrect — full round-trip has always been present. Global scope commands round-trip via `ANTIGRAVITY_GLOBAL_WORKFLOWS_DIR`. + - **Global workflows path corrected**: `ANTIGRAVITY_GLOBAL_WORKFLOWS_DIR` was changed on this branch from `.gemini/antigravity/workflows` (master) to `.gemini/config/workflows` — but no primary source confirms `.gemini/config/workflows` as a valid Antigravity global workflows location. Multiple sources (GitHub Issue #16058 on google-gemini/gemini-cli and the antigravity-minimal-setup community repo) document the correct path as `~/.gemini/antigravity/global_workflows/`. The constant is now set to `.gemini/antigravity/global_workflows`. The MCP path (`.gemini/config/mcp_config.json`) and skills path (`.gemini/config/skills/`) are unaffected — both are confirmed by primary sources (Google Codelabs). + - **Global paths summary (final state)**: + - Rules (global): `~/.gemini/GEMINI.md` (aggregate, all rules embedded) + - Skills (global): `~/.gemini/config/skills/` + - Commands/workflows (global): `~/.gemini/antigravity/global_workflows/` + - MCP (global): `~/.gemini/config/mcp_config.json` + - Hooks (global): `~/.gemini/config/hooks.json` + +- 91d3885: Augment Code: raise global Hooks from none to native. + - **Hooks (global, none → native)**: `~/.augment/settings.json` supports the same `hooks` key as the project-scope `.augment/settings.json` — the `buildSettingsContent` helper already serialises canonical hooks into AugmentCode's native format (`{ event: [{ matcher, hooks: [{ type, command, timeout }] }] }`), and `importAugmentSettings` already reads them back. The global capability was previously declared `none` even though generation and import were already wired. Only the `globalCapabilities.hooks` declaration needed to change from `none` to `native`; no code changes were required. + - **Project hooks (native — unchanged)**: project-scope hooks were already `native` and are unaffected. + +- 5369c3b: Add a capability provenance ledger (`src/targets/catalog/capability-ledger.json`) plus deterministic `pnpm capabilities:audit` / `capabilities:seed` and a CI conformance test that validates each target's generated files against a recorded path/extension/structure fingerprint. Reworks the `update-target-capabilities` skill to an audit-driven flow. +- 1991a3f: `agentsmesh check` now detects drift in generated outputs. `agentsmesh generate` records a checksum for every generated file in a new `outputs` map inside `.agentsmesh/.lock` (full runs replace the map; `--targets`/`--features` runs merge per-path), and `check` re-hashes those files, failing with exit code 1 when a generated output was hand-edited or deleted. JSON output gains `outputsModified`, `outputsRemoved`, and `outputsChecked` alongside the existing canonical-drift fields, so the two drift kinds are reported separately. A new `check --no-outputs` flag skips output verification (for setups that gitignore generated outputs in CI). Locks written by earlier versions skip output verification with a hint until the next `generate` upgrades them. The MCP `check` tool performs the same output verification. Checksums are BOM- and line-ending-normalized, so CRLF-only editor rewrites do not register as drift. +- 0fa6069: Cline: rebase on the standalone CLI's documented paths (docs.cline.bot/cli/cli-reference) instead of the VS Code extension's IDE-era layout, raise Permissions, and downgrade unsupported global surfaces. + - **Rules (project + global, native — fixed)**: project rules move from `.clinerules/{slug}.md` to `.cline/rules/{slug}.md`; global rules move from `~/Documents/Cline/Rules/` to `~/.cline/data/settings/rules/`. The legacy flat-file `.clinerules` convention (no directory) is dropped — it is undocumented for the CLI; only the directory form is imported. Root-rule detection order is unchanged: `_root.md`, then `AGENTS.md`, then the first alphabetically-sorted rule file. + - **Hooks (project + global, native — fixed)**: hooks move from `.clinerules/hooks/*.sh` to `.cline/hooks/*.sh`. Project and global scope now resolve to the identical relative path (project root vs. `$HOME`), matching the CLI's documented `~/.cline/hooks` default (also configurable via `--hooks-dir`/`CLINE_HOOKS_DIR`) — the importer reads a single directory instead of merging two. + - **MCP (project, native — fixed)**: the settings file moves from `.cline/cline_mcp_settings.json` to `.cline/mcp.json`. Both the old filename and `.cline/mcp_settings.json` are still accepted on import for backward compatibility. + - **MCP (global): native → none**. No global MCP config path is documented anywhere in the CLI reference's `~/.cline/data/settings/` tree; `.cline/mcp.json` is project-only. + - **Skills (global, native — fixed)**: global skills move from the project-identical `.cline/skills/` to the documented `~/.cline/data/settings/skills/`. + - **Agents (project, native — fixed)**: generator now writes a single combined `.cline/agents.yaml` (the CLI-documented surface: "Agent definitions") instead of per-agent `.cline/agents/.md` files (the undocumented per-file format used by earlier agentsmesh versions). The YAML file has a top-level `agents:` list; each entry uses a round-trippable `name`/`description`/`model`/`tools`/`prompt` shape plus the same `x-agentsmesh-*` extension keys used by other native-agent targets. The importer now reads `.cline/agents.yaml` as the primary format and falls back to the legacy `.cline/agents/.md` directory for backward compatibility. Because `.cline/agents.yaml` is a combined file, `agentPath()` returns `null` at project scope (no per-name destination for cross-reference rewriting) — the same pattern used by roo-code's `.roomodes`. + - **Agents (global): native → none**. No `agents.yaml` or `agents/` directory is documented anywhere under `~/.cline/data/settings/` — only `providers.json`, `rules/`, and `skills/`. + - **Ignore (global): native → none**. docs.cline.bot/customization/clineignore documents `.clineignore` as strictly per-workspace-root (each monorepo workspace root can have its own); no global/home-directory ignore file exists. + - **Permissions (project + global): none → partial**. Cline has no dedicated writable permissions file in either scope — approval is controlled via the `--auto-approve` CLI flag, the `CLINE_COMMAND_PERMISSIONS` environment variable (JSON allow/deny command-glob policy), or the extension UI's Auto Approve/YOLO Mode. A no-op generator stub plus a lint warning point users at these mechanisms directly. + + Commands (workflows) are unaffected by this change and remain at the pre-existing `.clinerules/workflows/` (project) and `~/Documents/Cline/Workflows/` (global) paths — the CLI reference does not document a workflows/commands surface, so this IDE-era path is left as-is. + +- 8cae45c: Codex CLI: raise Permissions to native and fix broken AdditionalRules/Hooks/MCP. + - **Permissions (project + global): none → native**. Canonical `allow`/`ask`/`deny` command patterns now project onto `.codex/rules/agentsmesh-permissions.rules`, Codex's real Starlark `prefix_rule(pattern, decision, justification)` execution-policy DSL (allow → `allow`, ask → `prompt`, deny → `forbidden`), per https://developers.openai.com/codex/rules. `Bash([:*])` entries get a real, enforceable `prefix_rule`; every entry (including non-Bash ones like `Read`/`WebFetch`, which have no Codex command-execution equivalent) is also recorded as a `# agentsmesh-permission : ` marker comment so import recovers the exact canonical string losslessly. Uses a dedicated filename distinct from both per-rule `{slug}.rules` execution files and Codex's own `default.rules` TUI-write destination, so nothing double-writes or collides. `.codex/config.toml`'s `sandbox_mode`/`approval_policy`/`[permissions.]` schema is real but has no faithful, round-trippable mapping from canonical allow/deny/ask command patterns, so it is intentionally not used here. + - **AdditionalRules (project, native — fixed)**: scoped/non-root advisory rules now generate real nested `/AGENTS.md` (or `AGENTS.override.md`) files that Codex's documented root-to-cwd directory walk actually loads, instead of `.codex/instructions/{slug}.md` (not part of Codex's instruction-loading hierarchy) plus a link-only index in the root `AGENTS.md`. `{dir}` is the first glob's directory prefix, falling back to the rule's slug when no glob yields one (no globs, root-wildcard globs, traversal/absolute/brace-ambiguous globs). Multiple rules resolving to the same directory are joined into one file. The old `.codex/instructions/` mirror is still imported for backward compatibility with repos generated by older agentsmesh versions. + - **Hooks (project + global, native — fixed)**: canonical `Notification` hooks are no longer written to `hooks.json` — Codex's real lifecycle events are `SessionStart`, `SubagentStart`, `PreToolUse`, `PermissionRequest`, `PostToolUse`, `PreCompact`, `PostCompact`, `UserPromptSubmit`, `SubagentStop`, `Stop` (https://learn.chatgpt.com/docs/hooks); there is no `Notification` event, so it silently never fired. A new lint warning flags any unsupported event. The shared `buildWrappedCommandHooks` helper (also used by factory-droid and goose) gained an optional `supportedEvents` filter, defaulting to unfiltered so those targets are unaffected. + - **MCP (project, native — fixed)**: `.codex/config.toml`'s `[mcp_servers.]` now supports Codex's remote/Streamable HTTP transport (`url`, `bearer_token_env_var`, `http_headers`, per https://developers.openai.com/codex/mcp) on both the generator and importer side. Previously the importer required `command` and silently dropped every URL-based server, and the generator only ever emitted stdio servers. A `bearer_token_env_var` round-trips through the canonical `Authorization: Bearer ${VAR}` header convention. The stale "codex-cli only generates stdio MCP servers" lint warning is replaced with one that only fires when a remote server has env vars (which codex-cli still can't project). + +- cec0e7a: Continue: raise ignore from partial to native for both project and global scopes — generate and import `.continueignore` and `~/.continue/.continueignore`. + - **Ignore (project, partial → native)**: agentsmesh now generates `.continueignore` at the project root from canonical ignore patterns (gitignore format, one pattern per line). Import reads `.continueignore` back into `.agentsmesh/ignore`. Verified against the official Continue docs: "If you'd like to exclude additional files, you can add them to a `.continueignore` file, which follows the exact same rules as `.gitignore`." (docs.continue.dev/reference/deprecated-codebase) + - **Ignore (global, partial → native)**: agentsmesh now generates `~/.continue/.continueignore` from canonical ignore patterns in global mode. Import reads it back into `.agentsmesh/ignore`. Verified: "Continue also supports a global `.continueignore` file that will be respected for all workspaces, which can be created at `~/.continue/.continueignore`." (docs.continue.dev/reference/deprecated-codebase) + - The `lintIgnore` warning that told users to configure ignore manually is removed — generation handles it natively. + - The global importer now correctly propagates the `scope` parameter to `runDescriptorImport` (was hardcoded to `'project'`), enabling scope-correct path resolution for all descriptor-driven imports. + +- df5bf44: GitHub Copilot: raise global MCP/Hooks to native, permissions to partial, fix the project-scope hooks matcher bug, and drop the phantom global commands surface. + - **MCP (global): none → native**. `~/.copilot/mcp-config.json` (`mcpServers` key — distinct from the project-scope `.vscode/mcp.json` `servers` key) now round-trips canonical MCP servers via generator + importer, wired through `globalSupport.scopeExtras` (per https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/add-mcp-servers). The prior ledger cell for this cell pointed at the OS-specific VS Code user-profile `mcp.json` (`~/.config/Code/User/mcp.json`, `servers` key) — that models the VS Code extension's own config, not this target's `~/.copilot`-based global scope (every other global path here — `copilot-instructions.md`, `agents/`, `skills/` — lives under `~/.copilot`), so the ledger cell is corrected to Copilot CLI's own path/key. + - **Hooks (global): none → native**. `~/.copilot/hooks/agentsmesh.json` (+ wrapper scripts under `~/.copilot/hooks/scripts/`) uses the exact same `{version, hooks}` schema as project scope (https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/use-hooks), wired independently via `scopeExtras` so the project-shaped generator never leaks into global paths. + - **Permissions (global): none → partial**. `~/.copilot/permissions-config.json` records saved tool/directory approvals, but per https://docs.github.com/en/copilot/reference/copilot-cli-reference/cli-config-dir-reference it has no deny rules, ask rules, default modes, URL rules, tool filtering, or repository-local shared policy — capped at `partial` with a lint warning rather than a generator that would overclaim control. + - **Commands (global): native → none**. Global commands were generated to `.copilot/prompts/{name}.prompt.md`, but no real Copilot surface reads that path: Copilot CLI has no prompt-file/slash-command mechanism at all (maintainer, github/copilot-cli#618, closed: "we do not plan on supporting prompt files"; confirmed absent from the official `~/.copilot` config-dir reference), and VS Code Copilot Chat's own user-level prompt files live in the OS-specific VS Code profile folder, not `~/.copilot`. The phantom path is no longer generated or imported in global scope; project-scope commands (`.github/prompts/*.prompt.md`, a real VS Code Copilot Chat workspace surface) are unaffected. + - **Hooks (project, native — fixed)**: `generateHooks` now emits the real top-level `matcher` regex field (https://docs.github.com/en/copilot/reference/hooks-configuration) instead of a non-functional `comment: "Matcher: ..."` field, so hooks actually filter by tool instead of firing unconditionally for every tool. The canonical `'*'` wildcard sentinel is omitted (Copilot compiles `matcher` as `^(?:PATTERN)$`, and `"*"` alone is an invalid regex that would cause the whole entry to be skipped). The importer prefers the real field, falling back to the legacy comment for previously-generated files. (Re-verification note: the research pass also flagged canonical `Notification` hooks as unsupported by Copilot — that does not hold up against the current docs, which document `notification` as a real, supported hook event; no change was made there.) + +- 944efc2: Crush: raise Commands to embedded, Permissions to native (project + global); fix generator key, add permissions round-trip importer. + - **Commands (project + global): none → embedded**. Canonical commands are projected as skill bundles under `.crush/skills/am-command-/SKILL.md` (with `x-agentsmesh-kind: command` frontmatter) via `serializeCommandSkill`. Crush has no native slash-command file format; embedded projection via `supportsConversion` is the correct level. Import recovers commands through `importEmbeddedSkills`. + - **Permissions (project + global): partial/none → native**. Canonical `allow` list maps to `permissions.allowed_tools` and `deny` list maps to `options.disabled_tools` in `crush.json` — both confirmed against the official crush schema.json (`charmbracelet/crush`) and `internal/config/config.go` (`Permissions.AllowedTools`, `Options.DisabledTools`). The generator previously wrote `permissions.denied_tools` (a non-existent field); this is corrected to `options.disabled_tools`. A new `parseCrushPermissions` function in the importer reads both fields back into canonical `permissions.yaml`, completing the native round-trip for both project scope (`crush.json`) and global scope (`~/.config/crush/crush.json`). + - **Generator fix**: `generatePermissions` now correctly emits deny-list entries under `options.disabled_tools` instead of the non-existent `permissions.denied_tools` key. + - **Lint comment updated**: file-level comment in `lint.ts` updated to reflect native support and the correct field names. + +- 45684bd: Cursor: raise permissions to native in both scopes, fix global path to `~/.cursor/cli-config.json`. + - **Permissions (project, native)**: canonical `allow`/`deny` entries are now written to `.cursor/cli.json` (project root) and imported back, completing the round-trip. The output format is `{ "permissions": { "allow": [...], "deny": [...] } }` per the official Cursor CLI docs. + - **Permissions (global, native — path fix)**: global scope now correctly writes to `.cursor/cli-config.json` (resolved as `~/.cursor/cli-config.json`), distinct from the project-scope `.cursor/cli.json`. The previous implementation used `.cursor/cli.json` for both scopes, landing permissions at `~/.cursor/cli.json` — an undocumented path that Cursor does not read. Source: https://cursor.com/docs/cli/reference/permissions. + - **lintPermissions removed**: the stale `'Cursor permissions are partial; tool-level allow/deny may lose fidelity.'` warning no longer fires. Cursor permissions are fully round-trippable at the `native` level; emitting a partial warning was factually incorrect after the capability upgrade. + - **Importer (global scope)**: `importSettings` now reads from `.cursor/cli-config.json` when called in global scope, matching the generate path. + +- cc7de10: Deep Agents CLI: fix per-agent global paths, raise Agents to native and Commands to embedded, downgrade project Hooks to none. + - **Hooks (project, native → none)**: docs.langchain.com/oss/javascript/deepagents/code/hooks documents only the global `~/.deepagents/hooks.json` — there is no project-level hooks surface at all. `generateHooks()` is removed from the project generator; project `capabilities.hooks` is now `none`, with a `lintHooks` warning when canonical hooks exist but can't be projected. + - **Hooks (global, native, broken-fix)**: the generator wrote the Claude-Code-style `{EventName: [{matcher, hooks:[...]}]}` shape, which Deep Agents Code never reads — the real schema is a flat `{"hooks":[{"command":[...], "events":[...]}]}` array with its own dotted event vocabulary (`session.start`, `task.complete`, …). Added `hooks-format.ts` (canonical ⇄ Deep Agents event mapping, limited to the 5 events with an honest 1:1 match — `SessionStart`/`SessionEnd`/`UserPromptSubmit`/`Stop`/`PreCompact`) and moved generation to `globalSupport.scopeExtras` (gated on `scope === 'global'`) so it never leaks into project scope. Added a matching importer (`importDeepagentsCliGlobalHooks`) — hooks previously had no round-trip at all. + - **Agents (project + global, none → native)**: `.deepagents/agents/{name}/AGENTS.md` (project) and `~/.deepagents/{agent}/agents/{name}/AGENTS.md` (global) are a dedicated on-disk subagent surface, distinct from skills — previously agentsmesh only projected agents as skills (`am-agent-*`). Added a native generator/importer (`agent-format.ts`) emitting the documented frontmatter (`name`, `description`, optional `model`) instead of the richer Claude-Code-style fields no Deep Agents subagent reads. + - **Commands (project + global, none → embedded)**: no dedicated command file format exists; commands are (and already were) projected as skills via the existing `am-command-*` mechanism. Only the declared capability level was wrong — output is unchanged. + - **Global paths (broken-fix)**: global rules/skills/agents were generated to the exact same flat paths as project scope (`.deepagents/AGENTS.md`, `.deepagents/skills/`) instead of the documented per-agent-instance directory `~/.deepagents/{agent}/...` (default instance name `"agent"`). Fixed via `DEEPAGENTS_CLI_DEFAULT_AGENT_NAME` threaded through the global constants — global root/skills/agents now resolve under `.deepagents/agent/...`. MCP (`.mcp.json`) and hooks (`hooks.json`) are unaffected — both are flat, unscoped globals per the docs. + - Updated `src/targets/catalog/capability-ledger.json`: added confirmed cells for `hooks/global`, `rules/global`, `skills/global`, `agents/project`, `agents/global`, `commands/project`, `commands/global`; `hooks/project` marked `rejected`. + +- 969b4a4: Factory Droid: fix hooks output path regression and raise permissions to native. + - **Hooks (project + global, native — path corrected)**: The primary hooks surface is `.factory/hooks.json` (project) and `~/.factory/hooks.json` (global) per the official Factory Droid reference docs (`docs.factory.ai/reference/hooks-reference`). The branch had regressed this to `.factory/settings.json` (only a documented fallback when `hooks.json` is absent), which would cause generated hooks to be silently ignored whenever a real `hooks.json` exists. The generator, importer, constants, and managed-outputs arrays are restored to target `.factory/hooks.json` as the primary surface. + - **Permissions (project + global): partial → native**. `commandAllowlist` and `commandDenylist` are documented top-level keys of `.factory/settings.json` at both project and global scope (plain JSON, not GUI-only or cloud-managed). A new `generatePermissions` generator writes canonical `allow` → `commandAllowlist` and `deny` → `commandDenylist`; a new `importFactoryDroidPermissions` helper reads them back into `.agentsmesh/permissions.yaml`. The misleading lint warning (which directed users to manually edit the file we now generate) is removed. + - **`.factory/settings.json`** is now the dedicated permissions file; **`.factory/hooks.json`** is the dedicated hooks file. Both are tracked in `managedOutputs.files`. + +- b4b78f5: Gemini CLI: upgrade hooks from partial to native and extend canonical↔Gemini hook event mapping to all supported events. + - **Hooks (project + global): partial → native**. Gemini CLI reads hooks natively from `.gemini/settings.json` (project) and `~/.gemini/settings.json` (global), confirmed against upstream `settingsSchema.ts`. Both scopes are file-writable with full round-trip support. + - **Extended hook event mapping**: the generator and importer previously mapped only 3 events (PreToolUse↔BeforeTool, PostToolUse↔AfterTool, Notification↔Notification). Gemini CLI's schema defines 11 hook events; 6 now have canonical equivalents and are fully wired: + + | Canonical (agentsmesh) | Gemini CLI | + | ---------------------- | ---------------------- | + | `PreToolUse` | `BeforeTool` | + | `PostToolUse` | `AfterTool` | + | `Notification` | `Notification` | + | `SubagentStart` | `BeforeAgent` _(new)_ | + | `SubagentStop` | `AfterAgent` _(new)_ | + | `SessionStart` | `SessionStart` _(new)_ | + + The 5 Gemini-only events (`SessionEnd`, `PreCompress`, `BeforeModel`, `AfterModel`, `BeforeToolSelection`) have no canonical equivalent and are silently dropped on import, as before for any unmapped event. The lint `supported` list is updated to match; `SubagentStart`, `SubagentStop`, and `SessionStart` no longer produce spurious unsupported-event warnings. + +- 27799dd: Goose: raise commands/agents to embedded, mcp/permissions to partial, fix lintMcp scope-gate. + - **commands (project + global): none → embedded**. Canonical commands are projected as Goose skills under `.agents/skills//SKILL.md` with `name`/`description` frontmatter, discoverable by Goose's skills system at both project and global (`~/.agents/skills/`) scope. + - **agents (project + global): none → embedded**. Canonical agents are projected as skills under `.agents/skills//SKILL.md`, consistent with the commands projection path. + - **mcp (project): none → partial**. Goose has no per-project MCP config file; all MCP extensions live in `~/.config/goose/config.yaml` (global, native). A lint warning is emitted when canonical MCP servers are present at project scope, directing users to configure extensions globally via `goose configure`. + - **permissions (project): none → partial**. Goose tool permissions live exclusively in `~/.config/goose/permission.yaml` (global, native). A lint warning is emitted when canonical permissions are present at project scope. + - **lintMcp scope-gate (bug fix)**. `lintMcp` previously accepted no `options` argument and always emitted a "project-level MCP is not projected" warning, even at global scope where MCP is native. The function now matches the `lintPermissions` pattern: it accepts `options?: unknown`, reads `scope` via narrowing, and returns `[]` at global scope. + +- ac93729: Jules: raise Commands, MCP, Hooks, Ignore, and Permissions from none to partial; fix engine ignore-dispatch gap. + - **Commands (project, none → partial)**: Jules has no slash-command or prompt-file mechanism. Tasks are submitted via GitHub issues or the web UI. No commands config file surface exists. `lintCommands` warns when canonical commands are present but cannot be projected. + - **MCP (project, none → partial)**: No MCP configuration surface is mentioned anywhere in Jules documentation. Jules is cloud-hosted with no writable file surface for MCP. `lintMcp` warns when canonical MCP servers are present but cannot be projected. + - **Hooks (project, none → partial)**: Jules has no lifecycle hook system. It is async and cloud-based with no local hook execution mechanism. `lintHooks` warns when canonical hooks are present but cannot be projected. + - **Ignore (project, none → partial)**: Jules has no dedicated ignore file (no `.julesignore` or similar surface). It is a cloud agent with no local ignore mechanism. `lintIgnore` warns when canonical ignore patterns are present but cannot be projected. + - **Permissions (project, none → partial)**: Jules has no permissions configuration file. Permission-like controls exist only via GitHub PR review workflows (GUI), not a writable file. `lintPermissions` warns when canonical permissions are present but cannot be projected. + - **Engine fix — ignore dispatch gap (linter.ts)**: `descriptor.lint?.ignore` was never dispatched by the engine. The `lintSilentFeatureDrops` guard only fires for `capability.level === 'none'`, so promoting ignore from `none` to `partial` silently dropped the only warning path. The engine now dispatches `descriptor.lint?.ignore` when the `ignore` feature is enabled, matching how commands, mcp, permissions, and hooks are dispatched. This fix applies to all targets with `ignore: 'partial'` and a `lint.ignore` hook. + + All primary-doc claims verified against https://jules.google/docs. + +- 5dcf731: fix(junie): correct allowlist.json schema, raise hooks/global to embedded, raise permissions/project to partial + + **Breaking fix — allowlist.json schema correction (permissions/global)** + + `generatePermissions` previously emitted `rules` as a flat array of + `{type, name, behavior}` objects. The real `~/.junie/allowlist.json` schema + requires `rules` to be an object with four categorized sub-keys + (`fileEditing`, `executables`, `mcpTools`, `readOutsideProject`), each + containing a `rules` array of `{prefix|pattern, action}` items — no `type`, + `name`, or `behavior` field exists anywhere in the real schema. The old output + was silently ignored by Junie, making all permission rules non-functional. + Canonical allow/deny/ask entries are now mapped to the `executables` category + using `prefix` (literal) or `pattern` (glob) fields with `action: allow|ask` + (Junie has no deny action; deny is mapped to ask as the safe equivalent). + + **hooks/global raised: partial → embedded** + + `~/.junie/config.json` is a writable multi-feature file with a top-level + `hooks` key that Junie auto-loads. Hooks are now folded into this file via + `emitScopedSettings`. A `mergeGeneratedOutputContent` hook preserves + pre-existing keys (model, provider, brave, mcp-locations, etc.) on + regeneration. The lint warning for project-scope hooks (which require + `--config-location` and are ignored from the default project config file for + safety) is preserved. + + **permissions/project raised: none → partial** + + `.junie/config.json` at project scope exposes a `brave` boolean (auto-approve + mode). This is a coarse project-level permission control. A `lintPermissions` + warning is now emitted when granular allow/deny/ask rules are configured, + explaining that only the `brave` flag is available at project scope. + + **generator.ts split** + + Global-scope config emitters (`generatePermissions`, `emitJunieScopedSettings`, + `mergeJunieConfig`) moved to a new `global-config.ts` module to keep both + files under 200 lines. + +- 41fd095: Kilo Code: fix broken global-scope capabilities, downgrade global Ignore, raise Hooks to partial. + - **Rules / AdditionalRules / Commands / Agents (global, native — fixed)**: global-scope paths now point at the documented `~/.config/kilo/` unified config directory instead of the old `~/.kilo/` mirror. Per https://kilo.ai/docs/getting-started/settings, kilo reads global config from `~/.config/kilo/kilo.jsonc` (and its sibling `~/.config/kilo/AGENTS.md`), not `~/.kilo/`. Root rule → `~/.config/kilo/AGENTS.md` (https://kilo.ai/docs/customize/custom-instructions); commands → `~/.config/kilo/commands/*.md` (https://kilo.ai/docs/customize/workflows); agents → `~/.config/kilo/agents/*.md` (https://kilo.ai/docs/customize/custom-subagents — note `custom-modes.md` inconsistently shows a singular `agent/` in three spots, but the dedicated `custom-subagents.md` page states plural `agents/` in its Configuration Precedence list, Method 2 directory list, and legacy-migration note; treated as authoritative). Additional (non-root) rules are now also registered under the `instructions` key of the shared `kilo.jsonc` (https://kilo.ai/docs/customize/custom-rules) — a bare `.kilo/rules/`-style directory is not auto-loaded at global scope, only `AGENTS.md` is. Global Skills were re-verified and are unchanged: `~/.kilo/skills/` is the correct, currently-documented location (https://kilo.ai/docs/customize/skills), separate from the `~/.config/kilo/` migration. + - **MCP (global, native — fixed)**: MCP servers now fold into the `mcp` key of the shared `~/.config/kilo/kilo.jsonc` (https://kilo.ai/docs/automate/mcp/using-in-kilo-code) using kilo's own schema (`{ type: "local"|"remote", command: [...], environment, url, headers }`), instead of a standalone `~/.kilo/mcp.json` with the unrelated `mcpServers` wrapper kilo does not read at global scope. A new `importGlobalKiloMcp()` reads it back. `mergeKiloConfig` now overlays `instructions` and `mcp` alongside the existing `permission` key so permissions, rules, and MCP writes to the same file compose correctly across one generate run (`pending?.content ?? existing` base, per this repo's settings-merge-discipline rule). + - **Ignore (global): native → none**. `~/.kilocodeignore` is no longer generated or imported. Per https://kilo.ai/docs/customize/context/kilocodeignore, `.kilocodeignore` is documented as a workspace-root-only file (patterns evaluated relative to the workspace root, auto-migrated into project-scope `permission` deny-rules) — there is no global ignore file or `kilo.jsonc` key. + - **Hooks (project + global, none → partial)**: Kilo Code hooks are supported via auto-loaded plugin files at `.kilo/plugin/*.{ts,js}` (and their global equivalents), not via a writable config surface. agentsmesh cannot generate plugin code from canonical hook definitions. `lintHooks` warns when canonical hooks are present and directs users to author plugin files manually. No hook file is generated at either scope. + - Updated `src/targets/catalog/capability-ledger.json`: added confirmed `kilo-code` global cells for `rules`, `additionalRules`, `commands`, `agents`, `skills`, and `mcp`; added a rejected `kilo-code/ignore/global` cell recording the downgrade rationale. + +- 054ff57: kiro: migrate hook schema to v1, raise hooks/global and permissions to partial + - Hook JSON schema migrated from deprecated beta format (`version:"1"`, `when`/`then`, `askAgent`/`shellCommand`) to the current Kiro v1.0.0 format (`version:"v1"`, top-level `hooks` array, `trigger`/`action` with `agent`/`command` types). Old-format hooks are no longer active in Kiro IDE v1.0.0+. + - Hook file extension renamed from `.kiro.hook` to `.json` (Kiro v1.0.0 requires `.json`). + - `globalCapabilities.hooks` raised from `none` to `partial` (global ~/.kiro/hooks/ path does not exist; hooks are workspace-only). + - `globalCapabilities.permissions` raised from `none` to `partial` (Kiro v3 CLI exposes `~/.kiro/settings/permissions.yaml`; agentsmesh does not yet generate it). + - `capabilities.permissions` (project scope) raised from `none` to `partial` (workspace permissions live at `~/.kiro/workspace-roots//permissions.yaml`, outside the repo). + - `lintPermissions` added: emits a warning when canonical permissions are non-empty, directing users to configure permissions manually. + +- 27f3a61: OpenCode: fix broken AdditionalRules and Agents, raise Hooks and Ignore to partial. + - **AdditionalRules (project + global, native — fixed)**: `.opencode/rules/.md` files are now also declared in `opencode.json`'s `instructions` array (project: `.opencode/rules/*.md`; global: an absolute `~/.config/opencode/rules/*.md`). Per https://opencode.ai/docs/rules/, OpenCode does not auto-scan any rules directory — only `AGENTS.md`/`CLAUDE.md` auto-discover via directory traversal, and every other instruction file needs an explicit `instructions` entry. Previously the generated rule files were invisible to stock OpenCode. + - **Engine gate fix (core)**: `generateScopedSettingsFeature` was only invoked when at least one of `mcp`, `ignore`, `hooks`, `agents`, or `permissions` was enabled — `rules` was absent from the gate condition. With `features: ['rules']` alone (no other features), `emitOpenCodeScopedSettings` was never reached, so the `instructions` glob was never written to `opencode.json`. The gate now also fires when `hasRules` is true, making the instructions entry visible again for rules-only configs. + - **Agents (project + global, native — fixed)**: `.opencode/agents/.md` now emits a real `permission` object (e.g. `permission: { edit: deny }`) mapped from canonical `tools`/`disallowedTools`, instead of `tools`/`disallowedTools` frontmatter keys. Per https://opencode.ai/docs/agents/, OpenCode has no `disallowedTools` key at all, and `tools` is deprecated ("Prefer the agent's permission field") and takes a boolean-map shape, not a string array — both emitted keys were silently non-functional. The importer now translates an imported `permission` object back into canonical `tools`/`disallowedTools` so the restriction round-trips (categorically, not by original tool name). + - **Fix (opencode.json merge)**: `mergeOpenCodeSettings` previously delegated to the generic Claude-shaped settings merger, which only ever carried over `permissions`(plural)/`hooks` from freshly generated content — silently freezing `mcp`/`permission`/`instructions` at whatever a first `generate` wrote, on every subsequent regenerate. It now merges OpenCode's own `mcp`/`permission`/`instructions` keys directly. + - **Hooks (project, none → partial)**: OpenCode hooks are plugin-based TypeScript/JavaScript lifecycle events (`.opencode/plugins/`), not a writable config surface that agentsmesh can generate. `lintHooks` warns when canonical hooks are present, directing users to author plugins manually. No hook file is generated. + - **Ignore (project, none → partial)**: OpenCode's ignore configuration lives under the `watcher.ignore` key of `opencode.json` rather than a standalone file, and mapping canonical glob-patterns to that key has no clean round-trip. `lintIgnore` warns when canonical ignore patterns are present, directing users to configure `watcher.ignore` manually. No ignore file is generated. + +- 27c977f: Correct pi-agent capability levels based on primary-source verification of the earendil-works/pi repository. + - `mcp` (project + global): `partial` → `none`. A full recursive tree scan of the earendil-works/pi repository finds zero MCP-related source files. Pi has no native MCP config file surface — neither at project scope (`.pi/`) nor global scope (`~/.pi/agent/`). The extension system supports custom TypeScript tools and lifecycle events but not the MCP protocol. Declaring `partial` was inaccurate; `none` re-enables the silent-drop guard. The `lintMcp` stub (which falsely claimed MCP was managed via `extensions`) is removed. + - `hooks` (project + global): `none` → `partial`. Pi Agent lifecycle hooks are supported via TypeScript extensions auto-discovered from `.pi/extensions/` (project) and `~/.pi/agent/extensions/` (global). These are hand-authored code files, not a writable config surface, which justifies `partial`. A `lintHooks` warning is emitted when canonical hooks are present. + - `ignore` (project + global): `none` → `partial`. Pi has no dedicated ignore file; it relies on `.gitignore`. A `lintIgnore` warning is emitted to inform users that canonical ignore patterns are not projected. + - `permissions` (project + global): `none` → `partial`. Pi has no built-in permissions config; permissions can be implemented via extension hooks. A `lintPermissions` warning is emitted when canonical permissions are present. + +- c24a762: Qwen Code: raise global AdditionalRules to native and fix broken rule/command frontmatter keys. + - **AdditionalRules (global, embedded → native)**: non-root rules now generate real files under `~/.qwen/rules/.md` instead of being folded into `~/.qwen/QWEN.md`'s body. Qwen Code's `loadRules()` (`rulesDiscovery.ts`) reads `.qwen/rules/` recursively from **both** the global `~/.qwen` dir and the project dir with the identical mechanism, so there's no reason to embed. A new `QWEN_GLOBAL_RULES_DIR` constant, global layout rewrite, and importer-spec global source wire this up; the now-unused `renderQwenGlobalInstructions` embedding helper is removed. + - **AdditionalRules (project + global, native — fixed)**: the emitted frontmatter key for path-scoped rules changes from `globs:` to `paths:` in `.qwen/rules/.md`. Qwen Code's `parseRuleFile()` only recognizes `paths:` for conditional (turn-level lazy) rule injection — `globs:` was never read, so any canonical rule with path-scoping silently became an always-injected baseline rule. The importer's `frontmatterRemap` now maps the on-disk `paths:` key back to the canonical `globs` field so round-tripping still works. + - **Commands (project + global, native — fixed)**: the generator no longer emits `allowed-tools` into `.qwen/commands/.md` frontmatter. Qwen Code's `MarkdownCommandDefSchema` (`markdown-command-parser.ts`) only maps `description`, `argument-hint`, `when_to_use`, and `disable-model-invocation` — there is no tool-restriction field, so the value was silently ignored. A new `lintCommands` warning flags canonical commands with non-empty `allowedTools` for this target. + + Source: https://github.com/QwenLM/qwen-code/blob/main/packages/core/src/utils/rulesDiscovery.ts, https://github.com/QwenLM/qwen-code/blob/main/packages/cli/src/services/markdown-command-parser.ts, https://github.com/QwenLM/qwen-code/blob/main/packages/cli/src/services/FileCommandLoader.ts + +- 73b4872: replit-agent: raise MCP, Hooks, Ignore, and Permissions from none to partial; add no-op generator stubs to satisfy schema contract. + - **MCP (project, none → partial)**: Replit Agent MCP servers are configured exclusively via the Integrations UI pane, not via any project-local file. `lintMcp` warns when canonical MCP servers are present but cannot be projected. `generateMcp` is a no-op stub (returns `[]`) satisfying the descriptor schema contract. + - **Hooks (project, none → partial)**: Replit Agent has no lifecycle hook file surface. Hook state transitions (Draft, Active, Queued, etc.) are internal platform states, not user-writable hooks. `lintHooks` warns when canonical hooks are present but cannot be projected. `generateHooks` is a no-op stub (returns `[]`). + - **Ignore (project, none → partial)**: Replit Agent has no dedicated ignore file (no `.replitignore` or similar). The agent relies on `.gitignore` for version-control purposes, but no Replit-specific file-based ignore surface exists. `lintIgnore` warns when canonical ignore patterns are present but cannot be projected. `generateIgnore` is a no-op stub (returns `[]`). + - **Permissions (project, none → partial)**: Replit Agent permissions are managed in the cloud UI with no writable file surface in the project. `lintPermissions` warns when canonical permissions are present but cannot be projected. `generatePermissions` is a no-op stub (returns `[]`). + + All primary-doc claims verified against https://docs.replit.com/references/mcp/overview, https://docs.replit.com/replitai/agent, https://docs.replit.com/references/agent/task-lifecycle, and https://docs.replit.com/replitai/replit-dot-md. + +- d715e69: Roo Code: fix broken capabilities and correct mislabeled support levels. + - **Agents (project, native — fixed)**: `.roomodes` custom modes now include a `groups` array (mapped from canonical agent `tools`) and always a non-empty `roleDefinition`. Roo Code's `modeConfigSchema` requires both fields with no default — omitting either made `CustomModesManager.loadModesFromFile()` silently drop every mode in the file. + - **Agents (global, partial — fixed)**: the settings file now writes to `~/.roo/settings/custom_modes.yaml` (was missing the `.roo/` prefix, i.e. `~/settings/custom_modes.yaml`) and carries the same `groups`/`roleDefinition` fix. Stays `partial`: Roo Code's real read path is a non-deterministic per-OS/per-fork VS Code extension `globalStorage` directory that `--global` cannot resolve deterministically. + - **Rules (global, native — fixed)**: the root rule now stays at `~/.roo/rules/00-root.md` instead of being redirected to `~/.roo/AGENTS.md`. Roo Code's `loadRuleFiles()` reads `.roo/rules/` from both the global `~/.roo` directory and the project directory; `loadAllAgentRulesFiles()` never reads a home-directory `AGENTS.md`. The old `~/.roo/AGENTS.md` path is kept as a lowest-priority import fallback for users migrating from the old (buggy) output. + - **MCP (global): native → partial**. `~/mcp_settings.json` is still written, but Roo Code's `McpHub.getMcpSettingsFilePath()` actually resolves via `context.globalStorageUri.fsPath + '/settings/mcp_settings.json'` — a non-deterministic per-OS/per-fork path agentsmesh cannot target. A new global-scope lint warning explains this. + - **Ignore (global): native → none**. `~/.rooignore` is no longer generated or imported. `RooIgnoreController` only ever reads `.rooignore` from the open workspace (`path.join(cwd, '.rooignore')`) — there is no home-directory/global ignore concept. A new global-scope lint warning explains the drop. + - **Permissions (project): partial → native**. Canonical `allow`/`deny` command-prefix rules are now written to (and read back from) `.vscode/settings.json` under `roo-cline.allowedCommands` / `roo-cline.deniedCommands` — real, workspace-scoped VS Code settings Roo Code's `package.json` contributes without a `scope: application` restriction. The file is merged (read-modify-write), never overwritten. Scoped to command-prefix allow/deny only — canonical "ask" rules have no Roo Code equivalent and are lint-warned instead. Global scope stays `partial` (no deterministic VS Code user-settings path). + - **Import-map (global root-rule) — fixed**: `src/core/reference/import-maps/roo-code.ts` now registers `.roo/rules/00-root.md` (`ROO_CODE_GLOBAL_ROOT_RULE`) as the primary `_root.md` alias in global scope, and adds a skip guard in the global rules-dir iteration so the root file is not double-mapped to `.agentsmesh/rules/00-root.md`. The legacy `.roo/AGENTS.md` path is kept as a secondary fallback alias for users migrating from the old (buggy) output. Previously, cross-reference rewriting for the global root rule was broken: links pointing to the root resolved to `.agentsmesh/rules/00-root.md` instead of `.agentsmesh/rules/_root.md`. + +- e61f789: Rovo Dev: raise Commands and Agents to native/embedded, add Ignore partial, fix MCP path, fix permissions schema. + - **Commands (project + global, none → native)**: `.rovodev/prompts.yml` (repo root) and `~/.rovodev/prompts.yml` (global user prompts) are real, documented saved-prompts manifests — https://support.atlassian.com/rovo/docs/save-and-reuse-a-prompt-in-rovo-dev-cli/ documents both the repo-root/cwd tier and the `~/.rovodev/prompts.yml` "global user prompts" tier. Canonical commands now generate a `prompts.yml` manifest entry (`name`/`description`/`content_file`) plus a `.rovodev/commands/.md` content file, and both are read back on import. Commands are no longer projected as skills (`am-command-*` skill dirs) for this target. + - **Agents (project + global, none → embedded)**: Rovo Dev has no native agent file format; agents are projected as skill bundles under `.rovodev/skills/am-agent-/SKILL.md` (project) and `~/.rovodev/skills/am-agent-/SKILL.md` (global) via `supportsConversion: { agents: true }`. The old `supportsConversion: { commands: true, agents: true }` for commands is removed since commands are now native (no longer need conversion). + - **Ignore (project + global, none → partial)**: Rovo Dev has no dedicated project-level ignore file surface. `lintIgnore` warns when canonical ignore patterns are present but cannot be projected. + - **MCP (project): native → partial**. No project-level MCP config file is documented for Rovo Dev — only `~/.rovodev/mcp_config.json` (global) exists. `.rovodev/mcp.json` is no longer generated or imported at project scope; a `lintMcp` warning explains the drop to users. (Final level is `partial`, not `none` — the lint stub satisfies the schema contract.) + - **MCP (global): native — fixed path**. The generated/imported file is renamed from `~/.rovodev/mcp.json` to `~/.rovodev/mcp_config.json`, the actual documented filename (configurable via `mcp.mcpConfigPath` in `~/.rovodev/config.yml`). + - **Permissions (global): native — fixed schema**. `~/.rovodev/config.yml`'s `toolPermissions` now emits the real nested shape (`toolPermissions.tools.: allow|ask|deny`, with bash rules under `toolPermissions.tools.bash.default` / `toolPermissions.tools.bash.commands[]`) instead of a flat `{allow:[],deny:[],ask:[]}` list the CLI never reads. + + Source: https://support.atlassian.com/rovo/docs/manage-rovo-dev-cli-settings/, https://support.atlassian.com/rovo/docs/save-and-reuse-a-prompt-in-rovo-dev-cli/ + +- f5df6c1: Trae: fix agents round-trip, raise hooks to native, add full test coverage. + - **Agents (project + global, native — fixed)**: `.trae/agents/.md` files are now imported back via the descriptor importer (`preset: 'agent'`). The `importer` block previously had no `agents` key, so `agentsmesh import` silently dropped every agent file written by `agentsmesh generate`, breaking every generate→edit→import round-trip. Project agents import from `.trae/agents/`, global agents from `.trae-cn/agents/` (Trae CN edition). A canonical `TRAE_CANONICAL_AGENTS_DIR` constant and `importer-spec.ts` module were added. + - **Hooks (project + global, partial → native)**: Trae's official documentation (docs.trae.cn/ide_hook-configuration-reference) confirms a fully writable file-based hook system. Project hooks live at `$PROJECT/.trae/hooks.json`; global hooks at `~/.trae-cn/hooks.json` (macOS/Linux). Both use a flat JSON schema: `{ "version": 1, "hooks": { "": [{ "matcher", "type", "command", "timeout"? }] } }`. `generateHooks` now serialises canonical command-type hooks to this format; `importHooks` in `importer.ts` reads them back into `hooks.yaml`. Prompt/agent hook types are dropped on both sides for a symmetric round-trip. The previous partial-level `lintHooks` warning is removed. + - **Test coverage added**: `generateAgents` unit tests (path, frontmatter fields, tools conditional, model conditional, empty-array short-circuit, body trim); `generateHooks` unit tests (null, empty, single event, multi-event, timeout omit, prompt-drop); `rewriteGeneratedPath` tests for agents (`.trae/agents/` → `.trae-cn/agents/`) and hooks (`.trae/hooks.json` → `.trae-cn/hooks.json`); project and global agentPath tests in `descriptor-paths.test.ts`; agents + hooks import round-trip tests in `importer.test.ts` for both project and global scope. + - **File size**: `index.ts` (was 205 lines) refactored to delegate the importer spec to `importer-spec.ts`, bringing it to 181 lines (within the 200-line rule). + +- c09be1c: Warp: correct project MCP path to `.warp/.mcp.json` (Warp's own native surface) and raise five additional capabilities. + + **Path correction (project MCP):** `agentsmesh generate` now writes MCP servers to `.warp/.mcp.json` at the project root — Warp's own native project-scope config — instead of `.mcp.json`. The root `.mcp.json` is a cross-tool compatibility path Warp reads via autodiscovery (not its own primary surface). `agentsmesh import --from warp` reads `.warp/.mcp.json` accordingly. + + **Capability raises (both scopes unless noted):** + - `commands`: none → embedded — commands are projected as Warp skill bundles under `.warp/skills/`. + - `hooks`: none → partial — Warp has no file-based lifecycle hooks; a lint warning is emitted when hooks are configured (no file is generated). + - `ignore`: none → partial — Warp has no ignore-file surface; a lint warning is emitted when ignore patterns are configured. + + **Global scope only:** + - `mcp`: none → native — `agentsmesh generate --global` writes MCP servers to `~/.warp/.mcp.json` (standard `mcpServers` JSON), and `agentsmesh import --global` reads it back to canonical. + +- 5be661a: fix(windsurf): raise additionalRules global partial→embedded, add permissions partial, add unit tests + + ## Changes + + **globalCapabilities.additionalRules: partial → embedded (global scope)** + + Windsurf's global additional rules are embedded into the single aggregate file + `~/.codeium/windsurf/memories/global_rules.md` (confirmed by official Devin Desktop + documentation at https://docs.devin.ai/desktop/cascade/memories). Per-rule files do + not exist at global scope. `renderWindsurfGlobalInstructions` is now wired as + `globalLayout.renderPrimaryRootInstruction` and appends non-root rules via + `appendEmbeddedRulesBlock`. Branch coverage tests added to + `tests/unit/targets/windsurf/rules-branches.test.ts`. + + **permissions: none → partial (project and global scopes)** + + `windsurf.cascadeCommandsAllowList` and `windsurf.cascadeCommandsDenyList` are real + VS Code extension settings (documented at https://docs.windsurf.com/windsurf/terminal). + The settings surface is real but does not meet the native threshold: no official + documentation specifies a writable file path or workspace-scope support for these keys — + all docs reference "Command Palette → Open Settings (UI)". Partial is the accurate level + for both scopes. `lintPermissions` is added to both the project and global descriptor + lint hooks to emit a warning when canonical permissions are present. Branch coverage tests + added to `tests/unit/targets/per-target-lint-branches-2.test.ts`. + + **Deferred: .devin/rules/ path (Devin Desktop rebrand)** + + Windsurf rebranded to Devin Desktop on June 2 2026. The new preferred workspace rules + directory is `.devin/rules/` (`.windsurf/rules/` is kept as legacy fallback per official + docs). The importer, generator, import-maps, and detection paths have not been updated to + support `.devin/rules/`. This is a deferred follow-up tracked separately; the `native` + level for `additionalRules` at project scope remains accurate because `.windsurf/rules/` + is still read by Devin Desktop. + +- 4cca64a: fix(zed): revert hooks to none, wire global skills round-trip + + ## Changes + + **hooks: partial → none (project and global)** + + Zed lifecycle hooks (agent.hooks) are a 2026 GitHub proposal (#57890, + #57943) that has never shipped. No writable hooks surface exists in any + stable or preview release. Reverted both project and global hooks capability + from 'partial' to 'none'. The lintHooks descriptor entry is removed; the + generic silent-drop-guard now issues the warning when canonical hooks are + present. The lintHooks message itself ("Zed has no lifecycle hook system") + confirmed the over-claim. + + **globalCapabilities.skills: wired generator and importer (native, confirmed)** + + Zed v1.4.0+ officially reads global skills from ~/.agents/skills/ (confirmed + at https://github.com/zed-industries/zed/blob/main/docs/src/ai/skills.md). + The native claim was correct per primary docs but the round-trip was broken: + - Added `ZED_GLOBAL_SKILLS_DIR = '.agents/skills'` constant (home-relative, + same suffix as project because Zed uses the same dir name at both scopes). + - Added `skillDir: ZED_GLOBAL_SKILLS_DIR` to globalLayout so the reference + rewriter maps skill references correctly in global scope. + - Added `.agents/skills` to `globalLayout.managedOutputs.dirs`. + - Removed the `scope === 'project'` guard in importFromZed so global import + reads skills from .agents/skills/ (relative to the home-dir projectRoot). + + The generator already emits to `.agents/skills/*` which, in global mode + (projectRoot = home dir), correctly resolves to ~/.agents/skills/\*. + +### Patch Changes + +- 059b177: Capability ledger: add a `text` format for plain-text surfaces (shell hook scripts, Starlark permission rules, gitignore-style files) so conformance checks them by path/extension instead of mislabeling them as markdown. +- 0ea9e4f: Crush: fix global-scope settings merge — `mergeGeneratedOutputContent` now also matches the global `~/.config/crush/crush.json` path, so mcp, hooks, and permissions merge into one file instead of overwriting each other in global mode. +- e205914: Fix capability-ledger engine: preserve researched maxAchievable ceilings and full fingerprints during merge, report over-declared cells independently from unverified. + - `scripts/merge-capability-ledger.ts`: fix two data-loss bugs — (1) confirmed/rejected cells now keep their researched `maxAchievable` ceiling instead of being overwritten by the descriptor level; (2) fingerprint preservation now checks all three arrays (topLevelKeys, requiredFrontmatter, keyChecks), not just topLevelKeys, so manually-added keyChecks and requiredFrontmatter entries are no longer silently wiped. + - `src/core/capabilities/merge.ts`: extract merge logic into a pure, unit-tested module (`mergeCell`, `hasNonEmptyFingerprint`). `pnpm capabilities:merge` is now a registered script. + - `src/core/capabilities/audit.ts`: report `over-declared` independently from `unverified` — a cell with `verifiedAt=null` and a descriptor that exceeds its `maxAchievable` now appears in the stale bucket with both reasons rather than masking the over-declared signal. + +- 2f27b64: schema-test: add no-op generator stubs to satisfy descriptor schema contract across 9 targets; remove pending-validation skip from the builtin-descriptor schema test so all builtins are validated on every run. + + The descriptor schema validator requires a `generateX` function (or `emitScopedSettings` / `globalSupport.scopeExtras`) for every capability whose level is not `'none'`. Nine built-in targets declared partial/native capabilities without a matching generator, causing the schema test to skip them. This change adds minimal no-op stubs (returning `[]`) to each affected target and removes the skip set so the test now validates all 30 built-in descriptors. + + **aider**: add `generateMcp`, `generateHooks`, `generatePermissions` no-op stubs. Aider has no MCP config file, no lifecycle hook system, and no permissions config. Existing lint functions (`lintMcp`, `lintHooks`, `lintPermissions`) surface advisory warnings. + + **deepagents-cli**: add `generateIgnore`, `generatePermissions` no-op stubs. Deep Agents CLI has no dedicated ignore file (relies on `.gitignore`) and permissions are env-var-based. Existing lint functions warn. + + **factory-droid**: add `generateIgnore` no-op stub. Factory Droid relies on `.gitignore` for ignore; the existing `lintIgnore` warns when canonical ignore patterns are present. + + **goose**: add `generatePermissions` no-op stub. Goose permissions are global-only (`~/.config/goose/permission.yaml`, emitted by `scopeExtras`); project-scope has no file surface. `lintPermissions` warns at project scope. + + **jules**: add `generateCommands`, `generateMcp`, `generateHooks`, `generateIgnore`, `generatePermissions` no-op stubs. Jules is a cloud-based async agent that only reads `AGENTS.md`; none of these surfaces exist. Existing lint functions warn. + + **kiro**: add `generatePermissions` no-op stub. Kiro permissions.yaml is not yet generated by agentsmesh. `lintPermissions` warns. + + **pi-agent**: add `generateHooks`, `generateIgnore`, `generatePermissions` no-op stubs. Pi Agent hooks are extension-based (not yet generated), has no dedicated ignore file, and no permissions config. Existing lint functions warn. + + **warp**: add `generateHooks`, `generateIgnore` no-op stubs. Warp has no lifecycle hook system and no dedicated ignore file. Existing lint functions warn. + + **windsurf**: add `generatePermissions` no-op stub in `generator/permissions.ts`. Windsurf terminal permissions are managed via the user settings UI. `lintPermissions` warns. + ## 0.30.2 ### Patch Changes diff --git a/package.json b/package.json index 3d941ca4..6607bdfd 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "agentsmesh", - "version": "0.30.2", + "version": "0.31.0", "description": "One canonical source for AI coding agent rules, commands, skills, MCP, hooks, and permissions — synced across every major AI coding tool.", "type": "module", "main": "./dist/index.js", From 9efbab1f0c7412361a8f0099d4f03bb2e7b49161 Mon Sep 17 00:00:00 2001 From: VVeb1250 Date: Tue, 21 Jul 2026 15:30:53 +0900 Subject: [PATCH 3/7] fix(check): detect stale generated outputs --- .changeset/check-stale-outputs.md | 5 ++ src/cli/command-result.ts | 6 +++ src/cli/commands/check.ts | 7 +++ src/cli/renderers/check.ts | 3 ++ src/core/check/lock-sync.ts | 45 ++++++++++++++---- src/core/generate/stale-cleanup.ts | 31 +++++++------ src/mcp/handlers/orchestrate-types.ts | 4 ++ src/mcp/handlers/orchestrate.ts | 4 ++ src/mcp/tool-tables/orchestrate-tools.ts | 2 +- tests/e2e/check-outputs.e2e.test.ts | 46 ++++++++++++++++++- .../mcp-generate-lock.integration.test.ts | 3 ++ tests/unit/cli/commands/check.test.ts | 9 ++++ tests/unit/cli/renderers/check.test.ts | 19 ++++++++ tests/unit/core/check/lock-sync.test.ts | 40 ++++++++++++++++ tests/unit/mcp/handlers/orchestrate.test.ts | 9 ++++ website/src/content/docs/cli/check.mdx | 6 +-- .../docs/guides/ci-drift-detection.mdx | 4 +- .../src/content/docs/reference/mcp-server.mdx | 2 +- 18 files changed, 216 insertions(+), 29 deletions(-) create mode 100644 .changeset/check-stale-outputs.md diff --git a/.changeset/check-stale-outputs.md b/.changeset/check-stale-outputs.md new file mode 100644 index 00000000..4da384f8 --- /dev/null +++ b/.changeset/check-stale-outputs.md @@ -0,0 +1,5 @@ +--- +"agentsmesh": patch +--- + +Detect stale files in managed generated-output locations during `agentsmesh check`, and expose `canonicalDrift`, `outputDrift`, and `outputsStale` across CLI, programmatic, and MCP results. diff --git a/src/cli/command-result.ts b/src/cli/command-result.ts index 8bc99949..14765d9e 100644 --- a/src/cli/command-result.ts +++ b/src/cli/command-result.ts @@ -65,6 +65,10 @@ export interface LintData { export interface CheckData { hasLock: boolean; + /** True when canonical files or extends differ from the lock. */ + canonicalDrift: boolean; + /** True when a generated output is modified, removed, or stale. */ + outputDrift: boolean; inSync: boolean; modified: string[]; added: string[]; @@ -75,6 +79,8 @@ export interface CheckData { outputsModified: string[]; /** Generated outputs recorded in the lock but missing from disk. */ outputsRemoved: string[]; + /** Managed generated outputs present on disk but absent from the lock. */ + outputsStale: string[]; /** * True when generated-output drift was actually verified. False for * old-format locks (no `outputs` map) or when `--no-outputs` was passed; diff --git a/src/cli/commands/check.ts b/src/cli/commands/check.ts index 0430f33c..7f1d9902 100644 --- a/src/cli/commands/check.ts +++ b/src/cli/commands/check.ts @@ -39,6 +39,7 @@ export async function runCheck( configDir: context.configDir, canonicalDir: context.canonicalDir, rootBase: verifyOutputs ? context.rootBase : undefined, + scope, }); if (!report.hasLock) { @@ -46,6 +47,8 @@ export async function runCheck( exitCode: 1, data: { hasLock: false, + canonicalDrift: false, + outputDrift: false, inSync: false, modified: [], added: [], @@ -54,6 +57,7 @@ export async function runCheck( lockedViolations: [], outputsModified: [], outputsRemoved: [], + outputsStale: [], outputsChecked: false, }, }; @@ -63,6 +67,8 @@ export async function runCheck( exitCode: report.inSync ? 0 : 1, data: { hasLock: true, + canonicalDrift: report.canonicalDrift, + outputDrift: report.outputDrift, inSync: report.inSync, modified: [...report.modified], added: [...report.added], @@ -71,6 +77,7 @@ export async function runCheck( lockedViolations: [...report.lockedViolations], outputsModified: [...report.outputsModified], outputsRemoved: [...report.outputsRemoved], + outputsStale: [...report.outputsStale], outputsChecked: report.outputsChecked, }, }; diff --git a/src/cli/renderers/check.ts b/src/cli/renderers/check.ts index bd611dfb..3feee38d 100644 --- a/src/cli/renderers/check.ts +++ b/src/cli/renderers/check.ts @@ -43,6 +43,9 @@ export function renderCheck(result: CheckCommandResult): void { for (const p of data.outputsRemoved) { ui.error(` generated output "${fwd(p)}" was removed`); } + for (const p of data.outputsStale) { + ui.error(` generated output "${fwd(p)}" is stale`); + } ui.note('Generated files are out of sync.', 'Check'); ui.info( "Run 'agentsmesh merge' to resolve, or 'agentsmesh generate --force' to accept current state.", diff --git a/src/core/check/lock-sync.ts b/src/core/check/lock-sync.ts index a3349f50..fd42d7a4 100644 --- a/src/core/check/lock-sync.ts +++ b/src/core/check/lock-sync.ts @@ -13,12 +13,18 @@ import { } from '../../config/core/lock.js'; import { resolveExtendPaths } from '../../config/resolve/resolver.js'; import { diffOutputChecksums } from '../../config/core/lock-outputs.js'; +import { findStaleGeneratedOutputs } from '../generate/stale-cleanup.js'; +import type { TargetLayoutScope } from '../../targets/catalog/target-descriptor.js'; export interface LockSyncReport { - /** True when the canonical state matches the lock file and no extend drifted. */ + /** True when canonical state and checked generated outputs are all in sync. */ readonly inSync: boolean; /** True when a `.lock` file was found at the canonical directory. */ readonly hasLock: boolean; + /** True when canonical files or extends differ from the lock. */ + readonly canonicalDrift: boolean; + /** True when a generated output is modified, removed, or stale. */ + readonly outputDrift: boolean; /** Canonical files whose checksum differs from the lock. */ readonly modified: readonly string[]; /** Canonical files present now but not in the lock. */ @@ -36,6 +42,8 @@ export interface LockSyncReport { readonly outputsModified: readonly string[]; /** Generated outputs recorded in the lock but missing from disk. */ readonly outputsRemoved: readonly string[]; + /** Managed generated outputs present on disk but absent from the lock. */ + readonly outputsStale: readonly string[]; /** * True when output drift was actually verified — requires `rootBase` and a * lock with an `outputs` map. False for old-format locks or when no @@ -55,6 +63,8 @@ export interface CheckLockSyncOptions { * verification is skipped (keeps the programmatic API backward compatible). */ readonly rootBase?: string; + /** Output-layout scope used when scanning managed locations for stale files. */ + readonly scope?: TargetLayoutScope; } /** @@ -65,13 +75,15 @@ export interface CheckLockSyncOptions { * callers decide whether that's a hard error (CI) or just informational. */ export async function checkLockSync(opts: CheckLockSyncOptions): Promise { - const { config, configDir, canonicalDir, rootBase } = opts; + const { config, configDir, canonicalDir, rootBase, scope = 'project' } = opts; const lock = await readLock(canonicalDir); if (lock === null) { return { inSync: false, hasLock: false, + canonicalDrift: false, + outputDrift: false, modified: [], added: [], removed: [], @@ -79,6 +91,7 @@ export async function checkLockSync(opts: CheckLockSyncOptions): Promise 0 || + added.length > 0 || + removed.length > 0 || + extendsModified.length > 0; + const outputDrift = + outputsModified.length > 0 || outputsRemoved.length > 0 || outputsStale.length > 0; + const inSync = !canonicalDrift && !outputDrift; return { inSync, hasLock: true, + canonicalDrift, + outputDrift, modified, added, removed, @@ -148,6 +174,7 @@ export async function checkLockSync(opts: CheckLockSyncOptions): Promise { return files; } -async function removeIfStale( - projectRoot: string, - relPath: string, - expected: Set, -): Promise { - if (expected.has(relPath)) return; - const abs = join(projectRoot, relPath); - if (await exists(abs)) await rm(abs, { recursive: true, force: true }); -} - -export async function cleanupStaleGeneratedOutputs(args: { +interface StaleGeneratedOutputsArgs { projectRoot: string; targets: string[]; expectedPaths: string[]; scope?: TargetLayoutScope; -}): Promise { +} + +export async function findStaleGeneratedOutputs( + args: StaleGeneratedOutputsArgs, +): Promise { const expected = new Set(args.expectedPaths); const stale = new Set(); const scope = args.scope ?? 'project'; @@ -51,7 +45,18 @@ export async function cleanupStaleGeneratedOutputs(args: { } } + const found: string[] = []; for (const relPath of stale) { - await removeIfStale(args.projectRoot, relPath, expected); + if (expected.has(relPath)) continue; + if (await exists(join(args.projectRoot, relPath))) found.push(relPath); + } + return found.sort(); +} + +export async function cleanupStaleGeneratedOutputs( + args: StaleGeneratedOutputsArgs, +): Promise { + for (const relPath of await findStaleGeneratedOutputs(args)) { + await rm(join(args.projectRoot, relPath), { recursive: true, force: true }); } } diff --git a/src/mcp/handlers/orchestrate-types.ts b/src/mcp/handlers/orchestrate-types.ts index 54d98b2d..4d683596 100644 --- a/src/mcp/handlers/orchestrate-types.ts +++ b/src/mcp/handlers/orchestrate-types.ts @@ -16,6 +16,8 @@ export interface LintHandlerResult { export interface CheckHandlerResult { drift: boolean; + canonicalDrift: boolean; + outputDrift: boolean; missing: string[]; extra: string[]; modified: string[]; @@ -23,6 +25,8 @@ export interface CheckHandlerResult { outputsModified: string[]; /** Generated outputs recorded in the lock but missing from disk. */ outputsRemoved: string[]; + /** Managed generated outputs present on disk but absent from the lock. */ + outputsStale: string[]; /** * True when generated-output drift was verified; false for old-format locks * without an `outputs` map. diff --git a/src/mcp/handlers/orchestrate.ts b/src/mcp/handlers/orchestrate.ts index 92a64f0f..b9180523 100644 --- a/src/mcp/handlers/orchestrate.ts +++ b/src/mcp/handlers/orchestrate.ts @@ -100,14 +100,18 @@ async function check(ctx: McpContext): Promise { canonicalDir: pctx.canonicalDir, // Enables generated-output verification (skipped for old-format locks). rootBase: pctx.projectRoot, + scope: pctx.scope, }); return { drift: !report.inSync, + canonicalDrift: report.canonicalDrift, + outputDrift: report.outputDrift, missing: [...report.removed], extra: [...report.added], modified: [...report.modified], outputsModified: [...report.outputsModified], outputsRemoved: [...report.outputsRemoved], + outputsStale: [...report.outputsStale], outputsChecked: report.outputsChecked, }; } catch (e) { diff --git a/src/mcp/tool-tables/orchestrate-tools.ts b/src/mcp/tool-tables/orchestrate-tools.ts index 90840b7b..9d2e284a 100644 --- a/src/mcp/tool-tables/orchestrate-tools.ts +++ b/src/mcp/tool-tables/orchestrate-tools.ts @@ -53,7 +53,7 @@ export const ORCHESTRATE_TOOL_DESCRIPTORS: ToolDescriptor[] = [ { name: 'check', description: - 'Detect drift between canonical and lockfile, including hand-edits to generated target outputs (outputsChecked is false for old-format locks without an outputs map)', + 'Detect canonical and generated-output drift, including hand-edits and stale files in managed output locations (outputsChecked is false for old-format locks without an outputs map)', inputSchema: NoInput, handler: (ctx) => orchestrateHandlers.check(ctx), }, diff --git a/tests/e2e/check-outputs.e2e.test.ts b/tests/e2e/check-outputs.e2e.test.ts index 01e0f18f..a76a0337 100644 --- a/tests/e2e/check-outputs.e2e.test.ts +++ b/tests/e2e/check-outputs.e2e.test.ts @@ -8,13 +8,22 @@ */ import { afterEach, describe, expect, it } from 'vitest'; -import { appendFileSync, readFileSync, rmSync, writeFileSync } from 'node:fs'; +import { + appendFileSync, + existsSync, + mkdirSync, + readFileSync, + rmSync, + writeFileSync, +} from 'node:fs'; import { join } from 'node:path'; import { cleanup, createTestProject } from './helpers/setup.js'; import { runCli } from './helpers/run-cli.js'; interface CheckData { hasLock: boolean; + canonicalDrift: boolean; + outputDrift: boolean; inSync: boolean; modified: string[]; added: string[]; @@ -23,6 +32,7 @@ interface CheckData { lockedViolations: string[]; outputsModified: string[]; outputsRemoved: string[]; + outputsStale: string[]; outputsChecked: boolean; } @@ -71,8 +81,11 @@ describe('check generated-output verification (e2e)', () => { const { data } = parseCheck(r.stdout); expect(data.inSync).toBe(false); expect(data.outputsChecked).toBe(true); + expect(data.canonicalDrift).toBe(false); + expect(data.outputDrift).toBe(true); expect(data.outputsModified).toEqual(['AGENTS.md']); expect(data.outputsRemoved).toEqual([]); + expect(data.outputsStale).toEqual([]); expect(data.modified).toEqual([]); }); @@ -87,11 +100,33 @@ describe('check generated-output verification (e2e)', () => { const { data } = parseCheck(r.stdout); expect(data.inSync).toBe(false); expect(data.outputsChecked).toBe(true); + expect(data.canonicalDrift).toBe(false); + expect(data.outputDrift).toBe(true); expect(data.outputsRemoved).toEqual(['AGENTS.md']); expect(data.outputsModified).toEqual([]); + expect(data.outputsStale).toEqual([]); expect(data.modified).toEqual([]); }); + it('hand-added managed output → exit 1, outputsStale only, without deleting it', async () => { + dir = createTestProject('canonical-full'); + await runCli('generate', dir); + + const stalePath = join(dir, '.cursor', 'rules', 'orphaned.mdc'); + mkdirSync(join(dir, '.cursor', 'rules'), { recursive: true }); + writeFileSync(stalePath, '# hand-added output\n'); + + const r = await runCli('check --json', dir); + expect(r.exitCode).toBe(1); + const { data } = parseCheck(r.stdout); + expect(data.canonicalDrift).toBe(false); + expect(data.outputDrift).toBe(true); + expect(data.outputsStale).toEqual(['.cursor/rules/orphaned.mdc']); + expect(data.outputsModified).toEqual([]); + expect(data.outputsRemoved).toEqual([]); + expect(existsSync(stalePath)).toBe(true); + }); + it('edited canonical file → exit 1, modified set but outputsModified empty', async () => { dir = createTestProject('canonical-full'); await runCli('generate', dir); @@ -103,9 +138,12 @@ describe('check generated-output verification (e2e)', () => { const { data } = parseCheck(r.stdout); expect(data.inSync).toBe(false); expect(data.outputsChecked).toBe(true); + expect(data.canonicalDrift).toBe(true); + expect(data.outputDrift).toBe(false); expect(data.modified).toEqual(['rules/typescript.md']); expect(data.outputsModified).toEqual([]); expect(data.outputsRemoved).toEqual([]); + expect(data.outputsStale).toEqual([]); }); it('old-format lock (no outputs block) → exit 0, outputsChecked:false', async () => { @@ -122,8 +160,11 @@ describe('check generated-output verification (e2e)', () => { const { data } = parseCheck(r.stdout); expect(data.inSync).toBe(true); expect(data.outputsChecked).toBe(false); + expect(data.canonicalDrift).toBe(false); + expect(data.outputDrift).toBe(false); expect(data.outputsModified).toEqual([]); expect(data.outputsRemoved).toEqual([]); + expect(data.outputsStale).toEqual([]); }); it('old-format lock → human output prints the skipped-verification note', async () => { @@ -149,7 +190,10 @@ describe('check generated-output verification (e2e)', () => { const { data } = parseCheck(r.stdout); expect(data.inSync).toBe(true); expect(data.outputsChecked).toBe(false); + expect(data.canonicalDrift).toBe(false); + expect(data.outputDrift).toBe(false); expect(data.outputsModified).toEqual([]); + expect(data.outputsStale).toEqual([]); }); it('human (non-JSON) drift output names the modified generated output on stderr', async () => { diff --git a/tests/integration/mcp-generate-lock.integration.test.ts b/tests/integration/mcp-generate-lock.integration.test.ts index 3e03ce01..b3c66ec7 100644 --- a/tests/integration/mcp-generate-lock.integration.test.ts +++ b/tests/integration/mcp-generate-lock.integration.test.ts @@ -33,11 +33,14 @@ describe('mcp generate — lockfile persistence', () => { // (outputsChecked: true) and finds no drift right after generation. expect(check).toEqual({ drift: false, + canonicalDrift: false, + outputDrift: false, missing: [], extra: [], modified: [], outputsModified: [], outputsRemoved: [], + outputsStale: [], outputsChecked: true, }); }); diff --git a/tests/unit/cli/commands/check.test.ts b/tests/unit/cli/commands/check.test.ts index 752f8344..b6583b38 100644 --- a/tests/unit/cli/commands/check.test.ts +++ b/tests/unit/cli/commands/check.test.ts @@ -206,9 +206,12 @@ outputs: expect(result.exitCode).toBe(1); expect(result.data.inSync).toBe(false); expect(result.data.outputsChecked).toBe(true); + expect(result.data.canonicalDrift).toBe(false); + expect(result.data.outputDrift).toBe(true); expect(result.data.outputsModified).toEqual(['AGENTS.md']); // Locked-but-absent output is reported as removed. expect(result.data.outputsRemoved).toEqual(['.cursor/rules/_root.mdc']); + expect(result.data.outputsStale).toEqual([]); }); it('returns exitCode 0 and outputsChecked:false for an old-format lock (no outputs map)', async () => { @@ -224,8 +227,11 @@ outputs: expect(result.exitCode).toBe(0); expect(result.data.inSync).toBe(true); expect(result.data.outputsChecked).toBe(false); + expect(result.data.canonicalDrift).toBe(false); + expect(result.data.outputDrift).toBe(false); expect(result.data.outputsModified).toEqual([]); expect(result.data.outputsRemoved).toEqual([]); + expect(result.data.outputsStale).toEqual([]); }); it('skips output verification and returns exitCode 0 with --no-outputs despite drift', async () => { @@ -249,8 +255,11 @@ outputs: expect(result.exitCode).toBe(0); expect(result.data.inSync).toBe(true); expect(result.data.outputsChecked).toBe(false); + expect(result.data.canonicalDrift).toBe(false); + expect(result.data.outputDrift).toBe(false); expect(result.data.outputsModified).toEqual([]); expect(result.data.outputsRemoved).toEqual([]); + expect(result.data.outputsStale).toEqual([]); }); it('reads ~/.agentsmesh/.lock when --global is set', async () => { diff --git a/tests/unit/cli/renderers/check.test.ts b/tests/unit/cli/renderers/check.test.ts index 7113aeef..5ffffc83 100644 --- a/tests/unit/cli/renderers/check.test.ts +++ b/tests/unit/cli/renderers/check.test.ts @@ -10,6 +10,8 @@ describe('renderCheck', () => { exitCode: 1, data: { hasLock: false, + canonicalDrift: false, + outputDrift: false, inSync: false, modified: [], added: [], @@ -18,6 +20,7 @@ describe('renderCheck', () => { lockedViolations: [], outputsModified: [], outputsRemoved: [], + outputsStale: [], outputsChecked: false, }, }); @@ -30,6 +33,8 @@ describe('renderCheck', () => { exitCode: 0, data: { hasLock: true, + canonicalDrift: false, + outputDrift: false, inSync: true, modified: [], added: [], @@ -38,6 +43,7 @@ describe('renderCheck', () => { lockedViolations: [], outputsModified: [], outputsRemoved: [], + outputsStale: [], outputsChecked: true, }, }); @@ -50,6 +56,8 @@ describe('renderCheck', () => { exitCode: 1, data: { hasLock: true, + canonicalDrift: true, + outputDrift: false, inSync: false, extendsModified: ['pack-a'], modified: ['rules/root.md', 'rules/open.md'], @@ -58,6 +66,7 @@ describe('renderCheck', () => { lockedViolations: ['rules/root.md', 'commands/deploy.md', 'skills/old/SKILL.md'], outputsModified: [], outputsRemoved: [], + outputsStale: [], outputsChecked: true, }, }); @@ -79,6 +88,8 @@ describe('renderCheck', () => { exitCode: 1, data: { hasLock: true, + canonicalDrift: false, + outputDrift: true, inSync: false, modified: [], added: [], @@ -87,6 +98,7 @@ describe('renderCheck', () => { lockedViolations: [], outputsModified: ['.cursor/rules/_root.mdc', 'AGENTS.md'], outputsRemoved: ['.claude/CLAUDE.md'], + outputsStale: ['.cursor/rules/orphaned.mdc'], outputsChecked: true, }, }); @@ -96,6 +108,7 @@ describe('renderCheck', () => { expect(errors).toContain('generated output ".cursor/rules/_root.mdc" was modified'); expect(errors).toContain('generated output "AGENTS.md" was modified'); expect(errors).toContain('generated output ".claude/CLAUDE.md" was removed'); + expect(errors).toContain('generated output ".cursor/rules/orphaned.mdc" is stale'); // Forward slashes only — never backslashes. expect(errors).not.toContain('\\'); }); @@ -105,6 +118,8 @@ describe('renderCheck', () => { exitCode: 0, data: { hasLock: true, + canonicalDrift: false, + outputDrift: false, inSync: true, modified: [], added: [], @@ -113,6 +128,7 @@ describe('renderCheck', () => { lockedViolations: [], outputsModified: [], outputsRemoved: [], + outputsStale: [], outputsChecked: false, }, }); @@ -126,6 +142,8 @@ describe('renderCheck', () => { exitCode: 0, data: { hasLock: true, + canonicalDrift: false, + outputDrift: false, inSync: true, modified: [], added: [], @@ -134,6 +152,7 @@ describe('renderCheck', () => { lockedViolations: [], outputsModified: [], outputsRemoved: [], + outputsStale: [], outputsChecked: true, }, }); diff --git a/tests/unit/core/check/lock-sync.test.ts b/tests/unit/core/check/lock-sync.test.ts index 186dd3f1..47e44d31 100644 --- a/tests/unit/core/check/lock-sync.test.ts +++ b/tests/unit/core/check/lock-sync.test.ts @@ -328,13 +328,53 @@ collaboration: }); expect(report.outputsChecked).toBe(true); + expect(report.canonicalDrift).toBe(false); + expect(report.outputDrift).toBe(true); expect(report.outputsModified).toEqual(['AGENTS.md']); expect(report.outputsRemoved).toEqual(['CLAUDE.md']); + expect(report.outputsStale).toEqual([]); expect(report.inSync).toBe(false); // Canonical is untouched. expect(report.modified).toEqual([]); }); + it('hand-added managed output is reported as stale without changing canonical drift', async () => { + const { projectRoot, canonicalDir } = setupBareProject( + 'version: 1\ntargets: [cursor]\nfeatures: [rules]\n', + ); + writeFileSync(join(canonicalDir, 'rules', '_root.md'), '# stable'); + const checksums = await buildChecksums(canonicalDir); + mkdirSync(join(projectRoot, '.cursor', 'rules'), { recursive: true }); + writeFileSync(join(projectRoot, '.cursor', 'rules', '_root.mdc'), '# generated'); + writeFileSync(join(projectRoot, '.cursor', 'rules', 'orphaned.mdc'), '# hand-added'); + await writeLock(canonicalDir, { + generatedAt: '2026-07-18T00:00:00Z', + generatedBy: 'test', + libVersion: '0.1.0', + checksums, + extends: {}, + packs: {}, + outputs: { + '.cursor/rules/_root.mdc': `sha256:${hashContent('# generated')}`, + }, + }); + + const config = await loadConfig(projectRoot); + const report = await checkLockSync({ + config, + configDir: projectRoot, + canonicalDir, + rootBase: projectRoot, + }); + + expect(report.canonicalDrift).toBe(false); + expect(report.outputDrift).toBe(true); + expect(report.outputsModified).toEqual([]); + expect(report.outputsRemoved).toEqual([]); + expect(report.outputsStale).toEqual(['.cursor/rules/orphaned.mdc']); + expect(report.inSync).toBe(false); + }); + it('old lock without outputs: outputsChecked=false and canonical inSync unaffected', async () => { const { projectRoot, canonicalDir } = setupBareProject( 'version: 1\ntargets: [claude-code]\nfeatures: [rules]\n', diff --git a/tests/unit/mcp/handlers/orchestrate.test.ts b/tests/unit/mcp/handlers/orchestrate.test.ts index 4ced73eb..0e6961a1 100644 --- a/tests/unit/mcp/handlers/orchestrate.test.ts +++ b/tests/unit/mcp/handlers/orchestrate.test.ts @@ -241,6 +241,8 @@ describe('orchestrateHandlers.check', () => { mockCheck.mockResolvedValue({ inSync: false, hasLock: true, + canonicalDrift: true, + outputDrift: true, modified: ['rules/foo.md'], added: ['rules/new.md'], removed: ['rules/old.md'], @@ -248,6 +250,7 @@ describe('orchestrateHandlers.check', () => { lockedViolations: [], outputsModified: ['AGENTS.md'], outputsRemoved: ['.claude/CLAUDE.md'], + outputsStale: ['.cursor/rules/orphaned.mdc'], outputsChecked: true, } satisfies LockSyncReport); @@ -257,8 +260,11 @@ describe('orchestrateHandlers.check', () => { expect(out.missing).toEqual(['rules/old.md']); expect(out.extra).toEqual(['rules/new.md']); expect(out.modified).toEqual(['rules/foo.md']); + expect(out.canonicalDrift).toBe(true); + expect(out.outputDrift).toBe(true); expect(out.outputsModified).toEqual(['AGENTS.md']); expect(out.outputsRemoved).toEqual(['.claude/CLAUDE.md']); + expect(out.outputsStale).toEqual(['.cursor/rules/orphaned.mdc']); expect(out.outputsChecked).toBe(true); }); @@ -266,6 +272,8 @@ describe('orchestrateHandlers.check', () => { mockCheck.mockResolvedValue({ inSync: true, hasLock: true, + canonicalDrift: false, + outputDrift: false, modified: [], added: [], removed: [], @@ -273,6 +281,7 @@ describe('orchestrateHandlers.check', () => { lockedViolations: [], outputsModified: [], outputsRemoved: [], + outputsStale: [], outputsChecked: true, } satisfies LockSyncReport); diff --git a/website/src/content/docs/cli/check.mdx b/website/src/content/docs/cli/check.mdx index 6738b609..afecbe99 100644 --- a/website/src/content/docs/cli/check.mdx +++ b/website/src/content/docs/cli/check.mdx @@ -36,9 +36,9 @@ The `.agentsmesh/` sources no longer match the checksums recorded in `.agentsmes ### Generated-output drift -A generated file (`.claude/`, `.cursor/`, `AGENTS.md`, etc.) was hand-edited or deleted. When `agentsmesh generate` runs, it records an `outputs` map in the lock — a checksum of every generated file it wrote or verified. `agentsmesh check` re-hashes those files on disk and reports any that were changed (`outputsModified`) or deleted (`outputsRemoved`). This is what lets `check` catch direct edits to generated files without running the generators. +A generated file (`.claude/`, `.cursor/`, `AGENTS.md`, etc.) was hand-edited, deleted, or added unexpectedly under a configured target's managed output locations. When `agentsmesh generate` runs, it records an `outputs` map in the lock — a checksum of every generated file it wrote or verified. `agentsmesh check` re-hashes those files on disk and reports any that were changed (`outputsModified`) or deleted (`outputsRemoved`), then scans managed locations for files absent from the lock (`outputsStale`). This catches generated-output drift without running the generators. -`check` does **not** flag brand-new unexpected files in output directories — stale-file cleanup during `generate` owns that. +JSON output exposes the two drift classes directly as `canonicalDrift` and `outputDrift`; the per-path arrays remain available for actionable diagnostics. ### Old-format locks @@ -89,7 +89,7 @@ jobs: | `0` | Canonical sources and recorded generated outputs both match the lock. | | `1` | Drift detected — canonical sources or generated outputs are out of sync. | -The MCP `check` tool mirrors this in its result payload, exposing `outputsModified`, `outputsRemoved`, and `outputsChecked` alongside the canonical-drift fields. +The MCP `check` tool mirrors this in its result payload, exposing `canonicalDrift`, `outputDrift`, `outputsModified`, `outputsRemoved`, `outputsStale`, and `outputsChecked`. ## Difference between `check` and `generate --check` diff --git a/website/src/content/docs/guides/ci-drift-detection.mdx b/website/src/content/docs/guides/ci-drift-detection.mdx index e9ed704a..0329b3dc 100644 --- a/website/src/content/docs/guides/ci-drift-detection.mdx +++ b/website/src/content/docs/guides/ci-drift-detection.mdx @@ -13,10 +13,11 @@ Add `agentsmesh check` and `agentsmesh lint` to your CI pipeline to catch config **Drift** happens when: - Someone edits `.agentsmesh/` but forgets to run `agentsmesh generate` - Someone edits a generated file (`.claude/`, `.cursor/`, etc.) directly +- Someone adds an unexpected file under a managed generated-output location - A PR merges canonical changes without regenerating - An `extends` source changes and the project hasn't refreshed -`agentsmesh check` detects all of these. It verifies two things against the stored lock: **canonical-source drift** (the `.agentsmesh/` sources vs. the recorded checksums) and **generated-output drift** (each generated file listed in the lock's `outputs` map, re-hashed on disk to catch direct hand-edits or deletions). Either kind of drift exits with code 1. +`agentsmesh check` detects all of these. It verifies two things against the stored lock: **canonical-source drift** (the `.agentsmesh/` sources vs. the recorded checksums) and **generated-output drift** (recorded files are re-hashed to catch hand-edits or deletions, and managed output locations are scanned for stale files absent from the lock). Either kind of drift exits with code 1. ## Basic CI setup @@ -64,6 +65,7 @@ Fails with exit code 1 if any lint errors are found. Verifies that both canonical sources and generated tool directories match the lock: - Re-hashes `.agentsmesh/` sources against the lock's `checksums` (canonical drift) - Re-hashes each generated file recorded in the lock's `outputs` map (generated-output drift) +- Scans configured targets' managed output locations for stale files absent from the lock - Fails with exit code 1 if either has drifted Locks written before generated-output tracking existed have no `outputs` map; `check` skips output verification and prints a note until you run `agentsmesh generate` once to upgrade the lock. Pass `--no-outputs` to skip output verification deliberately — for example, in CI setups that gitignore generated outputs, where every output would otherwise report as removed after checkout. diff --git a/website/src/content/docs/reference/mcp-server.mdx b/website/src/content/docs/reference/mcp-server.mdx index 9c8cec5a..92daa16c 100644 --- a/website/src/content/docs/reference/mcp-server.mdx +++ b/website/src/content/docs/reference/mcp-server.mdx @@ -127,7 +127,7 @@ The server exposes **50 tools** grouped by category. All tool errors return a st |------|-------------| | `generate` | Generate target-native config from canonical | | `lint` | Lint canonical files | -| `check` | Detect canonical-source and generated-output drift against the lock (result includes `outputsModified`/`outputsRemoved`/`outputsChecked`) | +| `check` | Detect canonical-source and generated-output drift against the lock (result includes `canonicalDrift`/`outputDrift`, per-path output arrays, and `outputsChecked`) | | `diff` | Preview generation changes | | `import` | Import another tool's config into canonical | | `convert` | Convert directly from one tool to another | From 076a683afe9bda0b6bc17035b3ac409635c6d54d Mon Sep 17 00:00:00 2001 From: Serhii Zhabskyi Date: Tue, 21 Jul 2026 18:01:43 +0200 Subject: [PATCH 4/7] fix(check): default pluginTargets when scanning for stale outputs The stale-output scan spread config.pluginTargets directly; every other call site in the codebase guards it with '?? []'. ValidatedConfig always populates the field, so this is a robustness/consistency fix for configs constructed outside the Zod parse path. --- src/core/check/lock-sync.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/check/lock-sync.ts b/src/core/check/lock-sync.ts index fd42d7a4..6e7b70dc 100644 --- a/src/core/check/lock-sync.ts +++ b/src/core/check/lock-sync.ts @@ -147,7 +147,7 @@ export async function checkLockSync(opts: CheckLockSyncOptions): Promise Date: Tue, 21 Jul 2026 18:03:04 +0200 Subject: [PATCH 5/7] test(check): cover stale detection for registered plugin descriptors Per the repo 'plugins are first-class targets' contract, any code that iterates targets must hold for registered plugin descriptors, not just builtins. The stale-output scan iterates config.pluginTargets, so add a lock-sync test that registers a plugin descriptor with a managedOutputs layout and asserts a hand-added file under its managed dir is reported in outputsStale (resolved via getDescriptor, not builtin-only). --- tests/unit/core/check/lock-sync.test.ts | 90 +++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/tests/unit/core/check/lock-sync.test.ts b/tests/unit/core/check/lock-sync.test.ts index 47e44d31..94ce7d88 100644 --- a/tests/unit/core/check/lock-sync.test.ts +++ b/tests/unit/core/check/lock-sync.test.ts @@ -12,6 +12,11 @@ import { checkLockSync } from '../../../../src/core/check/lock-sync.js'; import { writeLock, buildChecksums } from '../../../../src/config/core/lock.js'; import { loadConfigFromDir } from '../../../../src/config/core/loader.js'; import { hashContent } from '../../../../src/utils/crypto/hash.js'; +import { + registerTargetDescriptor, + resetRegistry, +} from '../../../../src/targets/catalog/registry.js'; +import type { TargetDescriptor } from '../../../../src/targets/catalog/target-descriptor.js'; import type { ValidatedConfig } from '../../../../src/config/core/schema.js'; const TEST_DIR = join(tmpdir(), `am-lock-sync-unit-${process.pid}`); @@ -35,6 +40,49 @@ function setupBareProject(yaml: string): { projectRoot: string; canonicalDir: st return { projectRoot, canonicalDir }; } +const PLUGIN_ID = 'check-stale-plugin'; + +/** + * Register a plugin descriptor whose managed output location is `.plugin/rules`. + * Proves the stale-output scan resolves plugin targets via the registry + * (`getBuiltinTargetDefinition(id) ?? getDescriptor(id)`), not builtins only. + */ +function registerPluginWithManagedOutputs(): void { + registerTargetDescriptor({ + id: PLUGIN_ID, + metadata: { + displayName: PLUGIN_ID, + category: 'cli', + officialUrl: 'https://example.test/', + shortDescription: 'Stale-output contract fixture', + }, + generators: { name: PLUGIN_ID, generateRules: () => [], importFrom: async () => [] }, + capabilities: { + rules: 'native', + additionalRules: 'none', + commands: 'none', + agents: 'none', + skills: 'none', + mcp: 'none', + hooks: 'none', + ignore: 'none', + permissions: 'none', + }, + emptyImportMessage: 'No plugin config found.', + lintRules: null, + project: { + paths: { + rulePath: (slug: string) => `.plugin/rules/${slug}.md`, + commandPath: () => null, + agentPath: () => null, + }, + managedOutputs: { dirs: ['.plugin/rules'], files: [] }, + }, + buildImportPaths: async () => {}, + detectionPaths: ['.plugin'], + } as unknown as TargetDescriptor); +} + describe('checkLockSync', () => { it('hasLock=false and inSync=false when .lock is missing — and all drift arrays are empty', async () => { const { projectRoot, canonicalDir } = setupBareProject('version: 1\n'); @@ -375,6 +423,48 @@ collaboration: expect(report.inSync).toBe(false); }); + describe('plugin-target managed outputs (registered descriptor)', () => { + beforeEach(() => registerPluginWithManagedOutputs()); + afterEach(() => resetRegistry()); + + it("reports a hand-added file under a plugin target's managed dir as stale", async () => { + const { projectRoot, canonicalDir } = setupBareProject( + `version: 1\ntargets: [claude-code]\npluginTargets: [${PLUGIN_ID}]\nfeatures: [rules]\n`, + ); + writeFileSync(join(canonicalDir, 'rules', '_root.md'), '# stable'); + const checksums = await buildChecksums(canonicalDir); + mkdirSync(join(projectRoot, '.plugin', 'rules'), { recursive: true }); + writeFileSync(join(projectRoot, '.plugin', 'rules', '_root.md'), '# generated'); + writeFileSync(join(projectRoot, '.plugin', 'rules', 'orphaned.md'), '# hand-added'); + await writeLock(canonicalDir, { + generatedAt: '2026-07-18T00:00:00Z', + generatedBy: 'test', + libVersion: '0.1.0', + checksums, + extends: {}, + packs: {}, + outputs: { + '.plugin/rules/_root.md': `sha256:${hashContent('# generated')}`, + }, + }); + + const config = await loadConfig(projectRoot); + const report = await checkLockSync({ + config, + configDir: projectRoot, + canonicalDir, + rootBase: projectRoot, + }); + + expect(report.canonicalDrift).toBe(false); + expect(report.outputDrift).toBe(true); + expect(report.outputsStale).toEqual(['.plugin/rules/orphaned.md']); + expect(report.outputsModified).toEqual([]); + expect(report.outputsRemoved).toEqual([]); + expect(report.inSync).toBe(false); + }); + }); + it('old lock without outputs: outputsChecked=false and canonical inSync unaffected', async () => { const { projectRoot, canonicalDir } = setupBareProject( 'version: 1\ntargets: [claude-code]\nfeatures: [rules]\n', From 49d70d2a098d43ac744d5d4853677b851a6ede80 Mon Sep 17 00:00:00 2001 From: Serhii Zhabskyi Date: Tue, 21 Jul 2026 18:03:29 +0200 Subject: [PATCH 6/7] chore(changeset): bump check-stale-outputs from patch to minor Stale-output detection adds new user-facing behavior plus additive CLI JSON and MCP result fields (canonicalDrift, outputDrift, outputsStale). Under semver that is a backward-compatible feature, so minor is the correct bump for a 0.x release rather than patch. --- .changeset/check-stale-outputs.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/check-stale-outputs.md b/.changeset/check-stale-outputs.md index 4da384f8..f84d7fe9 100644 --- a/.changeset/check-stale-outputs.md +++ b/.changeset/check-stale-outputs.md @@ -1,5 +1,5 @@ --- -"agentsmesh": patch +"agentsmesh": minor --- Detect stale files in managed generated-output locations during `agentsmesh check`, and expose `canonicalDrift`, `outputDrift`, and `outputsStale` across CLI, programmatic, and MCP results. From a180aedd95607500c4698a6331e1cba3b0a4a2af Mon Sep 17 00:00:00 2001 From: Serhii Zhabskyi Date: Tue, 21 Jul 2026 18:30:17 +0200 Subject: [PATCH 7/7] fix(deps): bump tar to 7.5.20 to clear high/critical advisories pnpm audit --prod --audit-level=high failed on tar versions up to 7.5.18: - critical GHSA-23hp-3jrh-7fpw (decompression/parse DoS), patched 7.5.19+ - high GHSA-8x88-c5mf-7j5w (negative entry size infinite loop), patched 7.5.18+ tar is a direct prod dependency, so bumping the pin from 7.5.13 to 7.5.20 clears both. Lockfile regenerated with pnpm 10 to match CI; frozen install and the audit gate both verified green. Pre-existing issue, unrelated to the check changes on this branch. --- package.json | 2 +- pnpm-lock.yaml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 6607bdfd..38a49a83 100644 --- a/package.json +++ b/package.json @@ -156,7 +156,7 @@ "diff": "8.0.4", "picomatch": "^4.0.4", "smol-toml": "^1.6.1", - "tar": "7.5.13", + "tar": "7.5.20", "yaml": "^2.8.3", "zod": "^4.3.6" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 604afddc..daaa8783 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,8 +36,8 @@ importers: specifier: ^1.6.1 version: 1.6.1 tar: - specifier: 7.5.13 - version: 7.5.13 + specifier: 7.5.20 + version: 7.5.20 yaml: specifier: ^2.8.3 version: 2.8.3 @@ -2465,8 +2465,8 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - tar@7.5.13: - resolution: {integrity: sha512-tOG/7GyXpFevhXVh8jOPJrmtRpOTsYqUIkVdVooZYJS/z8WhfQUX8RJILmeuJNinGAMSu1veBr4asSHFt5/hng==} + tar@7.5.20: + resolution: {integrity: sha512-9FcyK4PA6+WbzlTM9WhQm6vB5W7cP7dUiPsv1g7YDwEQnQ1CGpK3MGlKk/ITVWMk05kHZuBhmVhiv8LZoy/PFQ==} engines: {node: '>=18'} term-size@2.2.1: @@ -5313,7 +5313,7 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - tar@7.5.13: + tar@7.5.20: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0