Skip to content

feat: make missing hook prerequisites skip visibly (10 plugins) - #328

Merged
kyle-sexton merged 2 commits into
mainfrom
feat/prereq-visible-skip
Jul 18, 2026
Merged

feat: make missing hook prerequisites skip visibly (10 plugins)#328
kyle-sexton merged 2 commits into
mainfrom
feat/prereq-visible-skip

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

Tranche A of the prerequisite-visibility wave: land the shared visible-skip mechanism in lib/hook-utils.sh, resync all 10 carrying plugins, and convert every silent missing-prerequisite skip in the hook fleet into a once-per-session notice on both channels.

Shared mechanism (lib/hook-utils.sh)

Doctrine (PLUGIN-PHILOSOPHY § Prerequisites and failure behavior): absence must be surfaced to BOTH the agent and the user; a silently skipped feature is a defect. New helpers, all jq-free by design (the most common missing prerequisite is jq itself):

  • hook::json_escape — pure-bash JSON string escaper (backslash/quote/\n\r\t named, residual C0 dropped via tr with a no-tr fallback).
  • hook::emit_channels <event> <ctx> <sysmsg> — composes additionalContext (agent) and systemMessage (user, verified against the current hooks doc as the user-visible universal field) into ONE JSON document — a hook's stdout is parsed as a single doc, so a run with lint findings AND a pending notice must not print twice.
  • hook::emit_skip_notice / hook::emit_system_message — thin wrappers (same message both channels; systemMessage-only for events without an additionalContext channel, e.g. Notification).
  • hook::notice_once <key> <input> — once-per-session dedup via a marker under ${CLAUDE_PLUGIN_DATA}/skip-notices (session_id regex-extracted from the raw input, no jq; 7-day marker prune; fails open toward visibility when no data dir is available).
  • hook::require_jq <event> <plugin> <input> — jq gate: visible once-per-session skip + exit 0.

Per-plugin behavior changes

Plugin Version Change
actionlint 0.3.0 actionlint-absent and jq-absent now notice visibly (were silent no-ops)
bash-format 0.4.0 shellcheck-absent notices; shfmt-absent-with-.editorconfig-opt-in notices (no opt-in stays quiet); jq gate; findings + notice compose into one JSON doc
biome-format 0.3.0 biome-absent while a Biome config governs the repo notices (opt-out stays quiet); jq gate
ruff-format 0.3.0 ruff-absent while a Ruff config governs the repo notices (opt-out stays quiet); jq gate
eol-normalizer 0.3.0 jq-absent notices (previously silently disabled the whole hook)
desktop-notification 0.3.0 jq-absent notices via systemMessage only (Notification event has no additionalContext channel); previously every notification was silently dropped
markdown-format 0.4.0 existing jq/markdownlint-cli2 warnings migrated onto the shared mechanism — gain the user channel and once-per-session dedup (were agent-only, every edit)
powershell-format 0.3.0 jq gate added; pwsh/PSScriptAnalyzer absence deliberately stays quiet — classified not-applicable (machine without PowerShell), now stated in the README
guardrails 0.5.1 lib resync only — guards' documented jq fail-open + stderr notice unchanged
claude-ops 0.11.1 lib resync only — audit hooks unchanged; README states hook runtime + jq fail-open

All 10 READMEs now declare the full hook runtime (Bash via Git Bash on native Windows, jq, plugin CLI) with each prerequisite's absence classification — the fleet-wide dim-10 README gap flagged on actionlint.

Verification

  • lib/hook-utils.test.sh: 61 passing (new: escaper, emitters incl. empty-PATH emission, combined-channel composition, notice_once dedup across sessions/keys/fail-open, require_jq gate with a coreutils-but-no-jq stub PATH — the real missing-jq shape).
  • Per-plugin suites extended with missing-tool cases (notice on both channels, once-per-session dedup, single-JSON-doc composition, telemetry skipped still emitted): actionlint 34, bash-format 38, biome-format 3 (binary-independent, runs even where Biome is absent), ruff-format 50, eol-normalizer 35, desktop-notification 54, markdown-format 65 — all green locally, plus full scripts/run-plugin-tests.sh.
  • scripts/sync-hook-utils.sh --check and --check-bump origin/main green; scripts/validate-plugins.sh green; ShellCheck clean on all changed shell files.
  • Platform facts re-verified this session from https://code.claude.com/docs/en/hooks (systemMessage = user-visible universal output field; session_id in every hook input) and https://code.claude.com/docs/en/plugins-reference (CLAUDE_PLUGIN_DATA exported to hook processes, survives updates).

Deliberately out of scope

  • Migrating the bare-cat stdin readers onto hook::buffer_stdin (deferred hardening noted in the feat: migrate hook kill switches to native userConfig (10 plugins) #323 review) — orthogonal stdin-semantics change; keeping this PR single-purpose.
  • Guardrails/claude-ops hook behavior (dim 9 PASS in the audit; PreToolUse/audit semantics deserve their own change if ever revisited).
  • Tranche B (skill-channel prereq declarations: docs-hygiene, kindle-dedrm, source-control, work-items, review) and tranche C (dim-10 cross-platform declarations) follow as separate PRs.

Related

Part of #317. No linked issue is closed by this PR (tranches B and C remain before #317 can close).

🤖 Generated with Claude Code

Land the shared visible-skip mechanism in lib/hook-utils.sh — jq-free
notice emitters (hook::json_escape, hook::emit_channels,
hook::emit_skip_notice, hook::emit_system_message), a once-per-session
dedup gate (hook::notice_once, markers under
${CLAUDE_PLUGIN_DATA}/skip-notices), and a jq gate (hook::require_jq) —
resync all 10 carrying plugins, and convert every silent
missing-prerequisite skip in the hook fleet into a once-per-session
notice on both the agent (additionalContext) and user (systemMessage)
channels. All 10 READMEs now declare the full hook runtime (Bash via
Git Bash on Windows, jq, plugin CLI) with each prerequisite's absence
classification.

Part of #317 (tranche A).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a0aba999f5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread plugins/actionlint/hooks/actionlint-check.sh
Codex review P2: the Write|Edit matcher is broader than each hook's file
filter, so with jq missing the notice fired for out-of-scope edits (a
README edit surfacing "workflow lint skipped"). New
hook::raw_file_path extracts tool_input.file_path from the raw input
without jq; the six extension-scoped hooks (actionlint, bash-format,
biome-format, ruff-format, markdown-format, powershell-format) now run
that pre-filter before hook::require_jq. eol-normalizer (all files) and
desktop-notification (Notification event) stay unconditionally gated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton merged commit a961cdc into main Jul 18, 2026
19 of 20 checks passed
@kyle-sexton
kyle-sexton deleted the feat/prereq-visible-skip branch July 18, 2026 07:04
kyle-sexton added a commit that referenced this pull request Jul 18, 2026
)

Tranche C of the prerequisite-visibility wave: every dim-10
cross-platform FAIL from the conformance audit — undeclared
shell/coreutils assumptions get a stated requirement with the Git Bash
Windows path, and the two structural Windows hazards get real fixes.

## Per-plugin changes

| Plugin | Version | Change |
|---|---|---|
| mcp-tools | 0.2.1 | README Requirements section added (Bash +
coreutils, jq, Git Bash path). CHANGELOG created. |
| ai-briefing | 0.5.1 | README states the POSIX-shell requirement of
`setup --with-build-deps` (script's platform gate already accepts
MINGW/MSYS/CYGWIN — the doc prong was the gap). |
| education | 0.3.1 | README corrected from "none beyond Claude Code":
declares `sha256sum`/`shasum`, `realpath`, `tr`, `sed` with the Git Bash
path (bundles all of them). CHANGELOG created. |
| prototype | 0.2.4 | README declares the detection-script Bash
requirement and documents the no-Bash degrade honestly (pre-computed
context echoes "none detected"; the skills read the host project
directly — verified against `skills/logic/SKILL.md`, which never
branches on the detection output). |
| code-tidying | 0.4.3 | README declares Bash 4+ (`mapfile`,
case-conversion expansions) with the Git Bash path; notes the scripts'
existing CRLF/drive-letter handling. |
| knowledge | 0.7.1 | README declares the Git Bash shell mechanics
(`book-distill` hashes with `sha256sum` on every run) and the EPUB
branch's `unzip` requirement (not bundled with Git Bash); PDF-only use
needs neither. |
| event-storming | 0.5.3 | Simulation teardown phrased
shell-agnostically at both sites (`rm -rf` on POSIX/Git Bash,
`Remove-Item -Recurse -Force` on PowerShell) instead of unconditional
`rm -rf`. |
| repo-hygiene | 0.2.1 | `clean`'s PreToolUse destructive-guard hook
moves from a bare-`.sh` command to the interpreter-named exec form
(`command: "bash"`, `args: […]`) — the doctrine-prescribed Windows-safe
spawn shape, matching disk-hygiene's existing `command: "python"`
precedent. |

## Verification

- `scripts/validate-plugins.sh`, `node scripts/generate-catalog.mjs
--check`, and `node scripts/validate-plugin-contracts.mjs` (1429 files)
green.
- `plugins/repo-hygiene/.../destructive-guard.test.sh`: 30/30 passing
after the hook-form change.
- The dim-9 work landed separately: hook channel in #328, skill channel
in #329. With this tranche, every dim-9/dim-10 FAIL enumerated in #317
is addressed (actionlint's and work-items' dim-10 items were folded into
those PRs).

## Related

Closes #317 (together with #328 and #329, which carry tranches A and B).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 18, 2026
…equisite-visibility wave)

Second integration: main's prerequisite-visibility wave re-bumped the
carrying plugins, so re-reconcile against the newer versions.

- lib/hook-utils.sh: main's jq-free prerequisite-visibility helpers and
  this branch's argv parser (incl. heredoc handling) both retained.
- Carrying plugin versions resolved above new main: guardrails stays
  0.6.0 (> main 0.5.1); nine others -> main + patch (check-bump gate).
- CHANGELOGs: stack this branch's hook-utils-refresh entry above main's
  new prerequisite-visibility entries; guardrails [0.6.0] over [0.5.1].
- Catalog regenerated.

Contract suites 187 + 75 + lib 65 green; validate, check-bump, drift
all pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@claude

claude Bot commented Jul 18, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 42s —— View job


PR #328 Review

  • Gather context from PR metadata and changed files
  • Review lib/hook-utils.sh — shared mechanism correctness & security
  • Review lib/hook-utils.test.sh — test coverage adequacy
  • Review representative plugin hooks (actionlint, bash-format, desktop-notification, biome-format, ruff-format, powershell-format)
  • Check CLAUDE.md / PLUGIN-PHILOSOPHY alignment
  • Synthesize findings and post final review

This is a well-executed PR. The shared mechanism is correctly designed under the jq-free constraint, fail-open behavior is consistently applied, and the test suite is thorough. All findings below are minor. The Codex P2 bug (early jq notice for out-of-scope edits) was correctly fixed in b8741b3 and is confirmed in the current state.


Correctness

1. hook::notice_once check-then-create is inherently racylib/hook-utils.sh:106-108

[[ -f "$marker" ]] && return 1
: >"$marker" 2>/dev/null
return 0

When two hooks fire in parallel (e.g., the user saves multiple files simultaneously), both can pass the [[ -f "$marker" ]] check before either writes the marker, resulting in two notices for the same session key. This is a cosmetic issue, not a correctness failure — the "fail open toward visibility" design principle means emitting an extra notice is the safe direction. Worth a short inline comment acknowledging the known race so future readers don't attempt to "fix" it with a locking mechanism for a non-critical dedup.

2. key parameter is an unvalidated filename componentlib/hook-utils.sh:105

local marker="$dir/${key}.${session}"

session is sanitized to [A-Za-z0-9_-] (safe). key is caller-provided and not validated. All current callers use safe literals ("actionlint-missing", "bash-format-shfmt", etc.), but a future caller passing key="a/b" would create skip-notices/a/b.SESSION in a new subdirectory — unintended but not a traversal outside CLAUDE_PLUGIN_DATA. Adding a short assertion or a substitution of /- in key would close this.

3. Duplicate extension filters per plugin — e.g., plugins/actionlint/hooks/actionlint-check.sh:44-48 and plugins/actionlint/hooks/actionlint-check.sh:58-61

Every extension-scoped hook now has two extension checks: the jq-free pre-filter (RAW_FILE from hook::raw_file_path) and the authoritative jq-parsed check (FILE from hook::read_file_path). The duplication is intentional (the pre-filter prevents false jq notices), but the extension list must be kept in sync between the two case blocks within each plugin. This is not a bug today, but it's a maintenance surface — a new extension added to the authoritative filter but missed in the pre-filter would cause a jq-absent notice to fire for that extension's files. A short comment in each hook pointing from the pre-filter to the authoritative filter would help future maintainers know to update both.


Security

4. hook::json_escape C0 drop via tr: range boundary is correctlib/hook-utils.sh:48

out=$(printf '%s' "$s" | tr -d '\000-\010\013\014\016-\037' 2>/dev/null) || out="$s"

The ranges correctly drop: NUL–BS (0x00–0x08), VT (0x0B), FF (0x0C), SO–US (0x0E–0x1F). The excluded bytes (0x09 tab, 0x0A LF, 0x0D CR) were already name-escaped above. 0x7F (DEL) is not forbidden raw in JSON strings (JSON only requires escaping 0x00–0x1F), so the omission is correct. The || out="$s" fallback when tr is absent is the intended fail-open (notice text does not carry raw control bytes in practice). No issues here — just confirming it's correct.

5. Session ID extraction is correctly sandboxedlib/hook-utils.sh:96-98

The regex extracts the raw session_id, then sanitizes to [A-Za-z0-9_-]. Path separators (/) and null bytes are replaced with -, preventing traversal in marker filenames. Good.

6. hook::raw_file_path regex handles JSON-escaped paths correctlylib/hook-utils.sh:121

[[ "$1" =~ \"file_path\"[[:space:]]*:[[:space:]]*\"(([^\"\\]|\\.)*)\" ]] || return 1

The character class [^\"\\]|\\. correctly matches any non-quote/non-backslash character OR a backslash-escaped pair, matching standard JSON string encoding. This is used only for extension matching (.sh, .yml, etc.) on the JSON-escaped value, which works correctly: *.sh matches C:\\repo\\script.sh because glob * matches any sequence. The actionlint pre-filter additionally normalizes \\/ before the workflow path check. No security concern.


Test Coverage

The 61-test suite is solid:

  • Test 14 (json_escape): covers quote/backslash/newline/tab/CR escaping and residual C0 dropping.
  • Test 15 (emit_skip_notice / emit_channels / emit_system_message): covers both-channel emission, jq-free emission (empty-PATH case), combined ctx+sysmsg into one document, empty-both no-output, and systemMessage-only shape. These are the most critical new behaviors.
  • Test 16 (notice_once): covers first-call emit, same-session suppression, new-session emit, distinct-key emit, and fail-open when CLAUDE_PLUGIN_DATA is absent.
  • Test 16b (raw_file_path): covers plain path, JSON-escaped Windows path, quoted-name value, and absent file_path.
  • Test 17 (require_jq): correctly simulates real missing-jq shape with a stub PATH carrying coreutils but no jq — an important subtlety noted in the test comment (a shell-function shadow would not exercise the command -v jq guard).

Gap: No test for hook::notice_once with a key containing / or other special characters. Not blocking (all callers use safe literals), but a one-line negative test would close the surface.

Gap: No test for hook::emit_channels with a non-empty event but empty ctx, non-empty sysmsg. This is the hook::emit_system_message path and is exercised indirectly via test 15, but the composition branch ctx empty + event non-empty is not directly asserted.


CLAUDE.md / Plugin-Philosophy Alignment

  • Repo-agnostic: lib/hook-utils.sh uses only ${CLAUDE_PLUGIN_DATA}, ${CLAUDE_PROJECT_DIR}, and ${EPOCHREALTIME} — no hardcoded paths. ✓
  • Plugin-form-safe: each plugin sources $(dirname "${BASH_SOURCE[0]}")/hook-utils.sh (self-contained in the plugin's hooks dir). The sync script and CI gate enforce byte-identity with the source. ✓
  • Versioned: all 10 plugins have version bumps in their plugin.json. ✓
  • No PII/secrets: skip-notice markers hold only empty files keyed on session ID. Notice message text is all static. ✓
  • Security-reviewed per playbook: no code execution, no untrusted remote content, no egress. The HOOK_TELEMETRY_SINK egress path is consumer-configured and fire-and-forget; the relative-sink fail-open is correctly gated. ✓

The deliberate asymmetry in powershell-format (jq-absent → visible notice; pwsh/PSScriptAnalyzer absent → silent) is correctly classified as N/A and stated in the README. ✓


Summary

No blocking issues. Three actionable low-severity items:

  1. Acknowledge the notice-dedup race in a comment at lib/hook-utils.sh:106 — one line: # racy but acceptable: fail-open means a duplicate notice on a parallel-save race
  2. Sanitize or document the key parameter in hook::notice_once — either strip / from key before building $marker, or add a note that keys must be path-safe.
  3. Sync comment on the duplicate extension filters in each extension-scoped plugin, pointing from the pre-filter case to the authoritative case so future extension additions update both.

@claude

claude Bot commented Jul 18, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 33s —— View job


PR #328 Review

  • Gather context — PR metadata, changed files, prior review
  • Review lib/hook-utils.sh — shared mechanism correctness & security
  • Review lib/hook-utils.test.sh — test coverage adequacy
  • Verify b8741b3 fix (Codex P2: jq notice fires before scope filter)
  • Review representative plugin hooks (actionlint, bash-format, desktop-notification, biome-format, ruff-format, powershell-format)
  • Check CLAUDE.md / PLUGIN-PHILOSOPHY alignment
  • Synthesize findings and post final review

This is a well-executed PR. The shared mechanism is correctly designed under the jq-free constraint, fail-open behavior is consistently applied, and the test suite is thorough. The Codex P2 bug (early jq notice for out-of-scope edits) was correctly fixed in b8741b3 and is confirmed in the current state. All findings below are minor — no blocking issues.


P2 Fix Verification (b8741b3)

Confirmed correct. All six extension-scoped hooks (actionlint, bash-format, biome-format, ruff-format, markdown-format, powershell-format) now run a jq-free applicability pre-filter before hook::require_jq:

  • actionlint-check.sh:44-48: hook::raw_file_path → backslash-normalize → */.github/*workflows/*.yml|.yaml — the loose * on separators is deliberate and handles JSON-escaped Windows paths correctly.
  • bash-format.sh:45-49: *.sh|*.bash pre-filter before jq gate — clean.
  • Same pattern in biome-format, ruff-format, powershell-format — all consistent.

eol-normalizer correctly omits a pre-filter (it applies to every file), and desktop-notification uses hook::emit_system_message (Notification event, no file path) — both by design.

The hook::raw_file_path regex at lib/hook-utils.sh:121[^\"\\]|\\. — correctly handles standard JSON string encoding and returns the value JSON-escaped (fine for extension matching, which is all the pre-filter does).


Correctness

1. hook::notice_once check-then-create is inherently racylib/hook-utils.sh:106-108

[[ -f "$marker" ]] && return 1
: >"$marker" 2>/dev/null
return 0

When two hooks fire in parallel on a multi-file save, both can pass the [[ -f "$marker" ]] check before either writes the marker, producing duplicate notices. This is cosmetic and intentionally fail-open (a duplicate notice beats a missed one), but a one-line comment acknowledging the known race would prevent future readers from attempting a locking fix for a non-critical path.
Fix this →

2. key parameter is an unvalidated filename componentlib/hook-utils.sh:105

local marker="$dir/${key}.${session}"

session is sanitized to [A-Za-z0-9_-]. key is caller-provided and not validated — a future caller passing key="a/b" would create skip-notices/a/b.SESSION in a new subdirectory (unintended but not a traversal outside CLAUDE_PLUGIN_DATA). All current callers use safe literals. Adding a substitution key="${key//\//-}" or a one-line assertion would close this surface.
Fix this →

3. Duplicate extension filters per plugin — e.g., actionlint-check.sh:45-48 (pre-filter) and actionlint-check.sh:58-61 (authoritative post-jq filter)

Every extension-scoped hook now has two extension checks. The duplication is intentional (the pre-filter prevents false jq notices), but both case blocks must stay in sync. A new extension added to the authoritative filter but missed in the pre-filter would allow a jq-absent notice to fire for files of that extension. A short # keep in sync with the pre-filter case above comment on the authoritative block (or vice versa) would protect future maintainers.


Security

hook::json_escape C0 drop via tr is correctlib/hook-utils.sh:48

The ranges \000-\010\013\014\016-\037 correctly drop NUL–BS, VT, FF, SO–US while preserving the already-named-escaped tab/LF/CR. 0x7F (DEL) is not forbidden raw in JSON — the omission is correct. The || out="$s" fallback is the intended fail-open for absent tr. No issues.

Session ID extraction is correctly sandboxedlib/hook-utils.sh:96-98

The regex extracts raw session_id then sanitizes to [A-Za-z0-9_-], preventing path separator and null byte traversal in marker filenames. Good.

desktop-notification C0 stripping on untrusted .messagedesktop-notification.sh:66-67

Correct: tr -d '\000-\037' applied at parse time before the value reaches any sink (OSC 9 sequence, osascript argv, notify-send arg). The osascript heredoc (- reads from stdin; headline/body pass as argv, not interpolated) prevents shell injection. Good.


Test Coverage

The 61-test suite is solid. Test 16b (raw_file_path) correctly covers the new pre-filter foundation — plain path, JSON-escaped Windows path, escaped-quote value, and absent file_path. Test 17 (require_jq) correctly simulates real missing-jq shape with a stub PATH carrying coreutils-but-no-jq (a shell-function shadow would not exercise the command -v jq guard; the test comment correctly explains this).

Two minor gaps not blocking:

  • No test for hook::notice_once with a key containing /. All callers use safe literals so not urgent, but a one-liner would close the surface.
  • No direct test for hook::emit_channels with non-empty event, empty ctx, non-empty sysmsg (the hook::emit_system_message path). Exercised indirectly via test 15, but a direct assertion of the output shape (no hookSpecificOutput key) would make the coverage explicit.

CLAUDE.md / Plugin-Philosophy Alignment

  • Repo-agnostic: lib/hook-utils.sh uses only ${CLAUDE_PLUGIN_DATA}, ${CLAUDE_PROJECT_DIR}, and ${EPOCHREALTIME}. ✓
  • Plugin-form-safe: each plugin sources $(dirname "${BASH_SOURCE[0]}")/hook-utils.sh; CI byte-identity gate enforced by scripts/sync-hook-utils.sh --check. ✓
  • Versioned: all 10 plugins have version bumps in their plugin.json. ✓
  • No PII/secrets: skip-notice markers are empty files keyed on session ID; notice text is all static hook-authored copy. ✓
  • Security-reviewed: no code execution, no untrusted remote content, no uncontrolled egress. ✓

The deliberate asymmetry in powershell-format (jq-absent → visible; pwsh/PSScriptAnalyzer absent → silent) is correctly classified as N/A at powershell-format.sh:60-62 and documented in the README. ✓


Summary

No blocking issues. Three actionable low-severity items, unchanged from the pre-b8741b3 review:

  1. Acknowledge the notice-dedup race in a comment at lib/hook-utils.sh:106: # racy but acceptable: fail-open means a duplicate notice on a parallel-save race
  2. Sanitize the key parameter in hook::notice_once — strip / before building $marker, or add a note that keys must be path-safe.
  3. Add a sync comment on the duplicate extension filters in each extension-scoped plugin, pointing from the pre-filter case to the authoritative case so future extension additions know to update both.

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.

1 participant