Skip to content

feat: extract shared hook-utils.sh to an authoring-time SSOT#24

Merged
kyle-sexton merged 5 commits into
mainfrom
feat/shared-hook-utils
Jul 4, 2026
Merged

feat: extract shared hook-utils.sh to an authoring-time SSOT#24
kyle-sexton merged 5 commits into
mainfrom
feat/shared-hook-utils

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Summary

Four plugins carry byte-identical hooks/hook-utils.sh copies — the playbook's Rule-of-Three trigger. Per the decision reached with fresh-docs research (plugins-reference + plugin-dependencies, fetched 2026-07-03), the shared lib becomes an authoring-time SSOT with plain runtime copies:

  • lib/hook-utils.sh is the canonical source; scripts/sync-hook-utils.sh propagates it into every carrying plugin (sync / --check / --check-bump <ref> modes).
  • New hook-utils-sync CI lane (wired into ci-status): fails on copy drift, and on PRs fails when the lib changed without a version bump in every carrying plugin — the plugin version is the update cache key, so an unbumped plugin never delivers the change.
  • Runtime is untouched: each installed plugin stays self-contained under cache isolation; no cross-plugin coupling; one-plugin install UX unchanged.
  • Rejected/deferred alternatives are recorded in the playbook with citations and revisit triggers: a dependency plugin is not viable (no documented mechanism exposes a dependency's install path to another plugin's hook; cache dirs are version- and SHA-suffixed by design), and marketplace-internal symlinks are deferred (skipped for --plugin-dir/local installs; Windows-fragile).

All four plugin versions bumped (patch): the shared header no longer claims markdown-formatter heritage and the sourcing guard drops the _MDFMT prefix.

Stacked on #23 (feat/ruff-format-plugin) — do not merge into #23; retarget to main after #23 merges.

Verification

  • scripts/sync-hook-utils.sh --check passes; all copies byte-identical to source (sha1 e207414).
  • Bump gate exercised in all three paths: lib-unchanged → pass; changed+bumped → pass; changed+stale → exit 1 naming the manifest.
  • All four hook-utils.test.sh suites pass (37/37 ×3, 43/43).
  • claude plugin validate ×4 and --strict catalog validation pass; shellcheck, markdownlint, actionlint, editorconfig-checker clean locally.

🤖 Generated with Claude Code

https://claude.ai/code/session_012NNwc8U9MbgWsd5sbrfpPh


Note

Medium Risk
Touches shared hook runtime utilities used on every PostToolUse edit (path scoping and telemetry), with mandatory plugin version bumps so consumers actually receive the lib change.

Overview
Centralizes duplicated hooks/hook-utils.sh across four formatter/lint plugins into lib/hook-utils.sh, with scripts/sync-hook-utils.sh to copy or verify plugin copies (--check, --check-bump <ref>).

A new hook-utils-sync CI job (included in ci-status) fails PRs when copies drift from the lib or when the lib changes without a patch bump in every carrying plugin’s plugin.json (version is the consumer update key). docs/MIGRATION-PLAYBOOK.md records this SSOT-plus-copies model and why dependency plugins / marketplace symlinks were rejected or deferred.

Synced plugin copies pick up shared headers/guard (_HOOK_UTILS_LOADED) and a behavioral change: hook::physical_path plus hook::read_file_path now resolve symlinks before CLAUDE_PROJECT_DIR membership checks. Carrying plugins are bumped (bash-lint 0.1.2, biome/ruff 0.1.1, markdown-formatter 0.1.3).

Reviewed by Cursor Bugbot for commit 7bcb499. Bugbot is set up for automated code reviews on this repo. Configure here.

kyle-sexton and others added 4 commits July 3, 2026 23:37
Fourth migrated plugin: formats and lints Python (.py/.pyi) on Write|Edit
via the consuming repo's own Ruff config. Follows the biome-format model:

- Opt-in gate on a governing Ruff config (.ruff.toml, ruff.toml, or
  pyproject.toml with [tool.ruff]) — a pyproject.toml without [tool.ruff]
  does not opt in, mirroring Ruff's own discovery. No config -> untouched.
- ruff check --fix (safe fixes only) + ruff format, then a --no-fix verify
  pass whose concise diagnostics surface as advisory additionalContext.
- --unfixable F401 protects just-added imports from mid-edit auto-deletion
  (extends, not replaces, the consumer's own unfixable list — verified
  against ruff 0.15).
- Version-aware syntax errors come from Ruff's own parser (target-version /
  requires-python floor), replacing the source repo's separate AST-check
  layer.
- --force-exclude honors consumer excludes for explicitly-passed paths;
  --no-cache keeps .ruff_cache out of consumer repos.
- Binary resolved from the repo's .venv (bin/ or Scripts/) or PATH; never
  downloaded. Advisory: always exits 0. Kill switch
  HOOK_RUFF_FORMAT_ENABLED; telemetry envelope contract v1.0 unchanged.

Verified: 43/43 hook contract tests + 37/37 lib tests against ruff 0.15.20
(Windows Git Bash); claude plugin validate passes for the plugin and the
catalog (--strict).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012NNwc8U9MbgWsd5sbrfpPh
The membership check in hook::read_file_path compared lexically
normalized paths, so an in-project symlink pointing outside
CLAUDE_PROJECT_DIR passed the prefix check while Ruff's writes landed
outside the workspace (Cursor security review on PR #23, MEDIUM).

Canonicalize both sides of the comparison via realpath/readlink -f
before normalizing: escaping file and directory symlinks are now
rejected, and a project root reached through a symlink (e.g. macOS
/tmp) no longer causes false rejections. Hosts with no resolver fall
back to the historical lexical comparison rather than silently
disabling the hook.

Adds six guard tests (lexical accept/reject plus four symlink cases,
SKIP-passing where the host cannot create symlinks). Both scripts also
pick up this repo's canonical shfmt style (.editorconfig: 2-space,
no switch-case indent); the copies in the other plugins are synced in
a follow-up.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YLwWqDP9dvakPSnnqRxwW4
Four plugins carried byte-identical hooks/hook-utils.sh copies, meeting
the playbook's Rule-of-Three trigger. Establish lib/hook-utils.sh as the
single source of truth, synced into each carrying plugin by
scripts/sync-hook-utils.sh; plugins stay self-contained under cache
isolation with no runtime coupling and unchanged install UX.

The new hook-utils-sync CI lane fails on copy drift and, on PRs, when
the lib changed without a version bump in every carrying plugin (the
plugin version is the update cache key). All four plugin versions are
bumped: the shared header no longer claims markdown-formatter heritage
and the double-source guard drops the _MDFMT prefix.

The dependency-plugin and marketplace-symlink alternatives are recorded
in the playbook as rejected/deferred with doc citations and revisit
triggers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012NNwc8U9MbgWsd5sbrfpPh
@claude

claude Bot commented Jul 4, 2026

Copy link
Copy Markdown

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


PR #24 Review — feat: extract shared hook-utils.sh to an authoring-time SSOT

  • Gather context from PR description and changed files list
  • Read lib/hook-utils.sh (canonical source)
  • Read scripts/sync-hook-utils.sh (sync/check script)
  • Read .github/workflows/ci.yml changes
  • Read docs/MIGRATION-PLAYBOOK.md updates
  • Read plugin manifests and hook-utils copies
  • Compile and post review

Summary

The architecture decision is correct and well-justified: a single authoring-time SSOT with plain runtime copies is the right tradeoff given the constraints (cache isolation makes cross-plugin file references unviable; symlinks are skipped by --plugin-dir). The decision record in the playbook is thorough and citable. All five copies are byte-identical (sha1 e207414, confirmed locally). No blocking issues. Two medium findings worth addressing before merge.


Medium

1. --check-bump: $manifest at HEAD is not guarded before jq reads it

scripts/sync-hook-utils.sh L61:

head_version=$(jq -r '.version // empty' "$manifest")

If a plugin has a hooks/hook-utils.sh copy but its manifest is missing or at a non-standard path, jq errors and set -euo pipefail aborts the whole check — silently skipping the remaining plugins. The base-ref lookup two lines up is already guarded (|| true; empty = skip). HEAD should get the same treatment:

if [[ ! -f "$manifest" ]]; then
  echo "MISSING MANIFEST: $manifest" >&2
  stale=1
  continue
fi
head_version=$(jq -r '.version // empty' "$manifest")

Fix this →


2. _HOOK_UTILS_LOADED guard rename needs a justification comment

lib/hook-utils.sh L12–13:

[[ -n "${_HOOK_UTILS_LOADED:-}" ]] && return 0
readonly _HOOK_UTILS_LOADED=1

The rename from _MDFMT_HOOK_UTILS_LOADED (a copy-paste artifact from ruff-format's original) to a shared, non-namespaced variable is correct — hooks run in isolated subprocesses, so there is no cross-plugin contamination risk, and within a single process all copies are byte-identical so any one loaded = all loaded. But without that reasoning inline, a future contributor will see a suspiciously global variable and be tempted to re-namespace it, reintroducing divergence. A one-line comment is worth adding:

# Non-namespaced: all copies are byte-identical; hooks run in isolated subprocesses.
[[ -n "${_HOOK_UTILS_LOADED:-}" ]] && return 0

Fix this →


Low / Observations (no action required)

3. Sync mode: no early existence check on $src

scripts/sync-hook-utils.sh L16set -euo pipefail will catch a missing lib/hook-utils.sh at the cp call, but the error message will be a raw cp failure rather than a clear "source not found." Harmless in practice; easy to add if the script grows more modes.

4. Deferred test consolidation is correctly scoped

The playbook correctly records the 4 divergent hook-utils.test.sh suites as a known follow-up, deferred to the next behavioral lib change. No action needed here.

5. EPOCHREALTIME in hook::emit_telemetry is bash 5.0+

lib/hook-utils.sh L178–179 — On bash < 5.0 (macOS stock shell), EPOCHREALTIME is empty and the duration arithmetic would produce 0 or error. This was already the case in the pre-existing plugin copies; this PR doesn't regress it, and the telemetry function is opt-in and fail-open. Noting for awareness if bash < 5 support ever becomes explicit.


Positive callouts

  • Byte-identity confirmed: all 5 copies at sha1 e207414
  • Guard variable fix: _MDFMT_HOOK_UTILS_LOADED in ruff-format was a copy-paste error (that guard name belongs to markdown-formatter); the rename to _HOOK_UTILS_LOADED fixes a latent inconsistency ✓
  • CI bump gate is correctly scoped: if: github.event_name == 'pull_request' prevents the gate from running on push-to-main where the base ref concept doesn't apply ✓
  • fetch-depth: 0 is correctly required and documented in the YAML ✓
  • New-plugin opt-in path is documented and handled: the script skips plugins absent at the base ref; their first release already carries the new lib ✓
  • Telemetry fire-and-forget pattern: ("$sink" >/dev/null 2>&1) & correctly prevents the fd1 inheritance issue; the comment explaining the "C1 fd1-inheritance blocker" is the right level of detail ✓
  • Decision record in the playbook is exemplary: alternatives weighed with citations, revisit triggers named, known follow-ups scoped ✓

Base automatically changed from feat/ruff-format-plugin to main July 4, 2026 06:58
# Conflicts:
#	plugins/ruff-format/.claude-plugin/plugin.json
#	plugins/ruff-format/hooks/hook-utils.sh
#	plugins/ruff-format/hooks/ruff-format.sh
#	plugins/ruff-format/hooks/ruff-format.test.sh
@claude

claude Bot commented Jul 4, 2026

Copy link
Copy Markdown

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


PR #24 Review — feat: extract shared hook-utils.sh to an authoring-time SSOT


What changed in this sync

This synchronize event was a merge-back: 7bcb499 pulled origin/main (the now-merged PR #23 feat/ruff-format-plugin) into this branch, resolving conflicts in the ruff-format plugin. No new commits addressing the prior review findings were pushed.

Merge resolution is correct:


Medium (both still open from prior review)

1. --check-bump: $manifest at HEAD is not guarded before jq reads it

scripts/sync-hook-utils.sh L61:

head_version=$(jq -r '.version // empty' "$manifest")

The base-ref path two lines up is guarded (|| true; empty → continue). The HEAD path is not: if $manifest is missing, jq errors and set -euo pipefail aborts the whole check — silently dropping the remaining plugins. Suggested fix:

if [[ ! -f "$manifest" ]]; then
  echo "MISSING MANIFEST: $manifest" >&2
  stale=1
  continue
fi
head_version=$(jq -r '.version // empty' "$manifest")

Fix this →


2. _HOOK_UTILS_LOADED guard rename still lacks a justification comment

lib/hook-utils.sh L11–13:

# Guard against double-sourcing.
[[ -n "${_HOOK_UTILS_LOADED:-}" ]] && return 0
readonly _HOOK_UTILS_LOADED=1

The rename from _MDFMT_HOOK_UTILS_LOADED to a non-namespaced variable is correct, but the why is absent. Without it, a future contributor will see a suspiciously global variable and re-namespace it, introducing divergence again. Suggested:

# Non-namespaced: all copies are byte-identical; hooks run in isolated subprocesses.
[[ -n "${_HOOK_UTILS_LOADED:-}" ]] && return 0

Fix this →


Positive callouts

  • Merge resolution is clean — no drift introduced; byte-identity holds across all five copies ✓
  • ruff-format plugin version correctly resolved0.1.1 from this branch wins over 0.1.0 from the base ✓
  • ruff-format.sh is well-implemented — advisory exit 0 throughout, EPOCHREALTIME guarded (start=${EPOCHREALTIME:-}), proper --no-unsafe-fixes, --force-exclude, and opt-in via config discovery ✓
  • Test suite is comprehensive — 43 cases covering no-config opt-out, pyproject.toml variants, F401 guard, unsafe-fixes protection, excluded files, kill switch, PATH fallback, and telemetry envelope fields ✓
  • All low/observations from the prior review stand unchanged — no action required

Verdict

This PR is well-architected and the merge conflict was resolved correctly. The two medium findings remain open and should be addressed before merge — they are both small, self-contained fixes.

@kyle-sexton
kyle-sexton marked this pull request as ready for review July 4, 2026 10:04
@kyle-sexton
kyle-sexton merged commit 6ce8dc7 into main Jul 4, 2026
18 checks passed
@kyle-sexton
kyle-sexton deleted the feat/shared-hook-utils branch July 4, 2026 10:04
@claude

claude Bot commented Jul 4, 2026

Copy link
Copy Markdown

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


PR #24 Review — feat: extract shared hook-utils.sh to an authoring-time SSOT


Status of Prior Findings

This PR merged with both medium findings from prior review rounds still open. They are now in main.


Medium (both unresolved — now in main)

1. --check-bump: $manifest at HEAD unguarded before jq

scripts/sync-hook-utils.sh L61:

head_version=$(jq -r '.version // empty' "$manifest")

The base-ref lookup two lines up at L57 is guarded with || true (failure → empty string → continue). The HEAD path has no such guard: if $manifest is absent, jq errors and set -euo pipefail aborts the entire script — silently skipping any plugins that follow in the loop. Suggested fix:

if [[ ! -f "$manifest" ]]; then
  echo "MISSING MANIFEST: $manifest" >&2
  stale=1
  continue
fi
head_version=$(jq -r '.version // empty' "$manifest")

Fix this →


2. _HOOK_UTILS_LOADED guard still lacks a justification comment

lib/hook-utils.sh L11–13:

# Guard against double-sourcing.
[[ -n "${_HOOK_UTILS_LOADED:-}" ]] && return 0
readonly _HOOK_UTILS_LOADED=1

The comment explains what but not why the variable is intentionally non-namespaced. Without the reasoning, a future contributor will see a suspiciously global variable and re-namespace it (e.g. back to _MDFMT_HOOK_UTILS_LOADED), which would reintroduce divergence across the four copies. Suggested replacement:

# Non-namespaced: all copies are byte-identical; hooks run in isolated subprocesses.
[[ -n "${_HOOK_UTILS_LOADED:-}" ]] && return 0

Fix this →


Observations (unchanged from prior reviews)

  • $src existence check in sync mode: missing but harmless (cp will fail clearly enough); low priority.
  • EPOCHREALTIME requires bash 5.0+: pre-existing, opt-in, fail-open; not a regression.
  • Test consolidation: correctly deferred in the playbook.

Positive callouts (carry-forward)

All architecture decisions and implementation quality notes from prior reviews stand:

  • Byte-identity across all five copies at sha1 e207414
  • CI bump gate scoped correctly to pull_request events ✓
  • fetch-depth: 0 required and documented ✓
  • Fire-and-forget telemetry pattern with C1 fd1-inheritance blocker ✓
  • Decision record in playbook is exemplary ✓

Verdict

The two medium findings are small, self-contained fixes. Finding #2 (lib/hook-utils.sh comment) requires propagating the change to all four plugin copies via scripts/sync-hook-utils.sh and bumping each manifest version — the sync CI lane will enforce this. Recommended to address both in a follow-up patch before any behavioral lib change lands.

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 7bcb499. Configure here.

echo "Bump the version of every carrying plugin so consumers receive the lib change." >&2
exit 1
fi
echo "Lib changed vs $base and every carrying plugin bumped its version."

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bad ref skips bump gate

Medium Severity

In --check-bump, a failed git diff against the base ref is treated like a lib change, and carriers whose git show for plugin.json fails are skipped like new plugins. With an unresolvable base ref or unreadable base manifests, the script can exit 0 without detecting stale versions even when lib/hook-utils.sh changed and carrying plugins were not bumped.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 7bcb499. Configure here.

@cursor cursor 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.

Risk: medium. Not approving: Cursor Bugbot completed as skipped and reported 1 unresolved finding in scripts/sync-hook-utils.sh. The PR is already merged; no reviewers were assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@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: 7bcb499929

ℹ️ 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".

;;
--check-bump)
base="${2:?usage: sync-hook-utils.sh --check-bump <base-ref>}"
if git diff --quiet "$base" -- "$src"; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fail closed when the bump base is unreadable

When the supplied base ref is missing or otherwise unreadable, this git diff --quiet runs inside an if, so Git's fatal exit is treated the same as “the lib changed”; the later git show "$base:$manifest" ... || true then makes every base version empty and skips all manifests. I verified scripts/sync-hook-utils.sh --check-bump no-such-ref prints Git's fatal error but still exits 0, which means a PR whose base ref was not fetched/resolved could pass the version-bump gate with stale plugin versions instead of failing closed.

Useful? React with 👍 / 👎.

kyle-sexton added a commit that referenced this pull request Jul 4, 2026
## Summary

Closes the follow-up recorded in the shared-code decision record (#24):
the four per-plugin `hooks/hook-utils.test.sh` suites had diverged —
three were logic-identical (differing only in hook-id/extension
fixtures), ruff-format's added six `hook::read_file_path`
membership-guard cases — despite testing the byte-identical lib.

- The union (43 cases, ruff-format's superset with a neutralized
`sample-hook` id and a lib-scoped header) now lives at
`lib/hook-utils.test.sh`, beside the canonical source.
- The `hook-utils-sync` CI lane runs the suite — since the lane already
enforces that every plugin copy is byte-identical to the source, testing
the source covers the copies. Lib unit tests now run in CI for the first
time (previously manual-only).
- The four per-plugin copies are deleted; plugins keep only their own
black-box hook contract tests (`<plugin>.test.sh`).
- All four plugin versions bump patch so installed copies drop the
removed file; playbook follow-up note replaced with the settled state.

## Verification

- `lib/hook-utils.test.sh`: 43/43 pass.
- Remaining contract suites pass locally: bash-lint 31/31,
markdown-formatter 41/41, ruff-format 46/46 (biome-format skips without
a local Biome binary, as before).
- `claude plugin validate` ×4 + `--strict` catalog pass; shellcheck,
actionlint, markdownlint clean.
- No references to the removed files anywhere in the repo.

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

https://claude.ai/code/session_012NNwc8U9MbgWsd5sbrfpPh

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Test and packaging-only changes; no shared runtime library behavior is
modified in this diff.
> 
> **Overview**
> **Consolidates** duplicated `hook-utils` unit tests into a single
suite at `lib/hook-utils.test.sh` and **removes** the four per-plugin
`hooks/hook-utils.test.sh` copies (bash-lint, biome-format,
markdown-formatter, ruff-format).
> 
> The shared suite **generalizes** telemetry fixtures to a neutral
`sample-hook` id and documents that byte-identical plugin copies are
covered by testing the canonical lib. The **`hook-utils-sync` CI job**
now **runs** `bash lib/hook-utils.test.sh` after the sync check, so lib
unit tests are enforced in CI for the first time.
> 
> **Docs** in `MIGRATION-PLAYBOOK.md` replace the deferred “merge the
four suites” note with the settled single-suite model. **Patch version
bumps** on all four carrying plugins so installs drop the removed test
files from cached plugin trees.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
fed7c3d. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

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

## Summary

Closes decision #55 (`metadata-claude-md-encode-github-conventions`) for
this repo. Adds a **Branching & PRs** section to `CLAUDE.md` mirroring
medley's citation pattern (`AGENTS.md` "Branching & PRs": terse rule
inline + one pointer to the full convention source).

- Inline rule: PRs required, squash merge, Conventional Commits branch
`<type>/<description>` — already enforced here by
`.github/workflows/pr-title.yml`.
- Pointer: `melodic-software/standards` `conventions/` as the org-wide
convention home.

The pointer targets the `conventions/` directory rather than
`conventions/engineering/naming.md` specifically: `naming.md` today
covers only Clean-Code identifier naming (classes, files, variables),
not branch/PR grammar. Promoting medley's branch-naming grammar into
`standards` org-wide is tracked separately as decision #24
(`naming-branch-convention-org-wide`, WP-11). Once that lands,
`naming.md` (or wherever it lands) becomes the sharper anchor — the
directory-level pointer stays valid either way per `standards`'
`conventions/engineering/reference-dont-duplicate.md` (cite a stable
anchor, never a copy that can drift).

Decisions Log:
https://claude.ai/code/artifact/232ecdce-8316-4880-8c0a-dc3c7dcf3a63

## Test plan

- [x] `npx markdownlint-cli2 CLAUDE.md` — 0 errors
- [x] Manual read-through against medley's `AGENTS.md` "Branching & PRs"
and `.claude/rules/worktree/{branch-naming,pr-conventions}.md` for
pattern fidelity

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

https://claude.ai/code/session_01Us4azyXMBuXGV2sshG7UuB

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 22, 2026
…e IV, flip pre-build (#939)

Dated Phase-4 note: operator ruling (2026-07-22) to move at maximum
speed within the predicate's intent. Calendar gates stay binding;
sanctioned levers are front-loaded seeding (genuine items only),
parallel Phase IV ignition (gate already met), and pre-built Phase III
flip machinery. Records same-day watch activity: drain PRs #23/#24/#26 +
Dependabot #25 merged in kyle-sexton/autonomy-demo-scratch, queue
reseeded (scratch#27–#31), predicate completions=6.

## Related

- kyle-sexton/autonomy-demo-scratch#27, #28, #29, #30, #31. No linked
issue — status-note documentation; nothing in this repo closes.

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

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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