docs: source ADR-0029 claims and sync ADRs and hook docs with behaviour
- cite the VS Code prompt-file deprecation and the verbatim apm quote - add ADR-0029 boundary-clause enforcement and Consequences - mark superseded ADR-0019 passages; record neutral lock advice, source fork and reloadSkills, amend for the hook hardening - move the ADR-0025 amendment out of the Decision list - amend ADR-0022 for create keeping 0.1.0 - fix hooks.md merge and event claims, README guard caveat, gates.md Vale globs, and pin the research registry URL Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KkT7RSDwDbmrM9T34b6sTi
This commit is contained in:
@@ -34,7 +34,9 @@ behind and refreshes it in place.
|
||||
*session* loads, and that is the moment the staleness does damage. It also enables two things a git
|
||||
hook structurally cannot do: `additionalContext` puts the notice into the agent's context rather
|
||||
than terminal scrollback nobody reads, and `reloadSkills: true` makes the host re-scan the skill
|
||||
directories after the hook returns, so a refresh lands in the running session without a restart.
|
||||
directories after the hook returns, so a refresh lands in the running session without a restart
|
||||
(`reloadSkills` is a documented `SessionStart` `hookSpecificOutput` field:
|
||||
code.claude.com/docs/en/hooks, checked 2026-09-29).
|
||||
|
||||
apm's own lifecycle events (`pre-/post-install`, `pre-/post-update`, `pre-/post-uninstall`) were
|
||||
rejected: they fire around apm operations already chosen, so they can announce a refresh but never
|
||||
@@ -51,8 +53,9 @@ Three sub-decisions:
|
||||
drift. A hook shipped in a package is written into that file by apm itself, so it is apm's output
|
||||
and does not drift. `.claude/settings.local.json` also works but is gitignored and machine-local,
|
||||
which fails the requirement that this travel with the repo.
|
||||
- **`startup` matcher only.** `resume`, `clear`, `compact` and `fork` would re-run the check on every
|
||||
compaction, and a compaction is not an event after which the remote can have moved.
|
||||
- **`startup` matcher only.** `resume`, `clear`, `compact` and `fork` — the other documented
|
||||
`SessionStart` matchers (code.claude.com/docs/en/hooks, checked 2026-09-29) — would re-run the check
|
||||
on every compaction, and a compaction is not an event after which the remote can have moved.
|
||||
|
||||
The executable-trust gate is switched on at the same time. Root `apm.yml` gains an `executables:`
|
||||
block allowing kyberforge's hooks and bin.
|
||||
@@ -142,6 +145,22 @@ value to be larger — so changing either side without the other fails the suite
|
||||
> Re-measured: ~24–26 s for the same six-behind refresh, warm, on a LAN remote — well inside the
|
||||
> 380 s above.
|
||||
|
||||
> **Amendment (2026-09-29) — the time limits are harder to escape, and portable.** Four changes to
|
||||
> `check-apm-current.sh` (PR #144):
|
||||
>
|
||||
> - Both limits are now `timeout -k 5`: a child that ignores SIGTERM is SIGKILLed 5 s later. The
|
||||
> worst case is 60 + 5 + 300 + 5 = 370 s, still below the host's 380, and the test now sums each
|
||||
> limit plus its grace.
|
||||
> - `timeout` falls back to `gtimeout` (Homebrew coreutils on macOS). With neither on `PATH` the
|
||||
> hook emits a notice and exits without calling `apm`. Before, the missing binary exited 127, the
|
||||
> `|| exit 0` swallowed it, and the hook silently never ran.
|
||||
> - `GIT_TERMINAL_PROMPT=0` is exported, so a remote that wants credentials fails at once instead of
|
||||
> blocking on an invisible prompt until the timeout fires.
|
||||
> - `apm update` runs under `flock -n` on `apm_modules/.kyberforge-apm-update.lock` when `flock` exists
|
||||
> and `apm_modules/` does. A second session starting at the same moment skips its refresh and says
|
||||
> so, instead of running a second `apm update` over the same tree. Without `flock` (stock macOS) the
|
||||
> refresh runs unserialised, as before.
|
||||
|
||||
**Reading a human-readable CLI for a control decision cost a silent failure, again.** `apm outdated`
|
||||
has no `--json` or other machine-readable flag (confirmed against 0.28.0), so the hook must match
|
||||
its prose. The first attempt matched `outdated dependencies found` — plural only. apm emits
|
||||
@@ -204,9 +223,20 @@ Until then the repo has the mechanism in source and not in effect.
|
||||
> and it does not last. At the next session start the hook finds the restored lock behind `main`
|
||||
> and refreshes again.
|
||||
|
||||
> **Amendment (2026-09-19) — the advice is neutral when the default branch is unknown.** The hook
|
||||
> picks its lock advice by comparing the current branch with `refs/remotes/origin/HEAD`. That ref is
|
||||
> often unset: git writes it on clone, and `git remote add` never does. The fallback used to be
|
||||
> `main`, which told a checkout whose default branch is `master` to discard a real lock update while
|
||||
> it stood on its default branch. Now, when `origin/HEAD` is unset, the notice gives neither the
|
||||
> default-branch nor the feature-branch advice. It says only "commit it or discard it
|
||||
> deliberately", the same text used outside a git checkout or on a detached HEAD. Learning the
|
||||
> remote's default would need the network, so the hook asserts nothing and the reader decides
|
||||
> (`ea119d8`).
|
||||
|
||||
**`.claude/settings.json` stops being `{"hooks": {}}`.** apm merges the hook into it and tracks
|
||||
ownership in a `.claude/apm-hooks.json` sidecar, with the script copied to
|
||||
`.claude/hooks/<pkg>/`. The sidecar and the script directory are gitignored install output; the
|
||||
`.claude/hooks/<pkg>/`. The sidecar and the script directory are gitignored install output
|
||||
(*superseded for the sidecar: it is committed, see correction 2026-09-16 below*); the
|
||||
settings file remains committed, now with apm-generated content in it. ADR-0018's statement that the
|
||||
committed content is exactly `{"hooks": {}}` is superseded on that point only — the rule it was
|
||||
protecting, that nothing repo-authored goes in that file, is unchanged.
|
||||
@@ -223,7 +253,9 @@ protecting, that nothing repo-authored goes in that file, is unchanged.
|
||||
|
||||
**Native consumers are protected by a guard, not by the gate.** A host installing holocron through
|
||||
`claude plugin install` auto-discovers `hooks/hooks.json` and does not consult apm's trust gate at
|
||||
all. The script therefore exits silently when there is no `apm.lock.yaml` in the working directory,
|
||||
all. The script therefore exits silently when there is no `apm.lock.yaml` in the working directory
|
||||
(*superseded: it now checks `${CLAUDE_PROJECT_DIR}/apm.lock.yaml` and has no cwd fallback, see
|
||||
correction 2026-09-28 below*),
|
||||
which is what makes it inert in a repo that does not consume packages through apm. Copilot CLI sees
|
||||
no hook at all, for the reasons already documented in `plugins/kyberforge/docs/hooks.md`.
|
||||
|
||||
|
||||
@@ -191,6 +191,17 @@ comments (`:9-93`) have described all three correctly since it shipped.
|
||||
was wrong for the tree-identical case: the `same_subtree` skip makes a push **pass** that this ADR as
|
||||
written requires to **fail**, which is documented behaviour changing, not an implementation detail.
|
||||
|
||||
## Amendment (2026-09-29): a new skill stays at `0.1.0` until its first improve
|
||||
|
||||
The Decision says `skill-author`'s create/improve bump convention is "unchanged". It has changed
|
||||
since. Before, `skill-author` bumped the **minor** version on create, so a skill left its first
|
||||
session above the `0.1.0` scaffold. Now create keeps the scaffold's `0.1.0` and does not bump it;
|
||||
the first improve is the first bump, a **patch** (`skill-author` `SKILL.md`, `references/create.md`
|
||||
and `references/improve.md`; `965208b`, PR #144). This makes `0.1.0` mean what this ADR says it
|
||||
means, "created and never yet revised", instead of a value no created skill ever kept. The rules
|
||||
above are otherwise unaffected: `metadata.version` is still required, and the push gate still
|
||||
compares whatever value is there.
|
||||
|
||||
## Consequences
|
||||
|
||||
27 SKILL.md files gain `metadata.version: "1.0.0"`, and a 28th — `bin/write-docs` — reaches the same
|
||||
|
||||
@@ -172,31 +172,6 @@ fallback row. It could not route a `SKILL.md` file path, a trigger its own descr
|
||||
Its agent row ("a path under `.apm/agents/`… or an agent markdown file") was both wider than the
|
||||
script and circular.
|
||||
|
||||
> **Amendment (2026-09-28) — three more modes: hook, instruction and prompt.** The "two accepted
|
||||
> shapes" above are now five. The `primitive-author` change (issue #94, PR #144) added three rows to
|
||||
> the Step 0 table, each with its own self-contained flow file and a shared
|
||||
> `scripts/lib-checks-primitive.sh` that `validate.sh` sources for all three:
|
||||
>
|
||||
> - A file named `*.instructions.md` takes the instruction flow (`references/instruction-flow.md`).
|
||||
> - A file named `*.prompt.md` takes the prompt flow (`references/prompt-flow.md`); the prompt rules
|
||||
> themselves are ADR-0029's.
|
||||
> - A `.json` file whose *immediate* parent directory is `hooks/` (`.apm/hooks/`, or a package's
|
||||
> root `hooks/`) takes the hook flow (`references/hook-flow.md`).
|
||||
>
|
||||
> The suffix rows are checked before the `agents/`-parent rule, so a `*.prompt.md` or
|
||||
> `*.instructions.md` under `agents/` is not audited as an agent. The fallback row is unchanged in
|
||||
> kind: anything else stops, runs no validator, exits 2, and names every accepted shape rather than
|
||||
> two. The exit-code table below applies to the new modes as written; its "matches neither shape"
|
||||
> row now means "matches none of the five".
|
||||
>
|
||||
> This is ADR-0020's merge-siblings rule applied forward rather than a new decision. Auditing a hook,
|
||||
> an instruction or a prompt is the same job as auditing a skill or agent — deterministic checks,
|
||||
> a read, a qualitative pass, one shared report — over a different input type, which is exactly the
|
||||
> case the rule says belongs in one skill behind a dispatch table, not in a new sibling audit skill.
|
||||
> The authoring side follows the rule's other half: the author skills stay split because they emit
|
||||
> genuinely different artifacts, so the three primitives got their own `primitive-author` rather
|
||||
> than rows in `skill-author` or `agent-author`.
|
||||
|
||||
**3. One entry point per script, auto-detecting, with the mode-specific half sourced.**
|
||||
|
||||
- `scripts/validate.sh` detects the target type itself, then sources `scripts/lib-boundary-resolver.sh`
|
||||
@@ -303,6 +278,31 @@ both entry points, and that is what most of the table is. Every change below is
|
||||
A single `validate.sh` copied or symlinked out of its `scripts/` directory still does not work,
|
||||
because its libraries are not beside it. It now fails at exit 2 and says so.
|
||||
|
||||
> **Amendment (2026-09-28) — three more modes: hook, instruction and prompt.** The "two accepted
|
||||
> shapes" in point 2 above are now five. The `primitive-author` change (issue #94, PR #144) added
|
||||
> three rows to the Step 0 table, each with its own self-contained flow file and a shared
|
||||
> `scripts/lib-checks-primitive.sh` that `validate.sh` sources for all three:
|
||||
>
|
||||
> - A file named `*.instructions.md` takes the instruction flow (`references/instruction-flow.md`).
|
||||
> - A file named `*.prompt.md` takes the prompt flow (`references/prompt-flow.md`); the prompt rules
|
||||
> themselves are ADR-0029's.
|
||||
> - A `.json` file whose *immediate* parent directory is `hooks/` (`.apm/hooks/`, or a package's
|
||||
> root `hooks/`) takes the hook flow (`references/hook-flow.md`).
|
||||
>
|
||||
> The suffix rows are checked before the `agents/`-parent rule, so a `*.prompt.md` or
|
||||
> `*.instructions.md` under `agents/` is not audited as an agent. The fallback row is unchanged in
|
||||
> kind: anything else stops, runs no validator, exits 2, and names every accepted shape rather than
|
||||
> two. The exit-code table above applies to the new modes as written; its "matches neither shape"
|
||||
> row now means "matches none of the five".
|
||||
>
|
||||
> This is ADR-0020's merge-siblings rule applied forward rather than a new decision. Auditing a hook,
|
||||
> an instruction or a prompt is the same job as auditing a skill or agent — deterministic checks,
|
||||
> a read, a qualitative pass, one shared report — over a different input type, which is exactly the
|
||||
> case the rule says belongs in one skill behind a dispatch table, not in a new sibling audit skill.
|
||||
> The authoring side follows the rule's other half: the author skills stay split because they emit
|
||||
> genuinely different artifacts, so hooks, instructions and prompts got their own
|
||||
> `primitive-author` rather than rows in `skill-author` or `agent-author`.
|
||||
|
||||
## Considered options
|
||||
|
||||
**Keep two skills and rely on the byte-identity contract test alone (rejected).** This is the status
|
||||
|
||||
@@ -8,17 +8,21 @@ parameters. It is the text a user would otherwise type again and again. It carri
|
||||
beyond steering existing skills or agents by name. Once it holds reusable know-how, bundled files,
|
||||
or anything the model should find on its own, it is a **Skill** in the wrong container.
|
||||
|
||||
This is stricter than apm. apm's docs call a prompt "a callable program for an LLM", and 0.28.0
|
||||
scaffolds one as a numbered-steps workflow (`apm_cli/workflow/discovery.py`). On every harness this
|
||||
repo targets, a prompt with a full workflow in it is a worse skill:
|
||||
This is stricter than apm. apm frames a prompt as a program: its docs state that "a prompt is a
|
||||
program for an LLM" (microsoft.github.io/apm/concepts/what-is-apm/, "Secure by default"; the same
|
||||
sentence is in the apm-cli 0.28.0 package README), and 0.28.0 scaffolds one as a numbered-steps
|
||||
workflow (`apm_cli/workflow/discovery.py`). On every harness this repo targets, a prompt with a full
|
||||
workflow in it is a worse skill:
|
||||
|
||||
- **Claude Code.** Custom commands have been merged into skills. "Both create `/deploy` and work
|
||||
the same way", and both are model-invocable by default (code.claude.com/docs/en/skills.md).
|
||||
apm 0.28.0 keeps only `description`, `allowed-tools`, `model`, `argument-hint` and `input` for
|
||||
Claude (`_PRESERVED_COMMAND_KEYS`) and drops `disable-model-invocation`. A deployed prompt is
|
||||
therefore a model-visible skill with fewer frontmatter keys, and it cannot be made user-only.
|
||||
- **Copilot / VS Code.** Prompt files are marked deprecated for Agent Host, and VS Code offers a
|
||||
migration to agent skills.
|
||||
- **Copilot / VS Code.** "Prompt files are deprecated for Agent Host sessions and aren't loaded by
|
||||
Agent Host", and VS Code offers a migration that converts existing prompt files to agent skills
|
||||
(code.visualstudio.com/docs/agent-customization/prompt-files). They still load in the Local agent,
|
||||
which that page says will be removed in a future release.
|
||||
- **Codex.** Codex receives no prompts at all.
|
||||
|
||||
The one job a prompt does better than a skill is a short, parameterised "do this now, using X and Y"
|
||||
@@ -45,10 +49,28 @@ happen, the cost is bounded: the prompt is a thin wrapper that calls the right s
|
||||
- `description` is present and non-empty (FAIL).
|
||||
- It is 250 characters or fewer (SUGGESTION).
|
||||
- It has no "Use when" trigger clause (SUGGESTION).
|
||||
- It has no `Not X -> Y` boundary clause (SUGGESTION).
|
||||
- **`factory-audit`, judgment step.** A body that clearly carries reusable procedure is a FAIL. A
|
||||
borderline body is a SUGGESTION. There is deliberately no line-count or heading heuristic: the
|
||||
call is made by reading the content, because any threshold misfires.
|
||||
|
||||
## Consequences
|
||||
|
||||
- **`factory-audit` gains a prompt mode.** `*.prompt.md` is a Step 0 dispatch row with its own
|
||||
`references/prompt-flow.md`, and `scripts/lib-checks-primitive.sh` carries the three description
|
||||
checks above plus the judgment step (ADR-0025, amendment 2026-09-28). A prompt that was fine under
|
||||
apm's framing — a trigger clause, a boundary clause, a numbered workflow — now draws findings.
|
||||
- **`primitive-author` refuses procedure-bearing prompts.** Its prompt reference opens with the
|
||||
boundary gate, so a "make me a command" request that carries reusable know-how is redirected to
|
||||
`skill-author` before any file is written. That makes skill-vs-prompt a checked boundary rather
|
||||
than an authoring preference.
|
||||
- **Codex gets no prompts from this repo.** apm deploys none to Codex, so anything a prompt steers
|
||||
must also be reachable there through the skill it names. Keeping procedure in the skill is what
|
||||
makes that true; a fat prompt would be content Codex users silently never see.
|
||||
- **Reversing this is cheap in files, not in routing.** No prompt exists in the repo yet, so the rule
|
||||
constrains new work only. Loosening it later means re-deciding the description contract, and every
|
||||
prompt written under it would need a trigger clause added.
|
||||
|
||||
## Considered options
|
||||
|
||||
- **Fat workflow prompts as peers of skills (rejected).** This follows apm's framing. But every
|
||||
|
||||
Reference in New Issue
Block a user