Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 17 additions & 16 deletions .archcore/plugin/bump-plugin-version.cpat.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,22 @@ tags:

## Pattern

The plugin version is declared in **three per-host manifests** and nowhere else. On every release, the same version string is bumped in all three, kept **byte-identical**, then the merge commit is tagged `vX.Y.Z`.
The plugin version is declared in **four per-host manifests** and nowhere else. On every release, the same version string is bumped in all four, kept **byte-identical**, then the merge commit is tagged `vX.Y.Z`.

The three files (plugin root is `plugins/archcore/`):
The four files (plugin root is `plugins/archcore/`):

- `plugins/archcore/.claude-plugin/plugin.json`
- `plugins/archcore/.cursor-plugin/plugin.json`
- `plugins/archcore/.codex-plugin/plugin.json`
- `plugins/archcore/.plugin/plugin.json`

Set the same new `version` in all three. Do not touch anything else — the marketplace catalogs, MCP configs, and hook JSON carry no plugin version.
Set the same new `version` in all four. Do not touch anything else — the marketplace catalogs, MCP configs, and hook JSON carry no plugin version.

**Version source of truth is the latest git tag, not the manifest.** Compute the next version relative to the most recent tag (`git describe --tags --abbrev=0`, strip the leading `v`), apply the requested step (default `patch`, else `minor` / `major` / an explicit `X.Y.Z`), and write the result. Deriving from the tag rather than the manifest reconciles drift when a tag was cut without a manifest bump.
**Version source of truth is the latest git tag, not the manifest.** Compute the next version relative to the highest semantic version tag (`git tag --list 'v[0-9]*.[0-9]*.[0-9]*' --sort=-v:refname | head -n 1`, strip the leading `v`), apply the requested step (default `patch`, else `minor` / `major` / an explicit `X.Y.Z`), and write the result. Deriving from the tag rather than the manifest reconciles drift when a tag was cut without a manifest bump.

## Before

`plugins/archcore/.claude-plugin/plugin.json` (and the `.cursor-plugin` / `.codex-plugin` siblings):
`plugins/archcore/.claude-plugin/plugin.json` (and the `.cursor-plugin` / `.codex-plugin` / `.plugin` siblings):

```json
{
Expand All @@ -35,7 +36,7 @@ Set the same new `version` in all three. Do not touch anything else — the mark

## After

All three manifests, identically (latest tag `v0.4.18` → patch step → `0.4.19`):
All four manifests, identically (latest tag `v0.4.18` → patch step → `0.4.19`):

```json
{
Expand All @@ -46,11 +47,11 @@ All three manifests, identically (latest tag `v0.4.18` → patch step → `0.4.1
}
```

Only the `version` line changes. `name` and `description` MUST remain byte-identical across all three (cross-host parity), so leave them untouched.
Only the `version` line changes. `name` and `description` MUST remain byte-identical across all four (cross-host parity), so leave them untouched.

## Scope

Exactly the three `plugin.json` manifests under `plugins/archcore/{.claude-plugin,.cursor-plugin,.codex-plugin}/`. Nothing else in the repo declares the plugin version:
Exactly the four `plugin.json` manifests under `plugins/archcore/{.claude-plugin,.cursor-plugin,.codex-plugin,.plugin}/`. Nothing else in the repo declares the plugin version:

- Marketplace catalogs (`.claude-plugin/marketplace.json`, `.cursor-plugin/marketplace.json`, `.agents/plugins/marketplace.json`) carry no `version` field — do not add one.
- `hooks/cursor.hooks.json` has `"version": 1` — that is the **hook-schema** version, not the plugin version. Do not touch.
Expand All @@ -61,21 +62,21 @@ The bump edits files only. Merging, tagging (`git tag vX.Y.Z && git push origin

## Rationale

- **Single source, three copies.** Each host loads its own manifest; there is no shared version file. The version is duplicated by necessity, so the bump must fan out to all three or a host ships stale.
- **Parity is test-enforced.** `test/structure/json-configs.bats` asserts the version matches across Claude Code and Cursor; `test/structure/codex-plugin.bats` asserts the Codex manifest metadata (incl. `.version`) matches Claude Code. Bumping one host and forgetting another turns CI red — this is the single most common release regression.
- **Single source, four copies.** Each host loads its own manifest; there is no shared version file. The version is duplicated by necessity, so the bump must fan out to all four or a host ships stale.
- **Parity is test-enforced.** `test/structure/json-configs.bats` asserts the version matches across Claude Code and Cursor; `test/structure/codex-plugin.bats` asserts the Codex manifest metadata (incl. `.version`) matches Claude Code; `test/structure/copilot-plugin.bats` does the same for Copilot. Bumping one host and forgetting another turns CI red — this is the single most common release regression.
- **Tag-relative stepping avoids drift decisions.** The git tag drives the release workflow (`.github/workflows/release.yml` triggers on `v*`). Computing the next version from the latest tag keeps the manifest and the tag lineage aligned, even if a previous tag was cut on a docs commit without a manifest bump.
- **Mechanical and low-risk, but easy to do incompletely.** The change is three one-line edits — cheap to script, but the "identical across three files" invariant is exactly what a human eye skips. A dedicated flow (the `/bump-plugin-version` skill) removes that failure mode.
- **Mechanical and low-risk, but easy to do incompletely.** The change is four one-line edits — cheap to script, but the "identical across four files" invariant is exactly what a human eye skips. A dedicated flow (the `/bump-plugin-version` skill) removes that failure mode.

## Enforcement going forward

- Any version bump MUST update all three manifests to the same value in one change.
- Any version bump MUST update all four manifests to the same value in one change.
- Never bump the version directly on `main` — `main` is synthesized from a tagged `dev` commit (`docs/release.md`).
- The `/bump-plugin-version` local skill is the canonical way to perform this pattern: it reads this cpat, derives the next version from the latest tag, and edits the three manifests. Prefer it over hand-editing.
- After bumping, verify parity — `verify-plugin-integrity` Section 4 (cross-host consistency) checks `name`/`description`/`version` across the three manifests.
- The `/bump-plugin-version` local skill is the canonical way to perform this pattern: it reads this cpat, derives the next version from the latest tag, and edits the four manifests. Prefer it over hand-editing.
- After bumping, verify parity — `verify-plugin-integrity` Section 4 (cross-host consistency) checks `name`/`description`/`version` across the four manifests.

## Edge cases

- **Tag ahead of manifest.** A tag can exist that is newer than the manifest `version` (a tag cut without a bump). Because the next version is derived from the tag, not the manifest, the bump naturally moves forward from the true release marker rather than repeating a stale manifest value.
- **No tags yet.** If `git describe --tags` finds nothing, fall back to the current manifest `version` (or seed `0.0.0`) and step from there; surface the fallback to the user.
- **No tags yet.** If the semantic-version tag query finds nothing, fall back to the current manifest `version` (or seed `0.0.0`) and step from there; surface the fallback to the user.
- **First-digit/pre-1.0 semantics.** These are ordinary semver bumps — `minor` resets patch to 0, `major` resets minor and patch to 0. Pre-1.0 (`0.y.z`) is treated as normal semver here; a `major` step goes `0.y.z → 1.0.0` only when explicitly requested.
- **Explicit version.** When an exact `X.Y.Z` is provided, write it verbatim (no tag arithmetic), but still enforce it across all three files and that it is greater than the latest tag.
- **Explicit version.** When an exact `X.Y.Z` is provided, write it verbatim (no tag arithmetic), but still enforce it across all four files and that it is greater than the latest tag.
16 changes: 9 additions & 7 deletions .archcore/plugin/component-registry.doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ tags:

Reference document listing all components of the Archcore Plugin (multi-host: Claude Code, Cursor, Codex CLI).

Note: Claude Code and Cursor surface user-invoked workflows directly from skills (`skills/<name>/SKILL.md`). Codex CLI discovers slash commands from root-level `commands/*.md` wrappers — thin host-adapter shims that delegate to the matching skill. The skill remains the single behavioral source of truth across all three hosts; no workflow logic lives in `commands/`.
Note: Claude Code and Cursor surface user-invoked workflows directly from skills (`skills/<name>/SKILL.md`). Codex CLI discovers slash commands from root-level `commands/*.md` wrappers — thin host-adapter shims that delegate to the matching skill. The skill remains the single behavioral source of truth across all four hosts; no workflow logic lives in `commands/`.

Per `skill-surface-collapse.adr.md`, the visible `/` palette is exactly **7 auto-invocable intent skills**. There are no per-document-type skills, no track skills, and no utility skills. Track flows live as references under `skills/plan/references/`; drift detection lives at `skills/audit/lib/drift-detection.md`.

Expand Down Expand Up @@ -100,15 +100,15 @@ For Codex CLI, both subagents also ship as TOML variants (`agents/archcore-assis
| 5 | PostToolUse | `mcp__archcore__update_document` | `bin/check-cascade` | 3s |
| 6 | PostToolUse | `mcp__archcore__create_document\|mcp__archcore__update_document` | `bin/check-precision` | 3s |

Hook configs: `hooks/hooks.json` (Claude Code, PascalCase events), `hooks/cursor.hooks.json` (Cursor, camelCase events + `afterMCPExecution`; its `preToolUse` matcher is `Write` only — Cursor exposes no Edit tool), `hooks/codex.hooks.json` (Codex CLI, PascalCase events with `apply_patch` matcher addition for Codex's native edit primitive; commands use Codex's canonical `${PLUGIN_ROOT}` substitution).
Hook configs: `hooks/hooks.json` (Claude Code, PascalCase events), `hooks/cursor.hooks.json` (Cursor, camelCase events + `afterMCPExecution`; its `preToolUse` matcher is `Write` only — Cursor exposes no Edit tool), `hooks/codex.hooks.json` (Codex CLI, PascalCase events with `apply_patch` matcher addition for Codex's native edit primitive; commands use Codex's canonical `${PLUGIN_ROOT}` substitution), `hooks/copilot.hooks.json` (Copilot CLI, native camelCase events, `${COPILOT_PLUGIN_ROOT}` commands, deterministic `ARCHCORE_HOST=copilot`, and `cwd: "."` so hooks run from the user project).

Hook 2 and Hook 3 share the `Write|Edit` matcher. Hook 2 (`check-archcore-write`) blocks direct writes to `.archcore/*.md`. Hook 3 (`check-code-alignment`) injects relevant `.archcore/` context for source-file edits via `hookSpecificOutput.additionalContext`. They act on disjoint path sets by construction — no conflict.

Hook 6 (`check-precision`) is the Phase 1 implementation of the Precision Initiative (see `precision-over-coverage.adr.md`). It emits soft warnings via `additionalContext` for forbidden vague words, missing mandatory sections, frontmatter gaps, and stub-length bodies. It never blocks (always exits 0).

Historical note: a prior revision had a `PostToolUse` entry with matcher `Write|Edit` invoking `validate-archcore`. It was removed because PreToolUse already blocks all Write/Edit to `.archcore/*.md` (PostToolUse fires only on success), so the matcher was dead weight forking a shell on every Write/Edit anywhere in the repo. See `hooks-validation-system.spec.md` for the rationale. Structure tests guard against its re-introduction.

`bin/session-start` carries a plugin-install-dir guard (see `cursor-mcp-architecture.adr.md`): when cwd contains a sibling `.cursor-plugin/`, `.claude-plugin/`, or `.codex-plugin/` manifest, the hook exits silently. This prevents the hook from emitting the plugin's bundled `.archcore/` (if any future regression reintroduces it) as the user's knowledge base.
`bin/session-start` carries a plugin-install-dir guard (see `cursor-mcp-architecture.adr.md`): when cwd contains a sibling `.cursor-plugin/`, `.claude-plugin/`, `.codex-plugin/`, or `.plugin/` manifest, the hook exits silently. This prevents the hook from emitting the plugin's bundled `.archcore/` (if any future regression reintroduces it) as the user's knowledge base.

### Bin Scripts

Expand All @@ -132,7 +132,7 @@ The `bin/` tree contains hook scripts, shared shell libraries under `bin/lib/`,
| Component | Location | Tests | Description |
| --- | --- | --- | --- |
| Unit tests | `test/unit/` | — | Test each bin script: stdin parsing, host detection, exit codes, output format, edge cases. |
| Structure tests | `test/structure/` | — | Validate JSON configs, skill frontmatter, agent frontmatter, hook references, script permissions, rules. `hooks.bats` includes anti-regression invariants. `cursor-plugin.bats` locks `docs/cursor.mcp.example.json` shape. `codex-plugin.bats` enforces Codex manifest, marketplace schema, hooks shape, MCP wiring, TOML agents, and parity between `commands/*.md` wrappers (7) and `skills/<name>/SKILL.md`. `marketplace-discovery.bats` pins all three catalogs' `source`/`path` to the `plugins/archcore` subdirectory (issue #2 regression). |
| Structure tests | `test/structure/` | — | Validate JSON configs, skill frontmatter, agent frontmatter, hook references, script permissions, rules. `hooks.bats` includes anti-regression invariants. `cursor-plugin.bats` locks `docs/cursor.mcp.example.json` shape. `codex-plugin.bats` enforces Codex manifest, marketplace schema, hooks shape, MCP wiring, TOML agents, and parity between `commands/*.md` wrappers (7) and `skills/<name>/SKILL.md`. `copilot-plugin.bats` enforces Copilot manifest pointers, hooks shape, project-root execution, and version parity. `marketplace-discovery.bats` pins all three catalogs' `source`/`path` to the `plugins/archcore` subdirectory (issue #2 regression). |
| Fixtures | `test/fixtures/stdin/` | — | Mock stdin JSON for Claude Code, Cursor, Copilot, Codex CLI, and malformed inputs |
| Helpers | `test/helpers/` | — | common.bash (setup, mocks, timeout shim, exports `REPO_ROOT` + `PLUGIN_ROOT`), bats-support, bats-assert (git submodules) |
| Makefile | `Makefile` | — | Targets: `test`, `test-unit`, `test-structure`, `lint`, `check-json`, `check-perms`, `verify`. Dev-only — stripped from `main` distribution. |
Expand All @@ -143,7 +143,7 @@ Run `make verify` for full check. Run `make test` for tests only. See `plugin-te

### MCP Server

The plugin **ships MCP registration** for Claude Code via `.mcp.json` at the plugin root (`plugins/archcore/.mcp.json`):
The plugin **ships shared MCP registration** for Claude Code and GitHub Copilot CLI via `.mcp.json` at the plugin root (`plugins/archcore/.mcp.json`):

```json
{
Expand All @@ -156,7 +156,7 @@ The plugin **ships MCP registration** for Claude Code via `.mcp.json` at the plu
}
```

The `command` resolves through PATH — users must have the Archcore CLI installed globally (see https://docs.archcore.ai/cli/install/). If the CLI is missing at session start, the MCP server fails to register and `bin/session-start` prints the install instructions.
The `command` resolves through PATH — users must have the Archcore CLI installed globally (see https://docs.archcore.ai/cli/install/). If the CLI is missing at session start, the MCP server fails to register and `bin/session-start` prints the install instructions. Copilot CLI currently launches plugin MCP children from the plugin directory without exposing its project path; correct project-root MCP operation depends on github/copilot-cli#4234.

Codex CLI uses `.codex-plugin/plugin.json` to point at plugin-root `.codex.mcp.json`, which uses Codex's direct server map shape: `archcore.command: "archcore"`, `archcore.args: ["mcp"]`.

Expand All @@ -173,15 +173,17 @@ Component manifests, hooks, and MCP configs are plugin-root-relative (under `plu
| `.claude-plugin/plugin.json` | Claude Code | Plugin manifest (plugin root) |
| `.cursor-plugin/plugin.json` | Cursor | Plugin manifest (plugin root; with explicit component paths; **no `mcpServers` field** — deliberately disabled, see `cursor-mcp-architecture.adr.md`) |
| `.codex-plugin/plugin.json` | Codex CLI | Plugin manifest (plugin root) with `skills`, `hooks`, and `mcpServers` pointers |
| `.plugin/plugin.json` | GitHub Copilot CLI | Plugin manifest (plugin root) with explicit `skills`, `agents`, `hooks`, and `mcpServers` pointers |
| `.claude-plugin/marketplace.json` | Claude Code | Marketplace catalog — **repo root**, `source: ./plugins/archcore` |
| `.cursor-plugin/marketplace.json` | Cursor | Marketplace catalog — **repo root**, `source: ./plugins/archcore` |
| `.agents/plugins/marketplace.json` | Codex CLI | Marketplace catalog + default-install policy — **repo root**, `source.path: ./plugins/archcore` |
| `.mcp.json` | Claude Code | Plugin-provided MCP registration (plugin root; `command: "archcore"` on PATH) |
| `.mcp.json` | Claude Code / GitHub Copilot CLI | Shared plugin-provided MCP registration (plugin root; `command: "archcore"` on PATH) |
| `.codex.mcp.json` | Codex CLI | Plugin-provided MCP registration (plugin root; `command: "archcore"` on PATH) |
| `docs/cursor.mcp.example.json` | Cursor | Reference MCP config for users to copy into `~/.cursor/mcp.json` or `.cursor/mcp.json` (**repo root**). |
| `hooks/hooks.json` | Claude Code | Hook event config (PascalCase) |
| `hooks/cursor.hooks.json` | Cursor | Hook event config (camelCase + afterMCPExecution) |
| `hooks/codex.hooks.json` | Codex CLI | Hook event config (PascalCase + apply_patch matcher) |
| `hooks/copilot.hooks.json` | GitHub Copilot CLI | Hook event config (camelCase + native mutation matchers + project-root cwd) |
| `commands/*.md` | Codex CLI | Slash command wrappers (7) — thin shims delegating to `skills/<name>/SKILL.md` |
| `agents/archcore-assistant.toml` | Codex CLI | Codex TOML subagent (`sandbox_mode = "workspace-write"`); MD original used by Claude Code/Cursor |
| `agents/archcore-auditor.toml` | Codex CLI | Codex TOML subagent (`sandbox_mode = "read-only"` + `disabled_tools[]`); MD original used by Claude Code/Cursor |
Expand Down
Loading