Files
holocron/docs/spec/architecture.md
Defame1297 2e8732a8e5 build(apm): consume holocron plugins through apm instead of plugin install
Why:
The repo published apm packages but consumed them the old way — `claude plugin install
<name>@holocron`, six plugins enabled per project. Dogfooding stopped one layer short of the
install tooling kyberforge itself ships.

Implementation notes:
- Root apm.yml declares the six packages as dependencies.apm git+path objects against the
  holocron remote. Object form over `<name>@holocron` aliases on purpose: an alias first needs
  `apm marketplace add`, which writes to ~/.apm/marketplaces.json — user scope, absent on a fresh
  clone. Unpinned against the default branch, matching the autoUpdate the native install had.
- apm.lock.yaml is committed; .claude/skills/, .claude/agents/ and apm_modules/ are gitignored
  regenerable install output. Committing the deployed skills would add a third mirror of content
  ADR-0017 already governs two copies of.
- .mcp.json is generated by apm from plugins/bin/.mcp.json, so the obsidian MCP server survives
  the switch.
- .claude/settings.json is reduced to {"hooks": {}}. apm replays the install into a scratch tree
  and diffs, so any repo-owned key there is permanent drift that fails apm-audit-ci. Nothing was
  lost: enabledPlugins was empty after the uninstall and the only hooks entry was PreToolUse: [].
- tests/run-bats.sh and tests/run-tests.sh exclude apm_modules/. It holds a full copy of every
  plugin, and a copied .bats file resolves its helpers against the dependency root rather than
  this repo — 334 tests, 167 failures before the exclusion.

Impact:
Skills are now unnamespaced — `git-commits`, not `git:git-commits` — because apm deploys plain
project skills with no plugin to prefix. AGENTS.md, CONTEXT.md and docs/spec/architecture.md are
updated accordingly. Root apm.yml now declares dependencies, which arms apm-audit-ci's
lockfile-exists check for the root manifest. External consumers are unaffected: the marketplace
manifests are untouched and `apm pack --check-clean` stays clean. Project scope only.

ADR: 0018

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7GvKuJfy2WrdBmUttV4DT
2026-08-14 17:15:17 +00:00

67 lines
7.6 KiB
Markdown

# Architecture
## Layered model
```
this repo (global defaults)
└── scripts/install.sh → ~/.claude/ (Claude Code config + content)
project repo (local overrides)
└── .claude/settings.json, CLAUDE.md (overrides global)
```
## Content deployment model
`scripts/install.sh` is a **deployer**, not a composer. It sources `scripts/deploy-manifest.sh` and deploys three categories:
- **Files** (`DEPLOY_FILES`): `providers/claude-code/CLAUDE.md` → `~/.claude/CLAUDE.md`; `providers/claude-code/settings.json` → `~/.claude/settings.json`; `core/AGENTS.md` → `~/.agents/AGENTS.md`
- **Executables** (`DEPLOY_EXECUTABLES`): `providers/claude-code/statusline-command.sh` → `~/.claude/statusline-command.sh` (with `+x`)
- **Directories** (`DEPLOY_DIRS`): `core/` → `~/.claude/core/` (destination fully replaced on each deploy)
Skills are **not** deployed by `install.sh`. They are distributed as plugins and installed separately — in this repo by `apm install` against the `dependencies.apm` entries in the root `apm.yml`, which lands them in `.claude/skills/` and `.claude/agents/` (ADR-0018); elsewhere by `claude plugin install <name>@holocron`.
`~/.claude/CLAUDE.md` is a thin adapter, not a content source. It imports `~/.agents/AGENTS.md` (always-on rules) and `governance.md` (always-on governance), then lists the content index. All always-on content lives in `AGENTS.md` files so other providers can import the same source without duplication.
## Plugin model
Skills, agents, MCP servers, and hooks are distributed as self-contained plugin units under `plugins/`, installed independently — via `apm install` here, or `claude plugin install <name>@holocron` for a host consuming the marketplace natively (ADR-0018). Each plugin is an **apm package**: `plugins/<name>/apm.yml` plus a hand-authored `plugins/<name>/.apm/{skills,agents,hooks,commands,instructions,extensions}/` tree (ADR-0015). There is no hand-maintained `plugin.json` — every manifest and every host-visible content directory is compiled from that source.
Two compilers produce the plugin roots you see in the tree:
- **`apm pack` compiles the manifests** (ADR-0015). Per plugin: `.claude-plugin/plugin.json` and `.github/plugin/plugin.json`, both generated from `plugins/<name>/apm.yml`. Repo-wide, from the root `apm.yml`'s `marketplace:` block: `.claude-plugin/marketplace.json` (apm's `claude` output profile) and `.agents/plugins/marketplace.json` (its `codex` profile, a differently-shaped file). Those two are the only marketplace outputs apm has profiles for — the third root manifest, `.github/plugin/marketplace.json` (Copilot CLI's legacy path), is a byte-identical mirror of the Claude one maintained by `scripts/sync-marketplace-mirror.sh` and gated by the `check-marketplace-mirror-sync` pre-push hook.
- **`scripts/sync-plugin-content.sh` compiles the content mirror** (ADR-0017). It wraps `apm pack --format plugin` and copies the resulting bundle's flat `agents/`, `skills/`, `commands/`, `instructions/`, `extensions/`, and merged `hooks/hooks.json` back to the plugin root. Claude Code's installer convention-scans those flat paths and has no `.apm/` awareness whatsoever, so the mirror exists solely to satisfy the host's discovery contract.
`.apm/` is the sole hand-edited authoring source for plugin content. An edit made in the flat mirror is discarded by the next sync and is reported as drift by the `check-plugin-content-sync` pre-push hook. Hand-authored material that is not an `.apm/` primitive — `README.md`, `docs/`, `bin/`, `sources.md`, `.mcp.json`, and per-plugin extras such as `plugins/git/config.example.json`, `plugins/gitea/references/` and `plugins/bin/evals/` — lives at the plugin **root** and is untouched by either compiler.
That immunity is positional, not by filename. Anything placed *inside* a mirrored directory is destroyed regardless of what it is: `sync_dir` runs `rm -rf "$dst"` before every copy, and `sync_hooks_json` does the same to `hooks/`. A hand-written `README.md` under `plugins/<name>/hooks/` or `plugins/<name>/skills/` is deleted by the next sync with no drift report, because a file with no `.apm/` counterpart is simply absent from the regenerated tree. This has already cost the repo one document — `plugins/kyberforge/hooks/README.md`, since restored to `plugins/kyberforge/docs/hooks.md`. Plugin-root documentation belongs in `docs/`.
## Governance layer
`core/instructions/governance.md` is the always-on governance instruction file. Unlike the on-demand instruction files in the content index, governance.md is loaded into every Claude session via `@import` in `providers/claude-code/CLAUDE.md`. This is a technical guarantee, not a behavioural instruction — `@import` causes Claude Code to expand and load the file at launch, before any interaction begins.
The governance layer has two phases:
- **Phase 1** (complete): instruction and documentation layer — `governance.md` loaded via `@import`; `docs/ai-constitution.md` and `docs/wiki/HUMANS.md` as human-facing reference; `CONTEXT.md` extended with governance domain language.
- **Phase 2** (planned): deterministic enforcement layer — pre-commit hooks, CI gates, secret scanning, licence scanning. Specified in `docs/research/governance_principles/CONTROLS.md`.
## AGENTS.md pattern
This repo uses two `AGENTS.md` files as the provider-agnostic source of always-on rules (ADR-0003):
- **Repo-level `AGENTS.md`** — instructions for agents working inside this repo (structure, key rules). Imported by repo `CLAUDE.md` via `@AGENTS.md`.
- **Global `core/AGENTS.md`** — Communication and Behavior rules that apply across all projects. Deployed to `~/.agents/AGENTS.md`; imported by `~/.claude/CLAUDE.md` via `@~/.agents/AGENTS.md`.
Both `CLAUDE.md` files are thin adapters: they import from their respective `AGENTS.md` and add only Claude Code-specific syntax (`@import`, content index paths). They carry no original always-on content.
This repo also has a `CLAUDE.md` at its root — the Claude Code entry point for working in this repo. It imports `AGENTS.md` and nothing else; there is no `@CONTEXT.md` import. It is not import-only either: below the import sits a fenced `<!-- rtk-instructions v2 -->` … `<!-- /rtk-instructions -->` block carrying the RTK command-prefix convention, which is tool-specific content with no `AGENTS.md` source. This is distinct from `providers/claude-code/CLAUDE.md`, which is the global config deployed to `~/.claude/`.
`CONTEXT.md` is therefore **not** always-loaded. `AGENTS.md` instructs agents to read it at session start, which is a behavioural instruction, not an `@import` guarantee — `LESSONS.md`'s 2026-05-17 entry proposed adding the import and it was never applied. Treat that entry as open work rather than a record of a landed change.
## Provider model
`core/` is never tool-specific. `providers/` is never shared. When adding a new provider, write an adapter in `providers/<name>/` that translates core content into the tool's expected format and location. The core content itself does not change.
## Architectural decisions
Key hard-to-reverse decisions are recorded as ADRs in `docs/adr/`. There is no index file — the directory holds 17 numbered ADRs whose filenames state their decision, so `ls docs/adr/` is the index. Read a superseding ADR before the one it supersedes: ADR-0015 (apm as the authoring source of truth) supersedes ADR-0001 and moots ADR-0006, and ADR-0017 corrects ADR-0015's host-discovery gap. Entry points for the structure described on this page: ADR-0002 (two-tier CLAUDE.md), ADR-0003 (AGENTS.md as the provider-agnostic entry point), ADR-0015 and ADR-0017 (the two compilers behind the plugin roots).