Skip to content

fix(hook): resolve Charter through the repo package manager in the pre-commit hook (#366) - #379

Merged
stackbilt-admin merged 7 commits into
mainfrom
fix/366-hook-pnp
Oct 18, 2026
Merged

stackbilt-admin merged 7 commits into
mainfrom
fix/366-hook-pnp

Conversation

@stackbilt-admin

@stackbilt-admin stackbilt-admin commented Oct 18, 2026 •

Copy link
Copy Markdown
Member

Closes #366

Problem

Since #365, the generated pre-commit hook runs npx charter doctor --adf-only --ci --staged … before <pm> run verify:adf. It has always run npx charter for tidy and adf evidence too.

Under Yarn Plug'n'Play there is no node_modules/.bin, so npx misses the installed CLI and falls back to the unrelated registry package charter (0.0.2, which has no bin). The gate fails before yarn run verify:adf runs. In any repo without Charter installed, the same fallback fetches that package.

Fix (scope: the pre-commit hook only)

Package manager, picked once per hook run

HOOK_CLI_RESOLVE_BLOCK takes the first candidate whose marker exists and whose binary is on PATH. This is #365's cascade with PnP in front:

  1. Yarn Plug'n'Play: .pnp.cjs, .pnp.js, or .yarnrc.yml with nodeLinker: pnp → yarn
  2. pnpm-lock.yaml → pnpm
  3. yarn.lock → yarn
  4. npm otherwise

If the first marker it finds has no binary on PATH, the hook warns and names the runner it uses instead. Under PnP the warning adds corepack enable.

How Charter runs

charter_cli runs exactly $CHARTER_RUN, and every Run: hint prints that same command. The strings come from HOOK_CLI_COMMANDS. "Local" means node_modules/.bin/charter or charter.cmd exists; "global" means a charter on PATH.

Package manager Local Global only Neither
Yarn PnP yarn charter yarn charter (no fallback: there is no node_modules to check) yarn charter
Yarn, node_modules linker yarn charter (yarn --silent charter on Yarn 1) charter, run directly nothing
pnpm pnpm exec charter pnpm exec charter (it searches PATH) nothing
npm npm exec --no --no-update-notifier --fetch-retries=0 -- @stackbilt/cli charter, run directly nothing

"Nothing" means the hook calls neither Charter nor the package manager. It prints the devDependency hint once, skips tidy, and fails the gate. It prints no Run: command, since none would work.

One --version probe per commit

HOOK_CLI_PROBE_BLOCK runs once, before any other Charter call, and keeps stderr.

  • Not resolvable (no binary found, or exit 127): the devDependency hint, once.
  • A binary exists but --version fails: Charter is installed but failed to run: plus the first error line. No devDependency hint.
  • Output without charter v<digit>: a warning that the charter found is not @stackbilt/cli, and nothing else runs.
  • charter v0.x / v1.x: one warning to "Upgrade @stackbilt/cli to 2.x (as a devDependency, or your global install)". charter v10.0.0 is not flagged.
  • A doctor that ran and reported findings: no hint.

Network and timing

The hook never installs a package and never runs a downloaded one. The only network use left is Corepack downloading Yarn or pnpm on first use.

Measured offline, with npm 11.17, the registry at 127.0.0.1:9, a real @stackbilt/cli@2.1.0 and a real hook run:

Setup Time Registry requests Result
Local install 6-7 s none doctor ran and reported a finding; no hint
No install at all 0 s none hint once, no Run: line
Global charter v1.9.3 only 2 s none one upgrade warning

Tidy notes

Tidy's dry-run stderr is now kept. A note about a staged file it leaves alone (an unmanaged CLAUDE.md, verified against the real CLI) is shown under [pre-commit] adf tidy notes:. In that case, and when the dry run fails, the hook no longer prints "Vendor config files are clean."

This was a pre-existing bug. It is fixed in the shared template and in the repo's own .githooks/pre-commit.

Other changes

Existing installs

Installed hooks do not update themselves. Re-run charter hook install --pre-commit. It is required under Yarn PnP and recommended everywhere else. The CHANGELOG, docs/cli-reference.md and docs/upgrading-to-2.0.md say so.

Tests

  • hook-pm.test.ts runs the real hook under sh, with stubs for yarn, pnpm, npx, npm and charter on PATH that record their argv. No network. It covers:
    • the cascade, including PnP plus pnpm-lock, and both lockfiles with only yarn installed;
    • .pnp.cjs and .pnp.js alone, plus two mutation tests;
    • local, global and neither for npm, non-PnP Yarn and pnpm;
    • Yarn PnP never using a global install;
    • one probe per commit;
    • the broken, foreign and 1.x cases, and v10 / v2 / v3-beta not being flagged;
    • an unresolved CLI, which gets no Run: line;
    • Run: lines matching the command that actually ran;
    • tidy notes, and no "clean" message after a failed dry run;
    • hook grep and the JS regex agreeing on 15 inputs.
  • doctor-pnp-source.test.ts mocks the hook module's PnP exports, so a doctor that hard-codes detection fails.
  • hook.test.ts covers the doctor rows. integration/precommit-hook.test.ts redefines charter_cli ahead of the probe.
  • Full serial suite, after merging main e55afd7: 1893 passed. The 3 failures are the known Windows-only path-separator cases (bootstrap bootstrap: phases 6/7 collect warnings without printing them; absoluteAiDirArg misses Windows paths on POSIX #307, context-refresh, serve-context).

🤖 Generated with Claude Code

…repo package manager (#366)

The generated pre-commit hook called a bare `npx charter` for tidy, the
scoped doctor gate (#365) and adf evidence. Under Yarn Plug'n'Play npx
cannot resolve the installed CLI and falls back to the unrelated registry
package `charter`, so the gate failed before `yarn run verify:adf`.

The hook now picks the package manager once (PnP markers, then the #365
lockfile + PATH order) and defines charter_cli: `yarn charter`
(`yarn --silent charter` on Yarn 1), `pnpm exec charter`, or
`npx --no-install charter`, none of which downloads a package. A missing
pm binary warns and falls back to npx --no-install / npm run. A new
marker lets doctor report a pre-#366 hook as INFO in PnP repos. The
CHARTER_HOOK_SCOPED_DOCTOR token flow is unchanged.

Tests run the generated hook with stub yarn/pnpm/npx/npm on PATH for
yarn PnP, yarn classic, pnpm and npm fixtures, plus missing-pm fallbacks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kovermier and others added 4 commits October 17, 2026 19:37
…ade, scope the CLI hint (#366)

Round 2 on review findings:
- npm runner is `npm exec --no --no-update-notifier -- @stackbilt/cli`,
  never the bare name `charter`. Verified on npm 11.17 against an
  unreachable registry: a local install runs with no registry request;
  without one npm requests @stackbilt/cli metadata only (and may run a
  global install when online), and never installs anything.
- Package manager choice walks PnP -> pnpm-lock -> yarn.lock -> npm and
  takes the first whose marker exists and whose binary is on PATH (the
  #365 cascade), warning when the first marker's binary is missing.
- PnP detection comes from one source (PNP_MARKER_FILES,
  YARNRC_PNP_LINKER_ERE / yarnrcPnpLinkerRegExp) for the hook and doctor.
- The devDependency hint prints only when `charter_cli --version` also
  fails, not when doctor ran and reported findings.
- Docs: upgrading-to-2.0 drops the PnP known limitation; CHANGELOG and
  cli-reference state the exact network behaviour, global-install
  pickup, corepack first-use download, and that the fix covers the
  pre-commit hook only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps both the #366 and #371 CHANGELOG entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…l install (#366)

Round 3 on review findings:
- Resolve once at the top of the hook. Under npm, a local install
  (node_modules/.bin/charter or charter.cmd) runs through
  `npm exec --no --no-update-notifier --fetch-retries=0 -- @stackbilt/cli`;
  without one a global `charter` on PATH runs directly; with neither,
  charter_cli runs nothing, tidy is skipped and the gate fails after one
  devDependency hint, with no network request. Measured offline (npm
  11.17, registry unreachable): local 6 s, none 0 s, global 2 s.
- One `--version` probe per commit (HOOK_CLI_PROBE_BLOCK): unresolvable
  -> hint once; 1.x -> one warning to upgrade to @stackbilt/cli 2.x.
- charter_cli runs $CHARTER_RUN, so the printed Run: commands are exactly
  what ran (HOOK_CLI_COMMANDS is the single source for hook, help, tests).
- Shared PnP regex uses [ \t\r\f\v] instead of \s; the agreement test
  covers `nodeLinker:\n  pnp`. A mocked-module test fails if doctor stops
  using the shared source.
- hook --help lists the cascade order and the exact commands.
- A failed tidy dry run no longer prints "Vendor config files are clean."
  (template and the repo's own .githooks/pre-commit).
- docs/upgrading-to-2.0.md no longer says the hooks call `npx charter`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
kovermier and others added 2 commits October 17, 2026 21:02
…tidy notes (#366)

Round 4 on review nits:
- Yarn with a node_modules linker and no node_modules/.bin/charter now
  runs a global `charter` from PATH (as 2.1.0's `npx charter` found it),
  else reports Charter missing with no calls. Yarn PnP stays
  `yarn charter`. pnpm reports missing when neither a local bin nor a
  global charter exists (pnpm exec searches PATH itself).
- The --version probe keeps stderr. A found binary whose --version fails
  is "installed but failed to run: <first line>", with no devDependency
  hint. Output without `charter v<digit>` warns that the charter found
  is not @stackbilt/cli and runs nothing else. `charter v10.0.0` is not
  flagged as 1.x (tested). The 1.x warning says to upgrade
  @stackbilt/cli to 2.x (as a devDependency, or your global install).
- With nothing resolved, the gate prints no Run: command.
- Tidy's stderr notes from the dry run (an unmanaged file it skips) are
  shown under "adf tidy notes:", and the hook no longer then claims the
  vendor files are clean (template and .githooks/pre-commit).
- Docs: one table for local/global/neither per package manager in
  cli-reference; upgrading-to-2.0 and the CHANGELOG describe the global
  behaviour for npm, pnpm and Yarn.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Keeps every CHANGELOG entry; the #366 scope note now points at #380 for the CI workflow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@stackbilt-admin
stackbilt-admin merged commit 2bccbf2 into main Oct 18, 2026
8 checks passed
@stackbilt-admin
stackbilt-admin deleted the fix/366-hook-pnp branch October 18, 2026 02:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

hook: pre-commit gate fails under Yarn Plug'n'Play (npx charter can't resolve the PnP binary)

2 participants