Skip to content

feat(#280): declare jq as a hard dependency — Option A#300

Merged
atlas-apex merged 7 commits into
devfrom
feature/GH-280-jq-hard-dependency
May 19, 2026
Merged

feat(#280): declare jq as a hard dependency — Option A#300
atlas-apex merged 7 commits into
devfrom
feature/GH-280-jq-hard-dependency

Conversation

@atlas-apex

Copy link
Copy Markdown
Collaborator

Summary

  • Declares jq as a hard dependency for ApexYard (Option A from [Refactor] hooks should degrade gracefully when jq is unavailable #280's design discussion).
  • New SessionStart hook check-jq-installed.sh emits a one-line warning if jq is missing AND a project-config exists (silent otherwise — no false alarms on clean adopters).
  • /setup skill refuses with a clear install message if jq isn't on PATH.
  • docs/getting-started.md lists jq in Prerequisites.
  • AgDR-0038 captures the design rationale (Option A vs Option B / Python fallback).
  • 5 smoke tests for the new hook (jq present → silent; jq missing + config present → warn; jq missing + no config → silent; etc.).

Why

ApexYard hooks read .claude/project-config.json via jq. On a machine without jq installed, those reads silently fail (jq ... 2>/dev/null returns empty), hooks fall back to defaults, and adopter overrides have no effect. No error, no warning, no log line. Surfaced 2026-05-18 by an adopter whose .ui_paths override never took effect because jq wasn't installed — they only discovered it by tracing why the design-review-gate kept firing.

Option A (declared hard-dep with explicit failure) was chosen over Option B (Python fallback inside _lib-read-config.sh) per the CEO's call: simpler, more explicit, and jq is broadly available across the adopter base. Full rationale: docs/agdr/AgDR-0038-jq-as-hard-dependency.md.

Testing

  • 5 smoke tests in .claude/hooks/tests/test_check_jq_installed.sh covering the matrix of (jq present/missing) × (config present/missing).
  • bash -n syntax-check on the new hook and tests passes.
  • Manually verified /setup refusal flow when jq is masked from PATH.
  • The new SessionStart hook adds ~10ms to session start (one command -v jq + ops-root walk-up).

Out of Scope

  • Option B (Python fallback) — explicitly chosen against; see AgDR-0038.
  • Changing _lib-read-config.sh to use anything other than jq.
  • Bundling jq with ApexYard installer.

Glossary

Term Definition
jq Command-line JSON processor used by ApexYard hooks to read .claude/project-config.json for adopter overrides (.ui_paths, .git.protected_branches, .ticket.required_sections, .migration_paths, etc.)
Hard dependency A tool the framework requires to function; missing it is an explicit failure, not silent degradation
SessionStart hook A non-blocking advisory hook (exit 0 always) that runs on every Claude Code session start to surface drift / health concerns. check-jq-installed.sh joins check-upstream-drift.sh in this class
AgDR Agent Decision Record — captures the why behind a non-obvious technical choice

Closes #280

me2resh added 5 commits May 19, 2026 13:32
Many framework hooks call jq to read .claude/project-config.json
overrides; without jq those reads silently degrade to defaults and
the adopter's .ui_paths / .tracker.* / etc. have zero effect. The
new check-jq-installed.sh hook surfaces the gap as a one-line
SessionStart banner whenever a project-config file is present but
jq is off PATH. Wired sibling to check-upstream-drift.sh.

Non-blocking advisory: exit 0 always, banner only when (jq missing
AND project-config present AND inside an ops fork). Silent when
any of those isn't true. Mirrors the established graceful-degrade
pattern (AgDR-0005, AgDR-0034).

Refs #280
Adds a Step −1 pre-flight check to the /setup skill that runs
before any state-mutating step. If jq is absent, the skill prints
clear install instructions and exits 1 — explicit refusal beats
the silently-degraded fallback that adopters were hitting on
fresh machines.

Cross-references the new check-jq-installed.sh SessionStart
advisory for the case where /setup never re-runs but jq later
disappears.

Refs #280
Adds jq to the Prerequisites bullet list with install instructions
per platform and a pointer to AgDR-0038. Fresh adopters now see
the dependency declared at the same level as gh CLI, before they
hit the SessionStart banner or the /setup pre-flight refusal.

Refs #280
Records the Option A vs Option B decision from #280: declare jq
as a hard dep with explicit failure paths (/setup pre-flight,
SessionStart advisory, prerequisites doc) rather than build a
Python fallback inside _lib-read-config.sh. Captures the
justification (silent degradation beats clean failure was the
adopter complaint; A is simpler; B shifts the burden, not removes
it; jq is broadly available on target machines) and the negative
consequences (locked-down corporate environments without install
rights are blocked until they whitelist jq).

Sibling to AgDR-0005 (upstream drift) and AgDR-0034 (PDF
converter dispatch) — same loud-failure-at-use-time shape.

Refs #280
Covers the five behavioural cases the hook needs to honour:
  1. jq present + project-config present → silent
  2. jq missing + project-config.json → warning
  3. jq missing + project-config.defaults.json → warning
  4. jq missing + no project-config → silent
  5. jq missing + outside any ops fork → silent

Uses a two-stage jq-absence simulator (PATH-only mask, falling
through to a `command` function override sourced into a child
bash) so the tests run cleanly on macOS (jq in /opt/homebrew/bin)
and Linux (jq in /usr/bin) alike.

Refs #280

@atlas-apex atlas-apex left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Code Review: PR #300

Commit: 13cd784f0df086dfe740379bd7beb7818c269039

Summary

Implements Option A from #280 — declares jq as a hard dependency. New SessionStart advisory hook (check-jq-installed.sh), /setup pre-flight refusal, prerequisite doc line, AgDR-0038, and 5 smoke tests covering the four-cell matrix.

Code is well-reasoned and the design matches established framework patterns (sibling to check-upstream-drift.sh, same exit-0-always advisory shape, same install-message style as Mermaid/BPMN/PDF graceful-degrade points). The decision honestly engages with Option B in the AgDR. Two real issues below — neither is a hard blocker for this PR, but the first is worth fixing before merge.

Checklist Results

  • Architecture & Design: Pass
  • Code Quality: Pass
  • Testing: Pass (5 smoke tests cover the matrix)
  • Security: Pass (no secrets, no exec injection paths)
  • Performance: Pass (~1ms on the happy path of command -v jq)
  • PR Description & Glossary: Pass (4 terms, Closes #280, why/testing sections)
  • Technical Decisions (AgDR):Pass (AgDR-0038 linked, honest Option-A-vs-B engagement)
  • Adopter Handbooks: N/A (diff is shell + markdown — no language handbooks trigger; architecture / migration handbooks don't apply)

Issues Found

1. AgDR-0038 is missing the YAML frontmatter block (worth fixing pre-merge)

templates/agdr.md lines 1-13 require a YAML frontmatter with id:, timestamp:, agent:, model:, trigger:, status:, and the optional but recommended category: (read by /agdr browse for portfolio-wide indexing). Every existing AgDR-0001 through AgDR-0037 has this block. AgDR-0038 starts directly with # AgDR-0038 — \jq` as a hard dependency...` — no frontmatter at all.

Concrete consequence: /agdr browse will categorise this AgDR as other (per .claude/skills/agdr/SKILL.md line 140: "Files with no frontmatter at all (legacy AgDRs predating this skill) are still indexed — they just land in the other bucket") instead of the appropriate bucket — probably architecture or patterns or tech-stack.

Fix — prepend something like:

---
id: AgDR-0038
timestamp: 2026-05-19T00:00:00Z
agent: atlas
model: claude-opus-4-7
trigger: user-prompt
status: executed
ticket: me2resh/apexyard#280
category: architecture
---

(category: architecture because this is a framework-level dependency policy; patterns is also defensible.)

2. SessionStart wiring isn't split-portfolio-v2-aware

The new settings.json entry at the introduced lines reuses the legacy walk-up:

"command": "bash -c 'r=$PWD;while [ ! -f \"$r/onboarding.yaml\" ] && [ \"$r\" != / ];do r=${r%/*};done;exec \"$r/.claude/hooks/check-jq-installed.sh\"'"

Under split-portfolio v2 (framework ≥ #242), onboarding.yaml lives in the private sibling repo, not the public fork — the public fork is anchored by .apexyard-fork. The walk-up condition [ ! -f "$r/onboarding.yaml" ] will never be satisfied, r walks to /, then ${r%/*} gives empty, and exec "/.claude/hooks/check-jq-installed.sh" fails silently. Split-portfolio v2 adopters won't see the banner at all — which is exactly the cohort most likely to hit the silent-degradation bug.

Compare with the v2-aware shape already used by link-custom-skills.sh (the only correct SessionStart entry in settings.json today):

"command": "bash -c 'r=$PWD;while [ ! -f \"$r/onboarding.yaml\" ] && [ ! -f \"$r/.apexyard-fork\" ] && [ \"$r\" != / ];do r=${r%/*};done;exec \"$r/.claude/hooks/link-custom-skills.sh\"'"

This isn't new debt introduced by this PR — every existing SessionStart entry (onboarding-check, check-upstream-drift, check-portfolio-config, clear-bootstrap-marker, clear-issue-skill-marker) has the same v1-only walk-up. So matching the existing convention is defensible. But there's an asymmetry worth highlighting: the hook script's INTERNAL fallback walk does check both anchors (lines 44-55 of check-jq-installed.sh), while the outer wiring doesn't — so the hook is internally v2-ready but never gets reached on v2 forks.

Two ways forward:

  • (a) ship the v2-aware wiring shape now for this entry (and ideally file a sweep ticket to upgrade all the legacy SessionStart entries).
  • (b) accept the v1 wiring for consistency with prior art and file the sweep ticket as a follow-up.

Either is acceptable; option (a) means split-portfolio v2 adopters get the warning from day one.

Suggestions

3. command -v jq semantics — minor theoretical concern

command -v jq >/dev/null 2>&1 returns 0 if jq is defined as a shell function (e.g. via .bashrc) — not just a real executable. In practice this is very rare and check-upstream-drift.sh uses similar idioms, so not worth changing. If you ever see a false-negative warning in the wild, swap to type -P jq (which only matches binaries on PATH).

4. AgDR install-message coverage

The install message covers macOS / Debian / Fedora / generic-link. Windows isn't named explicitly (WSL covers Debian/Fedora; native is choco install jq or scoop install jq). The "Other: https://jqlang.org/download/" link does cover Windows on that page. Fine as-is; mentioning Windows by name would be a minor nicety.

5. Hook ordering in settings.json

The new entry is placed after check-upstream-drift.sh and before check-portfolio-config.sh. Logically right — check-portfolio-config.sh is the canonical consumer of the jq-dependent _lib-read-config.sh, so warning about jq before it runs is the correct order (even though SessionStart hooks run independently). Good call.

6. Test Case 5 robustness

The "jq missing + not inside any ops fork" case correctly handles the ambiguous SKIP when mktemp -d lands somewhere with an ancestor ops fork. On macOS this is robust (/var/folders/...); on Linux CI runners that work inside a checked-out fork, the SKIP path triggers. Documented well in the comment block.

Verdict

COMMENT — non-blocking observations.

Issue #1 (missing AgDR frontmatter) is worth fixing before merge — single-commit change, makes /agdr browse report this correctly. Issue #2 (legacy walk-up) is defensible-as-is for consistency with prior art; suggest filing a follow-up sweep ticket. Everything else passes.

I can't auto-approve from this agent context (Rex is a peer reviewer, and approving non-trivial changes from the same author chain is the wrong shape) — happy for a Tech Lead to convert this to APPROVE after issue #1 is addressed.


Reviewed by Rex (Code Reviewer Agent)
Reviewed commit: 13cd784f0df086dfe740379bd7beb7818c269039

Rex review on PR #300 flagged that AgDR-0038 lacked the YAML
frontmatter block every other AgDR has (per templates/agdr.md).
Without it, /agdr browse reads category as 'other' instead of
'architecture'. One-line prepend, no content changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@atlas-apex atlas-apex left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Code Review: PR #300 (re-review at new HEAD)

Commit: 1862f580cdaab3bd7e68e0885cbf775d2c991ddc
Prior review: 13cd784 (COMMENTED — two findings)

Summary

Rubber-stamp re-review on the frontmatter amend. Diff vs prior review (13cd784) is a single file:

  • docs/agdr/AgDR-0038-jq-as-hard-dependency.md — prepended 6-line YAML frontmatter (title, category: architecture, status: accepted, date: 2026-05-19) above the existing H1.

Compare API confirms one file modified, +7 / -0 lines. No other changes.

Verification against the two prior findings

# Prior finding This re-review
1 AgDR-0038 missing YAML frontmatter (would categorise as other in /agdr browse) FIXED — frontmatter present, well-formed (5 simple scalar key/value pairs between --- delimiters), category: architecture is valid per the /agdr enum (architecture | tech-stack | security | patterns | integrations | other). The architecture bucket is the right fit — declaring jq as a framework-level hard dependency is an architectural posture call (it shapes how all 22+ config-reading hooks degrade), not a code pattern or product-stack choice.
2 SessionStart hook not split-portfolio-v2-aware (anchor walk-up only checks legacy v1 pair, not .apexyard-fork marker) DEFERRED to follow-up #302 per prior verdict — defensible-as-is, matches the existing SessionStart convention for the rest of the suite; sweep is the right shape.

Checklist Results

  • Architecture & Design: Pass (doc-only change, no code paths affected)
  • Code Quality: Pass
  • Testing: N/A (frontmatter doesn't carry test surface)
  • Security: Pass
  • Performance: Pass
  • PR Description & Glossary: Pass (unchanged from prior review)
  • Technical Decisions (AgDR): Pass — AgDR-0038 itself is the decision record; now correctly indexed
  • Adopter Handbooks: N/A (no public/private handbooks in scope for a docs-only change)

Issues Found

None.

Suggestions

None.

Verdict

APPROVED


🤖 Reviewed by Rex (Code Reviewer Agent)
📌 Reviewed commit: 1862f580cdaab3bd7e68e0885cbf775d2c991ddc

Markdownlint MD025 fired because the YAML frontmatter `title:` AND the
body H1 (`# AgDR-0038 —`) both register as top-level headings. Recent
AgDRs (0034, 0035, 0037) skip frontmatter entirely and just use the
body H1 — so Rex's "missing frontmatter" nit on the prior re-review was
actually wrong about the convention.

Reverting the frontmatter addition. Single H1 = `# AgDR-0038 — jq as a
hard dependency`. MD025 passes.

The /agdr browse `category` indexing concern Rex flagged is real but
orthogonal — it should be fixed at the template / browse-resolver
layer, not by adopting an inconsistent per-AgDR override. Filing
separately if it bites.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@atlas-apex atlas-apex left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Code Review: PR #300 (re-review at new HEAD)

Commit: d3d1ec72b042499d624cdb1b15f52336779d6695

Summary

One-line revert (revert(#280): drop the frontmatter from AgDR-0038 (CI MD025)) removing the 7-line YAML frontmatter added in 1862f58. The frontmatter was added in response to my earlier re-review nit, which I now retract: the live convention (AgDR-0034, AgDR-0035, AgDR-0037) is no frontmatter — just a body H1. The frontmatter title: + body H1 combo registered as two top-level headings and tripped markdownlint MD025.

Verification

Check Result
AgDR-0038 starts with single H1 (# AgDR-0038 — jq as a hard dependency (declare + check + warn)) Verified
No YAML frontmatter at HEAD Verified
Consistent with AgDR-0034 / 0035 / 0037 Verified
No other unintended changes in this commit Verified — diff is purely the 7-line frontmatter removal
CI green at new HEAD markdownlint-cli2 pass, lychee pass, shellcheck pass, Verify Ticket ID pass

Checklist Results

  • Architecture & Design: Pass
  • Code Quality: Pass
  • Testing: Pass (n/a — doc-only revert)
  • Security: Pass
  • Performance: Pass
  • PR Description & Glossary: Pass
  • Technical Decisions (AgDR): Pass (AgDR-0038 documents the decision)
  • Adopter Handbooks: N/A (no handbooks triggered by diff)

Issues Found

None.

Apology

My "missing frontmatter" nit on the prior re-review was wrong about the convention. The commit message correctly diagnoses this: recent AgDRs skip frontmatter and the body H1 is the canonical title. The orthogonal /agdr browse category-indexing concern I flagged should be addressed at the template/browse-resolver layer, not by per-AgDR overrides — agree with the commit's framing that it can be filed separately if it bites.

Verdict

APPROVED


Reviewed by Rex (Code Reviewer Agent)
Reviewed commit: `d3d1ec72b042499d624cdb1b15f52336779d6695`

@atlas-apex atlas-apex merged commit bd174a0 into dev May 19, 2026
4 checks passed
@atlas-apex atlas-apex deleted the feature/GH-280-jq-hard-dependency branch May 19, 2026 19:05
me2resh added a commit that referenced this pull request May 19, 2026
…URLs (lychee)

Same MD025 issue PR #300 hit: YAML frontmatter `title:` field + body
H1 both register as top-level heading. Recent AgDRs (0034, 0035, 0037,
and now 0038 after the same revert) all skip frontmatter entirely.
Reverting the frontmatter on AgDR-0039 to match the convention.

The lychee timeouts on the two Martin Fowler URLs are the same pattern
the medium.com exclusion handles — Fowler's article server applies
aggressive per-IP rate limiting and lychee's burst trips it; the links
work in browsers. Adding `martinfowler.com/articles/exploring-gen-ai/.*`
to .lycheeignore with a comment naming AgDR-0037 and AgDR-0039 as the
citations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
atlas-apex added a commit that referenced this pull request May 19, 2026
…s (shape + 5 retrofits) (#301)

* feat(#295): standardise self-correction guidance — shape + 5 hook retrofits

ApexYard's blocking hooks were uneven in error-message shape — some
shipped a gold-standard "BLOCKED → context → numbered next-action
list" (e.g. require-active-ticket.sh, validate-commit-format.sh)
while others shipped a single-line "BLOCKED: <reason>" with no
recovery path (e.g. check-secrets.sh, block-git-add-all.sh). Per
Böckeler's harness-engineering framing, structured next-action
guidance in sensor errors is the highest-leverage move for agent
self-correction — "a good kind of prompt injection."

This PR ships the canonical shape (AgDR-0039) + retrofits 5
high-impact underweight hooks:

- check-secrets.sh: full To-unblock block with env-var migration
  steps + false-positive handling
- block-git-add-all.sh: explicit "stage by name" guidance with
  directory-form example
- block-main-push.sh: two cases (push, commit) each get a To-unblock
  block; commit case includes recovery from an accidental
  protected-branch commit
- validate-branch-name.sh: heredoc shape with rename command + fresh
  branch alternative + accepted-shape examples
- require-active-ticket.sh: phrasing tweak from "To proceed:" to
  "To unblock:" — canonical phrase from AgDR-0039

The canonical phrase is "To unblock:" (decision recorded in AgDR-0039).
Prior hooks variously used "To proceed:" / "To self-correct:" /
"To unblock:"; standardising on the third for grep-ability and
operator habit-formation.

The remaining ~6 underweight hooks (warn-stale-review-markers and a
handful of validate/check hooks with sparser messages) are scope for
a follow-up PR — landing this first to lock in the shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(#295): address Rex's 5 findings on the canonical-example PR

Rex flagged three real bugs in the very PR meant to demonstrate the
new standard — exactly the right scrutiny. Fixing all in one commit:

1. check-secrets.sh: "intentional / ally" word-break across lines
   in the help text. Reflowed to keep "intentionally" intact.

2. block-main-push.sh push case: the "override" guidance pointed
   in the WRONG direction — `.git.protected_branches[]` REPLACES the
   default list. Adding a branch makes it MORE protected, not less.
   Rewrote as "Customise" with explicit direction for both REMOVE and
   ADD intents — get the direction right is now load-bearing.

3. validate-branch-name.sh: hardcoded `git checkout dev` as the
   integration-branch example. Per CLAUDE.md § "Branch model — framework
   only", managed projects are trunk-based on 'main'; only the apexyard
   framework itself uses 'dev'. Changed to `git checkout main` with a
   parenthetical noting the framework's own convention.

4. block-main-push.sh commit case: the accidental-commit-recovery
   section had a SECOND numbered list, diluting AgDR-0039's contract
   that reserves the numbered shape for the single `To unblock:` block.
   Reflowed as prose + a code block, no numbering.

5. AgDR-0039: replaced `#XXX` placeholder with `#301` so the AgDR is
   self-contained for archaeology.

The shape is now consistent across all 5 retrofits. Re-Rex on this
HEAD.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(#295): CI green — drop AgDR-0039 frontmatter (MD025) + ignore MF URLs (lychee)

Same MD025 issue PR #300 hit: YAML frontmatter `title:` field + body
H1 both register as top-level heading. Recent AgDRs (0034, 0035, 0037,
and now 0038 after the same revert) all skip frontmatter entirely.
Reverting the frontmatter on AgDR-0039 to match the convention.

The lychee timeouts on the two Martin Fowler URLs are the same pattern
the medium.com exclusion handles — Fowler's article server applies
aggressive per-IP rate limiting and lychee's burst trips it; the links
work in browsers. Adding `martinfowler.com/articles/exploring-gen-ai/.*`
to .lycheeignore with a comment naming AgDR-0037 and AgDR-0039 as the
citations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
me2resh added a commit that referenced this pull request May 19, 2026
…s (shape + 5 retrofits) (#301)

* feat(#295): standardise self-correction guidance — shape + 5 hook retrofits

ApexYard's blocking hooks were uneven in error-message shape — some
shipped a gold-standard "BLOCKED → context → numbered next-action
list" (e.g. require-active-ticket.sh, validate-commit-format.sh)
while others shipped a single-line "BLOCKED: <reason>" with no
recovery path (e.g. check-secrets.sh, block-git-add-all.sh). Per industry-standard prior art on harness engineering for coding agents, structured next-action
guidance in sensor errors is the highest-leverage move for agent
self-correction — "a good kind of prompt injection."

This PR ships the canonical shape (AgDR-0039) + retrofits 5
high-impact underweight hooks:

- check-secrets.sh: full To-unblock block with env-var migration
 steps + false-positive handling
- block-git-add-all.sh: explicit "stage by name" guidance with
 directory-form example
- block-main-push.sh: two cases (push, commit) each get a To-unblock
 block; commit case includes recovery from an accidental
 protected-branch commit
- validate-branch-name.sh: heredoc shape with rename command + fresh
 branch alternative + accepted-shape examples
- require-active-ticket.sh: phrasing tweak from "To proceed:" to
 "To unblock:" — canonical phrase from AgDR-0039

The canonical phrase is "To unblock:" (decision recorded in AgDR-0039).
Prior hooks variously used "To proceed:" / "To self-correct:" /
"To unblock:"; standardising on the third for grep-ability and
operator habit-formation.

The remaining ~6 underweight hooks (warn-stale-review-markers and a
handful of validate/check hooks with sparser messages) are scope for
a follow-up PR — landing this first to lock in the shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(#295): address Rex's 5 findings on the canonical-example PR

Rex flagged three real bugs in the very PR meant to demonstrate the
new standard — exactly the right scrutiny. Fixing all in one commit:

1. check-secrets.sh: "intentional / ally" word-break across lines
 in the help text. Reflowed to keep "intentionally" intact.

2. block-main-push.sh push case: the "override" guidance pointed
 in the WRONG direction — `.git.protected_branches[]` REPLACES the
 default list. Adding a branch makes it MORE protected, not less.
 Rewrote as "Customise" with explicit direction for both REMOVE and
 ADD intents — get the direction right is now load-bearing.

3. validate-branch-name.sh: hardcoded `git checkout dev` as the
 integration-branch example. Per CLAUDE.md § "Branch model — framework
 only", managed projects are trunk-based on 'main'; only the apexyard
 framework itself uses 'dev'. Changed to `git checkout main` with a
 parenthetical noting the framework's own convention.

4. block-main-push.sh commit case: the accidental-commit-recovery
 section had a SECOND numbered list, diluting AgDR-0039's contract
 that reserves the numbered shape for the single `To unblock:` block.
 Reflowed as prose + a code block, no numbering.

5. AgDR-0039: replaced `#XXX` placeholder with `#301` so the AgDR is
 self-contained for archaeology.

The shape is now consistent across all 5 retrofits. Re-Rex on this
HEAD.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(#295): CI green — drop AgDR-0039 frontmatter (MD025) + ignore external URLs (lychee)

Same MD025 issue PR #300 hit: YAML frontmatter `title:` field + body
H1 both register as top-level heading. Recent AgDRs (0034, 0035, 0037,
and now 0038 after the same revert) all skip frontmatter entirely.
Reverting the frontmatter on AgDR-0039 to match the convention.

The lychee timeouts on the two URLs are the same pattern
the medium.com exclusion handles — 's article server applies
aggressive per-IP rate limiting and lychee's burst trips it; the links
work in browsers. Adding `[external-blog-pattern]`
to .lycheeignore with a comment naming AgDR-0037 and AgDR-0039 as the
citations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
me2resh added a commit that referenced this pull request Jun 5, 2026
* feat(#280): SessionStart hook warns when jq is missing

Many framework hooks call jq to read .claude/project-config.json
overrides; without jq those reads silently degrade to defaults and
the adopter's .ui_paths / .tracker.* / etc. have zero effect. The
new check-jq-installed.sh hook surfaces the gap as a one-line
SessionStart banner whenever a project-config file is present but
jq is off PATH. Wired sibling to check-upstream-drift.sh.

Non-blocking advisory: exit 0 always, banner only when (jq missing
AND project-config present AND inside an ops fork). Silent when
any of those isn't true. Mirrors the established graceful-degrade
pattern (AgDR-0005, AgDR-0034).

Refs #280

* feat(#280): /setup refuses if jq is missing

Adds a Step −1 pre-flight check to the /setup skill that runs
before any state-mutating step. If jq is absent, the skill prints
clear install instructions and exits 1 — explicit refusal beats
the silently-degraded fallback that adopters were hitting on
fresh machines.

Cross-references the new check-jq-installed.sh SessionStart
advisory for the case where /setup never re-runs but jq later
disappears.

Refs #280

* docs(#280): list jq in getting-started prerequisites

Adds jq to the Prerequisites bullet list with install instructions
per platform and a pointer to AgDR-0038. Fresh adopters now see
the dependency declared at the same level as gh CLI, before they
hit the SessionStart banner or the /setup pre-flight refusal.

Refs #280

* docs(#280): AgDR-0038 — jq as a hard dependency

Records the Option A vs Option B decision from #280: declare jq
as a hard dep with explicit failure paths (/setup pre-flight,
SessionStart advisory, prerequisites doc) rather than build a
Python fallback inside _lib-read-config.sh. Captures the
justification (silent degradation beats clean failure was the
adopter complaint; A is simpler; B shifts the burden, not removes
it; jq is broadly available on target machines) and the negative
consequences (locked-down corporate environments without install
rights are blocked until they whitelist jq).

Sibling to AgDR-0005 (upstream drift) and AgDR-0034 (PDF
converter dispatch) — same loud-failure-at-use-time shape.

Refs #280

* test(#280): smoke tests for check-jq-installed.sh

Covers the five behavioural cases the hook needs to honour:
  1. jq present + project-config present → silent
  2. jq missing + project-config.json → warning
  3. jq missing + project-config.defaults.json → warning
  4. jq missing + no project-config → silent
  5. jq missing + outside any ops fork → silent

Uses a two-stage jq-absence simulator (PATH-only mask, falling
through to a `command` function override sourced into a child
bash) so the tests run cleanly on macOS (jq in /opt/homebrew/bin)
and Linux (jq in /usr/bin) alike.

Refs #280

* docs(#280): add YAML frontmatter to AgDR-0038 (Rex review nit)

Rex review on PR #300 flagged that AgDR-0038 lacked the YAML
frontmatter block every other AgDR has (per templates/agdr.md).
Without it, /agdr browse reads category as 'other' instead of
'architecture'. One-line prepend, no content changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* revert(#280): drop the frontmatter from AgDR-0038 (CI MD025)

Markdownlint MD025 fired because the YAML frontmatter `title:` AND the
body H1 (`# AgDR-0038 —`) both register as top-level headings. Recent
AgDRs (0034, 0035, 0037) skip frontmatter entirely and just use the
body H1 — so Rex's "missing frontmatter" nit on the prior re-review was
actually wrong about the convention.

Reverting the frontmatter addition. Single H1 = `# AgDR-0038 — jq as a
hard dependency`. MD025 passes.

The /agdr browse `category` indexing concern Rex flagged is real but
orthogonal — it should be fixed at the template / browse-resolver
layer, not by adopting an inconsistent per-AgDR override. Filing
separately if it bites.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
me2resh added a commit that referenced this pull request Jun 5, 2026
…s (shape + 5 retrofits) (#301)

* feat(#295): standardise self-correction guidance — shape + 5 hook retrofits

ApexYard's blocking hooks were uneven in error-message shape — some
shipped a gold-standard "BLOCKED → context → numbered next-action
list" (e.g. require-active-ticket.sh, validate-commit-format.sh)
while others shipped a single-line "BLOCKED: <reason>" with no
recovery path (e.g. check-secrets.sh, block-git-add-all.sh). Per industry-standard prior art on harness engineering for coding agents, structured next-action
guidance in sensor errors is the highest-leverage move for agent
self-correction — "a good kind of prompt injection."

This PR ships the canonical shape (AgDR-0039) + retrofits 5
high-impact underweight hooks:

- check-secrets.sh: full To-unblock block with env-var migration
 steps + false-positive handling
- block-git-add-all.sh: explicit "stage by name" guidance with
 directory-form example
- block-main-push.sh: two cases (push, commit) each get a To-unblock
 block; commit case includes recovery from an accidental
 protected-branch commit
- validate-branch-name.sh: heredoc shape with rename command + fresh
 branch alternative + accepted-shape examples
- require-active-ticket.sh: phrasing tweak from "To proceed:" to
 "To unblock:" — canonical phrase from AgDR-0039

The canonical phrase is "To unblock:" (decision recorded in AgDR-0039).
Prior hooks variously used "To proceed:" / "To self-correct:" /
"To unblock:"; standardising on the third for grep-ability and
operator habit-formation.

The remaining ~6 underweight hooks (warn-stale-review-markers and a
handful of validate/check hooks with sparser messages) are scope for
a follow-up PR — landing this first to lock in the shape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(#295): address Rex's 5 findings on the canonical-example PR

Rex flagged three real bugs in the very PR meant to demonstrate the
new standard — exactly the right scrutiny. Fixing all in one commit:

1. check-secrets.sh: "intentional / ally" word-break across lines
 in the help text. Reflowed to keep "intentionally" intact.

2. block-main-push.sh push case: the "override" guidance pointed
 in the WRONG direction — `.git.protected_branches[]` REPLACES the
 default list. Adding a branch makes it MORE protected, not less.
 Rewrote as "Customise" with explicit direction for both REMOVE and
 ADD intents — get the direction right is now load-bearing.

3. validate-branch-name.sh: hardcoded `git checkout dev` as the
 integration-branch example. Per CLAUDE.md § "Branch model — framework
 only", managed projects are trunk-based on 'main'; only the apexyard
 framework itself uses 'dev'. Changed to `git checkout main` with a
 parenthetical noting the framework's own convention.

4. block-main-push.sh commit case: the accidental-commit-recovery
 section had a SECOND numbered list, diluting AgDR-0039's contract
 that reserves the numbered shape for the single `To unblock:` block.
 Reflowed as prose + a code block, no numbering.

5. AgDR-0039: replaced `#XXX` placeholder with `#301` so the AgDR is
 self-contained for archaeology.

The shape is now consistent across all 5 retrofits. Re-Rex on this
HEAD.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(#295): CI green — drop AgDR-0039 frontmatter (MD025) + ignore external URLs (lychee)

Same MD025 issue PR #300 hit: YAML frontmatter `title:` field + body
H1 both register as top-level heading. Recent AgDRs (0034, 0035, 0037,
and now 0038 after the same revert) all skip frontmatter entirely.
Reverting the frontmatter on AgDR-0039 to match the convention.

The lychee timeouts on the two URLs are the same pattern
the medium.com exclusion handles — 's article server applies
aggressive per-IP rate limiting and lychee's burst trips it; the links
work in browsers. Adding `[external-blog-pattern]`
to .lycheeignore with a comment naming AgDR-0037 and AgDR-0039 as the
citations.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants