feat(apm): consume plugins through apm and keep the install fresh at SessionStart #98

Merged
Defame1297 merged 7 commits from feat/apm-consumed-install into main 2026-08-14 18:36:10 +00:00
Collaborator

Switches this repo from claude plugin install <name>@holocron to consuming its own published packages through apm, then fixes the fact that nothing was keeping that install current.

Closes #78.

1. apm-consumed install (ADR-0018)

Root apm.yml declares all six packages under dependencies.apm as git:/path: objects against the holocron remote. apm install deploys them to .claude/skills/ and .claude/agents/.

  • Object form over the <name>@holocron alias — an alias needs apm marketplace add, which writes to ~/.apm/marketplaces.json (user scope, absent on a fresh clone). The object form needs nothing beyond the committed manifest.
  • Remote source over local path — keeps the working copy running the same released content every other consumer gets, instead of erasing the line between editing a skill and shipping one.
  • Unpinned against the default branch — parity with the autoUpdate: true the native install had.

Consequences worth knowing before review:

  • Skills lose their namespace. git:git-commits is now git-commits; a project skill has no plugin to prefix. AGENTS.md and CONTEXT.md are updated. The namespaced form still resolves for anyone installing holocron natively.
  • apm owns .claude/settings.json. apm audit --ci replays the install into a scratch tree and diffs, so any repo-authored key there is permanent drift. Verified both ways: with the old enabledPlugins block, 1 of 10 check(s) failed; reduced to apm's own output, All 10 check(s) passed.
  • apm_modules/ breaks naive tree walks. apm materializes a full copy of every dependency there, .bats files included, which took the suite from 167 passing to 334 tests, 167 failures on first install. Both discovery walks now exclude it.

2. SessionStart freshness hook (ADR-0019)

scripts/git-hooks/post-push was supposed to cover staleness. It could never have worked: git has no client-side post-push hook. install.sh copied it into .git/hooks/, so it looked installed and had never once fired. #78 reported it as skipping the gitea plugin — it was skipping everything. Both apparent tests only checked that the script behaved when invoked directly and that install.sh copied the file; neither asserted git ever runs it.

Refreshing on push is also the wrong shape: your install goes stale when someone else merges, so a push of your own is neither necessary nor sufficient.

kyberforge now ships a SessionStart hook (startup matcher only) that runs apm outdated, and when anything is behind runs apm update --yes and returns reloadSkills: true so the running session picks up redeployed content. It exits silently with no apm.lock.yaml present, which keeps it inert for hosts that installed the plugin natively.

Two findings drove the wiring, both verified in a sandbox rather than assumed:

  • apm resolves ${CLAUDE_PLUGIN_ROOT} against the installed package root, and apm pack keeps only *.json from .apm/hooks/. So a .../hooks/<script> reference points into the generated mirror where the script does not exist — apm prints Hook script not found and deploys a hook aimed at nothing. The reference must be .apm/-relative, and a test pins it. The script also cannot be hand-placed in plugins/kyberforge/hooks/, which every content sync rm -rfs.
  • apm's executable-trust gate is OFF by default. apm approve --list reports Executable-trust gate disabled -- all executables deploy until an executables: block exists. Every hook, bin, and MCP primitive a dependency shipped would have deployed unprompted. Root apm.yml now enables it.

3. Release

kyberforge 1.4.1 → 1.5.0 (MINOR); marketplace and root manifest 0.3.4 → 0.4.0, per the convention in bb9158d. The executables.allow key moves to kyberforge#1.5.0 in the same commit — it is version-pinned by apm's design, so shipping the hook without the bump would have silently blocked it.

Also corrected

ADR-0018 and AGENTS.md both named apm install as the refresh command. It is not: apm install deploys from apm.lock.yaml's pinned commit and does not re-resolve refs (apm install --force says so explicitly — "does NOT refresh refs; use 'apm update' for that"). Running it after a merge redeploys the same content and reports success.

Verification

  • bash tests/run-tests.sh — 18 suites, 0 skipped, 0 failed
  • pre-commit run --hook-stage pre-push --all-files — all 15 pass, including both network hooks and apm pack --check-clean
  • tests/test-apm-current-hook.sh — 23 assertions with apm mocked
  • Sandbox install proved the deployment: apm merged the hook into .claude/settings.json, copied the script to .claude/hooks/kyberforge/, tracked ownership in an apm-hooks.json sidecar, and the auto-update path refreshed a deliberately stale lock and redeployed 39 skills

Measured cost at session start: ~0.7 s when current, ~10.4 s when six packages are behind and the refresh runs.

Reviewer notes

  • The hook cannot install itself. Dependencies resolve from the remote, so it takes effect only after this merges and apm update runs once against the new main.
  • .claude/settings.json will stop being {"hooks": {}} once the hook lands there. The merged entry is apm's own output and belongs in a follow-up commit; the rule that nothing repo-authored goes in that file is unchanged.
  • Auto-refresh rewrites apm.lock.yaml. An unexplained modification to it after opening a session is expected, not a bug. This was a deliberate choice over report-only; the emitted notice says so.

🤖 Generated with Claude Code

https://claude.ai/code/session_01X7GvKuJfy2WrdBmUttV4DT

Switches this repo from `claude plugin install <name>@holocron` to consuming its own published packages through **apm**, then fixes the fact that nothing was keeping that install current. Closes #78. ## 1. apm-consumed install (ADR-0018) Root `apm.yml` declares all six packages under `dependencies.apm` as `git:`/`path:` objects against the holocron remote. `apm install` deploys them to `.claude/skills/` and `.claude/agents/`. - **Object form over the `<name>@holocron` alias** — an alias needs `apm marketplace add`, which writes to `~/.apm/marketplaces.json` (user scope, absent on a fresh clone). The object form needs nothing beyond the committed manifest. - **Remote source over local path** — keeps the working copy running the same released content every other consumer gets, instead of erasing the line between editing a skill and shipping one. - **Unpinned against the default branch** — parity with the `autoUpdate: true` the native install had. Consequences worth knowing before review: - **Skills lose their namespace.** `git:git-commits` is now `git-commits`; a project skill has no plugin to prefix. `AGENTS.md` and `CONTEXT.md` are updated. The namespaced form still resolves for anyone installing holocron natively. - **apm owns `.claude/settings.json`.** `apm audit --ci` replays the install into a scratch tree and diffs, so any repo-authored key there is permanent drift. Verified both ways: with the old `enabledPlugins` block, `1 of 10 check(s) failed`; reduced to apm's own output, `All 10 check(s) passed`. - **`apm_modules/` breaks naive tree walks.** apm materializes a full copy of every dependency there, `.bats` files included, which took the suite from 167 passing to `334 tests, 167 failures` on first install. Both discovery walks now exclude it. ## 2. SessionStart freshness hook (ADR-0019) `scripts/git-hooks/post-push` was supposed to cover staleness. It could never have worked: **git has no client-side `post-push` hook.** `install.sh` copied it into `.git/hooks/`, so it looked installed and had never once fired. #78 reported it as skipping the `gitea` plugin — it was skipping everything. Both apparent tests only checked that the script behaved when invoked directly and that install.sh copied the file; neither asserted git ever runs it. Refreshing on push is also the wrong shape: your install goes stale when *someone else* merges, so a push of your own is neither necessary nor sufficient. kyberforge now ships a `SessionStart` hook (`startup` matcher only) that runs `apm outdated`, and when anything is behind runs `apm update --yes` and returns `reloadSkills: true` so the running session picks up redeployed content. It exits silently with no `apm.lock.yaml` present, which keeps it inert for hosts that installed the plugin natively. Two findings drove the wiring, both verified in a sandbox rather than assumed: - apm resolves `${CLAUDE_PLUGIN_ROOT}` against the installed package root, and `apm pack` keeps only `*.json` from `.apm/hooks/`. So a `.../hooks/<script>` reference points into the generated mirror where the script does not exist — apm prints `Hook script not found` and deploys a hook aimed at nothing. The reference must be `.apm/`-relative, and a test pins it. The script also cannot be hand-placed in `plugins/kyberforge/hooks/`, which every content sync `rm -rf`s. - **apm's executable-trust gate is OFF by default.** `apm approve --list` reports `Executable-trust gate disabled -- all executables deploy` until an `executables:` block exists. Every hook, bin, and MCP primitive a dependency shipped would have deployed unprompted. Root `apm.yml` now enables it. ## 3. Release kyberforge `1.4.1 → 1.5.0` (MINOR); marketplace and root manifest `0.3.4 → 0.4.0`, per the convention in bb9158d. The `executables.allow` key moves to `kyberforge#1.5.0` in the same commit — it is version-pinned by apm's design, so shipping the hook without the bump would have silently blocked it. ## Also corrected ADR-0018 and `AGENTS.md` both named `apm install` as the refresh command. It is not: `apm install` deploys from `apm.lock.yaml`'s pinned commit and does not re-resolve refs (`apm install --force` says so explicitly — "does NOT refresh refs; use 'apm update' for that"). Running it after a merge redeploys the same content and reports success. ## Verification - `bash tests/run-tests.sh` — 18 suites, 0 skipped, 0 failed - `pre-commit run --hook-stage pre-push --all-files` — all 15 pass, including both network hooks and `apm pack --check-clean` - `tests/test-apm-current-hook.sh` — 23 assertions with `apm` mocked - Sandbox install proved the deployment: apm merged the hook into `.claude/settings.json`, copied the script to `.claude/hooks/kyberforge/`, tracked ownership in an `apm-hooks.json` sidecar, and the auto-update path refreshed a deliberately stale lock and redeployed 39 skills Measured cost at session start: **~0.7 s** when current, **~10.4 s** when six packages are behind and the refresh runs. ## Reviewer notes - **The hook cannot install itself.** Dependencies resolve from the remote, so it takes effect only after this merges and `apm update` runs once against the new `main`. - **`.claude/settings.json` will stop being `{"hooks": {}}`** once the hook lands there. The merged entry is apm's own output and belongs in a follow-up commit; the rule that nothing repo-authored goes in that file is unchanged. - **Auto-refresh rewrites `apm.lock.yaml`.** An unexplained modification to it after opening a session is expected, not a bug. This was a deliberate choice over report-only; the emitted notice says so. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01X7GvKuJfy2WrdBmUttV4DT
Claude added 3 commits 2026-08-14 17:59:02 +00:00
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
Why
---
ADR-0018 left deployed skills tracking the remote default branch with nothing
watching for drift. The mechanism that was supposed to cover this,
scripts/git-hooks/post-push, could never have worked: git has no client-side
post-push hook. install.sh copied it into .git/hooks/ so it looked installed,
and it had never once fired. Issue #78 reported it as skipping the gitea
plugin; it was skipping everything.

Refreshing on push was also the wrong shape. Your install goes stale when
someone else merges, so a push of your own is neither necessary nor sufficient
for staleness to have occurred.

Implementation notes
--------------------
kyberforge ships a SessionStart hook (startup matcher only) that runs
`apm outdated`, and when anything is behind runs `apm update --yes` and returns
reloadSkills:true so the running session picks up redeployed content. It exits
silently with no apm.lock.yaml present, which keeps it inert for hosts that
installed this plugin natively rather than through apm.

Two findings drove the wiring, both verified rather than assumed:

- apm resolves ${CLAUDE_PLUGIN_ROOT} against the installed package root, and
  `apm pack` keeps only *.json from .apm/hooks/. A .../hooks/<script> reference
  therefore points into the generated mirror where the script does not exist —
  apm reports "Hook script not found" and deploys a hook aimed at nothing. The
  reference must be .apm/-relative, and a test pins it.
- apm's executable-trust gate is OFF unless apm.yml carries an `executables:`
  block; until now every hook, bin and MCP primitive a dependency shipped would
  have deployed unprompted. Root apm.yml now enables it. The allow key is
  version-pinned by apm's design, so a kyberforge version bump silently blocks
  the hook until the key is bumped too — called out in the block and the ADR.

Also corrects ADR-0018 and AGENTS.md, which named `apm install` as the refresh
command. It is not: `apm install` deploys from apm.lock.yaml's pinned commit
and does not re-resolve refs. `apm update` does.

Impact
------
Session startup costs ~0.7s when current and ~10.4s when six packages are
behind. Auto-refresh rewrites apm.lock.yaml, so an unexplained modification to
it after opening a session is expected; the emitted notice says so.

.claude/settings.json stops being exactly {"hooks": {}} once the hook lands
there — the merged entry is apm's own output, and the rule that nothing
repo-authored goes in that file is unchanged. .claude/hooks/ and the
.claude/apm-hooks.json sidecar are gitignored install output.

The hook cannot install itself: dependencies resolve from the remote, so it
takes effect only after this merges and `apm update` runs once against the new
default branch.

scripts/git-hooks/ is now empty. install.sh's copy block is kept and
test-git-hooks-install.sh synthesizes its own fixture, so the mechanism stays
tested without requiring a dead hook to exist.

ADR: 0019
Refs: #78

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7GvKuJfy2WrdBmUttV4DT
Why
---
The SessionStart freshness hook is a new kyberforge feature and was committed
without a version bump. It also cannot ship unbumped: apm's executable-trust
allow key is version-pinned, so `kyberforge#1.4.1` would have stopped matching
the moment the package version moved, silently blocking the very hook the entry
exists to authorise.

Implementation notes
-----------------
kyberforge 1.4.1 -> 1.5.0 (MINOR, new feature); marketplace and root manifest
0.3.4 -> 0.4.0, following the convention in bb9158d where a package bump carries
the marketplace version with it. The executables.allow key moves to
kyberforge#1.5.0 in the same commit. Compiled manifests regenerated with
`apm pack` plus both sync scripts.

A bare `apm pack` also writes build/ and a root .claude-plugin/plugin.json,
neither of which is repo content — the pre-push gate only ever runs pack with
--dry-run, so they had not appeared before. Both removed; build/ is now
gitignored so a future release does not stage it by accident.

Impact
------
Consumers pinning kyberforge see a MINOR bump. All 15 pre-push hooks pass,
including apm pack --check-clean, so compiled output matches the manifests.

ADR: 0019

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7GvKuJfy2WrdBmUttV4DT
Defame1297 added 4 commits 2026-08-14 18:34:48 +00:00
apm approves a package's hooks and bin by an exact dictionary lookup on a
composed `name#version` key (apm_cli/security/executables.py,
is_package_approved). There is no wildcard and no version-less form, so
bumping plugins/kyberforge/apm.yml without editing root apm.yml's
`kyberforge#<version>` key errors nowhere: the entry stops matching, the
SessionStart hook stops deploying, and the install goes quietly stale.

ADR-0019 already named that as a live failure mode, mitigated only by a
comment in the executables block. This repo gates generated-content
drift, marketplace mirror drift and vale style drift deterministically,
and a silent-staleness failure is worse than any of them — a comment does
not survive the release that breaks it.

check-executables-allow-sync.sh parses the version out of the plugin
manifest and asserts the matching key exists in the root manifest. It
uses PyYAML where importable and falls back to a two-shape scan
otherwise, so a missing pip package cannot become the thing that blocks
every push; the test asserts both readers agree. 23 assertions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7GvKuJfy2WrdBmUttV4DT
plugins/bin/.mcp.json declared the obsidian server as
`npx @bitbonsai/mcpvault@latest`, so an unpinned third-party npm package
was fetched and executed at every session start. The apm-consumed install
promoted that string to committed repo-root content in .mcp.json, giving
every clone the same unpinned execution. Pinned to 0.15.0, the version
`latest` currently resolves to.

bin 1.1.2 -> 1.1.3 and marketplace 0.4.0 -> 0.4.1, following the mapping
bb9158d establishes and 3bfdf58 confirms: the marketplace takes the same
bump severity as the highest-severity package bump. kyberforge is not
bumped here, so executables.allow's `kyberforge#1.5.0` key is untouched.

The pin is not live for this working copy until this lands on the remote
and `apm update` re-resolves — apm.lock.yaml still records 1.1.2 and
`@latest`, because the six dependencies resolve from the remote rather
than from the tree beside them. Correct for a fresh clone immediately.

.gitignore gains /.claude-plugin/plugin.json: a bare `apm pack` emits a
root-package manifest there that has never been tracked on any branch.
Scoped to the file, since the sibling marketplace.json is compiled output
that is committed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7GvKuJfy2WrdBmUttV4DT
apm prints "1 outdated dependency found" in the singular when exactly one
package is behind (apm_cli/commands/outdated.py). check-apm-current.sh
matched only "outdated dependencies found", so one stale package was
invisible: the hook exited 0 silently and no refresh ran. With six
packages merging independently, one-behind is the ordinary case, so the
freshness mechanism failed most often in the situation it exists for.

Three further defects in the same hook:

- The host timeout was below the script's own budget. hooks.json declared
  320s while the script allows `timeout 60` plus `timeout 300` = 360s, so
  a slow remote let the host kill the hook mid-update and leave
  .claude/skills/ half-deployed with nothing emitted. Now 380. A test
  asserts the invariant rather than the literal: it sums every `timeout N`
  parsed out of the script and requires hooks.json to exceed it, so
  changing either side alone fails.

- The lockfile guard was cwd-relative, so a session opened in a
  subdirectory no-opped silently and ran both apm calls against the wrong
  directory. Now anchored on CLAUDE_PROJECT_DIR, falling back to the cwd
  so the hook stays inert under a host that does not set it.

- Every assertion mocked apm, so the suite was green over code that could
  not detect its own most common trigger. That blind spot is what hid the
  singular/plural bug, and it is the same shape as the deleted post-push
  tests. The suite now stages a genuinely outdated dependency against a
  local git remote — offline, via url.<path>.insteadOf, so the
  pass-under-unshare property survives — runs the real `apm outdated`, and
  replays its output through the real hook. Reverting the grep to
  plural-only fails it.

23 -> 35 assertions. Each fix mutation-tested individually. kyberforge
stays at 1.5.0: it is untagged, so this changes what 1.5.0 ships rather
than superseding it, and executables.allow needs no edit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7GvKuJfy2WrdBmUttV4DT
AGENTS.md and CONTEXT.md asserted that the `<plugin>:<skill>` form "no
longer resolves here". It does: ~/.claude.json still enables core, git,
gitea, kyberforge and lint at user scope, which ADR-0018 left in place
deliberately. Both names are live at once, so a working `gitea:gitea-prs`
is the user-scope copy answering — not evidence that the apm install is
broken and not something to "fix". ADR-0018 contradicted itself on this,
claiming every namespaced reference went stale while its own "User scope
is untouched" consequence said otherwise; recorded as a dated correction
alongside the existing one. Bare names stay the documented default.

Five stale pre-push hook counts updated for the new
check-executables-allow-sync gate: 13 -> 14 repo-defined hooks, 15 -> 16
reported by pre-commit, eleven -> twelve passing offline. The gate reads
two local manifests and makes no network call, so the SKIP pair for
offline pushes stays exactly two. "Four pre-push hooks shell out to apm"
is unchanged and still correct — the new hook parses YAML directly.

ADR-0019 gains the timeout arithmetic, the singular/plural failure and
why mocking every apm call hid it, and a consequence recording that the
trust gate is keyed on version rather than content: an edit to a hook
script landing on main deploys and executes unattended on the next
session start, since the dependency is unpinned and the hook runs
`apm update --yes`. That is accepted, not overlooked, but it is why the
gate should not be read as a supply-chain control.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X7GvKuJfy2WrdBmUttV4DT
Author
Collaborator

Reviewed and fixed in 099cf58..c9fe2e8.

Five findings, all resolved on this branch:

  1. executables.allow self-disabled on the next kyberforge release. apm matches the key by exact name#version dict lookup — no wildcard, no version-less form — so a version bump without a key edit silently stops the hook deploying. Now gated at pre-push by check-executables-allow-sync.
  2. Host timeout was below the script's own budget — hooks.json declared 320s against a 60+300s worst case, so a slow remote let the host kill the hook mid-update. Now 380, with a test asserting the invariant rather than the literal.
  3. <plugin>: form does still resolve here. Five plugins remain enabled at user scope, so both names are live; AGENTS.md/CONTEXT.md said otherwise and ADR-0018 contradicted itself. Corrected.
  4. A single stale package was invisible. apm prints 1 outdated dependency found in the singular; the hook matched only the plural. With six independently-merging dependencies, one-behind is the ordinary case — the freshness mechanism failed most often in exactly the situation it exists for. Found only by adding a probe that runs the real apm outdated instead of a mock; every prior assertion mocked apm, which is the same blind spot that let post-push look tested for six weeks.
  5. Lockfile guard was cwd-relative, so a session opened in a subdirectory no-opped silently. Now anchored on CLAUDE_PROJECT_DIR.

Also pinned @bitbonsai/mcpvault@latest → @0.15.0 (bin 1.1.3, marketplace 0.4.1) — unpinned third-party npm executing at every session start, now repo-root committed content.

Suite 18 → 19 green, all 16 pre-push hooks pass. Note the PR body above is now stale on four numbers: timeout: 320, 23 assertions, 18 suites, 13 pre-push hooks.

Reviewed and fixed in `099cf58..c9fe2e8`. Five findings, all resolved on this branch: 1. **`executables.allow` self-disabled on the next kyberforge release.** apm matches the key by exact `name#version` dict lookup — no wildcard, no version-less form — so a version bump without a key edit silently stops the hook deploying. Now gated at pre-push by `check-executables-allow-sync`. 2. **Host timeout was below the script's own budget** — `hooks.json` declared 320s against a 60+300s worst case, so a slow remote let the host kill the hook mid-update. Now 380, with a test asserting the invariant rather than the literal. 3. **`<plugin>:` form does still resolve here.** Five plugins remain enabled at user scope, so both names are live; AGENTS.md/CONTEXT.md said otherwise and ADR-0018 contradicted itself. Corrected. 4. **A single stale package was invisible.** apm prints `1 outdated dependency found` in the singular; the hook matched only the plural. With six independently-merging dependencies, one-behind is the ordinary case — the freshness mechanism failed most often in exactly the situation it exists for. Found only by adding a probe that runs the real `apm outdated` instead of a mock; every prior assertion mocked apm, which is the same blind spot that let `post-push` look tested for six weeks. 5. **Lockfile guard was cwd-relative**, so a session opened in a subdirectory no-opped silently. Now anchored on `CLAUDE_PROJECT_DIR`. Also pinned `@bitbonsai/mcpvault@latest` → `@0.15.0` (bin 1.1.3, marketplace 0.4.1) — unpinned third-party npm executing at every session start, now repo-root committed content. Suite 18 → 19 green, all 16 pre-push hooks pass. Note the PR body above is now stale on four numbers: `timeout: 320`, 23 assertions, 18 suites, 13 pre-push hooks.
Defame1297 approved these changes 2026-08-14 18:36:02 +00:00
Defame1297 merged commit f9b919d7e3 into main 2026-08-14 18:36:10 +00:00
Defame1297 deleted branch feat/apm-consumed-install 2026-08-14 18:36:11 +00:00
Sign in to join this conversation.