AGENTS.md told an offline agent to push with SKIP=apm-marketplace-check and asserted that hook was "the only one whose failure mode is 'no network'". Running all 12 pre-push hooks under a network namespace shows two fail, for one shared cause: apm-pack-check-clean resolves the same remote entry. An exact pin does not remove the ls-remote, so both hooks are named now. AGENTS.md also said everything in a plugin root except .apm/ is generated. Plugin roots carry hand-authored README.md, docs/, bin/, sources.md and .mcp.json, so an agent would hunt for an .apm/ source that does not exist or refuse the edit. The rule is positional: immunity belongs to the plugin root, and anything inside a mirrored directory is still rm -rf'd. ADR-0017 said apm strips a hooks field. The real loop is (agents, skills, commands, instructions) -- hooks absent, instructions never mentioned -- and it can never fire, because synthesize_plugin_json_from_apm_yml only emits the eight identity fields. The decision stands; the mechanism was overstated. Its mcpServers amendment is rewritten for the pointer payload and now records the real reason: inlining bypassed apm's credential sanitizer. ADR-0015's owner.email and version-pin passages are corrected against the apm source, and ADR-0016 gains the disallowedTools amendment. agent-audit's allowlist is data, so it gains disallowedTools too -- the ADR and the validator that enforces it had come apart. architecture.md described a root CLAUDE.md that imports two files (it imports one, plus an RTK block) and pointed at an ADR index that does not exist. Seven skill READMEs listed tests/ files the mirror strips, promising installed users files their install lacks; those rows are marked source-only, with the depth-4 template tests explicitly called out as surviving. And plugins/kyberforge/hooks/README.md, deleted during the conversion and preserved nowhere, is restored to a path the mirror does not own -- verified by running a sync against a scratch copy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X7GvKuJfy2WrdBmUttV4DT
9.7 KiB
Working in this repo
This repo is the global AI development configuration repository — the authoritative source for agent definitions, skills, workflows, and prompts across all projects. Built as a homelab tool intended to scale to professional environments.
Structure
plugins/— installable plugin units; each is an apm package (apm.yml+.apm/) carrying skills, agents, hooks, MCP servers, and bundled assets; install separately viaclaude plugin install <name>@holocronproviders/claude-code/— Claude Code adapter (deployed to~/.claude/viainstall.sh)
Edit .apm/, never the flat mirror
Inside a plugin, plugins/<name>/.apm/ is the only hand-edited source for plugin content — the skills, agents, commands, instructions, extensions and hooks a host discovers. Everything in a plugin root that mirrors an .apm/ primitive, plus both plugin.json manifests, is generated:
scripts/sync-plugin-content.shgenerates the flatplugins/<name>/{skills,agents,commands,instructions,extensions}/directories and the mergedplugins/<name>/hooks/hooks.json(ADR-0017)apm packgenerates both per-plugin manifests —plugins/<name>/.claude-plugin/plugin.jsonandplugins/<name>/.github/plugin/plugin.json— and two of the three root marketplace manifests:.claude-plugin/marketplace.json(apm'sclaudeoutput profile) and.agents/plugins/marketplace.json(itscodexprofile, a differently-shaped file) (ADR-0015)scripts/sync-marketplace-mirror.shgenerates the third,.github/plugin/marketplace.json— Copilot CLI's legacy manifest path. No apm output profile targets it: apm ships exactly two marketplace output profiles,claudeandcodex(documented inplugins/kyberforge/.apm/skills/apm-workflow/references/marketplace.md). The mirror is a byte-identical copy of.claude-plugin/marketplace.json, gated by thecheck-marketplace-mirror-syncpre-push hook. Do not expectapm packto refresh it — that assumption is exactly the drift this pair exists to prevent
A plugin root is not wholly generated. Material that is not an .apm/ primitive is hand-authored there and no compiler touches it: README.md, docs/, bin/, sources.md, .mcp.json, plus per-plugin extras like plugins/git/config.example.json, plugins/gitea/references/ and plugins/bin/evals/. Edit those in place — they have no .apm/ source, and looking for one wastes a search. The rule is per-path, not per-directory: plugins/<name>/skills/ is generated, plugins/<name>/docs/ is not. docs/spec/architecture.md carries the same carve-out.
One qualification: "hand-authored, untouched" holds only at the plugin root. A file placed inside a mirrored directory is destroyed — sync_dir runs rm -rf "$dst" before every copy, so a README.md under plugins/<name>/hooks/ or plugins/<name>/skills/ is deleted on the next sync whether or not .apm/ has a counterpart. Put root-level plugin documentation in docs/, never in a mirrored directory.
Nothing labels a generated file as generated — plugins/kyberforge/skills/forge/SKILL.md is byte-identical to its .apm/ original, with no marker in either. Check the path before you edit. An edit to the mirror is discarded by the next sync and is reported as drift by the check-plugin-content-sync pre-push hook, which is the earliest anyone finds out. Details in docs/spec/architecture.md.
Prefer plugin skills over raw shell
This repo dogfoods its own plugins. Before shelling out to git, gitea, or lint tooling directly, check whether an installed skill already owns the operation — it usually does:
- Commits, branches, history, worktrees, remotes →
git:git-commits,git:git-branches,git:git-history,git:git-worktrees,git:git-remotes - Pre-commit hook install/config/troubleshooting →
git:pc-run/git:pc-author - Issues, PRs, labels, milestones →
gitea:gitea-issues,gitea:gitea-prs,gitea:gitea-labels-milestones; alsogitea:gitea-branches,gitea:gitea-files,gitea:gitea-releases, orgitea:gitea-workflowwhen the domain is ambiguous - Vale prose linting →
lint:vale-config/lint:vale-run - This repo's own AGENTS.md →
core:agentsmd-author/core:agentsmd-audit
Fall back to raw shell only when no skill covers it.
Setup and testing
- Install git hooks via
git:pc-run, wiring all three stages — this repo's.pre-commit-config.yamlhas nodefault_install_hook_types, so a plain install silently skipscommit-msg(Conventional Commits) andpre-push(the 12-hook gate described below). - Install the
apmCLI — four pre-push hooks shell out to it:apm-marketplace-check,apm-audit-ci,apm-pack-check-clean, andcheck-plugin-content-sync(viascripts/sync-plugin-content.sh, which wrapsapm pack). The first three are bareapm …hook entries, so without it the push dies with an unhelpful "command not found". Usekyberforge:apm-install, orcurl -sSL https://aka.ms/apm-unix | sh; verify withapm --version. - Install
jq— required byscripts/check-manifests.shandscripts/sync-plugin-content.sh, both pre-push. These at least fail loudly (Error: jq is required but not installed). - Install the
valebinary — required by thevale-audit-prefilter-skill/-agentpre-commit hooks. Theirfiles:patterns are.apm/-scoped:^plugins/[^/]+/\.apm/skills/[^/]+/SKILL\.md$and^plugins/[^/]+/\.apm/agents/[^/]+\.agent\.md$. Only the authoring source triggers them — aSKILL.mdin the generated mirror matches neither pattern, so prose findings surface only when you edit the file you are supposed to be editing. Without the binary the hooks fail with a bare "command not found" and no install pointer.brew install vale(macOS),snap install vale(Linux),choco install vale(Windows), or see https://vale.sh/docs/vale-cli/installation/. Novale syncneeded — theKyberforgestyles are committed underplugins/kyberforge/.apm/skills/{skill-audit,agent-audit}/assets/vale/styles/, not downloaded packages (see ADR-0014). valeis also a pre-push dependency, not only pre-commit.check-vale-style-syncruns six glob-coverage probes by invokingvale --config— they are the only assertions in it that catch a.vale.iniglob typo, the failure mode where every text-level check stays clean while vale lints zero files. Missingvaleis therefore a hard failure there. The opt-out isCHECK_VALE_STYLE_SYNC_ALLOW_MISSING_VALE=1, and it is notSKIP=: the hook still runs and still asserts everything verifiable from file text, but the six probes do not, and its summary says so explicitly —Vale style sync check passed (text-level only, vale unavailable): … 0 glob probe(s) verified. Use it only on a machine that genuinely cannot installvale, and read that summary line as "the glob axis was not checked", not as a pass.- Run
bash tests/run-tests.shbefore considering any change done — it runs everytest-*.shscript in the repo plus the bats suite (--bats-onlyfor just bats). First run auto-initializes the bats submodules; no manualgit submodule updateneeded. - Pushing runs 12 repo-defined pre-push hooks, not just the test suite —
run-testsandcheck-manifests, plus generated-content drift gates (check-plugin-content-sync,check-marketplace-mirror-sync,check-vale-style-sync,check-scope-walkup-sync), apm's own gates (apm-marketplace-check,apm-audit-ci,apm-pack-check-clean), host validators (validate-plugins,validate-marketplace, both needing theclaudeCLI), andcheck-release-needed. Runpre-commit run --hook-stage pre-push --all-fileslocally — one command, the whole gate. That command reports 14, not 12: pre-commit's ownmetahooks,check-hooks-applyandcheck-useless-excludes, declare nostages:and so run at every stage including this one. - Two pre-push hooks need the network, for one shared reason: root
apm.yml'smarketplace.packages[]contains exactly one remote entry (mattpocock-skills,source: mattpocock/skills), and resolving it needs agit ls-remote.apm-marketplace-checkresolves every entry and isalways_run, so it fails withNo cached refs (offline).apm-pack-check-clean(apm pack --check-versions --check-clean --dry-run) re-resolves the same entry and fails withError: Git network timeout during ls-remote. Pinning the entry to an exact version does not remove the call — an exact pin still ls-remotes.--offlinerescues neither. To push without a network, skip both using pre-commit's own mechanism:SKIP=apm-marketplace-check,apm-pack-check-clean git push. Skip those two alone — verified underunshare -rn, the other ten pre-push hooks pass offline because they are real local checks, and adding one of them toSKIPdisarms it silently. - Author commits with
git:git-commits— it validates Conventional Commits (enforced atcommit-msg) for you.
Key documents
Read CONTEXT.md at the start of every session in this repo.
Read these on demand:
docs/spec/architecture.md— current directory structure, install pipeline, provider modeldocs/adr/— architectural decisions; read before answering design questions or proposing structural changesdocs/ai-constitution.md— full governance evidence base; read when a governance decision needs justificationdocs/research/ai-coding-factory/ai-coding-factory-principles.md— factory design rationale; read when implementing, auditing, or reviewing skills or factory structuredocs/notes/factory-integration-decisions.md— decisions from the factory integration grill; read when making skill authoring or factory design decisions- Governance rules are always in effect —
core/instructions/governance.md(agent rules);docs/research/governance_principles/CONTROLS.md