Files
holocron/AGENTS.md
Defame1297 f4bb1cf4e5 docs: finish reconciling the agent write fence across the toolchain
The previous round taught agent-audit's validator to permit disallowedTools but
left the skill that writes agents still forbidding it, in six places. Running
agent-author on any of the three fenced orchestrators would have stripped the
fence, and nothing would have caught it: the validator's allowlist is a permit
list, so an absent field passes. The template was the worst of them, since its
comment is copied verbatim into every new plugin-scope agent.

Where a list had to be restated it is now a pointer to field-inventory.md's
apm-agent-allowlist instead -- the same data validate.sh reads -- because a
roster copied into a template goes stale one step further out than the roster
itself. Where the text has to teach something it teaches the shape rule rather
than the exception: tools is an allowlist whose vocabulary differs per harness,
so verbatim copy makes one value wrong on one target; disallowedTools is a
denylist, where an unrecognised name denies nothing, so the worst case is a
missing fence rather than a wrongly granted capability.

ADR-0016's amendment claimed an unrecognised key is inert on Copilot while the
same ADR's Context says that behaviour is unconfirmed by research -- asserting
as settled the exact thing it flags as unknown, and justifying it with apm's
compile-time behaviour, which says nothing about Copilot's runtime. It is
rewritten into labelled tiers: confirmed for Claude Code with citations,
inferred by analogy for Copilot with the analogy's limits stated, unverified
where it is unverified, and the residual risk accepted explicitly with its
blast radius. It also no longer claims to restore a write sandbox: the denylist
does not deny Bash, which these agents inherit and legitimately need.

docs/hooks.md called the old root hooks.json a stale sync artifact -- it was
added in the plugin's creating commit and pointed at by main's Copilot manifest
-- and claimed both ecosystems now resolve hooks/hooks.json. Copilot does not:
its hooks field has no default and no compiled manifest declares one, so it
resolves nothing. Recorded as the gap it is, with re-injection noted as a
follow-up rather than asserted away. Its event list is marked partial.

Also: new-agent.bats asserted a hardcoded four-field allowlist and would have
rejected a scaffolded agent carrying the field the ADR now blesses; it reads
field-inventory.md too. And ADR-0016's premise that Claude's tools: is
space-separated was wrong -- it takes a comma-separated string or a YAML list.
The incompatibility with Copilot is the vocabulary, not the punctuation.

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

11 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 via claude plugin install <name>@holocron
  • providers/claude-code/ — Claude Code adapter (deployed to ~/.claude/ via install.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.sh generates the flat plugins/<name>/{skills,agents,commands,instructions,extensions}/ directories and the merged plugins/<name>/hooks/hooks.json (ADR-0017)
  • apm pack generates both per-plugin manifests — plugins/<name>/.claude-plugin/plugin.json and plugins/<name>/.github/plugin/plugin.json — and two of the three root marketplace manifests: .claude-plugin/marketplace.json (apm's claude output profile) and .agents/plugins/marketplace.json (its codex profile, a differently-shaped file) (ADR-0015)
  • scripts/sync-marketplace-mirror.sh generates 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, claude and codex (documented in plugins/kyberforge/.apm/skills/apm-workflow/references/marketplace.md). The mirror is a byte-identical copy of .claude-plugin/marketplace.json, gated by the check-marketplace-mirror-sync pre-push hook. Do not expect apm pack to 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; also gitea:gitea-branches, gitea:gitea-files, gitea:gitea-releases, or gitea:gitea-workflow when 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.yaml has no default_install_hook_types, so a plain install silently skips commit-msg (Conventional Commits) and pre-push (the 12-hook gate described below).
  • Install the apm CLI — four pre-push hooks shell out to it: apm-marketplace-check, apm-audit-ci, apm-pack-check-clean, and check-plugin-content-sync (via scripts/sync-plugin-content.sh, which wraps apm pack). The first three are bare apm … hook entries, so without it the push dies with an unhelpful "command not found". Use kyberforge:apm-install, or curl -sSL https://aka.ms/apm-unix | sh; verify with apm --version.
  • Install jq — required by scripts/check-manifests.sh and scripts/sync-plugin-content.sh, both pre-push. These at least fail loudly (Error: jq is required but not installed).
  • Install the vale binary — required by the vale-audit-prefilter-skill/-agent pre-commit hooks. Their files: patterns are .apm/-scoped: ^plugins/[^/]+/\.apm/skills/[^/]+/SKILL\.md$ and ^plugins/[^/]+/\.apm/agents/[^/]+\.agent\.md$. Only the authoring source triggers them — a SKILL.md in 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/. No vale sync needed — the Kyberforge styles are committed under plugins/kyberforge/.apm/skills/{skill-audit,agent-audit}/assets/vale/styles/, not downloaded packages (see ADR-0014).
  • vale is also a pre-push dependency, not only pre-commit. check-vale-style-sync runs six glob-coverage probes by invoking vale --config — they are the only assertions in it that catch a .vale.ini glob typo, the failure mode where every text-level check stays clean while vale lints zero files. Missing vale is therefore a hard failure there. The opt-out is CHECK_VALE_STYLE_SYNC_ALLOW_MISSING_VALE=1, and it is not SKIP=: 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 install vale, and read that summary line as "the glob axis was not checked", not as a pass.
  • Run bash tests/run-tests.sh before considering any change done — it runs every test-*.sh script in the repo plus the bats suite (--bats-only for just bats). First run auto-initializes the bats submodules; no manual git submodule update needed.
  • A suite that exits 77 because a dependency is missing is reported as SKIPPED, and does not fail an ad-hoc run. The pre-push hook invokes the same script as --strict (RUN_TESTS_STRICT=1 is equivalent), where a skip does fail the push: at pre-push a skip means one of the dependencies above is absent on this machine, so the gate would otherwise report success having run fewer suites than it appears to. Without vale, for instance, three suites skip (test-check-vale-style-sync.sh, test-vale-hooks-consumer.sh, test-vale-wrap.sh) and the strict failure names each one and what to install.
  • tests/run-bats.sh derives the set of .bats files it expects from git ls-files, so a .bats file deleted from the worktree but still tracked in the index fails the run rather than silently shrinking the suite. Remove one with git rm (or stage the deletion) when the removal is intentional; an untracked new .bats file is picked up and needs no ceremony.
  • Pushing runs 12 repo-defined pre-push hooks, not just the test suite — run-tests and check-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 the claude CLI), and check-release-needed. Run pre-commit run --hook-stage pre-push --all-files locally — one command, the whole gate. That command reports 14, not 12: pre-commit's own meta hooks, check-hooks-apply and check-useless-excludes, declare no stages: and so run at every stage including this one.
  • Two pre-push hooks need the network, for one shared reason: root apm.yml's marketplace.packages[] contains exactly one remote entry (mattpocock-skills, source: mattpocock/skills), and resolving it needs a git ls-remote. apm-marketplace-check resolves every entry and is always_run, so it fails with No cached refs (offline). apm-pack-check-clean (apm pack --check-versions --check-clean --dry-run) re-resolves the same entry and fails with Error: Git network timeout during ls-remote. Pinning the entry to an exact version does not remove the call — an exact pin still ls-remotes. --offline rescues 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 under unshare -rn, the other ten pre-push hooks pass offline because they are real local checks, and adding one of them to SKIP disarms it silently.
  • Author commits with git:git-commits — it validates Conventional Commits (enforced at commit-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 model
  • docs/adr/ — architectural decisions; read before answering design questions or proposing structural changes
  • docs/ai-constitution.md — full governance evidence base; read when a governance decision needs justification
  • docs/research/ai-coding-factory/ai-coding-factory-principles.md — factory design rationale; read when implementing, auditing, or reviewing skills or factory structure
  • docs/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