feat(#280): declare jq as a hard dependency — Option A#300
Conversation
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
left a comment
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this comment.
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
left a comment
There was a problem hiding this comment.
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`
…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>
…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>
…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>
* 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>
…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>
Summary
jqas a hard dependency for ApexYard (Option A from [Refactor] hooks should degrade gracefully when jq is unavailable #280's design discussion).check-jq-installed.shemits a one-line warning ifjqis missing AND a project-config exists (silent otherwise — no false alarms on clean adopters)./setupskill refuses with a clear install message ifjqisn't onPATH.docs/getting-started.mdlistsjqin Prerequisites.Why
ApexYard hooks read
.claude/project-config.jsonviajq. On a machine withoutjqinstalled, those reads silently fail (jq ... 2>/dev/nullreturns 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_pathsoverride never took effect becausejqwasn'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, andjqis broadly available across the adopter base. Full rationale:docs/agdr/AgDR-0038-jq-as-hard-dependency.md.Testing
.claude/hooks/tests/test_check_jq_installed.shcovering the matrix of (jq present/missing) × (config present/missing).bash -nsyntax-check on the new hook and tests passes./setuprefusal flow whenjqis masked fromPATH.command -v jq+ ops-root walk-up).Out of Scope
_lib-read-config.shto use anything other thanjq.jqwith ApexYard installer.Glossary
jq.claude/project-config.jsonfor adopter overrides (.ui_paths,.git.protected_branches,.ticket.required_sections,.migration_paths, etc.)exit 0always) that runs on every Claude Code session start to surface drift / health concerns.check-jq-installed.shjoinscheck-upstream-drift.shin this classCloses #280