chore(#3): sync ops fork with upstream apexyard (v2.2.0 → v3.1.4)#4
Merged
Conversation
…ject feat(#1): initial ApexStack project
… SENTINEL nit (#3) Post-merge cleanup after the Rex re-review of PR #1. Four blockers and one nit — all small, all correctness fixes, no scope creep. N1 — Default-mode contradiction (CRITICAL) Four files claimed single-project was the default, contradicting CLAUDE.md, README, onboarding.yaml, docs/multi-project.md, the site, and 7 of 8 new skills (which all say multi-project is the default). The commit message for ba892bc literally said "multi-project mode (default)". These four were the stragglers; they now align: - apexstack.projects.yaml.example: header comment rewritten to say this file is the registry for the default multi-project mode, single-project is the opt-in - workspace/README.md: section order swapped (multi-project first as the default, single-project second as opt-in), intro paragraph reworded, mode comments in the onboarding.yaml snippet flipped - projects/README.md: opening paragraph flipped so multi-project is described as the default - .claude/skills/projects/SKILL.md: mode detection table row order flipped (multi-project "or missing" is now default), sections swapped so multi-project comes first as the default, single-project moved below as opt-in, the "how to flip back" prompt reworded N2 — Roles count - README.md:166 — "20 software development roles" → "19" (actual count: 7 Engineering + 3 Product + 3 Design + 3 Security + 3 Data = 19) N3 — Site file count - site/index.html:956 — dropped the "79 files · " prefix from the tree header line to kill the drift-prone number entirely. The descriptive tagline "the runnable + portfolio layers added in v0.1" stays. Any time a file is added to the repo, the header no longer lies. Stale SENTINEL comment - golden-paths/pipelines/ci.yml:93 — the security section comment said "SENTINEL — Security Scanning" but the job itself is named "Shield: Security" (the Sentinel→Shield rename from PR #1). Fixed the comment to match. What's NOT in this commit - N4 (PR body "5 skills" + Sentinel/Scout in glossary) — GitHub PR bodies are immutable after merge, so this is recorded in the tracking issue as NOT FIXABLE. - Retroactive AgDRs — intentionally skipped per the CEO's call (v0.1 doesn't need them backfilled). Verification grep -n "20 software development roles" README.md → no hits grep -n "79 files" site/index.html → no hits grep -n "SENTINEL" golden-paths/pipelines/ci.yml → no hits grep for "single-project is the default" in the 4 N1 files → no hits Closes me2resh/apexscript-org#99 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
) * docs(me2resh#100): positioning rewrite — "where projects get forged" Reframes apexstack from "an opinionated stack for Claude Code" to "a multi-project ops repo where projects get forged." Targets the solo founder / technical CEO running 2-5 products and drowning in context-switching. Hero (site/index.html) - Eyebrow flips from "plain markdown · zero dependencies" to "for founders forging multiple products" - H1 changes from the brand name ("apexstack") to the value prop ("Where projects get forged.") - New tagline: "The multi-project ops repo where your projects reference each other, learn from shared experience, and ship production-ready under a strict SDLC." - New subhead leans into the forge framing: "You don't add apexstack to a project — projects get forged inside it. One ops repo. Every product. Shared memory. Strict gates. Production-ready MVPs." Addresses the Claude-Code-lock-in objection head-on: "Claude Code is the default driver, but the rules, hooks, and templates are plain markdown and shell. Swap the AI. Keep the forge." - NEW hero CTA block with star-first priority: [★ Star on GitHub] · [or install it now ↓] - Install command fixed from `~/.apexstack` (home dir, broken per the audit) to `.apexstack` (per-ops-repo, canonical) - Caveat rewritten to match and point at both install paths CSS - Added .hero__cta, .cta, .cta--primary, .cta--ghost, .cta__sep to the existing terminal-brutalism style block. Sharp corners, hairline borders, no gradients. Primary CTA goes from ink → accent on hover. Ghost CTA is text-only with accent on hover. Install section (site/index.html SECTION 05) - Kept the six-step per-ops-repo walkthrough as the canonical path - Kept the single-project opt-out at the bottom - NEW "alternative — global install" step for users running several ops repos (founders with multiple orgs, consultants with multiple clients). Documents the tradeoff: symlinks use absolute paths, breaks if ~/.apexstack/ moves. - Hero caveat links to #install-global for the alternative README.md - Hero rewritten end-to-end to match the site - Added "Global install (alternative)" section after the single-project opt-out, mirroring the new site step - gstacks.org attribution reworded to say "for teams running more than one product at a time" CLAUDE.md - Intro title changed from "AI-Native Development Stack" to "A Multi-Project Forge for Claude Code" - First paragraph expanded with the forge framing and the explicit "projects get forged inside it, not added to it" phrasing, while keeping the operational instructions untouched so the rest of the file still drives Claude correctly. Audience discovery (recorded in the epic issue) - Primary persona: solo founder / technical CEO juggling 2-5 products - Tone: punchy and confident, short sentences, strong verbs - Forge metaphor: literal - Conversion: star first, clone second - Objections addressed: Claude-Code lock-in (explicit), opinionated (leaned into — "strict gates" is the point) NOT in this PR (tracked in the epic) - Commands sub-page (PR 2) - Hero shell animation (PR 3, depends on PR 2) - Roles integration (PR 4 — the audit flagged all 19 roles are completely disconnected from workflows/skills/CLAUDE.md imports, will fix there) - /idea and /handover skill polish (PR 5) - AgDR backfill — explicitly skipped - Anvil / metalwork SVG iconography — holding off on visual changes until the copy is locked Refs me2resh/apexscript-org#100 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs(me2resh#100): fix page title, meta description, CTA separator, caveat copy Address Rex's review on PR #4 commit a044bd2: B1 (blocker) — Page title and meta description still said "an opinionated stack for Claude Code" and "one drop-in stack for Claude Code". These are the highest-leverage strings in the file (browser tabs, search results, social cards) and they directly contradicted the rest of the PR. Replaced with the forge framing: <title>ApexStack — where projects get forged</title> <meta description="... a multi-project ops repo where your projects reference each other, learn from shared experience, and ship production-ready under a strict SDLC. Built for founders running 2–5 products at once. Open source. Claude Code native, but plain markdown underneath."> S2 (non-blocking) — On narrow screens where .hero__cta wraps, the `·` separator could orphan onto its own line. Added @media (max-width: 520px) { .cta__sep { display: none; } } so the two CTAs stack cleanly on mobile. S3 (non-blocking) — Hero caveat said "Five more steps in install below" but the install section now has 6 per-ops-repo steps plus the single-project opt-out and the new global-install alternative. Rephrased to "Full walkthrough in install below" so the copy is count-agnostic. S1 (skipped) — Rex noted CLAUDE.md doesn't carry "Swap the AI. Keep the forge." but also explicitly said the spec only required the line in site + README, so leaving CLAUDE.md's operational tone intact. Refs me2resh/apexscript-org#100 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs(me2resh#100): revert H1 to "apexstack_" with flicker, promote forge to tagline CEO feedback on the live site at a044bd2: - Missed the flickering "_" cursor on the old H1 — the three-word H1 ("Where projects get forged.") also overflowed the hero width because .hero__title has white-space: nowrap; overflow: hidden; and was clipping at ~"projects" on a standard laptop - Forge statement belongs in the subheading, not the H1 Changes: - H1 reverts to "apexstack" (was "Where projects get forged."). The .hero__title::after pseudo-element already appends a "_" and animates it via the blink keyframe, so the flicker comes back for free. - Tagline becomes "Where projects get forged." — the forge statement now sits in the visually-prominent subheading slot right under the H1. - .hero__tagline font-size bumped from clamp(1rem, 1.6vw, 1.25rem) to clamp(1.5rem, 3vw, 2.5rem) so a 4-word punchy statement carries visual weight. Also font-weight 500 → 600, line-height 1.5 → 1.25, and a small negative letter-spacing for the display-style treatment. - Sub paragraph absorbs the previous tagline content merged with the existing subhead — one longer descriptive paragraph covering the multi-project promise, the forge framing, and the Claude-Code lock-in objection. Typography hierarchy is now: - H1 (huge, monospace, with blinking cursor) — brand mark - Tagline (display-size, punchy) — the one-line promise - Sub (body-size, descriptive) — the multi-paragraph pitch Refs me2resh/apexscript-org#100 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PR 3 of the apexstack positioning epic. Adds an animated shell
window showing a real /idea invocation end-to-end, placed in the
hero between the CTA block and the install command so readers see
proof of what the stack feels like before they commit to cloning.
The user picked /idea (over /handover or the full ticket flow)
because it's relatable, lightweight, and demonstrates the multi-
project story: the demo creates IDEA-025, stores it in the shared
projects/ideas-backlog.md, and files a tracking issue against
your-org/example-app — all three surfaces the copy above promises.
Demo script (~11s, typewriter timing)
$ /idea Usage-based pricing for creators
Category? (1) New Product (2) Feature (3) Internal Tool (4) Process
> 2
Description? (one line)
> Let creators tier paid access by usage instead of seat count
Create GitHub Issue in your-org/example-app? (y/n)
> y
✓ Captured: IDEA-025 — Usage-based pricing for creators
Backlog: projects/ideas-backlog.md
Tracking: your-org/example-app#31
Next: triage, then /write-spec
Multi-project neutrality: no real project names leak — placeholder
is `your-org/example-app`, matching the `apexstack.projects.yaml.example`
registry convention the rest of the repo already uses.
Implementation
- New `.shell-demo` block in the hero (after `.hero__cta`, before
`.install`), with a chrome bar (three brutalist dots, title,
replay button) and a body that the JS drives
- Pre-rendered static HTML inside the body, so the demo is readable
even with JS disabled or reduced motion
- CSS for the shell window, line types (cmd/sys/you/ok/muted),
prompt coloring (accent for $, ink-faint for >), and replay
button (hidden by default, revealed by JS)
- ~90 lines of JS (IIFE) in the existing end-of-body script tag:
- Typewriter effect with variable delays per script entry
- Per-char type speed + ±15ms jitter so it feels human
- Clears and replays on replay-button click
- Respects `prefers-reduced-motion`: if set, keeps the static
pre-rendered content, hides the replay button, and exits
without animating (no forced 10s wait for reduced-motion users)
No new dependencies. No bundler. Inline JS at the bottom of body,
runs after the DOM is parsed.
Placement rationale
- C1 = /idea (CEO picked in this thread)
- C2 = CSS + small JS (from the epic UX discovery)
- C3 = hero (from the epic UX discovery)
- Inserted between .hero__cta and .install so the funnel reads:
pitch → CTA → proof → install → caveat → metrics
Accessibility
- `aria-label` on the shell-demo wrapper describes what it is
- `aria-hidden` on the decorative chrome dots
- Replay button has `aria-label`
- `prefers-reduced-motion` fully respected (no animation, no
forced delay, no replay loop)
- Static pre-rendered content is a functional fallback — the
demo is readable even with JS disabled
What's NOT in this PR (tracked in the epic me2resh#100)
- Commands sub-page (intentionally skipped per CEO call — we
picked the animation target without needing the reference
page first)
- Roles integration (PR 4 in the original plan)
- /handover and /idea skill polish (PR 5)
Refs me2resh/apexscript-org#100
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…me2resh#6) * feat(me2resh#100): lifecycle demo + catchier /idea content + Swift proof line Bundles three related landing-page upgrades that the CEO asked for in one review thread: 1. /idea demo content rewrite - Swapped the placeholder "Usage-based pricing for creators" idea for "Swift menu-bar app for photo library dedup" — a concrete, catchier idea that also surfaces the fact that apexstack has been used to ship native Swift macOS apps. - Category now 1 (New Product) instead of 2 (Feature), since it's a new app. - Tracking target changed from `your-org/example-app#31` to `your-org/ops#17` because a new-product idea doesn't belong in an existing app's issue tracker — it lives in the ops repo. - Both the pre-rendered static HTML and the JS script array are updated line-for-line. 2. New SECTION 00 / LIFECYCLE — full ticket lifecycle demo A second animated shell window placed directly below the hero, covering all 14 phases of the apexstack workflow end-to-end: /inbox → pick up me2resh#58 → Tech Lead planning → plan approval → Backend Engineer implementation → AgDR via /decide → local lint/types/tests/build → commit/push/PR → /code-review → Rex verdict → CEO 👍 → merge → PR→QA state transition → pipeline on main → QA Engineer verification on staging → Done Playback: scroll-triggered via IntersectionObserver (0.3 threshold), plays ONCE when the section enters the viewport, never blocks page load. Replay button at top-right of the chrome. Respects prefers-reduced-motion by exiting early and keeping the static content in place. Fallback: if IntersectionObserver is unavailable, auto-plays after a 3s delay. Pre-rendered static HTML mirrors the animation line-for-line so the demo is readable with JS disabled. ASPIRATIONAL role lines flagged: "Tech Lead role active", "Backend Engineer role active", "QA Engineer role active" — roles are currently PASSIVE per the audit. PR 7 (renumbered roles integration) will wire the activation for real. The demo previews that future state. 3. Proven-stacks line + README bullet New small `<p class="hero__proof">` caption below the hero metrics: Proven shipping · TypeScript + AWS Lambda backends · Next.js web apps · Chrome extensions · native Swift macOS apps Uses the existing .kw accent-coloured span for the stack keywords so they pop without adding new CSS. README.md hero gains a matching bullet in the second paragraph calling out the four proven stack types — directly answers the CEO's "do we have this somewhere?" question. We did not. Now we do. Also - Titlebar nav: replaced #stack (dead anchor since PR 1 reshuffle) with #lifecycle pointing at the new section. - .shell-demo--tall variant: reserves 52rem min-height on the body so the demo doesn't collapse while the JS rebuilds it line by line. Smaller font-size (12px vs 12.5px) so 50+ lines fit. - .hero__proof: small caption style matching the existing ink-faint + .kw accent pattern. The lifecycle demo's JS IIFE shares ~80 lines of helper code with the /idea IIFE (typeInto, play loop, timeouts cleanup). Tolerated for now as copy-paste because extracting a shared helper is a separate refactor; both IIFEs remain self-contained and easy to remove individually. Multi-project neutrality verified - grep -iE "flat-?mate|curios|sharp ?pick|movetwo|yumyum" site/index.html returns 0 hits. The Swift reference is via the generic phrase "Swift menu-bar app for photo library dedup" — no SharpPick name leak. Repo placeholders use your-org/{ops,billing-api} per the apexstack.projects.yaml.example convention. Refs me2resh/apexscript-org#100 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(me2resh#100): lifecycle demo scrolls like a terminal, not a full box CEO feedback on the new animation in PR me2resh#6: "i like the new animation, but yes too much on the page, doesn't have to be a full box displaying everything, can be smaller like 10 lines and scrolling with new data like terminal". Changes - .shell-demo--tall .shell-demo__body — fixed height 17rem (~10 lines at 1.55 line-height), overflow-y: auto, hidden scrollbars on both Firefox (scrollbar-width: none) and WebKit (::-webkit-scrollbar). Replaces the old min-height: 52rem that reserved the full 54-line height on the page. - Added a soft top fade gradient via .shell-demo--tall::before that sits just under the chrome bar. Implies depth — content scrolled above the viewport fades out rather than hard-clipping. Uses --paper-2 → transparent so it flips correctly in dark mode. - Mobile breakpoint adjusted: 18rem height + 11px font at ≤720px. - Lifecycle IIFE play() now calls a tiny scrollToBottom() helper after every line append and after every typeInto callback, so the viewport snaps to the latest line like a real terminal. scrollTop resets to 0 at the start of play() so replay works from the top. - scroll-behavior: auto (not smooth) — instant snap matches the terminal feel and avoids "scroll is still happening while the next line is typing" jank. Behaviour - JS-enabled: terminal-like viewport, new lines stream in at the bottom, old lines scroll out of view - No-JS: same 17rem viewport with the full static content; overflow-y: auto means the user can scroll the box manually to see all 54 lines - Reduced motion: static pre-rendered content, no animation, user scrolls manually (same as no-JS) Page footprint drops from ~52rem to 17rem — the lifecycle section now takes roughly the same vertical space as the /idea hero demo. Refs me2resh/apexscript-org#100 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(me2resh#100): replace /idea demo with lifecycle in the hero CEO feedback on the live preview: "i like this more than the idea, lets replace it". Dropping the /idea hero demo entirely and moving the lifecycle demo into its slot. Changes - Hero: /idea shell-demo block deleted, replaced in the same slot with the lifecycle shell-demo (now carrying `rise rise-4` classes to match the rest of the hero fade-in sequence). - Section 00 wrapper (`#lifecycle` section with its header + lede) deleted entirely. The demo is now the hero's visual focal point instead of a separate section below the fold. - `/idea` JS IIFE deleted (~100 lines). - Lifecycle JS IIFE comment updated from "(section 00)" to "(hero)" explaining the new placement. Code itself untouched — still uses IntersectionObserver with the `hasPlayed` gate + 500ms delay, so it auto-plays once on load (since the hero is visible immediately) and never re-plays on scroll. - Titlebar nav: removed the `<a href="#lifecycle">lifecycle</a>` link. The demo is in the hero now, so a jump-to link is redundant. Nav drops from 6 items to 5 (tree · layers · examples · install · github →). Behaviour - JS on: lifecycle demo auto-plays once on load in the hero, new lines stream into the 17rem viewport, old lines scroll up and out like a terminal. - No-JS: 17rem scrollable viewport with full static content, replay button hidden. - Reduced motion: static content, no animation, replay button hidden. Page structure now: hero → tree → layers → examples → install → scope → footer (SECTION 00 is gone; tree becomes the first post-hero section) The footprint below the hero is ~52rem lighter than before the revert — the single in-hero demo is the only shell demo on the page. Refs me2resh/apexscript-org#100 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(me2resh#100): lifecycle animation — autonomous by default, 3 human touchpoints CEO feedback: "for the animation, it looks like we run every command in the cycle, it should be autonomous unless plan & push & waiting for approval". The old script prefixed every step with '$ ' prompts as if a human was typing each command. Reality: apexstack runs almost everything autonomously once a ticket is picked up. The human only intervenes at THREE well-defined touchpoints. Three human touchpoints now explicit 1. Plan approval → "Approve plan? (y/n)" → > y 2. Push approval → "Ready to push PR me2resh#84?" → > y 3. CEO approval → "Waiting for CEO 👍" → CEO 👍 Everything between those is narrated autonomously — no $ prompts, no implied human typing. The demo now reads as a log stream of what Atlas did between the three decision points. Script rewrite (both static HTML and JS array) - Removed every "$ /command" and "$ git ..." line. They become sys-type narrations: "Inbox:", "Picking up me2resh#58...", "Plan for me2resh#58:", "Detected decision: ...", "Local checks:", "Ready to push PR me2resh#84?", "Pushed · Opened PR me2resh#84", "Rex reviewing a3f9c21...", "Pipeline on main:", etc. - Added a new "Ready to push PR me2resh#84? → > y" touchpoint between the local-checks block and the PR creation. Makes the "push" human intervention explicit. - Role lines softened: "Tech Lead role active — planning" → "Tech Lead role: planning" (less shouty; still aspirational — roles become real in PR 7). - Dropped the square-bracket theatrics on the CEO wait line: "[waiting for CEO 👍]" → "Waiting for CEO 👍" (cleaner, and the long 1500ms delay already conveys "we're waiting"). JS next() simplification - Removed the `cmd` branch special-case in next(). The `$ ` prompt emission is gone. Only `you` type still prefixes with a `prompt` span (the `> ` marker). Every other type is rendered as plain textContent, with colour driven by the CSS class. - The `.cmd` CSS class is still in the stylesheet for possible future use but nothing in the current script emits it. Runtime impact - ~55 script entries (was 52) — +3 for the new push-approval touchpoint (sys + you + blank) - Total runtime drops from ~40-50s to ~18-22s because most lines are now instant (speed=0) instead of typewriter. The only typewriter action left is the two "y" responses. Much snappier. Behaviour - JS on: narrated log stream, only "> y" lines are typed, demo auto-plays once on load, scrolls like a terminal - No JS / reduced motion: same static content, manual scroll - Multi-project neutrality: 0 hits for flat-mate/curios/sharppick Refs me2resh/apexscript-org#100 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…UDE.md (me2resh#7) Closes the biggest gap the PR #1 Rex audit flagged: all 19 role files exist and are well-specified, but nothing in the running system references them. CLAUDE.md listed them in a table but didn't @import anything. No workflow named a role file. No skill invoked a specific role. Users had to manually say "please read roles/engineering/qa-engineer.md and act as the QA Engineer" for anything to happen. Roles were passive reference docs. This PR makes them first-class participants. New file — .claude/rules/role-triggers.md (~107 lines) - Activation table for all 19 roles (department, file path, when to activate with 2-4 concrete conditions per role) - Activation protocol (read file → adopt identity → follow handoff rules → stay in role until task completes) - Auto-activation signal table (ticket label, PR path match, incident, PRD draft, etc. → which role activates) - Prompted activation examples ("act as the QA Engineer for me2resh#42") - Role boundary enforcement (CAN/CANNOT from each role file is strict — hand off when you hit a CANNOT) - Handoff artefact table (PRD, tech design, testable build, security findings, AC sign-off, etc.) — the contracts between roles - Explicit "aspirational → real" closing note explaining what this file makes concrete CLAUDE.md - ROLES section gains an "Activation" subsection explaining the model in three short paragraphs - New @.claude/rules/role-triggers.md import at the end of the subsection — the trigger table is loaded into every session so Claude always knows which role to activate on which signal workflows/sdlc.md — each phase gets a "Primary role" header - Phase 1 Planning → Tech Lead (+ Product Manager, Head of Eng on escalation) - Phase 2 Tech Design → Tech Lead (+ Head of Eng, UX/UI Designer) - Phase 3 Build → Backend / Frontend Engineer (+ Tech Lead coordinator) - Phase 4 Code Review → Tech Lead + Rex (+ Security Auditor on auth/crypto diff, UI Designer on UI diff) - Phase 5 QA → QA Engineer (MANDATORY — merged code is never Done without QA sign-off; expands the existing "QA gate" warning) - Phase 6 Deploy → Platform Engineer (+ SRE for runbook/rollback) - Phase 7 Monitor → SRE (+ Head of Eng escalation) - "Roles Summary" table at the bottom rewritten with every role as a markdown link to its file workflows/code-review.md — Roles table expanded - Author column now names the actual Backend/Frontend Engineer roles - Automated reviewer named explicitly (Rex agent) - Human approval gate = Tech Lead with a link to the role file - Conditional Security Auditor and UI Designer rows added with their trigger conditions - QA Engineer called out as "not a reviewer" to kill the common misconception that QA approves merges workflows/deployment.md — new Roles table at the top - Maps each deployment stage (CI/CD maintenance, staging, prod gate, incident response, post-deploy monitoring, security gate) to its activating role and trigger condition - Head of Eng sign-off noted for risky production promotions Skills — 5 files get an "Activated role" section between the intro and the process - /decide → Tech Lead (+ Head of Eng for arch-review threshold, Security Auditor if decision touches auth/secrets) - /write-spec → Product Manager (+ Head of Product escalation, UX/UI Designer for design-heavy features; hands off to Tech Lead at Tech Design phase) - /code-review → Rex + Tech Lead (+ conditional Security Auditor, conditional UI Designer) — the richest of the five because code review has the most conditional branches - /security-review → Shield + Security Auditor (+ Head of Security for strategic calls, Penetration Tester for active testing) - /roadmap → Head of Product (+ Product Analyst for data-driven reprioritisation) Not in scope for this PR - The 19 role files themselves are untouched — their content was already good per the audit - Skills /inbox, /status, /tasks, /projects, /idea, /handover, /audit-deps, /stakeholder-update don't get role activation sections because they're portfolio / CEO-facing or the role is implicit from the task context - No new roles added - The .claude/rules/role-triggers.md file is NOT imported from every workflow/skill — only CLAUDE.md imports it once, and individual files reference it relatively when they need to cite the activation protocol. Keeps the context footprint lean. Token budget - role-triggers.md is ~107 lines (~1.3k tokens). Loaded once per session via the CLAUDE.md @import. The 19 full role files are NOT pre-loaded — they're read on demand when their trigger fires (saves ~22k tokens that would otherwise sit idle). Multi-project neutrality - grep for flat-mate/curios/sharppick/movetwo/yumyum across all 10 changed files → 0 hits - All file paths use `roles/{department}/{role}.md` and `.claude/rules/role-triggers.md` — no org-specific references Refs me2resh/apexscript-org#100 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…sh#8) * refactor(me2resh#100): multi-project only + fork-first install Two strategic direction changes from the CEO rolled into one PR because they're deeply coupled — removing single-project mode changes the install flow, and the fork-first install changes every doc that talked about cloning into .apexstack/. 1. Drop single-project mode entirely --------------------------------------- Single-project mode was a dual-mode compromise that added complexity without matching how apexstack is actually used. The audit found every skill had a mode branch that was never exercised in practice. Users who thought they only had one repo almost always ended up with two within a few months. - onboarding.yaml: removed the apexstack.mode field + the multi-paragraph mode explainer - apexstack.projects.yaml.example: rewritten header comment to lead with "no single-project fallback mode; register your one repo and the skills work the same" - CLAUDE.md: OPERATING MODE section replaced with PORTFOLIO MODEL section explaining the fork-as-ops-repo mental model. SETUP section dropped the "read apexstack.mode" step. - docs/multi-project.md: full rewrite as the canonical setup guide. Removed the TL;DR comparison table, the "when to switch to single-project" section, the "migrating from single → multi" section, the "going back: multi → single" section, and every trade-off framed against single-project. - 8 skill files (handover, idea, inbox, projects, roadmap, stakeholder-update, status, tasks): dropped every mode detection block (grep onboarding.yaml for mode:), dropped every mode-comparison table, simplified scope statements to "iterates the registry" or equivalent. Dead code removed. - workspace/README.md, projects/README.md: removed the "two modes" sections, simplified to describe the single portfolio pattern. - README.md: removed the Operating modes table + the Single-project install (opt-out) section + the Global install (alternative) section (see item 2 for why the global install is gone). - site/index.html: removed the "Operating modes (single & multi-project)" hero metric, updated the layers section lede, removed every single-project mention in Layer 05, removed the "opt-out — single-project mode" install step, removed the "alternative — global install" install step. The only remaining "single-project" strings in the codebase are TWO intentional negation disclaimers (in docs/multi-project.md and site/index.html Layer 05) that tell anyone searching for the concept: "there is no single-project fallback". That's a feature, not a bug. 2. Fork-first install (replaces clone-into-.apexstack/) -------------------------------------------------------- The old install model cloned apexstack into a hidden .apexstack/ subdirectory of a separate ops repo, then symlinked .claude/ up one level. Three problems: a. Brand invisibility — .apexstack/ is a dotfile, hidden from ls and GitHub views. Nobody knew you were using apexstack. b. Two repos to maintain — your ops repo plus the nested clone. c. Symlink fragility — the .claude/ symlink broke on dotfile sync tools and Windows setups. New model: your ops repo IS a fork of apexstack. One repo, no nesting, no symlinks. Upgrades flow via `git fetch upstream && git merge upstream/main` — the standard fork workflow. Per the CEO's call, users can rename the fork to your-org/ops or keep it as your-org/apexstack. GitHub handles the rename cleanly. The install docs describe the rename as optional. - Hero install block: "git clone ... .apexstack" → "gh repo fork me2resh/apexstack --clone" - Hero CTA: "★ Star on GitHub" → "★ Star · ⑂ Fork on GitHub" (single button, per Q3.c) - Hero install caveat: rewrote to explain "the fork IS your ops repo, no nested installs, no symlinks" - SECTION 05 / INSTALL: full rewrite. 6 steps became 5. The 5 steps are: star+fork, add upstream remote, configure onboarding.yaml, create the registry, start working. The old "symlink runnable layer" step is gone (no symlink needed). The old "wire CLAUDE.md with @-import" step is gone (CLAUDE.md is already at the fork root). - README.md Quick Start: same 5-step rewrite, matched to the site flow. - docs/multi-project.md: complete rewrite as a setup guide covering the fork flow, directory layout, daily workflow, upgrade path (git pull upstream), trade-offs, and FAQ. ~314 lines, honest about the pros and cons. Side effects (positive) ----------------------- - Layers section UX bug from the earlier screenshot is gone. Layer 05 no longer has "content only fills left 1/3" — added a new .layer--wide CSS variant with a 2-column internal grid (description on the left, registry file list on the right). Fills the full width naturally. - Hero metric #4 changed from "2 Operating modes" to "1 Fork · your ops repo in one command" — celebrates the simplification instead of the dropped complexity. Not in scope ------------ - The lifecycle demo script in the hero is untouched (it doesn't reference install or mode) - Role files in roles/ are untouched (no mode refs) - Workflow files (sdlc, code-review, deployment) are untouched (no mode refs) - .claude/agents/ are untouched (no mode refs) - Hooks in .claude/hooks/ are untouched (no mode refs) - The apexstack.mode field in the existing onboarding.yaml.example — this is the site's own onboarding.yaml which gets removed with the mode config Verification ------------ grep -rn "single-project" --include="*.md" --include="*.yaml" \ --include="*.html" --include="*.sh" → 2 hits, both intentional negation disclaimers grep -rn "mode:" --include="*.md" --include="*.yaml" \ --include="*.html" → 0 hits grep -rn "\.apexstack/" --include="*.md" --include="*.yaml" \ --include="*.html" → only in docs/multi-project.md explaining why the old pattern was dropped (historical context paragraph) Multi-project neutrality ------------------------ grep -iE "flat-?mate|curios|sharp ?pick|movetwo|yumyum" \ on all 16 changed files → 0 hits Refs me2resh/apexscript-org#100 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(me2resh#100): address Rex review — blockers + CTA contradiction + doc fork Rex's delta review on 160148f found 4 blockers + 1 site CTA contradiction + a few vestigial mentions. Plus the deeper grep I ran caught two more: a stale .apexstack/ example in golden-paths/pipelines/README.md, and docs/getting-started.md still documenting the old .apexstack/ + symlink flow. Blockers fixed -------------- - B1: idea/SKILL.md rule 5 "Mode-aware — detect apexstack.mode" → "Single backlog — every idea goes into projects/ideas-backlog.md" - B2: roadmap/SKILL.md rule 6 "Mode-aware — ROADMAP.md or projects/<name>/roadmap.md" → "One roadmap per project — always write to projects/<name>/roadmap.md" - B3: status/SKILL.md had a "## Multi-project mode" section and a "Single-project mode default:" output format header. Renamed to "## Portfolio output" and "## Output format (one-project view with --project <name>)". No behaviour change — just language that doesn't frame /status as mode-switching. Non-blocking suggestions fixed ------------------------------ - S1 (site CTA contradiction): index.html:1732 final CTA was "one git clone, one symlink, and one config file away" → "one fork, one clone, and one config file away". The old line directly contradicted the "no symlinks" story. - S3 (CLAUDE.md QUICK REFERENCE): - "Project registry (multi-project)" → "Portfolio registry" - "Per-project docs (multi-project)" → "Per-project docs" - "Live working copies (multi-project)" → "Live working copies (gitignored)" - "Multi-project guide" → "Full setup guide" Also cleaned up while I was in there ------------------------------------ - inbox/SKILL.md rule 3: "Multi-project mode iterates the registry" → "Registry-scoped — only projects listed in apexstack.projects.yaml count" - projects/SKILL.md error row: "Multi-project mode but no apexstack.projects.yaml" → "No apexstack.projects.yaml at the ops-repo root" - projects/README.md directory layout: "ideas-backlog.md (multi-project mode)" → just "shared ideas backlog" Out-of-scope but too tangled to leave ------------------------------------- - golden-paths/pipelines/README.md: `cp` examples pointed at `.apexstack/golden-paths/...` as if apexstack were still a nested clone. Rewrote to "copy from ~/apexstack (your fork) to your managed project's .github/workflows/". Not strictly within the mode-removal scope but the README is now self-consistent with the fork-first install. - docs/getting-started.md: rewrote Step 1 and Step 3 to match the new fork flow. Was still documenting the old .apexstack/ + submodule + @.apexstack/CLAUDE.md @-import pattern — guaranteed to confuse a new adopter. Keeps parity with docs/multi-project.md and the site install section. Verification ------------ grep -rin "single-project" --include="*.md" --include="*.yaml" \ --include="*.html" → 2 hits, both intentional negation disclaimers in docs/multi-project.md:5 and site/index.html:1367 grep -rn "apexstack.mode\|mode: multi-project\|mode: single-project\|Mode-aware" \ --include="*.md" --include="*.yaml" --include="*.html" → 0 hits grep -rn "\.apexstack/" --include="*.md" --include="*.html" → only in docs/multi-project.md historical context paragraph (explaining why the old pattern was dropped) and README.md negation disclaimer ("no .apexstack/ symlinks") Refs me2resh/apexscript-org#100 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…d gaps) (me2resh#9) Closes the last sub-task of the apexstack positioning epic (me2resh#100). The Explore agent audit flagged specific gaps in both skills; these were deferred through PRs #4-me2resh#8 because they're independent of the positioning work. Now fixed. /handover — 3 gaps closed ------------------------- 1. **Auto-append to the portfolio registry** (was the CRITICAL gap from the audit). Previously the skill printed a YAML snippet and told the user "add this to apexstack.projects.yaml". Users forgot, or miscopied, or broke the YAML indentation. Fix: new step 6 prompts the user `Ready to add {name} to apexstack.projects.yaml? (y/n)`. On y, the skill: - Locates the registry at the ops-repo root (creates from .example if missing, with a warning) - Appends the entry via `yq` if available, otherwise plain-text append with careful indentation - Validates the result by parsing the YAML (yq or python yaml) - Rolls back on failure (never leaves the registry broken) - Confirms the append to the user with the derived roles list On n, prints the snippet for manual copy-paste and continues without writing. 2. **Dynamic "Next Steps"** derived from the risks found. The old template had generic placeholders like `{first concrete action — usually "run /audit-deps and triage criticals"}` which was embarrassing to ship. New behaviour: a mapping table in the skill spec defines the derivation. Examples: - CVEs detected → `/audit-deps {name} — triage the {severity} {package} CVE before any new feature work` - Failing tests → `Fix the {N} failing tests in {module} before merging new PRs` - No observability → `/decide on observability ({top 2 options for this stack})` - Stale CI → `Re-enable CI — copy in golden-paths/pipelines/ci.yml` - Coverage unknown → `Set up test coverage reporting` - Backlog > 10 → `Triage the issue backlog with previous owner` - Missing README → `Write a minimum-viable README` 3. **Post-handover checklist** added to the assessment template, also dynamically tailored to the risks found. Items like "close {top risk} before the first feature PR", "add {name} to weekly /stakeholder-update rollup", "set up a coverage baseline". Bonus gap closed: **derive the role list dynamically** from the tech stack + CI config + security surface, instead of hard-coding `[tech-lead, backend-engineer]`. Mapping table in the spec: - backend deps → backend-engineer - UI code → frontend-engineer - CI config → platform-engineer - deployment evidence → sre - auth/crypto/secrets → security-auditor - always → tech-lead A typical handover ends up with 3-5 roles in the registry entry. /idea — 3 gaps closed + 1 numbering bug fixed ---------------------------------------------- 1. **Input validation for category**. The old step 2 asked for category but silently accepted anything. A user who typed `foo` got `Category: foo` in the backlog. Fix: the prompt now explicitly lists 1/2/3/4, accepts either the number or the word (case-insensitive), and **re-prompts on invalid input** with `Please pick 1-4 or type the category name.` Loops until valid. Same treatment for the description field — re-prompt if empty. 2. **Dedup check before appending** (new step 3). Prevents submitting the same idea twice. Implementation: a simple token-overlap heuristic. Normalise both titles (lowercase, strip punctuation), compare with a threshold (≥ 80% of the words in the shorter title appear in the longer one). If a match is found: ⚠ Similar idea already in the backlog: IDEA-025 — {existing title} (status: {status}) Is this a duplicate? (y = skip, n = add anyway) On y: skip the append, suggest `/write-spec IDEA-025` if the user wants to work on the existing idea. On n: continue to step 4. 3. **Error handling for `gh issue create`** in step 6. Old behaviour: if the tracking issue creation failed, the idea was half-saved or the whole thing errored out. New behaviour: ⚠ Couldn't create the tracking issue: {reason} The idea is still saved in projects/ideas-backlog.md as IDEA-NNN. Try again? (y = retry, n = skip, gh = show the gh error) With a failure-modes table: missing auth scope, label not found, rate limit, network error, etc. — each has a specific recovery path. Guiding principle (now rule me2resh#8): **the backlog entry is the primary artefact; the tracking issue is a bonus**. Never lose the backlog entry because GitHub was flaky. 4. **Section numbering bug** fixed. The old file had steps 1, 2, 4, 5, 6 — step 3 was missing because an earlier refactor deleted it without renumbering. With the new step 3 (dedup check) the sequence is now 1, 2, 3, 4, 5, 6 again. Rules sections updated for both skills -------------------------------------- - /handover: rule 4 is now "Auto-append to the registry (with confirmation)"; rule 5 is "Derive roles from the stack"; rule 6 is "Derive next steps from the risks"; rule 10 is "Never break the registry". - /idea: rule 6 is "Validate before accepting"; rule 7 is "Dedup before appending"; rule 8 is "The backlog is the primary artefact". What's NOT in this commit ------------------------- - The tabbed terminal animation (me2resh#102) — separate ticket, not part of the epic closure - Any changes to the skill frontmatter - Any changes to the `/handover` assessment output format beyond the Next Steps and Post-Handover Checklist sections - Any changes to `/idea`'s backlog file format or ID scheme This closes the epic me2resh#100. After this merges, apexstack is at its v0.2 shape: positioning rewritten, lifecycle demo in the hero, 19 roles wired in, multi-project-only + fork-first install, and now the /handover and /idea skills that actually work end-to-end. Closes me2resh/apexscript-org#101 Closes me2resh/apexscript-org#100 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…w, onboarding Test run of ApexStack on a real project exposed three SDLC compliance gaps that all traced back to the same root cause: workflow rules lived as prose in CLAUDE.md, .claude/rules/, and workflows/ — advice the model drops under pressure. This PR moves the four highest-leverage rules into .claude/hooks/ where the harness executes them mechanically. ## New hooks - require-active-ticket.sh — PreToolUse on Edit/Write/MultiEdit; blocks code-path edits when the active-ticket marker is missing. Exempts .claude/, docs/, projects/*/docs/, and any *.md file. - auto-code-review.sh — PostToolUse on Bash; fires after PR creation and nudges Claude to invoke the code-reviewer agent (Rex) immediately. - block-unreviewed-merge.sh — PreToolUse on Bash; blocks PR merge unless a Rex approval file exists at .claude/session/reviews/<pr>-rex.approved AND its SHA matches HEAD. - onboarding-check.sh — SessionStart; warns on every new session until /onboard writes the onboarded marker. ## New skills - /start-ticket <issue> — verifies the issue via `gh issue view`, writes the active-ticket marker, suggests a branch name per git-conventions.md. - /onboard — day-one discovery pass (project identity, tracker repo, required CI checks, reviewers, UI work, deploy targets, sensitive topics). Writes the onboarding marker and .claude/project-config.json. Idempotent. ## Infrastructure - .claude/settings.json registers SessionStart, new PreToolUse matchers for Edit/Write/MultiEdit and pre-merge, and a PostToolUse Bash matcher for post-PR-creation. Existing six-hook Bash block untouched. $schema preserved. - .gitignore (new file — apexstack had none) excludes .claude/session/, .claude/project-config.json, .claude/settings.local.json, and workspace/*/ per the multi-project model in docs/multi-project.md. workspace/README.md stays visible via a negation rule. - .claude/hooks/README.md documents the four new flows, the exit-code semantics (exit 2 blocks in PreToolUse, nudges in PostToolUse), the session-state directory layout, the merge-gate trust model, and how to add new hooks. ## Review history Rex approved across two passes. Three nice-to-haves from the first pass were applied in a follow-up commit: - onboarding-check.sh switched to an interpolating heredoc so the "no onboarding marker at" message prints the real absolute path and a copy-pasteable workaround. - auto-code-review.sh comment expanded to cite harness version drift (Claude Code 2.x+ vs 1.x vs earliest builds) and flag when the older fallback paths can be dropped. - Hooks README documents the merge-gate's local trust model: the approval file is session state, not a remote trust boundary, so branch protection and CODEOWNERS remain the adversarial layer. Refs me2resh/apexscript-org#103 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…er from "go" Closes the failure mode where I merged apexstack PR me2resh#10 after a plan-level "go" that was not a per-PR merge approval. The rule existed in prose but was ambiguous, and there was no mechanical enforcement for the human side of the 2-reviews gate. ## Prose tightening - `.claude/rules/pr-workflow.md` "Before PR merge" section rewritten with a concrete wrong/right example showing that plan-level "go" does NOT carry through to the merge step. Lists other destructive actions (force pushes, branch deletes, closing issues with dependents, external posts) that need the same per-action explicit approval. - `CLAUDE.md` Quality Rules adds an explicit bullet calling out per-PR CEO approval, linking to the full rule. ## Mechanical enforcement - `.claude/hooks/block-unreviewed-merge.sh` now requires TWO approval markers, not one: - `.claude/session/reviews/<pr>-rex.approved` — from the code-reviewer agent (existing) - `.claude/session/reviews/<pr>-ceo.approved` — from the /approve-merge skill, on explicit per-PR user invocation (new) Both must contain the current HEAD SHA. Any mismatch blocks the merge. - New /approve-merge <pr> skill at .claude/skills/approve-merge/SKILL.md. Its one job is to write the CEO marker on explicit per-PR user invocation. Definition includes valid/invalid invocation triggers and an anti-pattern section describing the exact failure mode the skill exists to prevent. - Marker paths anchored at `git rev-parse --show-toplevel` so invocation from any subdirectory (e.g. workspace/<project>/) still writes to the path the hook reads from. - .claude/hooks/README.md documents both markers, why there are two, and the trust model: local session state, not a remote trust boundary. The failure mode closed is "invisible inference" (Claude decides "go" meant "merge"); the hook converts it into "visible rule violation" (Claude touched a file it wasn't supposed to). Grep-able instead of invisible. ## Dogfooding This PR is the first one merged under the new rule. Flow: 1. PR created, auto-code-review hook fires (already in place from me2resh#10) 2. Rex reviews — APPROVED, flags 4 nice-to-haves, 1 of which is a real correctness bug (cwd-relative path in skill step 5) 3. Fix commit pushed addressing the cwd bug; other 3 nice-to-haves deferred to a follow-up ticket 4. Rex re-reviews the new HEAD — APPROVED, no further issues 5. CEO is asked per-PR, explicitly, for merge approval on PR me2resh#12 6. CEO replies "12 approved" — explicit per-PR approval 7. /approve-merge 12 invoked — writes CEO marker at repo root 8. Confirmation returned to CEO, separate per-action nod requested 9. CEO replies "ship it" 10. This squash-merge runs — both markers present, both SHAs match HEAD, merge-gate hook allows through Rex reviewed twice. CEO explicitly approved twice (once for the merge, once for executing the merge) — each moment discrete, each named the PR. Closes me2resh#11 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Closes the vocabulary-collision failure mode where Claude's internal plan decomposition wears tracker clothing and the user reasonably reads it as tracker state. Root cause identified on 2026-04-11 after a friend of the CEO hit it on a real project: the agent presented a 10-item "Ticket N ... blocked by #M" plan in chat, none of which existed on GitHub. ## Root-cause rule (primary fix) - New .claude/rules/ticket-vocabulary.md reserves `Ticket`, `#N`, and dependency notation (`blocked by #N`, `depends on #N`, etc.) for real GitHub issues only. Prescribes safe vocabulary for in-conversation planning: `Step N`, `Item A`, plain bullets, phases. Includes a boundary-crossing rule — if plan items need tracker properties, STOP and run `gh issue create` before presenting. Contains the 2026-04-11 anti-pattern verbatim and a corrected version side-by-side. - CLAUDE.md Quality Rules links the new rule alongside the per-PR merge approval bullet (both are "don't fake process state" rules). ## Mechanical backstops - validate-pr-create.sh extended: extracts the issue number from the PR title and runs `gh issue view` against the resolved tracker repo. Blocks PR creation if the referenced issue doesn't exist. Tracker repo resolves from .claude/project-config.json first, falls back to origin remote. - New verify-commit-refs.sh on PreToolUse / Bash git-commit: parses the commit message from -m or -F args, scans for Closes/Fixes/ Resolves/Refs/Related-to #N patterns, verifies each via gh issue view. Blocks on any missing reference. Interactive commits (no -m / -F) are skipped. - settings.json registers the new hook under the existing git-commit matcher. - hooks/README.md adds a "Ticket-Vocabulary Backstops" section framing both hooks as backstops, not primary fixes. ## Multi-line -m fix (second commit) Rex's first review labeled a multi-line gap as "nice-to-have, rarely fires". Testing revealed it fires ALWAYS on Claude-generated commits, because Claude uses HEREDOC substitution for nearly every commit message, which produces literal newlines in the -m value. The hook's `sed -nE` processes line-by-line and couldn't match across lines — result: the hook was silently inert on its own introducing commit. The "dogfood success" in the PR me2resh#16 body was a false positive. Fix: `COMMAND_FLAT=$(echo "$COMMAND" | tr '\n' ' ')` before all sed extraction. Multi-line -m values now parse as a single logical line. Scoped only to parsing — extracted MSG preserves original content. Single-line paths unregressed (zero newlines → tr is a no-op). ## Smoke tests (post both fixes) 1. settings.json jq parse ✓ 2. validate-pr-create allows real me2resh#11 / me2resh#14 in title ✓ 3. validate-pr-create blocks fake #999999 in title (exit 2) ✓ 4. verify-commit-refs allows single-line real ref ✓ 5. verify-commit-refs blocks single-line fake ref ✓ 6. verify-commit-refs skips interactive commits ✓ 7. verify-commit-refs allows no-ref messages silently ✓ 8. verify-commit-refs blocks multi-line HEREDOC with fake ref ✓ 9. verify-commit-refs allows multi-line HEREDOC with real ref ✓ ## Why the rule comes first, hooks are backstops Hooks can only see tool calls, not prose output. A fabricated `#N` in a chat message is invisible to any hook. The vocabulary rule is the primary defense; the hooks catch the downstream symptom at the moment fabrication becomes durable (PR title, commit message). Linting Claude's prose output was considered and rejected — it depends on Claude remembering to check itself, which is exactly the failure mode this ticket exists to prevent. ## Review history Rex reviewed twice. First pass: approved lean with 3 nice-to-haves, one of which (multi-line gap) turned out to be must-fix on closer inspection. Fix commit addressed it. Rex re-reviewed the fix commit and approved cleanly. CEO approved per-PR explicitly (naming PR me2resh#16) before this merge, then gave a separate explicit merge instruction in the same message. Closes me2resh#14 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…d-CI, commit-format Closes four previously-unenforced MUST rules from the apexstack rule files. Ships them as hooks with narrow default path lists and project-config overrides. Design decisions captured in AgDR-0001, the first AgDR in the apexstack repo. This is the third rule-enforcement PR in a single session, after me2resh#12 (explicit merge approval) and me2resh#16 (ticket vocabulary + verify-issue- exists). Together, me2resh#11 / me2resh#14 / me2resh#13 convert 10+ prose MUST rules into mechanical hooks. ## New hooks - require-agdr-for-arch-changes.sh — PreToolUse on git commit. When the staged diff touches architecture files (infrastructure/, *.tf, Dockerfile*, docker-compose*, .github/workflows/), requires an AgDR reference in the commit message or a new AgDR file staged alongside. Closes the prose-only claim in agdr-decisions.md. - require-design-review-for-ui.sh — PreToolUse on the merge step. When the PR diff touches UI files (.tsx, .jsx, .vue, .svelte, .css, .scss, .sass, .less, design-tokens), requires a design approval marker at .claude/session/reviews/<pr>-design.approved. Regex is .tsx$ / .jsx$ EXACTLY — NOT .tsx? — so backend .ts files don't false-positive. Closes the prose-only gate in pr-quality.md and code-review.md. - block-merge-on-red-ci.sh — PreToolUse on the merge step. Runs the pr-checks lookup and blocks on any failing/cancelled/timed-out/ pending/in-progress check. Allows the "no checks reported" case with a NOTE (legitimate state for repos without CI). Closes the prose-only "No Red CI Before Merge" rule in pr-quality.md. - validate-commit-format.sh — PreToolUse on git commit. Validates the subject against ^(feat|fix|refactor|test|docs|chore|style|perf|build |ci|revert)(\(scope\))?: text. Matches the PR-title type list in git-conventions.md. Multi-line -m HEREDOC safe. ## AgDR-0001 (first AgDR in apexstack) docs/agdr/AgDR-0001-rule-mechanization-hooks.md records: - Options considered (broad defaults rejected, narrow + config chosen, one-PR-per-hook rejected) - Rules that explicitly stay advisory (>80% coverage, no bare any, one-ticket-at-a-time, role trigger patterns, /decide triggers) - Threshold decisions for each of the four hooks - Consequences and follow-up tickets Sets the pattern for all future apexstack AgDRs. ## Smoke tests (all passing; two bugs found mid-test and fixed) 1. settings.json jq parse ................................ valid 2. validate-commit-format: - feat: ok / feat(scope): ok / multi-line HEREDOC ok .. exit=0 - "added thing" (no type) ............................. exit=2 3. require-agdr-for-arch-changes: - no staged / non-arch file ........................... exit=0 - Dockerfile + no AgDR ................................ exit=2 - Dockerfile + "per AgDR-0042" in message ............. exit=0 4. block-merge-on-red-ci: - "no checks reported" case ........................... exit=0 (NOTE) - all green CI ........................................ exit=0 5. require-design-review-for-ui: - regex .tsx matches / .ts does NOT match ............. verified - PR with no UI changes ............................... exit=0 Two bugs were caught mid-smoke-test and fixed pre-commit: - block-merge-on-red-ci.sh assumed "no checks configured" returns exit 8 but the pr-checks tool returns non-zero with "no checks reported" text. Fixed to pattern-match the text. - require-design-review-for-ui.sh regex was .tsx? / .jsx? which matched plain .ts / .js (backend code). Fixed to .tsx / .jsx exactly. ## Review history Rex approved cleanly on the first pass (commit d7c9641). Unlike the previous two reviews where nice-to-haves turned out to be must-fix on closer inspection, this time Rex tested the key regexes live against filename fixtures and verified the severity calibration honestly. Four nice-to-haves from the review, deferred to a follow-up ticket: - ^Dockerfile misses monorepo paths (backend/Dockerfile). Fix: (^|/)Dockerfile. Same for ^docker-compose. Material for monorepo forks but mitigated by .architecture_paths project-config override. - infrastructure/ unanchored — mild false-positive on paths like docs/infrastructure/. Fix: (^|/)infrastructure/. - Conventional Commits breaking marker (feat!:) is rejected by both this hook and the existing PR-title regex. Consistent, not a bug. - Dead code (FAILED_COUNT, PENDING_COUNT) in block-merge-on-red-ci.sh. ## Deferred to follow-up tickets - docs/rule-audit.md — full audit table of every MUST with mechanization status. Pure docs, small scope. - validate-branch-name.sh / validate-pr-create.sh warning→blocker upgrade. Breaking change, deserves its own ticket. - commit_types project-config override. - /approve-design skill (analogous to /approve-merge) for writing the design-review marker. - The four nice-to-haves from this review (bundled as one small ticket). ## Dogfooding All four new pre-commit hooks fired on this PR's own commit and allowed it cleanly. The commit-format and AgDR-arch hooks became self-aware for the first time — and correctly. Closes me2resh#13 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…leanup Addresses three of the four nice-to-haves Rex flagged on PR me2resh#17. The fourth (feat!: Conventional Commits breaking marker) is tracked separately as me2resh#23 because it needs a design decision. ## Changes - require-agdr-for-arch-changes.sh: monorepo-safe anchoring. The original ^Dockerfile / ^docker-compose.* only matched root-level files and silently skipped backend/Dockerfile, web/docker-compose.yml, services/api/Dockerfile.prod — the common real-world layout. Updated to (^|/) prefix anchors. Also dropped two ambiguous directory patterns: - (^|/)infrastructure/ — matched docs/infrastructure/notes.md and src/types/infrastructure/foo.ts as false positives - (^|/)terraform/ — same ambiguity, redundant with \.tf$ Terraform files are caught unambiguously via \.tf$ / \.tfvars$ at any depth. CDK / Pulumi projects that use plain .ts/.py files in an infrastructure/ directory need to override via .architecture_paths in project-config.json. - block-merge-on-red-ci.sh: deleted unused FAILED_COUNT and PENDING_COUNT variables. - .claude/hooks/README.md: updated the architecture-paths section with the new regex list and an explicit CDK known-gap callout. - docs/agdr/AgDR-0001-rule-mechanization-hooks.md: new "Post-ship amendments" section at the bottom with a dated changelog entry. The original decision block is untouched — sets a pattern for threshold refinement without rewriting historical records. ## Smoke tests 21 path fixtures tested against the new regex list, all passing. 13 should-match cases (including monorepo layouts at various depths) and 8 should-NOT-match cases (including the previously false-positive docs/infrastructure/ and docs/terraform-primer.md). ## Review Rex tested the patterns against 26 fixtures (5 beyond my 21) and verdict was approved, clean. Two surfaced nice-to-haves: - README known-gap callout could name Helm/K8s/CFN/SAM, not just CDK. Deferred to the SAM-coverage follow-up ticket. - Dockerfile permissiveness is intentional — catches .prod/.dev variants correctly. Material finding (not blocking, follow-up): SAM template.yaml is NOT caught by the defaults. FlatMate specifically is SAM-heavy and would benefit from a project-config override OR a default addition for (^|/)serverless\.ya?ml$ (the word "serverless" has no library-code collision, unlike "infrastructure"). Tracked as a new follow-up ticket to be opened post-merge. Rex also offered an amendment rule-of-thumb worth codifying: changelog append for regex/threshold refinement and scope additions, new AgDR for decision reversals and deprecations. To be folded into me2resh#19 (the full rule-audit doc) or a new tiny docs/agdr/README.md ticket. Closes me2resh#18 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ApexStack now dogfoods its own CI. Four GitHub Actions workflows added so PRs to the framework repo get the same quality gates it ships to adopters via golden-paths/pipelines/. - pr-title-check.yml — copied verbatim from golden-paths. Enforces ticket ID in PR titles. - markdown-lint.yml — markdownlint-cli2 on **/*.md with relaxed config (.markdownlint.json disables MD013/MD033/MD041 etc. because existing docs have bare URLs, long lines, inline HTML). - shellcheck.yml — shellcheck on .claude/hooks/*.sh at warning severity. - link-check.yml — lychee on site/index.html + **/*.md, with weekly cron for link-rot detection. - .markdownlint.json — relaxed ruleset. Real structural problems still caught (duplicate headings, broken fences, malformed lists). Optional html-validate.yml skipped — site/index.html is 2000-line marketing HTML that would need non-trivial config. Rex approved. No must-fix. Three nice-to-haves deferred: - ShellCheck will flag pre-existing SC2059 in validate-pr-create.sh - markdownlint-cli2-action could be bumped from v16 to v23 - pr-title-check validates ticket-ID presence only (stricter check lives in the local hook) Refs #2 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Full rule-audit table at docs/rule-audit.md. Every MUST / NEVER / HARD-STOP rule across CLAUDE.md, 8 .claude/rules/*.md files, and 3 workflows/*.md files, each mapped to its enforcement mechanism (hook, agent, or prose) and classified as mechanized / partial / advisory / deferred. 73 rows across 9 sections: - 26 mechanized (enforced by a hook) - 6 partial (some sub-parts hooked, others prose) - 36 advisory (self-discipline, not mechanizable in shell harness) - 5 deferred (mechanizable, tracked as open tickets me2resh#15-me2resh#25) Also adds one row to CLAUDE.md Quick Reference pointing to the audit. Includes a "How to use this doc" header with the changelog-vs-new-AgDR rule of thumb: refined threshold → changelog append; reversed decision → new AgDR with Supersedes reference. Rex approved. No must-fix. One nice-to-have: two DDD prescriptions from code-standards.md lack MUST language and were omitted; adding them as advisory rows would be exhaustive-completist. Refs me2resh#19 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…from warning to blocker Upgrades validate-branch-name.sh and validate-pr-create.sh from warning (exit 0) to blocker (exit 2). Breaking change — users who pushed with non-conforming branch names or created PRs with malformed titles will now be blocked. The rule-mechanization audit (me2resh#13, AgDR-0001) found that warning-only hooks are "prose in disguise" — the harness runs them but the output is silently ignorable. This was confirmed in the first real test run. - validate-branch-name.sh: rejects non-conforming branches at push time with a rename suggestion - validate-pr-create.sh: rejects malformed titles, missing glossary, and missing branch ticket IDs at PR creation time. The issue-existence check from me2resh#14 was already a blocker — unchanged. - CLAUDE.md: migration note in Quality Rules - hooks/README.md: table updated "Warns" → "Blocks" - AgDR-0002: decision record comparing upgrade / keep-warnings / configurable-severity, chosen: upgrade both Closes me2resh#20 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ommit-format
validate-commit-format.sh now reads a commit_types field from
.claude/project-config.json before falling back to the hardcoded
default list. When set, ONLY those types are accepted — the override
replaces the defaults entirely.
Example: {"commit_types": ["wip", "feat", "fix"]} accepts wip: and
rejects refactor:. Remove the field to restore the default 11-type
list (feat|fix|refactor|test|docs|chore|style|perf|build|ci|revert).
- validate-commit-format.sh: reads .commit_types via jq, joins with
|, uses as the regex type group. Falls back to defaults when no
override is present.
- hooks/README.md: documents the override with an example
- skills/onboard/SKILL.md: optional follow-up in Q3 about
non-standard commit types
Closes me2resh#22
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Both validate-commit-format.sh and validate-pr-create.sh now accept the ! breaking-change marker per Conventional Commits 1.0: feat!: / feat(scope)!: / feat(#N)!: Minimal regex change: !? (optional) before the colon. Also updates git-conventions.md to document the marker with examples. No BREAKING CHANGE: footer enforcement — the ! alone is sufficient per spec. Closes me2resh#23 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds the design-review equivalent of /approve-merge. Writes .claude/session/reviews/<pr>-design.approved so the design-review merge gate lets UI PRs through. Same pattern: verify PR state, verify Rex marker at HEAD, write marker at repo root, confirm, stop. Does NOT merge. Key distinction: mockup approval (design phase) != implementation- review approval (PR phase). Anti-pattern section makes this explicit with a wrong/right example. UI PRs now need three markers: rex, design, ceo. Closes me2resh#21 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…pdate CLAUDE.md New /setup skill for first-run bootstrap — "describe your stack, accept defaults, done in 3 exchanges." onboarding-check.sh now checks onboarding.yaml for placeholder values (committed, persists across clones) instead of a gitignored session marker. /onboard deprecated with a redirect to /setup (framework) and /handover (project). CLAUDE.md skills table updated: 13 → 17 skills, 14 hooks. Closes me2resh#32 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…pdate New /launch-check skill — 8-dimension production readiness audit (security, accessibility, compliance, analytics, SEO, performance, monitoring, docs) with go/conditional-go/no-go verdict. Designed for milestone boundaries, not per-PR. Output is one scannable table with one row per dimension, a verdict, and a blocking-items checklist. Also updates CLAUDE.md (skills 17→18, hooks count corrected to 15) and site/index.html (hooks 6→15, skills 13→19, hooks+rules layer description rewritten to cover the full enforcement layer). Closes me2resh#34 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…aunch-check 8 expert audit skills, each using an industry-standard framework: /threat-model (STRIDE), /accessibility-audit (WCAG 2.1 AA), /compliance-check (GDPR), /analytics-audit (event taxonomy), /seo-audit (Google best practices), /performance-audit (CWV), /monitoring-audit (observability maturity), /docs-audit (Diataxis). /launch-check is the 30-second overview. When a dimension shows WARN or FAIL, the user runs the specific expert skill for a prioritized, actionable deep dive. /launch-check updated with a "Deep-dive companions" table. CLAUDE.md: skills 18 → 27. Site: skill count updated to 27. Closes me2resh#36 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CHANGELOG.md with entries for v0.1.0 (2026-04-09) and v0.2.0 (2026-04-12). Site hero updated with version badge (v0.2.0) and changelog link. Closes me2resh#38. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Run markdownlint-cli2 --fix on all .md files (MD022, MD032 blank-line violations) - Add .lycheeignore to exclude private portfolio repo links from unauthenticated checks Closes me2resh#42 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix(me2resh#42): CI — markdown lint auto-fix and lycheeignore for private repos
- settings.json: all 14 hook commands use inline resolver that walks up from $PWD to find onboarding.yaml (the ops root marker), then exec the hook from the resolved absolute path - validate-pr-create.sh: parse --repo flag for cross-repo PR creation - block-merge-on-red-ci.sh: pass --repo to gh pr checks/view - block-unreviewed-merge.sh: pass --repo to gh pr view fallback - require-design-review-for-ui.sh: pass --repo to gh pr view/diff Previously all hooks failed with "No such file or directory" when cwd was inside workspace/<project>/ because the relative .claude/hooks/ path only resolved from the ops repo root. Closes me2resh#45 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ulti-repo-support feat(me2resh#45): hooks resolve ops root from any workspace directory
- /feature: guided feature request with user story, ACs, design notes, priority - /bug: guided bug report with Given/When/Then, repro steps, severity - /task: guided technical task with driver, scope, ACs, risks - Each skill asks one question at a time and confirms before creating - Auto-labels: enhancement+priority, bug+severity, chore/refactor/test/ci+priority - Advisory hook on gh issue create suggests using the skills instead - Updated CLAUDE.md skill count (27 → 30) and skill table Closes me2resh#49 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…2resh#95) Two additions to the landing so apexyard has a human face and a discoverable trail back to the creator's other work. 1. Hero attribution line immediately under the tagline: "Built by me2resh" where "me2resh" links to me2resh.com. Small uppercase monospace (12px, --ink-faint), accent-coloured on hover. Tagline margin tightened from 1rem to 0.4rem so the new line sits visually joined to the tagline rather than floating. 2. Footer refresh. Left span swaps the generic "apexyard v1.0 · MIT · plain markdown · zero dependencies" brand line for the attribution: "Built by me2resh · MIT · plain markdown · zero dependencies" (drops v1.0 - already shown in the hero eyebrow + version badge). Right span adds twitter + blog to the existing github link: "github · twitter · blog". Removed the issues + prs links because they're duplicated in the "final" CTA block just above the footer and the social-style footer reads cleaner without them. Footer link styling now has an explicit 1px underline so anchors look clickable; existing hover-to-accent behaviour kept. role contentinfo landmark added for a11y. No CSP change needed - me2resh.com, github.com, twitter.com are navigation targets not resource fetches, outside CSP's remit. All external links carry target="_blank" rel="noopener". Closes me2resh#94 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Three count drifts Rex flagged during PR me2resh#97 review, all single-token edits: - Line 172: "17 shell scripts" -> 18 (ls .claude/hooks/*.sh excluding _lib-*.sh returns 18 real hook files; _lib-extract-pr.sh is a shared library that several hooks source, not a hook itself). - Line 173: "9 modular rule files" -> 8 (ls .claude/rules/*.md returns 8; the parenthetical already names the correct 8 categories, only the number was stale). - Line 249: "Skills (13 slash commands)" -> 33 (line 175 already said 33, line 249 was a stale reference in the Quick Reference table). README.md was already corrected in me2resh#89. This closes the analogous drift in CLAUDE.md before the launch post ships with a direct link to this repo. Closes me2resh#98 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(me2resh#100): add SDLC-loop manifesto callout between hero and quickstart Real-world tester feedback said the "every PR ties to a ticket..." line needs more surface area. Landing now carries a dedicated full-width manifesto block between the hero section and the /quickstart section. Styling: - Brutalist aesthetic matches the rest of the page: cream paper-3 background (slight tonal shift from paper-2 sections), sharp corners, top + bottom ink rules for visual separation, no gradients or shadows. - Text at 1rem-1.25rem scale clamped by viewport, line-height 1.7, max 60ch line length so the three clauses land cleanly on their own lines on desktop and wrap gracefully on mobile. - JetBrains Mono inherited from body, so no font-family override. - Three clauses kept as separate visual lines via <br>, reinforcing the "every X" rhythm. Placement chosen deliberately - between the hero and the quickstart so a first-time visitor hits the manifesto immediately after the install-row and shell-demo, before being invited to run commands. aria-label on the section for accessibility. Closes me2resh#100 * style(me2resh#100): drop manifesto max-width cap - fix awkward 3-line wrap CEO spotted that the third clause ("Code review validates against those criteria, and QA verifies...") was wrapping into 3 lines on desktop, breaking at "QA" and "closes" mid-idea. Root cause: the 60ch max-width was narrower than the clause needs, forcing ugly wraps. Fix: remove max-width from .manifesto__text. Container's max-width (.manifesto__inner at --max-w) already caps line length sensibly. Also tightened the font-size clamp a fraction so the text doesn't feel oversized at full width. Each clause now sits on one line at desktop width and wraps naturally on mobile without the awkward mid-word break. --------- Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
…sh#103) * feat(me2resh#102): tag-based upstream drift detection (v1.1.0) Stops the SessionStart drift banner from firing on every single commit to me2resh/apexyard:main. New behaviour: only a new upstream tag fires the banner. Small commits (README typos, CI tweaks, docs-only PRs) stay silent so fork owners don't train themselves to tune out the banner. Why: the old behaviour scaled poorly for a framework repo. Every push to upstream main nagged every downstream fork with "N commits behind, run /update". After a while that becomes background noise and people stop acting on it. Tag-based drift makes the signal rare and actionable again - "v1.1.0 available" means something. Changes: - .claude/hooks/check-upstream-drift.sh - Fetches --tags alongside the existing main fetch. - Primary signal: latest upstream tag (semver-sorted, --merged upstream/main) vs latest local tag (--merged main). Different -> banner names the release. - Fallback: if upstream has never been tagged, keeps the old commit-count behaviour so early-stage forks still see a banner. - Same cache TTL (10 min), same silent-on-failure paths, same DEFAULT_BRANCH detection. Zero behavioural change for forks that are up to date. - .claude/skills/update/SKILL.md § Preview - Adds tag-based signal alongside the existing ahead/behind counts. - New scenario: "on latest release but main has unreleased commits" - defaults the sync prompt to NO since it's typically noise. - New scenario: "new release available" - surfaces the tag name and defaults the prompt to YES. - Keeps existing "ahead and behind" preview for diverged forks. - CHANGELOG.md - New 1.1.0 entry documenting the feature, the why, what changed, and migration notes. Minor bump is correct per semver - it's a new feature, backward-compatible, no breaking changes. Manual testing plan (static analysis + intent): - bash -n passes on the hook. - Fallback path fires only when `git tag --merged upstream/main` is empty. Existing apexyard HAS tags (v0.1.0, v0.2.0, v0.3.0, v1.0.0), so for this repo the primary path is always taken. Fallback is there for fresh OSS projects that adopt the stack before tagging. - New apexyard tag v1.1.0 will ship as part of this change - I'll cut it after this PR merges so the v1.0.0 -> v1.1.0 transition is the first live test of the new banner on every existing fork. Closes me2resh#102 * chore(me2resh#102): bump landing version badges from v1.0 to v1.1 CEO flagged that the site still showed v1.0 while the PR bumps the framework version to 1.1.0. Three references updated: - Titlebar path: "main · v1.0" -> "main · v1.1" - Hero eyebrow pill: "apexyard v1.0" -> "apexyard v1.1" - Version badge link: v1.0.0 -> v1.1.0 (also points at the new release tag URL that gh release will create post-merge) No other copy changed. No structural edits. * fix(me2resh#102): address Rex blockers - strict-newer semver + AgDR + polish Rex review flagged two blocking issues on aa5f26f: 1. Local-tag false-positive. If a fork owner tagged their own main past upstream (e.g. private v2.0.0-acme while upstream is still v1.0.0), the original `!=` check fired a misleading banner for the older upstream tag. Fix: require upstream to be strictly newer by semver. Implementation uses `sort -V` (version-aware sort) and picks the last line as the newer tag. Banner fires only when NEWER == UPSTREAM_TAG and UPSTREAM_TAG != LOCAL_TAG. Also tightened the first-run path: if LOCAL_TAG is empty (fork has never merged any tag), the banner fires naming the upstream tag. Otherwise requires strict-newer. 2. Missing AgDR for a tag-vs-commit-vs-config design decision. The repo's own rules (.claude/rules/agdr-decisions.md) say an AgDR is required for "X vs Y vs Z" implementation choices. Added AgDR-0005-tag-based-upstream-drift.md with the three options, the weighted decision dimensions, and the consequences (including the "upstream must tag hotfixes" accepted trade-off). Non-blocking Rex suggestions also addressed: - Added `--prune-tags` to the fetch so locally-cached copies of retracted upstream tags are removed, preventing stale-tag false positives on top of the strict-newer check. - CHANGELOG entry gains a bullet on the cache interaction - existing installs may have a pre-1.1.0 `last-upstream-fetch` file and the first v1.1.0 session may wait up to the 10-min TTL before the new --tags fetch runs. Workaround: rm the cache file. - SKILL.md "ahead and behind" preview now documents that the merge prompt default branches on tag drift - [Y/n] when a new release is available, [y/N] when only main has drifted (likely noise). bash -n passes on the hook. * fix(me2resh#102): escape pipe inside backticks in AgDR-0005 table cell markdownlint MD056 flagged the config-flag option cell as having 4 columns instead of 3. Root cause: the literal `|` inside `upstream_sync_mode: tag | main` got treated as a column separator even though it was inside code backticks. Escaped to `\|` so the table parses as 3 columns. --------- Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#109): project-configurable ticket / branch / commit / PR schema (#118)
* chore(#109): project-configurable ticket / branch / commit / PR schema
Lift the prefix / type whitelists hardcoded across skills, hooks, and
CI into a versioned JSON config read through a shared shell library.
Shipped defaults at .claude/project-config.defaults.json; per-fork
overrides at the optional .claude/project-config.json; one reader
(_lib-read-config.sh) that every consumer now uses.
Added:
- .claude/project-config.defaults.json (v1 schema)
- .claude/hooks/_lib-read-config.sh (shared reader)
- docs/project-config.md (schema reference + extension guide)
- docs/agdr/AgDR-0006-project-configurable-ticket-schema.md
Migrated (still pass with no config present via last-resort fallback):
- validate-branch-name.sh → .branch.type_whitelist
- validate-commit-format.sh → .commit.type_whitelist (legacy
`commit_types` top-level key honoured as backward-compat fallback)
- validate-pr-create.sh → .pr.title_type_whitelist
- /feature, /task, /bug skills reference the config in their Rules
sections; none hardcodes the list any more
Unlocks subsequent config-readers for #107 / #110 / #111 / #112 /
#113 / #114 / #115 — each extends the schema under its own subtree
without further changes to the loader.
https://github.com/me2resh/apexyard/issues/109
* fix(#109): satisfy markdownlint MD032 and MD060 on new docs
Auto-fix MD032 (blank lines around lists) in AgDR-0006 and format
table-separator rows with surrounding spaces (MD060) in both new
doc files. Content unchanged; CI green.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#110): add block-private-refs-in-public-repos.sh hook (#119)
Adds a new PreToolUse hook that blocks gh issue/PR/comment creation and
gh api .../issues|/pulls calls targeting a public framework repo
(default: me2resh/apexyard + whatever `upstream` resolves to) when the
title or body references any registered private project from
apexyard.projects.yaml (by name, repo slug, owner/repo#N ticket ref, or
workspace path).
The hook is a sibling to check-secrets.sh — both scan outgoing content
for identifiers that should never leave the local environment. Skip
marker `<!-- private-refs: allow -->` in the body lets a deliberate
reference through with a visible warning.
Files touched:
- .claude/hooks/block-private-refs-in-public-repos.sh (new)
- .claude/hooks/tests/test_block_private_refs.sh (new)
- .claude/rules/leak-protection.md (new)
- .claude/settings.json (wire PreToolUse matchers for the 5 gh shapes)
- docs/rule-audit.md (append section 10 + bump counts)
Refs: https://github.com/me2resh/apexyard/issues/110
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#115): add warn-stale-review-markers.sh PostToolUse hook (#120)
Fires after `git push` to surface review markers that have gone stale
because new commits were pushed past an existing Rex / CEO / design
approval. The merge gate already catches this at `gh pr merge` time,
but only then -- this hook closes the gap by flagging it immediately
at push-time so the author isn't surprised at merge.
- `.claude/hooks/warn-stale-review-markers.sh`
- PostToolUse, non-blocking (PostToolUse exit 2 would push noise
into the conversation; this hook is purely informational).
- Resolves the PR HEAD via `gh pr view --json headRefOid` -- same
source-of-truth as the merge-gate hooks post-apexyard#47 / #55.
Falls back to local HEAD with a visible WARN when gh is offline.
- Silent on: no PR for branch, no markers, fresh markers,
failed push (detected via `rejected` / `failed to push` /
`fatal:` / `error:` markers in tool_response.stderr).
- Modes: `warn` (default) prints one stderr line per stale marker;
`delete` opts in to auto-removal via
`.claude/project-config.json` -> `review_markers.on_stale`.
TODO(apexyard#109): switch to the shared project-config reader
once it lands.
- `.claude/settings.json`
- Wires the hook on PostToolUse / Bash / `git push *`.
- `docs/rule-audit.md`
- Adds a row under section 3 (Code review & PR quality) and
bumps the mechanized count 26 -> 27 / total 73 -> 74.
- `.claude/hooks/tests/test_warn_stale_review_markers.sh`
- 8 cases: no PR, no markers, fresh markers, stale rex / ceo /
design (warn), delete mode, failed push. All pass locally.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#111): upgrade pre-push-gate from advisory reminder to blocking check-runner (#121)
* chore(#111): upgrade pre-push-gate from reminder to blocking check-runner
Previously pre-push-gate.sh just printed a checklist of things to run
locally before pushing — it was advisory. The rule it enforces is a
HARD STOP per pr-workflow.md. That asymmetry meant agents routinely
pushed broken work and discovered it only when CI went red.
Replaces the reminder with a blocking runner that reads the list of
shell commands from project config (.pre_push.commands) and executes
them in sequence before a push is allowed through. First non-zero
exit blocks the push with exit 2 and prints the failing command plus
the last 20 lines of its output.
- Config key: .pre_push.commands[] — array of {name, run} objects.
Shipped default is an empty list (hook stays a no-op on repos that
haven't configured their checks yet, including the framework repo
itself until it wires its own CI).
- Emergency bypass: '<!-- pre-push: skip -->' in the HEAD commit
message. Grep-able on purpose so bypasses stay auditable.
- Fail-fast: once a command fails, the rest don't run. Parallel
execution is a follow-up polish.
- 7 test cases in .claude/hooks/tests/test_pre_push_gate.sh — all
pass on the shipped default + a minimal custom config.
Updates docs/rule-audit.md to flip "partial" → "yes" for the
"before git push" rule.
Integrates with the shared config reader landed in #109.
https://github.com/me2resh/apexyard/issues/111
* fix(#111): remove orphaned footnote reference from rule-audit
The previous advisory-mode footnote was superseded by pre-push-111
but its definition was accidentally kept, tripping markdownlint MD053
(unused reference definition). Drop it.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#107): add validate-issue-structure.sh PreToolUse hook (#122)
Mechanically enforces the ticket body schema when an agent files raw
`gh issue create` calls instead of going through the interactive
/feature, /task, /bug skills. Matches bracketed title prefix
([Feature] / [Chore] / [Bug] / [Docs] / etc.) against
`.ticket.required_sections` in project-config, and blocks (exit 2)
when any required section is missing or empty. Skip marker
`<!-- validate-issue-structure: skip -->` bypasses with a visible
stderr WARN for legitimate off-template tickets (epics, meta-threads).
Changes:
- .claude/hooks/validate-issue-structure.sh — the hook; reads schema
via the shared _lib-read-config.sh, with inlined defaults for bare
checkouts predating the config-schema rollout. Handles
--body / --body-file / -F path.
- .claude/project-config.defaults.json — extends .ticket with
required_sections (Feature/Chore/Refactor/Testing/CI/Docs/Bug) and
skip_marker; other .ticket fields untouched.
- .claude/settings.json — new PreToolUse matcher on Bash(gh issue
create *) alongside the existing suggest-ticket-template.sh and
block-private-refs-in-public-repos.sh hooks.
- .claude/hooks/tests/test_validate_issue_structure.sh — 15 cases
covering pass + fail paths per prefix, empty section detection,
skip marker, unknown prefix, non-gh invocation, --body-file path.
- docs/rule-audit.md — new section 11 row, mechanized count +1.
Upstream ticket: https://github.com/me2resh/apexyard/issues/107
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#112): add require-agdr-for-arch-pr.sh PreToolUse hook (#123)
Closes the asymmetry noted in .claude/rules/agdr-decisions.md: every
other HARD STOP in the ruleset (merge approval, ticket-first,
migration-first) is mechanically enforced, but the /decide HARD STOP
was prose-only. The commit-time hook require-agdr-for-arch-changes.sh
catches one architectural change at commit; this new PR-time hook
catches the cumulative diff so reviewers always have a pointer to the
decision record.
- New hook at .claude/hooks/require-agdr-for-arch-pr.sh
- Fires on Bash(gh pr create *)
- Parses --title/--body/--body-file/-F <path>
- Resolves base branch from --base, else upstream/dev, origin/dev,
upstream/main, origin/main, main, master (in that order)
- Computes `git diff <merge-base>..HEAD --name-only`
- Triggers on any changed file matching .agdr_trigger_paths[], OR any
dep-file addition (package.json via jq key-set diff; other
dep files via a commented +/- line-count heuristic — version
bumps match +/- counts and do not fire)
- Blocks (exit 2) with a helpful message naming the triggers and
pointing at /decide if the body has no `AgDR-\d+-[a-z0-9-]+`
reference
- Skip marker `<!-- agdr: not-applicable -->` bypasses with a
visible WARN on stderr
- Silent exit 0 on non-gh commands, empty diffs, unresolvable base
- Wired via .claude/settings.json PreToolUse Bash(gh pr create *)
- Adds two new top-level keys to .claude/project-config.defaults.json:
agdr_trigger_paths (shell globs — domain/, infrastructure/,
migrations/, *.tf, .github/workflows/, etc.)
agdr_trigger_dep_files (literal basenames — package.json,
pyproject.toml, Cargo.toml, go.mod, Gemfile)
Hook has inline fallback defaults kept in sync.
- Adds docs/rule-audit.md entry in the AgDR section; bumps mechanized
count 26 to 27 and total rows 73 to 74.
- Adds .claude/hooks/tests/test_require_agdr_for_arch_pr.sh (7 cases;
all green): path-triggered without AgDR (block), with AgDR (pass),
dep-file added (block), version-only bump (no fire), skip marker
(pass + warn), non-matching diff (pass), non-gh command (no-op).
Closes https://github.com/me2resh/apexyard/issues/112
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#113): require Testing section in PR body (config-driven) (#124)
Extends validate-pr-create.sh with a required-sections check that
replaces the hardcoded Glossary-only grep. The list of required H2
headings is project-configurable via `.pr.required_sections[]`.
Shipped default is ["Testing", "Glossary"], matching the canonical PR
description shape in workflows/code-review.md.
- Each entry must appear as `## <Name>` (case-insensitive).
- Empty sections are tolerated at this layer (the issue-structure hook
#107 does stricter empty-content checks for issue bodies; for PR
bodies, empty sections are left to the reviewer's judgement).
- Skip marker `<!-- pr-sections: skip -->` bypasses with a visible
stderr WARN — for trivial PRs (lint-only fixes, version bumps)
where the full template is overkill.
- Reads from project config via the shared _lib-read-config.sh (#109).
Inline fallback matches shipped defaults so bare checkouts predating
#109 keep working.
- 8 test cases cover: all-sections pass, each missing section,
missing-both (both errors printed), skip marker, case-insensitive
headings, H3 rejection.
https://github.com/me2resh/apexyard/issues/113
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#114): enforce single Closes-keyword per PR body (#125)
* chore(#114): enforce single Closes-keyword per PR body
Caps distinct auto-closing references (close/closes/closed, fix/fixes/
fixed, resolve/resolves/resolved + N or owner/repo+N) at one per PR
body. Closes the loophole where the title validator limited the title
to one ticket but multiple Closes lines in the body would still auto-
close all of them on merge.
- Scans stripped of fenced code blocks so closing keywords inside a
code sample do not count.
- Distinct counting: the same number referenced twice (e.g. via Fixes
and Closes) counts as one.
- Cross-repo refs (owner/repo+N) count normally.
- Opt-in escape hatch: pr.allow_multiple_closes=true in
project-config disables the check for teams that deliberately batch
rollbacks or dependency bumps.
- Per-PR bypass: a multi-close-approved HTML comment in the body
prints a visible stderr WARN and lets that PR through. Grep-able
trace so bypasses are auditable.
- 10 test cases cover: one close passes, no-keyword passes, two
distinct block, three mixed block, same-number-twice passes, code-
fence-ignored, skip marker, cross-ref without keyword, opt-in
config, cross-repo close.
Reads configuration via the shared _lib-read-config.sh (apexyard+109).
https://github.com/me2resh/apexyard/issues/114
* fix(#114): strip inline backticks and tilde fences from close-count scan
Rex caught a self-reflexive bug in the initial commit: documentation
mentioning closing keywords inside inline backticks (say a PR body
that explains the new hook with examples) counted as real closes, and
a skip marker inside inline backticks silently bypassed the check.
Future PRs that document the feature would trip the same trap.
Fix the code-region stripper to cover:
- Triple-backtick fences (already handled)
- Tilde fences (new)
- Inline-backtick spans (new)
Also run the skip-marker check against the stripped body, so a marker
used purely as documentation no longer activates a real bypass.
Three new test cases pin the behaviour:
- closing keywords in inline backticks are ignored
- skip marker inside inline backticks does NOT bypass
- tilde fences also get stripped
13/13 tests pass.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#108): add /tickets-batch skill for bulk-file flow (#127)
The fast happy path for filing 5–20 structured tickets in one intent
without dropping to raw `gh issue create` (non-conformant) or running
`/feature` 20 times serially (~100 turns of interview).
- .claude/skills/tickets-batch/SKILL.md — new skill spec. Asks
shared-context questions (priority, epic, area-labels, repo) ONCE
for the whole batch, then runs a ≤3-question micro-interview per
ticket (type, one-line purpose, optional clarification when the
inference is low-confidence). Confirms the full batch as a table,
then files each via specific `gh issue create` calls (never a
bulk JSON dump — the validator runs per-issue). Output conforms
to `.ticket.required_sections` by construction. Caps at 20
tickets per invocation.
- CLAUDE.md — added a row for /tickets-batch in the Available
Skills table; bumped the count references from 33 to 34.
Refs https://github.com/me2resh/apexyard/issues/108
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#117): add /fan-out skill + parallel-work rule doc (#128)
- Add `.claude/skills/fan-out/SKILL.md` — spawns N parallel Agent calls
in a single assistant message, with per-task agent type, worktree
isolation, and foreground/background mode. Caps at 5 concurrent
agents. Refuses fan-out when tasks share file write targets or have
sequential dependencies. Includes pre-spawn active-ticket safety
check and worktree merge-back flow that pauses on conflict.
- Add `.claude/rules/parallel-work.md` — trigger heuristic for when an
agent should proactively offer fan-out (>= 2 file-independent,
context-independent, individually substantial work items). Pairs
with the skill: rule says when, skill says how.
- Update `CLAUDE.md` — bump rules count to 9, skills count to 34, add
`/fan-out` row to the skills table.
Refs https://github.com/me2resh/apexyard/issues/117
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#116): adopt release-cut branch model (dev/main + tags) — framework only (#126)
* chore(#116): adopt release-cut branch model (dev/main + tags)
Formalises the dev/main split that already exists informally — dev is
the daily-work branch where every PR lands, main is release-only,
tagged with semver on each merge. Framework-only: managed projects
under apexyard governance stay trunk-based.
Added:
- AgDR-0007 — decision record (options table covers full git flow vs
trunk-only vs gitflow-lite; chose gitflow-lite)
- /release skill — diff dev against main, propose semver bump from
conventional commits, generate CHANGELOG, open release PR, tag
after merge
- docs/release-process.md — prose runbook for cutting a release
(manual fallback for the skill)
- .git.protected_branches in project-config.defaults.json
(main/master/dev/develop)
Modified:
- block-main-push.sh — now blocks direct pushes/commits to all
configured protected branches (was: hardcoded main/master). Reads
.git.protected_branches via the shared config reader (apexyard#109).
- CLAUDE.md — new section under Git Conventions explaining the
dev/main model + the framework-only scope. Skill table entry for
/release. Skills count bumped to 34.
- docs/multi-project.md — note that upstream/main is release-only
and the dev/main split is framework-only.
Non-consequences (per AgDR-0007):
- No release/* or hotfix/* branches. Hotfixes are normal patches
cut quickly. Revisit if multi-version maintenance becomes a need.
- No automatic on-merge issue closing for dev PRs. The release PR's
body aggregates all Closes references for the batch and triggers
auto-close en masse when it merges to main. Manual close in the
meantime.
- CI workflows trigger on pull_request regardless of base, so
dev-targeting PRs already get the full check matrix — no
workflow file edits needed.
https://github.com/me2resh/apexyard/issues/116
* fix(#116): satisfy markdownlint MD032 + MD060 in 116 docs
Auto-fix added blank lines around lists in AgDR-0007 (MD032) and
spaced the table separators in AgDR-0007 + multi-project.md (MD060).
Content unchanged.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* fix(#106): CHANGELOG fallback in drift hook for squash-merged forks (#129)
* fix(#106): CHANGELOG fallback in drift hook for squash-merged forks
The v1.1.0 tag-reachability check (`git tag --merged main`) misfires on
forks that sync via GitHub's default squash-merge: the squash collapses
the upstream-tag commit into a synthetic SHA, the tag stops being
reachable, and the banner keeps firing forever.
Discovered live on the first real-world `/update` flow: ops fork
squash-merged the v1.1.0 sync PR, banner kept saying "v1.1.0 available"
even though the fork was content-caught-up.
This commit adds a CHANGELOG-content fallback that fires only when the
primary tag check fails. If the fork's main has a heading
`## [X.Y.Z]` matching the upstream tag's version, treat the release as
absorbed and stay silent. Tolerant grep (matches the apexyard CHANGELOG
format from v1.1.0 onward, with leading-`v` stripping for tag→heading
conversion).
The merge-commit and rebase paths are unchanged — primary tag check
still works for them, and the fallback never fires when it shouldn't.
Test coverage (5 cases):
- squash-merge fork caught up to v1.1.0 → silent (the fix)
- merge-commit fork caught up to v1.1.0 → silent (regression check)
- fork stopped at v1.0.0 → banner fires
- fork has its own newer tag → silent
- squash-merge but no CHANGELOG on fork → banner fires (no false silence)
Records the strategy update in docs/agdr/AgDR-0008-…md (extends
AgDR-0005's tag-based-drift design).
https://github.com/me2resh/apexyard/issues/106
* fix(#106): satisfy markdownlint MD032/MD060 + shellcheck SC2164
Rex flagged two CI-blocking issues on the original 106 commit:
- AgDR-0008 had three bulleted sub-lists in the Consequences section
without surrounding blank lines (MD032). Added blanks and padded the
one tight-pipe table separator (MD060).
- The 106 test fixture had five subshell `cd "$fk"` calls without
`|| exit 1` (SC2164). Added the guard to all five.
5/5 tests still pass after the fix. No semantic change to the hook
or the test logic.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#130): add /validate-idea skill — lightweight pre-spec gate (#131)
A 10-minute, 5-question check that sits between /idea and /write-spec.
Designed for solo founders running ApexYard — not a heavyweight
methodology like event storming or Wardley mapping.
Five questions, asked one at a time:
1. Who is this specifically for?
2. What do they do today instead?
3. What's the smallest version that proves the value?
4. What would prove this is wrong? (kill criteria)
5. Build, buy, or rent?
Output: a one-page validation doc with a GREEN/YELLOW/RED verdict.
RED auto-updates the IDEA-NNN backlog row to WONTDO.
Integration:
/idea — adds an optional default-no "Validate now?" step after
capture (and after the optional GitHub Issue offer).
/handover — adds a conditional "this looks dormant, validate?"
step at the end of the integration plan, gated on the dormancy
heuristic (last commit > 90d AND zero open PRs AND no recent
issue activity). Healthy projects don't see the prompt.
CLAUDE.md skills count bumped to 35; new skills row added.
https://github.com/me2resh/apexyard/issues/130
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat: configurable voice prompts on assistant pause (AgDR-0009-voice-prompts-on-pause) (#135)
Stop hook that speaks the assistant's question aloud (Jarvis-style)
when it pauses for user input. Initial phase is macOS-only via `say`,
no voice input — user replies via keyboard.
Default OFF. Adopters opt in by overriding `voice_prompts.enabled` to
true in `.claude/project-config.json`.
Files:
- .claude/hooks/voice-prompt-on-pause.sh — Stop hook with config gate,
trigger heuristic (questions-only by default), markdown stripping,
sentence-boundary truncation, fire-and-forget say invocation
- .claude/hooks/tests/test_voice_prompt_on_pause.sh — 9 cases covering
disabled-default, enabled+question, enabled+statement, approved-pattern,
abc-menu, malformed-transcript, no-say-on-PATH, trigger-always,
markdown-stripping
- .claude/project-config.defaults.json — voice_prompts schema block
added (enabled, voice, max_chars, rate_wpm, trigger), default OFF
- .claude/settings.json — new Stop hook entry wired with the standard
ops-root resolver wrapper
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — design rationale,
options matrix (status quo / macOS say / cloud TTS / ML detection),
consequences, future phases
- docs/project-config.md — new "Voice prompts" section with override
examples and privacy notes
Test mode: hook respects VOICE_PROMPTS_SYNC=1 to run say synchronously
(test runners need this so assertions don't race against orphaned
background processes). Production invocations always run async.
Future phases (out of scope here, AgDR §"Future phases"):
- Phase 2: cross-platform TTS (Linux espeak, Windows SpeechSynthesizer)
- Phase 3: cloud TTS providers (OpenAI, ElevenLabs) — privacy AgDR-worthy
- Phase 4: voice input via Whisper-based STT
- Phase 5: per-message overrides
Refs: https://github.com/me2resh/apexyard/issues/134
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#141): add /debug skill — structured hypothesis-driven debugging (#142)
* feat(#141): add /debug skill — structured hypothesis-driven debugging
Adds a methodology skill that enforces five disciplines:
1. Capture the symptom precisely (exact URL, exact response, exact step)
2. Read the architecture before guessing (map every layer the request
touches, file by file)
3. Form a hypothesis ladder (3–5 candidates, each with an explicit
evidence test that confirms or refutes it)
4. Gather evidence first, fix second
5. Verify the fix against the original symptom evidence (re-run the
same `curl` / browser repro you used in step 4 — unit tests
verify code, not feature, correctness)
Stack appendices (Web, Desktop) carry stack-specific surface-evidence
requirements (step 1), architecture-surface maps (step 2), and
evidence-tests cookbooks (step 4). The methodology body stays portable
across stacks; appendices are where stack-specific knowledge accrues
over time.
Web appendix covers browser routing, framework configs (Next/Nuxt/Vite),
SPA-fallback layers, CDN, origin, the shared API client, backend
handlers, and auth providers. Desktop appendix covers Electron / Tauri
/ native-shell concerns: app entry points, IPC bridges, native modules,
auto-updater, sandbox / entitlements, code signing, crash reports.
Includes "When NOT to use" guidance so the methodology overhead doesn't
sandbag simple bugs (typos, off-by-ones, greenfield exploration).
Motivated by a real OAuth debug session in a managed project where
three sequential fixes chased adjacent symptoms because each was
hypothesis-then-fix without evidence in between. The skill is the
"never do that again" guardrail.
Closes #141
* fix(#141): scrub private project issue numbers from anti-pattern table
Rex review on PR #142 caught that line 155 of the skill's anti-pattern
table still named the originating PRs (#375, #377, #380) from the
private project where the methodology was first exercised. The PR body
and commit message were correctly abstracted earlier, but this in-file
reference slipped through — the leak-protection hook only scans gh
issue/pr writes, not staged file content, so mechanical enforcement
didn't catch it.
Replaced with "Three sequential PRs chasing the same symptom because
each was based on a different guess (no evidence test in between
cycles)" — same pedagogical value, zero attribution.
Refs #141
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(#143): document split-portfolio mode + add /setup privacy gate (#144)
Adopters on GitHub Free with any private project hit a silent privacy bug
following today's docs: forking apexyard makes a public fork that you cannot
later flip to private (GitHub policy), and committing `apexyard.projects.yaml`
+ `projects/<name>/` to the fork publishes private project names + handover
findings on a public GitHub repo.
This PR documents the supported workaround — split-portfolio mode — and
adds an upfront privacy gate to the /setup skill so new adopters never
hit the trip-wire silently.
docs/multi-project.md:
- New "Two setup modes — pick the one that matches your privacy needs"
section before TL;DR, with a side-by-side table and the explicit
trip-wire callout.
- Existing TL;DR retitled "TL;DR — single-fork mode (default)" with a
one-line pointer to the split-portfolio section.
- New "Split-portfolio mode — public framework + private portfolio"
section between the existing setup steps and the directory-layout
section. Includes:
- The two-repo layout (~/ops/apexyard public + ~/ops/portfolio private)
- 7-step setup walkthrough with copy-pasteable commands
- Daily workflow + upstream sync notes (both unchanged)
- Trade-offs (two repos to maintain, two clones per machine, one
upstream-sync conflict path on `projects/README.md`)
- "Migrating from single-fork to split-portfolio" recovery flow with
the explicit warning that GitHub Issue / PR edit history survives a
force-push and must be redacted separately
.claude/skills/setup/SKILL.md:
- New Step 2a: privacy gate — asks "are any projects private?" before
proposing the config. Branches on the answer:
- All public → single-fork mode
- GitHub Pro / Team / Enterprise → single-fork mode (private
forks of public repos
are supported on those
plans)
- Any private + GitHub Free → split-portfolio mode
- New Step 2b: walks through the split-portfolio setup interactively
(private repo create, sibling clone, gitignore + symlink) when the
privacy gate triggers.
- Detection: `test -L apexyard.projects.yaml` short-circuits Step 2b
for adopters already in split mode.
- Explicit "do NOT auto-migrate" rule for adopters already in single-fork
mode with private names already pushed — that path is destructive
(force-push history rewrite + redact issue/PR bodies + delete backup
branch) and warrants a deliberate, eyes-open run, not a /setup side
effect.
Out of scope for this PR (tracked separately on #143):
- `portfolio:` config block in `onboarding.yaml` schema
- Skill audit + refactor to honour configured `registry` / `projects_dir`
/ `ideas_backlog` paths instead of hardcoded fork-relative paths
- `/split-portfolio` migration helper skill that automates the recovery
flow currently documented manually
This is the docs-and-setup-question minimum-viable starter — the
framework code refactor is mechanical and lands as a follow-up.
Refs #143
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#145): portfolio config + self-healing + /split-portfolio helper (#147)
* feat(me2resh/apexyard#145): portfolio config block + self-healing + /split-portfolio helper
Closes the framework primitive deferred from me2resh/apexyard#144. Adds
first-class config-driven path resolution for the portfolio registry,
projects dir, and ideas backlog, with self-healing surfacing of broken
config at session start, plus a new /split-portfolio skill that automates
the destructive recovery flow.
Schema, helper, and hook:
- .claude/project-config.defaults.json: new portfolio: block
(registry, projects_dir, ideas_backlog) with defaults matching
today's single-fork layout
- .claude/hooks/_lib-portfolio-paths.sh: new sourceable helper exposing
portfolio_registry, portfolio_projects_dir, portfolio_ideas_backlog,
portfolio_validate, portfolio_clear_cache. Resolves relative paths
against the ops-fork root.
- .claude/hooks/check-portfolio-config.sh: new SessionStart hook —
silent on OK, one-line banner on broken config, never blocks session
- .claude/hooks/tests/test_portfolio_paths.sh: 13 cases covering
defaults, absolute/relative overrides, validate states, cache clear
Skill audit (18 SKILL.md files):
- Adds Path resolution callout pointing at the helper
- handover bash blocks now source helper and use $(portfolio_registry)
instead of literal apexyard.projects.yaml
- setup Step 2b now writes the portfolio: config block (recommended)
and validates via portfolio_validate before declaring success;
symlink approach kept as legacy fallback
New skill (.claude/skills/split-portfolio/SKILL.md):
- 10-step migration with explicit operator-confirmation gates at each
destructive step (force-push, body redaction, branch deletion)
- --verify mode: read-only state report (mode, paths, validate, drift)
- --dry-run mode: prints commands without executing
- Pre-flight refusals: already-private fork, paid GitHub plan, dirty
working tree, already-migrated state
- Step 9 writes the portfolio: config block (not symlinks) — symlink
fallback documented for adopters on older framework versions
- Step 9 surfaces the GitHub timeline-API survival caveat verbatim
- Idempotent re-runs: detects partial-migration state and resumes
Docs (docs/multi-project.md):
- Layout section describes both modes (config-block recommended,
symlink legacy) with self-healing notes
- Setup steps split into config-block mode and legacy symlink mode
- Migration section now points at /split-portfolio skill; manual
recipe preserved as fallback
AgDR (docs/agdr/AgDR-0010-portfolio-config-and-self-healing.md):
- Full Y-statement, options, decision, consequences, future phases
- Schema decision rationale: project-config.json over onboarding.yaml
because runtime path resolution belongs in project-config
Closes me2resh/apexyard#145
Refs me2resh/apexyard#146 (delivered same PR; closed manually post-merge
per the single-Closes-keyword rule in validate-pr-create.sh)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(me2resh/apexyard#145): markdownlint MD031 — blank lines around fences
CI's markdownlint-cli2 (v0.34.0) flagged the JSON + bash fenced code
blocks I added in setup/SKILL.md Step 2b without surrounding blank
lines. Added the required blanks. No content change.
Refs me2resh/apexyard#147
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>
* docs(me2resh/apexyard#148): correct privacy-gate wording — adopter action, not framework auto-publish (#149)
The privacy-gate wording introduced in PR #144 (and unchanged in PR #147)
attributed the publication to the framework rather than the adopter:
"the standard fork-and-commit setup will silently publish your private
project names on a public GitHub repo"
That's factually wrong. ApexYard never pushes anything without explicit
operator approval — the publication only happens when the adopter
themselves runs git push. The "silently publish" framing read as if the
framework auto-publishes, which is misleading and undermines trust in
the rest of the framework's safety claims.
Two prose-only edits, no code, no behavior change:
- .claude/skills/setup/SKILL.md Step 2a — replaced "will silently
publish ..." with adopter-action language ("you might accidentally
publish ... a stray git push after registering them — I won't push
without your approval, but the risk is on the adopter once the data
is committed locally")
- docs/multi-project.md trip-wire callout — replaced "silently publish
their portfolio names the moment they push" with "risk accidentally
publishing their portfolio names with a stray push (the framework
itself never pushes without operator approval, but once the registry
is committed locally the next push exposes it)"
Verified: `grep -r "silently publish" .claude/skills/ docs/` returns no
hits.
Closes me2resh/apexyard#148
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#150): bootstrap-skill exemption + Bash-write coverage (#152)
Closes the legitimate-bypass case (me2resh/apexyard#150) and the
illegitimate-bypass case (me2resh/apexyard#151) together so the
ticket-first gate is coherent. Shipping either alone would leave a
window where the framework is internally inconsistent — see AgDR-0011
for the full rationale.
Bootstrap exemption (me2resh/apexyard#150):
- .claude/session/active-bootstrap marker, written by /setup,
/handover, /update, /split-portfolio on entry; cleared on exit
- SessionStart sweep (clear-bootstrap-marker.sh) for stale markers
from interrupted sessions
- require-active-ticket.sh reads the marker and exempts skills on
the configured ticket.bootstrap_skills list
- bootstrap_skills list lives in .claude/project-config.defaults.json
(extendable per fork via .claude/project-config.json)
Bash-write coverage (me2resh/apexyard#151):
- new _lib-detect-bash-write.sh — heuristic detector for output
redirection, tee, sed -i, awk -i inplace, python/node/ruby
embedded interpreters
- require-active-ticket.sh + require-migration-ticket.sh now fire
on Bash in addition to Edit|Write|MultiEdit
- design choice: false-negatives preferred over false-positives
(the matcher errs toward "let through" rather than block legit
read-only commands)
Tests: 32 unit cases on the lib + 12 integration cases on the hook,
including the exact me2resh/apexyard#151 bypass repro from the issue
body.
Closes me2resh/apexyard#150
Will manually close me2resh/apexyard#151 post-merge per the
single-Closes-per-PR rule (precedent: AgDR-0010 / PR #147).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#153): extend Bash-write matcher beyond first-version coverage (#155)
Closes me2resh/apexyard#153.
Extends `_lib-detect-bash-write.sh` (introduced for me2resh/apexyard#151
in PR #152) with the matcher families flagged by Rex's review of #152.
AgDR-0011 already frames the matcher as a living list extended on
observation; this commit just walks the list.
New matcher families:
- File-moving builtins: `cp`, `mv`, `rm`, `dd`, `install` (anchored at
command-start; `--help`/`--version` and `git rm`/`git mv` excluded)
- Archive / network writes: `tar -x` / `tar --extract`, `curl -o` /
`--output`, `wget -O` / `--output-document`
- Additional embedded interpreters: `perl -e`, `php -r` (keyword-gated
like python/node/ruby); `go run`, `deno run`/`deno script.ts`,
`bun run`/`bun script.ts` (categorical script runners)
- Python helpers: `pathlib.Path().touch()`, `shutil.copy*`,
`shutil.move`, `os.rename` added to the `python -c` and python
heredoc keyword list
- Heredoc variants for `ruby` and `node` (previously only python
heredoc was covered)
Extractor extensions:
- `cp` / `mv`: last positional arg
- `curl -o` / `--output`: file argument
- `wget -O` / `--output-document`: file argument
- `tar -x`, `go run`, `deno`, `bun`, `perl -e`, `php -r`: return empty
(caller applies gate categorically per AgDR-0011)
Test count rose from 32 to 86. Negative-class counterexamples cover
the trickiest false-positive surfaces: `tar -t` listing, `cp --help`,
`rm --version`, `git rm`, `curl` bare URL fetch, `wget` bare URL fetch,
`deno fmt`, `deno test`, `go build`. Existing
`test_require_active_ticket_bash.sh` regression suite still passes 12/12.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(#154): mock gh in test sandboxes to remove live-tracker dependency (#156)
- add _lib-mock-gh.sh helper that installs a fake `gh` on the sandbox PATH;
intercepts `gh issue view <N> ... --json ...` and returns synthetic
`{"number":N,"state":"OPEN"}` (overridable per-num via mock_gh_set_state)
- wire the shim into test_single_closes_per_pr.sh and
test_validate_pr_required_sections.sh so the validator's CLOSED-issue
refusal no longer breaks the suite when upstream issues are closed
- both files previously failed every case (0/13 and 0/8) because their PR
titles reference #114 / #113, which are now CLOSED upstream
- post-fix: 13/13 and 8/8; full suite remains green
Closes me2resh/apexyard#154
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#132): structured CEO marker + same-turn merge in /approve-merge (#158)
* feat(#132): structured CEO marker + same-turn merge in /approve-merge
Closes me2resh/apexyard#132 (drop the "stop before merge" rule) and
me2resh/apexyard#48 (harden CEO marker against self-approval bypass)
together. The two threads compose — see AgDR-0012 for the full
rationale.
Streamline (#132):
- /approve-merge now runs `gh pr merge --squash --delete-branch`
in the same turn as the marker write, by default
- --no-merge opt-out preserves the deferred-merge case
- The discrete approval moment is the SKILL INVOCATION, not a
follow-up "now do the merge" message
Harden (#48):
- CEO marker is now a structured key/value file with required fields:
sha=<HEAD>
approved_by=user
skill_version=2
Validated by block-unreviewed-merge.sh; bare-SHA legacy markers
rejected with a clear "stale format" error pointing at /approve-merge
- The model's bare `echo SHA > <pr>-ceo.approved` bypass is now
mechanically rejected. Forging the structured fields requires a
deliberate, visible rule violation rather than a one-line accident
- Optional audit fields (approved_at, approval_summary) capture the
"what did the user say when they approved" trail
- Rex marker stays bare-SHA — different threat model (automated
reviewer, not human authorization moment)
pr-workflow.md reframed: "the load-bearing rule is explicit per-PR
approval, not two user messages." The merge is a deterministic
consequence of the approval invocation.
Tests: 12 cases on the hardened hook covering the new format end-to-end
(valid v2, missing rex/ceo, bare-SHA legacy rejected, missing
approved_by, wrong approved_by, skill_version=1, sha mismatch,
non-merge no-op, gh-api shape gated). Full suite: 205/205 across 13
test files.
Will manually close me2resh/apexyard#48 post-merge per the
single-Closes-per-PR rule (precedent: PR #152 / AgDR-0011).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#132): redact private project reference from AgDR-0012
Abstracted two references to a registered private project that named
the project's owner/repo. The leak-protection hook caught one in the
PR body; this fixup removes the matching references from the
AgDR-0012 file content (which would otherwise have shipped the names
to me2resh/apexyard public repo via the merge).
The pre-existing reference at .claude/rules/pr-workflow.md:130
(documenting #47) is untouched — it predates this PR and is already
on the public repo's history.
Refs me2resh/apexyard#132.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#132): markdownlint blanks-around-fences + typo fix
Two small fixups against red CI / Rex feedback:
- AgDR-0012 line 63: fenced code block now has a blank line before it
(MD031). markdownlint-cli2 0.13.0 was rejecting the indented fence
inside the bullet because the fence's preceding line was the bullet
text (no blank).
- approve-merge SKILL.md line 172: typo `deferes` → `defers` (Rex flag
on PR #158).
Refs me2resh/apexyard#132.
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>
* chore(#157): remove voice-prompts feature + correct hook/skill counts (#161)
* chore(#157): remove voice-prompts-on-pause feature + correct hook/skill counts
Closes me2resh/apexyard#157 (sunset the voice-prompts feature) and
me2resh/apexyard#77 (hook count off-by-one in CHANGELOG / CLAUDE.md)
in one bundled PR. AgDR-0013 supersedes AgDR-0009 with the full
rationale; both AgDRs are preserved (decision records are append-only
history).
Removed (#157):
- .claude/hooks/voice-prompt-on-pause.sh
- .claude/hooks/tests/test_voice_prompt_on_pause.sh
- Stop matcher block in .claude/settings.json (became empty after
voice removal)
- voice_prompts block in .claude/project-config.defaults.json
- "## Voice prompts" section in docs/project-config.md
- voice_prompts mention in AgDR-0010 line 32 (replaced with
leak_protection / ticket as still-current example config blocks)
Preserved:
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — historical record;
new "Superseded by: AgDR-0013" header at the top
- AgDR-0010 line 115 reference to AgDR-0009 — still accurate as a
historical pattern reference
Counts corrected (#77):
- CHANGELOG.md v0.3.0 stats: "17 hooks" → "18 hooks" (historical
fix — at v0.3.0 there were actually 18 hooks)
- CLAUDE.md table line: "18 shell scripts" → "24 shell scripts"
(current count after this removal)
- CLAUDE.md table line: "35 slash commands" → "39 slash commands"
- CLAUDE.md "Available skills (34)" → "Available skills (39)"
- CLAUDE.md quick-reference "Skills (35 slash commands)" →
"(39 slash commands)"
Why bundled: #77's correct count depends on whether voice is still in
the framework. Shipping #77 before #157 would write a number that's
wrong by one again the moment #157 lands. Same shape as previous
bundles (PR #152 / AgDR-0011, PR #158 / AgDR-0012).
Why no adopter-facing changelog mention of the voice removal: the
feature never reached a tagged release on main. v1.1.0 didn't have
it; v1.2.0 won't have it. From the adopter's perspective there's
nothing to retire. AgDR-0013 captures the framework's internal record
for future contributors. See AgDR-0013 § "No adopter-facing changelog
mention".
Tests: full hook test suite green (196 cases across 12 files —
test_voice_prompt_on_pause.sh removed). No regressions.
Will manually close me2resh/apexyard#77 post-merge per the
single-Closes-per-PR rule (precedent: PRs #152, #158).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#157): unwire voice from settings + configs + docs + AgDR-0013
Continuation of d09d7b5 (the file deletions). Squash-merge will
collapse both into one PR commit. This commit captures:
- .claude/settings.json — Stop matcher block removed (was the only
hook in it; entire matcher gone)
- .claude/project-config.defaults.json — voice_prompts block + its
_comment removed
- docs/project-config.md — "## Voice prompts" section removed
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — "Superseded by"
header added at the top, content otherwise preserved as history
- docs/agdr/AgDR-0010-portfolio-config-and-self-healing.md — line 32
example reference swapped from voice_prompts to
leak_protection / ticket (still-current config blocks)
- docs/agdr/AgDR-0013-sunset-voice-prompts.md — new supersession AgDR
- CLAUDE.md — hook count 18 → 24, skill count 35 → 39 (three
occurrences each, all aligned to current reality)
- CHANGELOG.md v0.3.0 stats — "17 hooks" → "18 hooks" historical fix
(#77 acceptance criterion 1)
Refs me2resh/apexyard#157 + me2resh/apexyard#77.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#157): markdownlint MD028 + resolve stale AgDR-numbering ref
Three small fixups against Rex CHANGES-REQUESTED on PR #161:
- AgDR-0009 line 3: promote "Superseded by:" header out of a
blockquote. The original "I decided ..." canonical blockquote at
line 5 was being merged with the new supersession blockquote
(markdownlint MD028 — "no blanks inside blockquote").
- AgDR-0013 line 3: same shape — "Supersedes:" header now a plain
bold paragraph, canonical "I decided ..." blockquote untouched.
- approve-merge SKILL.md line ~187: stale conditional reference
"AgDR-0012 (or 0013 — depends on whether voice-removal lands
first)" — order is now resolved (12 = approve-merge bundle, 13 =
voice removal). Drop the parenthetical.
Refs me2resh/apexyard#157 + me2resh/apexyard#77.
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(#160): multi-tab terminal demo on the landing site (#162)
Landing-site `site/index.html` terminal demo previously played one
canonical flow ("one ticket, start to finish") on autoplay. With the
v1.2.0 skill surface expansion (4 new skills, 8+ new hooks), one flow
no longer represents the framework's breadth.
Adds tabs to the terminal chrome — four flows visitors can either let
auto-cycle or click directly:
1. one ticket — existing flow, unchanged content
2. /handover — adopt an external repo into the portfolio
3. /setup — first-run framework bootstrap on a fresh fork
4. /fan-out — spawn 3 parallel agents on independent tickets
Auto-advance: on completion of the active tab's script, the demo
pauses ~1.8s then advances to the next tab. Loops at the end. User
can interrupt by clicking any tab or hitting Replay.
Implementation:
- HTML chrome — single title span replaced with a tablist of 4 button
tabs. ARIA `role="tablist"` / `role="tab"` / `aria-selected` so
keyboard + screen-reader users get the same semantics as sighted
ones.
- CSS — new `.shell-demo__tabs` + `.shell-demo__tab` with an accent
underline on the active tab. Tabs scroll horizontally on narrow
viewports (mobile responsive).
- JS — refactored the existing IIFE from one `script` array to an
array of four. Added `setActiveTab()` for ARIA state, `play(idx)`
takes a tab index, end-of-script auto-advances to `(idx + 1) % N`
unless the user clicked away during the pause. Adds a new `cmd`
type alongside `you` for slash-command invocations (renders with
the same `>` prompt prefix). prefers-reduced-motion still bails
early and leaves the static seed visible.
- Static seed (the non-JS fallback) still shows tab 0's content, so
reduced-motion / no-JS visitors see the one-ticket flow as before.
Hero metrics also corrected to current reality (#77 / PR #161 covers
CLAUDE.md and CHANGELOG.md; this PR catches the same numbers in the
landing site):
Skills 32 → 39
Hooks 18 → 24
The tabs ship in v1.2.0 alongside the framework changes that make
the new flows worth showcasing.
Refs me2resh/apexyard#160 (release v1.2.0 + landing-site refresh).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#163): default the split-portfolio sibling repo name to <fork>-portfolio (#164)
Closes me2resh/apexyard#163.
The split-portfolio mode docs and skills previously suggested
`your-org/ops` as the default name for the private sibling repo, and
`portfolio/` as the local clone directory. Both too generic — adopters
running multiple ops setups end up with `your-org/ops` collisions, and
a bare `portfolio/` dir gives no signal about which framework it
belongs to when it sits next to other unrelated `portfolio/` dirs.
`<fork>-portfolio` is now the default — keeps the relationship to the
public fork explicit on disk and on GitHub. If the fork is named
`your-org/apexyard`, the portfolio defaults to
`your-org/apexyard-portfolio`. If the fork was renamed (e.g. `cos`),
the portfolio defaults to `cos-portfolio`. Adopters with custom names
keep working — the `portfolio:` config block resolves whatever path
they configured.
Files updated:
docs/multi-project.md
- Layout diagrams use `apexyard-portfolio/` as the sibling
- Setup walkthrough Step 2 + Step 3 use `your-org/apexyard-portfolio`
and explain the `<fork>-portfolio` pattern
- Config-block + symlink path examples updated to
`../apexyard-portfolio/...`
- Daily workflow + cross-machine clone commands updated
- The two existing `your-org/ops` references that remain are
fork-rename examples (lines 52, 64) — kept as-is, since renaming
the fork to `ops` is still valid (the portfolio would then
default to `ops-portfolio`)
.claude/skills/setup/SKILL.md
- Step 2b's "default suggestion" for the private repo name is now
`your-org/<fork>-portfolio`, computed dynamically from the
fork's repo name via `gh repo view --json name -q .name` so the
suggestion is correct even when the fork was renamed
- Clone command no longer needs a second arg — the repo name IS
the directory name
- Config-block paths updated to `../apexyard-portfolio/...`
.claude/skills/split-portfolio/SKILL.md
- Step 3's suggested-name template is now `<account>/<fork>-portfolio`
with the same dynamic-fork-name resolution
Mechanism unchanged. The `portfolio:` config block in
`.claude/project-config.json` still takes any path; this PR is purely
default-suggestion + example prose.
No tests required (skills are markdown instructions; no automated
coverage today).
Refs `apexyard.projects.yaml.example` uses "ops repo" as a generic
term meaning "the operational management fork" — not a name — kept
as-is.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#165): skills reference page on the landing site + changelog link (#167)
Closes me2resh/apexyard#165.
Adds a public, browseable index of every apexyard slash command
alongside a one-click changelog link from the homepage nav.
site/skills.html (new):
- Lists all 39 skills currently shipping in .claude/skills/
- Each entry: slash command, argument hint, description (taken
verbatim from the SKILL.md frontmatter so the page matches the
runtime exactly)
- 10 categories: Setup & onboarding, Daily ops, Tickets & ideas,
Specs & decisions, Code review & merge, Architecture & dev tools,
Production-readiness audits, Workflow primitives, Communications,
Deprecated
- Same brutalist-terminal design tokens as the homepage — JetBrains
Mono, paper-cream background, single warning-red accent, sharp
corners. Inlined CSS to keep the static-only no-build-step
convention; design vars duplicated rather than extracted to a
shared file (~18 vars; cheap to keep in sync).
- Mobile responsive — skill grid collapses to single-column under
720px; titlebar nav hides non-CTA items on narrow viewports.
- Reduced-motion friendly (no animation in the first place).
- Internal anchor TOC at the top so the page scans in seconds.
site/index.html (nav addition):
- Added two nav links to the titlebar between "what's in the box"
and the github CTA:
• skills → ./skills.html
• changelog → https://github.com/me2resh/apexyard/releases
- The changelog link points at the GitHub releases page (not the
raw CHANGELOG.md file) so it auto-resolves to the latest tagged
release on each visit. v1.2.0 lands and the link is already there.
No new dependencies, no build step, no JS for the skills page. The
existing site convention (one-html-file-per-route, inlined CSS,
optional progressive-enhancement JS) is preserved.
Refs me2resh/apexyard#160 (release v1.2.0 + landing-site refresh) —
this is the second site-side deliverable for that ticket; the release
tag itself follows once me2resh/apexyard#159 (testing) closes.
Follow-up worth a separate ticket: a small generator script that
walks .claude/skills/*/SKILL.md, parses YAML frontmatter, and emits
the skills.html sections automatically. Out of scope for v1.2.0 —
first version is hand-curated and will need maintenance until that
generator lands.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#168): accept release/vN.N.N branches + release(...) PR titles (#169)
Closes me2resh/apexyard#168.
The /release skill prescribed `release/vA.B.C` as the source-branch
name and `release: vA.B.C` as the PR title for the dev → main release
PR (per AgDR-0007). Both were rejected by the framework's own
validators:
validate-branch-name.sh required {type}/{TICKET-ID}-{description};
release/v1.2.0 has no ticket-id portion.
validate-pr-create.sh required type(SCOPE): form with `release` not
in pr.title_type_whitelist.
The contradiction surfaced cutting v1.2.0 — the first release under
the dev/main model.
Three small changes:
1. .claude/hooks/validate-branch-name.sh — added an early-out branch
that accepts ^release/vN.N.N(-rcN)?$ as a valid name. Narrow,
intentional exception for the framework's release-cut convention;
release branches don't carry a ticket-id because the release itself
IS the ticket.
2. .claude/project-config.defaults.json — added "release" to
pr.title_type_whitelist so a title like `release(#160): v1.2.0`
passes validate-pr-create.sh's existing regex unchanged.
3. .claude/skills/release/SKILL.md step 4 — corrected the prescribed
PR title to `release(#<release-ticket>): vA.B.C` so future /release
invocations produce a title that satisfies the validators by
construction.
Tested:
bash .claude/hooks/validate-branch-name.sh against:
release/v1.2.0 → 0 (allowed, release-special-case)
release/v1.2.0-rc1 → 0 (allowed, RC variant)
release/v9.9.9 → 0 (allowed)
release/foo → 2 (correctly blocked)
release/v1 → 2 (correctly blocked)
chore/GH-168-fix → 0 (allowed, standard pattern)
feature/GH-1-x → 0 (allowed, standard pattern)
Full hook test suite: 196/196 cases green across 12 test files.
Refs: surfaced 2026-05-04 cutting the first release under AgDR-0007.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#170): exempt release/vN.N.N from validate-pr-create's branch-id check (#171)
Closes me2resh/apexyard#170.
Completes the work started in #169 (closing #168). #169 added a
release-pattern early-out to validate-branch-name.sh so release/vN.N.N
branches pass the branch-name validator. But validate-pr-create.sh has
its own independent branch-id check at line 273 that #169 didn't
touch — and it still rejects release/v1.2.0 because that name doesn't
contain a ticket-id substring.
This is the same class of contradiction #168 fixed; the fix is the
same shape. Add the same release-pattern early-out to the branch-id
check in validate-pr-create.sh:
- if the branch matches ^release/vN.N.N(-rcN)?$ → exempt (release
branches don't carry ticket-ids; the release itself is the ticket)
- otherwise → require a ticket-id substring as before
#168's acceptance criterion 3 ("validate-pr-create.sh accepts a PR
title `release(#160): v1.2.0` against the `release/v1.2.0` branch")
was checked off based on the title regex alone but didn't catch the
secondary branch-id check living in the same file. Surfaced trying
to open the v1.2.0 release PR.
Tested:
bash .claude/hooks/validate-pr-create.sh against:
release/v1.2.0 → 0 (allowed, exempt)
release/v1.2.0-rc1 → 0 (allowed, RC variant)
chore/GH-1-fix → 0 (allowed, has ticket-id)
release/foo → 2 (correctly blocked)
chore/no-ticket → 2 (correctly blocked)
Refs me2resh/apexyard#168 (the parent bug) + #169 (the partial fix).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#160): release v1.2.0 — CHANGELOG entry
Adds the v1.2.0 section to CHANGELOG.md ahead of the dev → main
release PR. 25 commits since v1.1.0 (was 26 — one feat removed
post-supersession per AgDR-0013, intentionally unenumerated in the
adopter-facing changelog because the feature never reached a tagged
release).
Highlights:
- Bootstrap-skill exemption + Bash-write coverage (#150 + #151)
- /approve-merge structured marker + same-turn merge (#132 + #48)
- Portfolio config block + /split-portfolio helper (#143 + #145)
- Four new skills: /debug, /validate-idea, /tickets-batch, /fan-out
- Release-cut branch model (#116, AgDR-0007)
- Landing-site multi-tab terminal + skills page + changelog link
(#160 / #165)
Refs me2resh/apexyard#160 (release-and-site-refresh ticket).
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>
* chore(#109): project-configurable ticket / branch / commit / PR schema (#118)
* chore(#109): project-configurable ticket / branch / commit / PR schema
Lift the prefix / type whitelists hardcoded across skills, hooks, and
CI into a versioned JSON config read through a shared shell library.
Shipped defaults at .claude/project-config.defaults.json; per-fork
overrides at the optional .claude/project-config.json; one reader
(_lib-read-config.sh) that every consumer now uses.
Added:
- .claude/project-config.defaults.json (v1 schema)
- .claude/hooks/_lib-read-config.sh (shared reader)
- docs/project-config.md (schema reference + extension guide)
- docs/agdr/AgDR-0006-project-configurable-ticket-schema.md
Migrated (still pass with no config present via last-resort fallback):
- validate-branch-name.sh → .branch.type_whitelist
- validate-commit-format.sh → .commit.type_whitelist (legacy
`commit_types` top-level key honoured as backward-compat fallback)
- validate-pr-create.sh → .pr.title_type_whitelist
- /feature, /task, /bug skills reference the config in their Rules
sections; none hardcodes the list any more
Unlocks subsequent config-readers for #107 / #110 / #111 / #112 /
#113 / #114 / #115 — each extends the schema under its own subtree
without further changes to the loader.
https://github.com/me2resh/apexyard/issues/109
* fix(#109): satisfy markdownlint MD032 and MD060 on new docs
Auto-fix MD032 (blank lines around lists) in AgDR-0006 and format
table-separator rows with surrounding spaces (MD060) in both new
doc files. Content unchanged; CI green.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#110): add block-private-refs-in-public-repos.sh hook (#119)
Adds a new PreToolUse hook that blocks gh issue/PR/comment creation and
gh api .../issues|/pulls calls targeting a public framework repo
(default: me2resh/apexyard + whatever `upstream` resolves to) when the
title or body references any registered private project from
apexyard.projects.yaml (by name, repo slug, owner/repo#N ticket ref, or
workspace path).
The hook is a sibling to check-secrets.sh — both scan outgoing content
for identifiers that should never leave the local environment. Skip
marker `<!-- private-refs: allow -->` in the body lets a deliberate
reference through with a visible warning.
Files touched:
- .claude/hooks/block-private-refs-in-public-repos.sh (new)
- .claude/hooks/tests/test_block_private_refs.sh (new)
- .claude/rules/leak-protection.md (new)
- .claude/settings.json (wire PreToolUse matchers for the 5 gh shapes)
- docs/rule-audit.md (append section 10 + bump counts)
Refs: https://github.com/me2resh/apexyard/issues/110
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#115): add warn-stale-review-markers.sh PostToolUse hook (#120)
Fires after `git push` to surface review markers that have gone stale
because new commits were pushed past an existing Rex / CEO / design
approval. The merge gate already catches this at `gh pr merge` time,
but only then -- this hook closes the gap by flagging it immediately
at push-time so the author isn't surprised at merge.
- `.claude/hooks/warn-stale-review-markers.sh`
- PostToolUse, non-blocking (PostToolUse exit 2 would push noise
into the conversation; this hook is purely informational).
- Resolves the PR HEAD via `gh pr view --json headRefOid` -- same
source-of-truth as the merge-gate hooks post-apexyard#47 / #55.
Falls back to local HEAD with a visible WARN when gh is offline.
- Silent on: no PR for branch, no markers, fresh markers,
failed push (detected via `rejected` / `failed to push` /
`fatal:` / `error:` markers in tool_response.stderr).
- Modes: `warn` (default) prints one stderr line per stale marker;
`delete` opts in to auto-removal via
`.claude/project-config.json` -> `review_markers.on_stale`.
TODO(apexyard#109): switch to the shared project-config reader
once it lands.
- `.claude/settings.json`
- Wires the hook on PostToolUse / Bash / `git push *`.
- `docs/rule-audit.md`
- Adds a row under section 3 (Code review & PR quality) and
bumps the mechanized count 26 -> 27 / total 73 -> 74.
- `.claude/hooks/tests/test_warn_stale_review_markers.sh`
- 8 cases: no PR, no markers, fresh markers, stale rex / ceo /
design (warn), delete mode, failed push. All pass locally.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#111): upgrade pre-push-gate from advisory reminder to blocking check-runner (#121)
* chore(#111): upgrade pre-push-gate from reminder to blocking check-runner
Previously pre-push-gate.sh just printed a checklist of things to run
locally before pushing — it was advisory. The rule it enforces is a
HARD STOP per pr-workflow.md. That asymmetry meant agents routinely
pushed broken work and discovered it only when CI went red.
Replaces the reminder with a blocking runner that reads the list of
shell commands from project config (.pre_push.commands) and executes
them in sequence before a push is allowed through. First non-zero
exit blocks the push with exit 2 and prints the failing command plus
the last 20 lines of its output.
- Config key: .pre_push.commands[] — array of {name, run} objects.
Shipped default is an empty list (hook stays a no-op on repos that
haven't configured their checks yet, including the framework repo
itself until it wires its own CI).
- Emergency bypass: '<!-- pre-push: skip -->' in the HEAD commit
message. Grep-able on purpose so bypasses stay auditable.
- Fail-fast: once a command fails, the rest don't run. Parallel
execution is a follow-up polish.
- 7 test cases in .claude/hooks/tests/test_pre_push_gate.sh — all
pass on the shipped default + a minimal custom config.
Updates docs/rule-audit.md to flip "partial" → "yes" for the
"before git push" rule.
Integrates with the shared config reader landed in #109.
https://github.com/me2resh/apexyard/issues/111
* fix(#111): remove orphaned footnote reference from rule-audit
The previous advisory-mode footnote was superseded by pre-push-111
but its definition was accidentally kept, tripping markdownlint MD053
(unused reference definition). Drop it.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#107): add validate-issue-structure.sh PreToolUse hook (#122)
Mechanically enforces the ticket body schema when an agent files raw
`gh issue create` calls instead of going through the interactive
/feature, /task, /bug skills. Matches bracketed title prefix
([Feature] / [Chore] / [Bug] / [Docs] / etc.) against
`.ticket.required_sections` in project-config, and blocks (exit 2)
when any required section is missing or empty. Skip marker
`<!-- validate-issue-structure: skip -->` bypasses with a visible
stderr WARN for legitimate off-template tickets (epics, meta-threads).
Changes:
- .claude/hooks/validate-issue-structure.sh — the hook; reads schema
via the shared _lib-read-config.sh, with inlined defaults for bare
checkouts predating the config-schema rollout. Handles
--body / --body-file / -F path.
- .claude/project-config.defaults.json — extends .ticket with
required_sections (Feature/Chore/Refactor/Testing/CI/Docs/Bug) and
skip_marker; other .ticket fields untouched.
- .claude/settings.json — new PreToolUse matcher on Bash(gh issue
create *) alongside the existing suggest-ticket-template.sh and
block-private-refs-in-public-repos.sh hooks.
- .claude/hooks/tests/test_validate_issue_structure.sh — 15 cases
covering pass + fail paths per prefix, empty section detection,
skip marker, unknown prefix, non-gh invocation, --body-file path.
- docs/rule-audit.md — new section 11 row, mechanized count +1.
Upstream ticket: https://github.com/me2resh/apexyard/issues/107
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#112): add require-agdr-for-arch-pr.sh PreToolUse hook (#123)
Closes the asymmetry noted in .claude/rules/agdr-decisions.md: every
other HARD STOP in the ruleset (merge approval, ticket-first,
migration-first) is mechanically enforced, but the /decide HARD STOP
was prose-only. The commit-time hook require-agdr-for-arch-changes.sh
catches one architectural change at commit; this new PR-time hook
catches the cumulative diff so reviewers always have a pointer to the
decision record.
- New hook at .claude/hooks/require-agdr-for-arch-pr.sh
- Fires on Bash(gh pr create *)
- Parses --title/--body/--body-file/-F <path>
- Resolves base branch from --base, else upstream/dev, origin/dev,
upstream/main, origin/main, main, master (in that order)
- Computes `git diff <merge-base>..HEAD --name-only`
- Triggers on any changed file matching .agdr_trigger_paths[], OR any
dep-file addition (package.json via jq key-set diff; other
dep files via a commented +/- line-count heuristic — version
bumps match +/- counts and do not fire)
- Blocks (exit 2) with a helpful message naming the triggers and
pointing at /decide if the body has no `AgDR-\d+-[a-z0-9-]+`
reference
- Skip marker `<!-- agdr: not-applicable -->` bypasses with a
visible WARN on stderr
- Silent exit 0 on non-gh commands, empty diffs, unresolvable base
- Wired via .claude/settings.json PreToolUse Bash(gh pr create *)
- Adds two new top-level keys to .claude/project-config.defaults.json:
agdr_trigger_paths (shell globs — domain/, infrastructure/,
migrations/, *.tf, .github/workflows/, etc.)
agdr_trigger_dep_files (literal basenames — package.json,
pyproject.toml, Cargo.toml, go.mod, Gemfile)
Hook has inline fallback defaults kept in sync.
- Adds docs/rule-audit.md entry in the AgDR section; bumps mechanized
count 26 to 27 and total rows 73 to 74.
- Adds .claude/hooks/tests/test_require_agdr_for_arch_pr.sh (7 cases;
all green): path-triggered without AgDR (block), with AgDR (pass),
dep-file added (block), version-only bump (no fire), skip marker
(pass + warn), non-matching diff (pass), non-gh command (no-op).
Closes https://github.com/me2resh/apexyard/issues/112
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#113): require Testing section in PR body (config-driven) (#124)
Extends validate-pr-create.sh with a required-sections check that
replaces the hardcoded Glossary-only grep. The list of required H2
headings is project-configurable via `.pr.required_sections[]`.
Shipped default is ["Testing", "Glossary"], matching the canonical PR
description shape in workflows/code-review.md.
- Each entry must appear as `## <Name>` (case-insensitive).
- Empty sections are tolerated at this layer (the issue-structure hook
#107 does stricter empty-content checks for issue bodies; for PR
bodies, empty sections are left to the reviewer's judgement).
- Skip marker `<!-- pr-sections: skip -->` bypasses with a visible
stderr WARN — for trivial PRs (lint-only fixes, version bumps)
where the full template is overkill.
- Reads from project config via the shared _lib-read-config.sh (#109).
Inline fallback matches shipped defaults so bare checkouts predating
#109 keep working.
- 8 test cases cover: all-sections pass, each missing section,
missing-both (both errors printed), skip marker, case-insensitive
headings, H3 rejection.
https://github.com/me2resh/apexyard/issues/113
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#114): enforce single Closes-keyword per PR body (#125)
* chore(#114): enforce single Closes-keyword per PR body
Caps distinct auto-closing references (close/closes/closed, fix/fixes/
fixed, resolve/resolves/resolved + N or owner/repo+N) at one per PR
body. Closes the loophole where the title validator limited the title
to one ticket but multiple Closes lines in the body would still auto-
close all of them on merge.
- Scans stripped of fenced code blocks so closing keywords inside a
code sample do not count.
- Distinct counting: the same number referenced twice (e.g. via Fixes
and Closes) counts as one.
- Cross-repo refs (owner/repo+N) count normally.
- Opt-in escape hatch: pr.allow_multiple_closes=true in
project-config disables the check for teams that deliberately batch
rollbacks or dependency bumps.
- Per-PR bypass: a multi-close-approved HTML comment in the body
prints a visible stderr WARN and lets that PR through. Grep-able
trace so bypasses are auditable.
- 10 test cases cover: one close passes, no-keyword passes, two
distinct block, three mixed block, same-number-twice passes, code-
fence-ignored, skip marker, cross-ref without keyword, opt-in
config, cross-repo close.
Reads configuration via the shared _lib-read-config.sh (apexyard+109).
https://github.com/me2resh/apexyard/issues/114
* fix(#114): strip inline backticks and tilde fences from close-count scan
Rex caught a self-reflexive bug in the initial commit: documentation
mentioning closing keywords inside inline backticks (say a PR body
that explains the new hook with examples) counted as real closes, and
a skip marker inside inline backticks silently bypassed the check.
Future PRs that document the feature would trip the same trap.
Fix the code-region stripper to cover:
- Triple-backtick fences (already handled)
- Tilde fences (new)
- Inline-backtick spans (new)
Also run the skip-marker check against the stripped body, so a marker
used purely as documentation no longer activates a real bypass.
Three new test cases pin the behaviour:
- closing keywords in inline backticks are ignored
- skip marker inside inline backticks does NOT bypass
- tilde fences also get stripped
13/13 tests pass.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#108): add /tickets-batch skill for bulk-file flow (#127)
The fast happy path for filing 5–20 structured tickets in one intent
without dropping to raw `gh issue create` (non-conformant) or running
`/feature` 20 times serially (~100 turns of interview).
- .claude/skills/tickets-batch/SKILL.md — new skill spec. Asks
shared-context questions (priority, epic, area-labels, repo) ONCE
for the whole batch, then runs a ≤3-question micro-interview per
ticket (type, one-line purpose, optional clarification when the
inference is low-confidence). Confirms the full batch as a table,
then files each via specific `gh issue create` calls (never a
bulk JSON dump — the validator runs per-issue). Output conforms
to `.ticket.required_sections` by construction. Caps at 20
tickets per invocation.
- CLAUDE.md — added a row for /tickets-batch in the Available
Skills table; bumped the count references from 33 to 34.
Refs https://github.com/me2resh/apexyard/issues/108
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#117): add /fan-out skill + parallel-work rule doc (#128)
- Add `.claude/skills/fan-out/SKILL.md` — spawns N parallel Agent calls
in a single assistant message, with per-task agent type, worktree
isolation, and foreground/background mode. Caps at 5 concurrent
agents. Refuses fan-out when tasks share file write targets or have
sequential dependencies. Includes pre-spawn active-ticket safety
check and worktree merge-back flow that pauses on conflict.
- Add `.claude/rules/parallel-work.md` — trigger heuristic for when an
agent should proactively offer fan-out (>= 2 file-independent,
context-independent, individually substantial work items). Pairs
with the skill: rule says when, skill says how.
- Update `CLAUDE.md` — bump rules count to 9, skills count to 34, add
`/fan-out` row to the skills table.
Refs https://github.com/me2resh/apexyard/issues/117
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#116): adopt release-cut branch model (dev/main + tags) — framework only (#126)
* chore(#116): adopt release-cut branch model (dev/main + tags)
Formalises the dev/main split that already exists informally — dev is
the daily-work branch where every PR lands, main is release-only,
tagged with semver on each merge. Framework-only: managed projects
under apexyard governance stay trunk-based.
Added:
- AgDR-0007 — decision record (options table covers full git flow vs
trunk-only vs gitflow-lite; chose gitflow-lite)
- /release skill — diff dev against main, propose semver bump from
conventional commits, generate CHANGELOG, open release PR, tag
after merge
- docs/release-process.md — prose runbook for cutting a release
(manual fallback for the skill)
- .git.protected_branches in project-config.defaults.json
(main/master/dev/develop)
Modified:
- block-main-push.sh — now blocks direct pushes/commits to all
configured protected branches (was: hardcoded main/master). Reads
.git.protected_branches via the shared config reader (apexyard#109).
- CLAUDE.md — new section under Git Conventions explaining the
dev/main model + the framework-only scope. Skill table entry for
/release. Skills count bumped to 34.
- docs/multi-project.md — note that upstream/main is release-only
and the dev/main split is framework-only.
Non-consequences (per AgDR-0007):
- No release/* or hotfix/* branches. Hotfixes are normal patches
cut quickly. Revisit if multi-version maintenance becomes a need.
- No automatic on-merge issue closing for dev PRs. The release PR's
body aggregates all Closes references for the batch and triggers
auto-close en masse when it merges to main. Manual close in the
meantime.
- CI workflows trigger on pull_request regardless of base, so
dev-targeting PRs already get the full check matrix — no
workflow file edits needed.
https://github.com/me2resh/apexyard/issues/116
* fix(#116): satisfy markdownlint MD032 + MD060 in 116 docs
Auto-fix added blank lines around lists in AgDR-0007 (MD032) and
spaced the table separators in AgDR-0007 + multi-project.md (MD060).
Content unchanged.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* fix(#106): CHANGELOG fallback in drift hook for squash-merged forks (#129)
* fix(#106): CHANGELOG fallback in drift hook for squash-merged forks
The v1.1.0 tag-reachability check (`git tag --merged main`) misfires on
forks that sync via GitHub's default squash-merge: the squash collapses
the upstream-tag commit into a synthetic SHA, the tag stops being
reachable, and the banner keeps firing forever.
Discovered live on the first real-world `/update` flow: ops fork
squash-merged the v1.1.0 sync PR, banner kept saying "v1.1.0 available"
even though the fork was content-caught-up.
This commit adds a CHANGELOG-content fallback that fires only when the
primary tag check fails. If the fork's main has a heading
`## [X.Y.Z]` matching the upstream tag's version, treat the release as
absorbed and stay silent. Tolerant grep (matches the apexyard CHANGELOG
format from v1.1.0 onward, with leading-`v` stripping for tag→heading
conversion).
The merge-commit and rebase paths are unchanged — primary tag check
still works for them, and the fallback never fires when it shouldn't.
Test coverage (5 cases):
- squash-merge fork caught up to v1.1.0 → silent (the fix)
- merge-commit fork caught up to v1.1.0 → silent (regression check)
- fork stopped at v1.0.0 → banner fires
- fork has its own newer tag → silent
- squash-merge but no CHANGELOG on fork → banner fires (no false silence)
Records the strategy update in docs/agdr/AgDR-0008-…md (extends
AgDR-0005's tag-based-drift design).
https://github.com/me2resh/apexyard/issues/106
* fix(#106): satisfy markdownlint MD032/MD060 + shellcheck SC2164
Rex flagged two CI-blocking issues on the original 106 commit:
- AgDR-0008 had three bulleted sub-lists in the Consequences section
without surrounding blank lines (MD032). Added blanks and padded the
one tight-pipe table separator (MD060).
- The 106 test fixture had five subshell `cd "$fk"` calls without
`|| exit 1` (SC2164). Added the guard to all five.
5/5 tests still pass after the fix. No semantic change to the hook
or the test logic.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#130): add /validate-idea skill — lightweight pre-spec gate (#131)
A 10-minute, 5-question check that sits between /idea and /write-spec.
Designed for solo founders running ApexYard — not a heavyweight
methodology like event storming or Wardley mapping.
Five questions, asked one at a time:
1. Who is this specifically for?
2. What do they do today instead?
3. What's the smallest version that proves the value?
4. What would prove this is wrong? (kill criteria)
5. Build, buy, or rent?
Output: a one-page validation doc with a GREEN/YELLOW/RED verdict.
RED auto-updates the IDEA-NNN backlog row to WONTDO.
Integration:
/idea — adds an optional default-no "Validate now?" step after
capture (and after the optional GitHub Issue offer).
/handover — adds a conditional "this looks dormant, validate?"
step at the end of the integration plan, gated on the dormancy
heuristic (last commit > 90d AND zero open PRs AND no recent
issue activity). Healthy projects don't see the prompt.
CLAUDE.md skills count bumped to 35; new skills row added.
https://github.com/me2resh/apexyard/issues/130
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat: configurable voice prompts on assistant pause (AgDR-0009-voice-prompts-on-pause) (#135)
Stop hook that speaks the assistant's question aloud (Jarvis-style)
when it pauses for user input. Initial phase is macOS-only via `say`,
no voice input — user replies via keyboard.
Default OFF. Adopters opt in by overriding `voice_prompts.enabled` to
true in `.claude/project-config.json`.
Files:
- .claude/hooks/voice-prompt-on-pause.sh — Stop hook with config gate,
trigger heuristic (questions-only by default), markdown stripping,
sentence-boundary truncation, fire-and-forget say invocation
- .claude/hooks/tests/test_voice_prompt_on_pause.sh — 9 cases covering
disabled-default, enabled+question, enabled+statement, approved-pattern,
abc-menu, malformed-transcript, no-say-on-PATH, trigger-always,
markdown-stripping
- .claude/project-config.defaults.json — voice_prompts schema block
added (enabled, voice, max_chars, rate_wpm, trigger), default OFF
- .claude/settings.json — new Stop hook entry wired with the standard
ops-root resolver wrapper
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — design rationale,
options matrix (status quo / macOS say / cloud TTS / ML detection),
consequences, future phases
- docs/project-config.md — new "Voice prompts" section with override
examples and privacy notes
Test mode: hook respects VOICE_PROMPTS_SYNC=1 to run say synchronously
(test runners need this so assertions don't race against orphaned
background processes). Production invocations always run async.
Future phases (out of scope here, AgDR §"Future phases"):
- Phase 2: cross-platform TTS (Linux espeak, Windows SpeechSynthesizer)
- Phase 3: cloud TTS providers (OpenAI, ElevenLabs) — privacy AgDR-worthy
- Phase 4: voice input via Whisper-based STT
- Phase 5: per-message overrides
Refs: https://github.com/me2resh/apexyard/issues/134
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#141): add /debug skill — structured hypothesis-driven debugging (#142)
* feat(#141): add /debug skill — structured hypothesis-driven debugging
Adds a methodology skill that enforces five disciplines:
1. Capture the symptom precisely (exact URL, exact response, exact step)
2. Read the architecture before guessing (map every layer the request
touches, file by file)
3. Form a hypothesis ladder (3–5 candidates, each with an explicit
evidence test that confirms or refutes it)
4. Gather evidence first, fix second
5. Verify the fix against the original symptom evidence (re-run the
same `curl` / browser repro you used in step 4 — unit tests
verify code, not feature, correctness)
Stack appendices (Web, Desktop) carry stack-specific surface-evidence
requirements (step 1), architecture-surface maps (step 2), and
evidence-tests cookbooks (step 4). The methodology body stays portable
across stacks; appendices are where stack-specific knowledge accrues
over time.
Web appendix covers browser routing, framework configs (Next/Nuxt/Vite),
SPA-fallback layers, CDN, origin, the shared API client, backend
handlers, and auth providers. Desktop appendix covers Electron / Tauri
/ native-shell concerns: app entry points, IPC bridges, native modules,
auto-updater, sandbox / entitlements, code signing, crash reports.
Includes "When NOT to use" guidance so the methodology overhead doesn't
sandbag simple bugs (typos, off-by-ones, greenfield exploration).
Motivated by a real OAuth debug session in a managed project where
three sequential fixes chased adjacent symptoms because each was
hypothesis-then-fix without evidence in between. The skill is the
"never do that again" guardrail.
Closes #141
* fix(#141): scrub private project issue numbers from anti-pattern table
Rex review on PR #142 caught that line 155 of the skill's anti-pattern
table still named the originating PRs (#375, #377, #380) from the
private project where the methodology was first exercised. The PR body
and commit message were correctly abstracted earlier, but this in-file
reference slipped through — the leak-protection hook only scans gh
issue/pr writes, not staged file content, so mechanical enforcement
didn't catch it.
Replaced with "Three sequential PRs chasing the same symptom because
each was based on a different guess (no evidence test in between
cycles)" — same pedagogical value, zero attribution.
Refs #141
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(#143): document split-portfolio mode + add /setup privacy gate (#144)
Adopters on GitHub Free with any private project hit a silent privacy bug
following today's docs: forking apexyard makes a public fork that you cannot
later flip to private (GitHub policy), and committing `apexyard.projects.yaml`
+ `projects/<name>/` to the fork publishes private project names + handover
findings on a public GitHub repo.
This PR documents the supported workaround — split-portfolio mode — and
adds an upfront privacy gate to the /setup skill so new adopters never
hit the trip-wire silently.
docs/multi-project.md:
- New "Two setup modes — pick the one that matches your privacy needs"
section before TL;DR, with a side-by-side table and the explicit
trip-wire callout.
- Existing TL;DR retitled "TL;DR — single-fork mode (default)" with a
one-line pointer to the split-portfolio section.
- New "Split-portfolio mode — public framework + private portfolio"
section between the existing setup steps and the directory-layout
section. Includes:
- The two-repo layout (~/ops/apexyard public + ~/ops/portfolio private)
- 7-step setup walkthrough with copy-pasteable commands
- Daily workflow + upstream sync notes (both unchanged)
- Trade-offs (two repos to maintain, two clones per machine, one
upstream-sync conflict path on `projects/README.md`)
- "Migrating from single-fork to split-portfolio" recovery flow with
the explicit warning that GitHub Issue / PR edit history survives a
force-push and must be redacted separately
.claude/skills/setup/SKILL.md:
- New Step 2a: privacy gate — asks "are any projects private?" before
proposing the config. Branches on the answer:
- All public → single-fork mode
- GitHub Pro / Team / Enterprise → single-fork mode (private
forks of public repos
are supported on those
plans)
- Any private + GitHub Free → split-portfolio mode
- New Step 2b: walks through the split-portfolio setup interactively
(private repo create, sibling clone, gitignore + symlink) when the
privacy gate triggers.
- Detection: `test -L apexyard.projects.yaml` short-circuits Step 2b
for adopters already in split mode.
- Explicit "do NOT auto-migrate" rule for adopters already in single-fork
mode with private names already pushed — that path is destructive
(force-push history rewrite + redact issue/PR bodies + delete backup
branch) and warrants a deliberate, eyes-open run, not a /setup side
effect.
Out of scope for this PR (tracked separately on #143):
- `portfolio:` config block in `onboarding.yaml` schema
- Skill audit + refactor to honour configured `registry` / `projects_dir`
/ `ideas_backlog` paths instead of hardcoded fork-relative paths
- `/split-portfolio` migration helper skill that automates the recovery
flow currently documented manually
This is the docs-and-setup-question minimum-viable starter — the
framework code refactor is mechanical and lands as a follow-up.
Refs #143
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#145): portfolio config + self-healing + /split-portfolio helper (#147)
* feat(me2resh/apexyard#145): portfolio config block + self-healing + /split-portfolio helper
Closes the framework primitive deferred from me2resh/apexyard#144. Adds
first-class config-driven path resolution for the portfolio registry,
projects dir, and ideas backlog, with self-healing surfacing of broken
config at session start, plus a new /split-portfolio skill that automates
the destructive recovery flow.
Schema, helper, and hook:
- .claude/project-config.defaults.json: new portfolio: block
(registry, projects_dir, ideas_backlog) with defaults matching
today's single-fork layout
- .claude/hooks/_lib-portfolio-paths.sh: new sourceable helper exposing
portfolio_registry, portfolio_projects_dir, portfolio_ideas_backlog,
portfolio_validate, portfolio_clear_cache. Resolves relative paths
against the ops-fork root.
- .claude/hooks/check-portfolio-config.sh: new SessionStart hook —
silent on OK, one-line banner on broken config, never blocks session
- .claude/hooks/tests/test_portfolio_paths.sh: 13 cases covering
defaults, absolute/relative overrides, validate states, cache clear
Skill audit (18 SKILL.md files):
- Adds Path resolution callout pointing at the helper
- handover bash blocks now source helper and use $(portfolio_registry)
instead of literal apexyard.projects.yaml
- setup Step 2b now writes the portfolio: config block (recommended)
and validates via portfolio_validate before declaring success;
symlink approach kept as legacy fallback
New skill (.claude/skills/split-portfolio/SKILL.md):
- 10-step migration with explicit operator-confirmation gates at each
destructive step (force-push, body redaction, branch deletion)
- --verify mode: read-only state report (mode, paths, validate, drift)
- --dry-run mode: prints commands without executing
- Pre-flight refusals: already-private fork, paid GitHub plan, dirty
working tree, already-migrated state
- Step 9 writes the portfolio: config block (not symlinks) — symlink
fallback documented for adopters on older framework versions
- Step 9 surfaces the GitHub timeline-API survival caveat verbatim
- Idempotent re-runs: detects partial-migration state and resumes
Docs (docs/multi-project.md):
- Layout section describes both modes (config-block recommended,
symlink legacy) with self-healing notes
- Setup steps split into config-block mode and legacy symlink mode
- Migration section now points at /split-portfolio skill; manual
recipe preserved as fallback
AgDR (docs/agdr/AgDR-0010-portfolio-config-and-self-healing.md):
- Full Y-statement, options, decision, consequences, future phases
- Schema decision rationale: project-config.json over onboarding.yaml
because runtime path resolution belongs in project-config
Closes me2resh/apexyard#145
Refs me2resh/apexyard#146 (delivered same PR; closed manually post-merge
per the single-Closes-keyword rule in validate-pr-create.sh)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(me2resh/apexyard#145): markdownlint MD031 — blank lines around fences
CI's markdownlint-cli2 (v0.34.0) flagged the JSON + bash fenced code
blocks I added in setup/SKILL.md Step 2b without surrounding blank
lines. Added the required blanks. No content change.
Refs me2resh/apexyard#147
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>
* docs(me2resh/apexyard#148): correct privacy-gate wording — adopter action, not framework auto-publish (#149)
The privacy-gate wording introduced in PR #144 (and unchanged in PR #147)
attributed the publication to the framework rather than the adopter:
"the standard fork-and-commit setup will silently publish your private
project names on a public GitHub repo"
That's factually wrong. ApexYard never pushes anything without explicit
operator approval — the publication only happens when the adopter
themselves runs git push. The "silently publish" framing read as if the
framework auto-publishes, which is misleading and undermines trust in
the rest of the framework's safety claims.
Two prose-only edits, no code, no behavior change:
- .claude/skills/setup/SKILL.md Step 2a — replaced "will silently
publish ..." with adopter-action language ("you might accidentally
publish ... a stray git push after registering them — I won't push
without your approval, but the risk is on the adopter once the data
is committed locally")
- docs/multi-project.md trip-wire callout — replaced "silently publish
their portfolio names the moment they push" with "risk accidentally
publishing their portfolio names with a stray push (the framework
itself never pushes without operator approval, but once the registry
is committed locally the next push exposes it)"
Verified: `grep -r "silently publish" .claude/skills/ docs/` returns no
hits.
Closes me2resh/apexyard#148
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#150): bootstrap-skill exemption + Bash-write coverage (#152)
Closes the legitimate-bypass case (me2resh/apexyard#150) and the
illegitimate-bypass case (me2resh/apexyard#151) together so the
ticket-first gate is coherent. Shipping either alone would leave a
window where the framework is internally inconsistent — see AgDR-0011
for the full rationale.
Bootstrap exemption (me2resh/apexyard#150):
- .claude/session/active-bootstrap marker, written by /setup,
/handover, /update, /split-portfolio on entry; cleared on exit
- SessionStart sweep (clear-bootstrap-marker.sh) for stale markers
from interrupted sessions
- require-active-ticket.sh reads the marker and exempts skills on
the configured ticket.bootstrap_skills list
- bootstrap_skills list lives in .claude/project-config.defaults.json
(extendable per fork via .claude/project-config.json)
Bash-write coverage (me2resh/apexyard#151):
- new _lib-detect-bash-write.sh — heuristic detector for output
redirection, tee, sed -i, awk -i inplace, python/node/ruby
embedded interpreters
- require-active-ticket.sh + require-migration-ticket.sh now fire
on Bash in addition to Edit|Write|MultiEdit
- design choice: false-negatives preferred over false-positives
(the matcher errs toward "let through" rather than block legit
read-only commands)
Tests: 32 unit cases on the lib + 12 integration cases on the hook,
including the exact me2resh/apexyard#151 bypass repro from the issue
body.
Closes me2resh/apexyard#150
Will manually close me2resh/apexyard#151 post-merge per the
single-Closes-per-PR rule (precedent: AgDR-0010 / PR #147).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#153): extend Bash-write matcher beyond first-version coverage (#155)
Closes me2resh/apexyard#153.
Extends `_lib-detect-bash-write.sh` (introduced for me2resh/apexyard#151
in PR #152) with the matcher families flagged by Rex's review of #152.
AgDR-0011 already frames the matcher as a living list extended on
observation; this commit just walks the list.
New matcher families:
- File-moving builtins: `cp`, `mv`, `rm`, `dd`, `install` (anchored at
command-start; `--help`/`--version` and `git rm`/`git mv` excluded)
- Archive / network writes: `tar -x` / `tar --extract`, `curl -o` /
`--output`, `wget -O` / `--output-document`
- Additional embedded interpreters: `perl -e`, `php -r` (keyword-gated
like python/node/ruby); `go run`, `deno run`/`deno script.ts`,
`bun run`/`bun script.ts` (categorical script runners)
- Python helpers: `pathlib.Path().touch()`, `shutil.copy*`,
`shutil.move`, `os.rename` added to the `python -c` and python
heredoc keyword list
- Heredoc variants for `ruby` and `node` (previously only python
heredoc was covered)
Extractor extensions:
- `cp` / `mv`: last positional arg
- `curl -o` / `--output`: file argument
- `wget -O` / `--output-document`: file argument
- `tar -x`, `go run`, `deno`, `bun`, `perl -e`, `php -r`: return empty
(caller applies gate categorically per AgDR-0011)
Test count rose from 32 to 86. Negative-class counterexamples cover
the trickiest false-positive surfaces: `tar -t` listing, `cp --help`,
`rm --version`, `git rm`, `curl` bare URL fetch, `wget` bare URL fetch,
`deno fmt`, `deno test`, `go build`. Existing
`test_require_active_ticket_bash.sh` regression suite still passes 12/12.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(#154): mock gh in test sandboxes to remove live-tracker dependency (#156)
- add _lib-mock-gh.sh helper that installs a fake `gh` on the sandbox PATH;
intercepts `gh issue view <N> ... --json ...` and returns synthetic
`{"number":N,"state":"OPEN"}` (overridable per-num via mock_gh_set_state)
- wire the shim into test_single_closes_per_pr.sh and
test_validate_pr_required_sections.sh so the validator's CLOSED-issue
refusal no longer breaks the suite when upstream issues are closed
- both files previously failed every case (0/13 and 0/8) because their PR
titles reference #114 / #113, which are now CLOSED upstream
- post-fix: 13/13 and 8/8; full suite remains green
Closes me2resh/apexyard#154
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#132): structured CEO marker + same-turn merge in /approve-merge (#158)
* feat(#132): structured CEO marker + same-turn merge in /approve-merge
Closes me2resh/apexyard#132 (drop the "stop before merge" rule) and
me2resh/apexyard#48 (harden CEO marker against self-approval bypass)
together. The two threads compose — see AgDR-0012 for the full
rationale.
Streamline (#132):
- /approve-merge now runs `gh pr merge --squash --delete-branch`
in the same turn as the marker write, by default
- --no-merge opt-out preserves the deferred-merge case
- The discrete approval moment is the SKILL INVOCATION, not a
follow-up "now do the merge" message
Harden (#48):
- CEO marker is now a structured key/value file with required fields:
sha=<HEAD>
approved_by=user
skill_version=2
Validated by block-unreviewed-merge.sh; bare-SHA legacy markers
rejected with a clear "stale format" error pointing at /approve-merge
- The model's bare `echo SHA > <pr>-ceo.approved` bypass is now
mechanically rejected. Forging the structured fields requires a
deliberate, visible rule violation rather than a one-line accident
- Optional audit fields (approved_at, approval_summary) capture the
"what did the user say when they approved" trail
- Rex marker stays bare-SHA — different threat model (automated
reviewer, not human authorization moment)
pr-workflow.md reframed: "the load-bearing rule is explicit per-PR
approval, not two user messages." The merge is a deterministic
consequence of the approval invocation.
Tests: 12 cases on the hardened hook covering the new format end-to-end
(valid v2, missing rex/ceo, bare-SHA legacy rejected, missing
approved_by, wrong approved_by, skill_version=1, sha mismatch,
non-merge no-op, gh-api shape gated). Full suite: 205/205 across 13
test files.
Will manually close me2resh/apexyard#48 post-merge per the
single-Closes-per-PR rule (precedent: PR #152 / AgDR-0011).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#132): redact private project reference from AgDR-0012
Abstracted two references to a registered private project that named
the project's owner/repo. The leak-protection hook caught one in the
PR body; this fixup removes the matching references from the
AgDR-0012 file content (which would otherwise have shipped the names
to me2resh/apexyard public repo via the merge).
The pre-existing reference at .claude/rules/pr-workflow.md:130
(documenting #47) is untouched — it predates this PR and is already
on the public repo's history.
Refs me2resh/apexyard#132.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#132): markdownlint blanks-around-fences + typo fix
Two small fixups against red CI / Rex feedback:
- AgDR-0012 line 63: fenced code block now has a blank line before it
(MD031). markdownlint-cli2 0.13.0 was rejecting the indented fence
inside the bullet because the fence's preceding line was the bullet
text (no blank).
- approve-merge SKILL.md line 172: typo `deferes` → `defers` (Rex flag
on PR #158).
Refs me2resh/apexyard#132.
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>
* chore(#157): remove voice-prompts feature + correct hook/skill counts (#161)
* chore(#157): remove voice-prompts-on-pause feature + correct hook/skill counts
Closes me2resh/apexyard#157 (sunset the voice-prompts feature) and
me2resh/apexyard#77 (hook count off-by-one in CHANGELOG / CLAUDE.md)
in one bundled PR. AgDR-0013 supersedes AgDR-0009 with the full
rationale; both AgDRs are preserved (decision records are append-only
history).
Removed (#157):
- .claude/hooks/voice-prompt-on-pause.sh
- .claude/hooks/tests/test_voice_prompt_on_pause.sh
- Stop matcher block in .claude/settings.json (became empty after
voice removal)
- voice_prompts block in .claude/project-config.defaults.json
- "## Voice prompts" section in docs/project-config.md
- voice_prompts mention in AgDR-0010 line 32 (replaced with
leak_protection / ticket as still-current example config blocks)
Preserved:
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — historical record;
new "Superseded by: AgDR-0013" header at the top
- AgDR-0010 line 115 reference to AgDR-0009 — still accurate as a
historical pattern reference
Counts corrected (#77):
- CHANGELOG.md v0.3.0 stats: "17 hooks" → "18 hooks" (historical
fix — at v0.3.0 there were actually 18 hooks)
- CLAUDE.md table line: "18 shell scripts" → "24 shell scripts"
(current count after this removal)
- CLAUDE.md table line: "35 slash commands" → "39 slash commands"
- CLAUDE.md "Available skills (34)" → "Available skills (39)"
- CLAUDE.md quick-reference "Skills (35 slash commands)" →
"(39 slash commands)"
Why bundled: #77's correct count depends on whether voice is still in
the framework. Shipping #77 before #157 would write a number that's
wrong by one again the moment #157 lands. Same shape as previous
bundles (PR #152 / AgDR-0011, PR #158 / AgDR-0012).
Why no adopter-facing changelog mention of the voice removal: the
feature never reached a tagged release on main. v1.1.0 didn't have
it; v1.2.0 won't have it. From the adopter's perspective there's
nothing to retire. AgDR-0013 captures the framework's internal record
for future contributors. See AgDR-0013 § "No adopter-facing changelog
mention".
Tests: full hook test suite green (196 cases across 12 files —
test_voice_prompt_on_pause.sh removed). No regressions.
Will manually close me2resh/apexyard#77 post-merge per the
single-Closes-per-PR rule (precedent: PRs #152, #158).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#157): unwire voice from settings + configs + docs + AgDR-0013
Continuation of d09d7b5 (the file deletions). Squash-merge will
collapse both into one PR commit. This commit captures:
- .claude/settings.json — Stop matcher block removed (was the only
hook in it; entire matcher gone)
- .claude/project-config.defaults.json — voice_prompts block + its
_comment removed
- docs/project-config.md — "## Voice prompts" section removed
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — "Superseded by"
header added at the top, content otherwise preserved as history
- docs/agdr/AgDR-0010-portfolio-config-and-self-healing.md — line 32
example reference swapped from voice_prompts to
leak_protection / ticket (still-current config blocks)
- docs/agdr/AgDR-0013-sunset-voice-prompts.md — new supersession AgDR
- CLAUDE.md — hook count 18 → 24, skill count 35 → 39 (three
occurrences each, all aligned to current reality)
- CHANGELOG.md v0.3.0 stats — "17 hooks" → "18 hooks" historical fix
(#77 acceptance criterion 1)
Refs me2resh/apexyard#157 + me2resh/apexyard#77.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#157): markdownlint MD028 + resolve stale AgDR-numbering ref
Three small fixups against Rex CHANGES-REQUESTED on PR #161:
- AgDR-0009 line 3: promote "Superseded by:" header out of a
blockquote. The original "I decided ..." canonical blockquote at
line 5 was being merged with the new supersession blockquote
(markdownlint MD028 — "no blanks inside blockquote").
- AgDR-0013 line 3: same shape — "Supersedes:" header now a plain
bold paragraph, canonical "I decided ..." blockquote untouched.
- approve-merge SKILL.md line ~187: stale conditional reference
"AgDR-0012 (or 0013 — depends on whether voice-removal lands
first)" — order is now resolved (12 = approve-merge bundle, 13 =
voice removal). Drop the parenthetical.
Refs me2resh/apexyard#157 + me2resh/apexyard#77.
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(#160): multi-tab terminal demo on the landing site (#162)
Landing-site `site/index.html` terminal demo previously played one
canonical flow ("one ticket, start to finish") on autoplay. With the
v1.2.0 skill surface expansion (4 new skills, 8+ new hooks), one flow
no longer represents the framework's breadth.
Adds tabs to the terminal chrome — four flows visitors can either let
auto-cycle or click directly:
1. one ticket — existing flow, unchanged content
2. /handover — adopt an external repo into the portfolio
3. /setup — first-run framework bootstrap on a fresh fork
4. /fan-out — spawn 3 parallel agents on independent tickets
Auto-advance: on completion of the active tab's script, the demo
pauses ~1.8s then advances to the next tab. Loops at the end. User
can interrupt by clicking any tab or hitting Replay.
Implementation:
- HTML chrome — single title span replaced with a tablist of 4 button
tabs. ARIA `role="tablist"` / `role="tab"` / `aria-selected` so
keyboard + screen-reader users get the same semantics as sighted
ones.
- CSS — new `.shell-demo__tabs` + `.shell-demo__tab` with an accent
underline on the active tab. Tabs scroll horizontally on narrow
viewports (mobile responsive).
- JS — refactored the existing IIFE from one `script` array to an
array of four. Added `setActiveTab()` for ARIA state, `play(idx)`
takes a tab index, end-of-script auto-advances to `(idx + 1) % N`
unless the user clicked away during the pause. Adds a new `cmd`
type alongside `you` for slash-command invocations (renders with
the same `>` prompt prefix). prefers-reduced-motion still bails
early and leaves the static seed visible.
- Static seed (the non-JS fallback) still shows tab 0's content, so
reduced-motion / no-JS visitors see the one-ticket flow as before.
Hero metrics also corrected to current reality (#77 / PR #161 covers
CLAUDE.md and CHANGELOG.md; this PR catches the same numbers in the
landing site):
Skills 32 → 39
Hooks 18 → 24
The tabs ship in v1.2.0 alongside the framework changes that make
the new flows worth showcasing.
Refs me2resh/apexyard#160 (release v1.2.0 + landing-site refresh).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#163): default the split-portfolio sibling repo name to <fork>-portfolio (#164)
Closes me2resh/apexyard#163.
The split-portfolio mode docs and skills previously suggested
`your-org/ops` as the default name for the private sibling repo, and
`portfolio/` as the local clone directory. Both too generic — adopters
running multiple ops setups end up with `your-org/ops` collisions, and
a bare `portfolio/` dir gives no signal about which framework it
belongs to when it sits next to other unrelated `portfolio/` dirs.
`<fork>-portfolio` is now the default — keeps the relationship to the
public fork explicit on disk and on GitHub. If the fork is named
`your-org/apexyard`, the portfolio defaults to
`your-org/apexyard-portfolio`. If the fork was renamed (e.g. `cos`),
the portfolio defaults to `cos-portfolio`. Adopters with custom names
keep working — the `portfolio:` config block resolves whatever path
they configured.
Files updated:
docs/multi-project.md
- Layout diagrams use `apexyard-portfolio/` as the sibling
- Setup walkthrough Step 2 + Step 3 use `your-org/apexyard-portfolio`
and explain the `<fork>-portfolio` pattern
- Config-block + symlink path examples updated to
`../apexyard-portfolio/...`
- Daily workflow + cross-machine clone commands updated
- The two existing `your-org/ops` references that remain are
fork-rename examples (lines 52, 64) — kept as-is, since renaming
the fork to `ops` is still valid (the portfolio would then
default to `ops-portfolio`)
.claude/skills/setup/SKILL.md
- Step 2b's "default suggestion" for the private repo name is now
`your-org/<fork>-portfolio`, computed dynamically from the
fork's repo name via `gh repo view --json name -q .name` so the
suggestion is correct even when the fork was renamed
- Clone command no longer needs a second arg — the repo name IS
the directory name
- Config-block paths updated to `../apexyard-portfolio/...`
.claude/skills/split-portfolio/SKILL.md
- Step 3's suggested-name template is now `<account>/<fork>-portfolio`
with the same dynamic-fork-name resolution
Mechanism unchanged. The `portfolio:` config block in
`.claude/project-config.json` still takes any path; this PR is purely
default-suggestion + example prose.
No tests required (skills are markdown instructions; no automated
coverage today).
Refs `apexyard.projects.yaml.example` uses "ops repo" as a generic
term meaning "the operational management fork" — not a name — kept
as-is.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#165): skills reference page on the landing site + changelog link (#167)
Closes me2resh/apexyard#165.
Adds a public, browseable index of every apexyard slash command
alongside a one-click changelog link from the homepage nav.
site/skills.html (new):
- Lists all 39 skills currently shipping in .claude/skills/
- Each entry: slash command, argument hint, description (taken
verbatim from the SKILL.md frontmatter so the page matches the
runtime exactly)
- 10 categories: Setup & onboarding, Daily ops, Tickets & ideas,
Specs & decisions, Code review & merge, Architecture & dev tools,
Production-readiness audits, Workflow primitives, Communications,
Deprecated
- Same brutalist-terminal design tokens as the homepage — JetBrains
Mono, paper-cream background, single warning-red accent, sharp
corners. Inlined CSS to keep the static-only no-build-step
convention; design vars duplicated rather than extracted to a
shared file (~18 vars; cheap to keep in sync).
- Mobile responsive — skill grid collapses to single-column under
720px; titlebar nav hides non-CTA items on narrow viewports.
- Reduced-motion friendly (no animation in the first place).
- Internal anchor TOC at the top so the page scans in seconds.
site/index.html (nav addition):
- Added two nav links to the titlebar between "what's in the box"
and the github CTA:
• skills → ./skills.html
• changelog → https://github.com/me2resh/apexyard/releases
- The changelog link points at the GitHub releases page (not the
raw CHANGELOG.md file) so it auto-resolves to the latest tagged
release on each visit. v1.2.0 lands and the link is already there.
No new dependencies, no build step, no JS for the skills page. The
existing site convention (one-html-file-per-route, inlined CSS,
optional progressive-enhancement JS) is preserved.
Refs me2resh/apexyard#160 (release v1.2.0 + landing-site refresh) —
this is the second site-side deliverable for that ticket; the release
tag itself follows once me2resh/apexyard#159 (testing) closes.
Follow-up worth a separate ticket: a small generator script that
walks .claude/skills/*/SKILL.md, parses YAML frontmatter, and emits
the skills.html sections automatically. Out of scope for v1.2.0 —
first version is hand-curated and will need maintenance until that
generator lands.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#168): accept release/vN.N.N branches + release(...) PR titles (#169)
Closes me2resh/apexyard#168.
The /release skill prescribed `release/vA.B.C` as the source-branch
name and `release: vA.B.C` as the PR title for the dev → main release
PR (per AgDR-0007). Both were rejected by the framework's own
validators:
validate-branch-name.sh required {type}/{TICKET-ID}-{description};
release/v1.2.0 has no ticket-id portion.
validate-pr-create.sh required type(SCOPE): form with `release` not
in pr.title_type_whitelist.
The contradiction surfaced cutting v1.2.0 — the first release under
the dev/main model.
Three small changes:
1. .claude/hooks/validate-branch-name.sh — added an early-out branch
that accepts ^release/vN.N.N(-rcN)?$ as a valid name. Narrow,
intentional exception for the framework's release-cut convention;
release branches don't carry a ticket-id because the release itself
IS the ticket.
2. .claude/project-config.defaults.json — added "release" to
pr.title_type_whitelist so a title like `release(#160): v1.2.0`
passes validate-pr-create.sh's existing regex unchanged.
3. .claude/skills/release/SKILL.md step 4 — corrected the prescribed
PR title to `release(#<release-ticket>): vA.B.C` so future /release
invocations produce a title that satisfies the validators by
construction.
Tested:
bash .claude/hooks/validate-branch-name.sh against:
release/v1.2.0 → 0 (allowed, release-special-case)
release/v1.2.0-rc1 → 0 (allowed, RC variant)
release/v9.9.9 → 0 (allowed)
release/foo → 2 (correctly blocked)
release/v1 → 2 (correctly blocked)
chore/GH-168-fix → 0 (allowed, standard pattern)
feature/GH-1-x → 0 (allowed, standard pattern)
Full hook test suite: 196/196 cases green across 12 test files.
Refs: surfaced 2026-05-04 cutting the first release under AgDR-0007.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#170): exempt release/vN.N.N from validate-pr-create's branch-id check (#171)
Closes me2resh/apexyard#170.
Completes the work started in #169 (closing #168). #169 added a
release-pattern early-out to validate-branch-name.sh so release/vN.N.N
branches pass the branch-name validator. But validate-pr-create.sh has
its own independent branch-id check at line 273 that #169 didn't
touch — and it still rejects release/v1.2.0 because that name doesn't
contain a ticket-id substring.
This is the same class of contradiction #168 fixed; the fix is the
same shape. Add the same release-pattern early-out to the branch-id
check in validate-pr-create.sh:
- if the branch matches ^release/vN.N.N(-rcN)?$ → exempt (release
branches don't carry ticket-ids; the release itself is the ticket)
- otherwise → require a ticket-id substring as before
#168's acceptance criterion 3 ("validate-pr-create.sh accepts a PR
title `release(#160): v1.2.0` against the `release/v1.2.0` branch")
was checked off based on the title regex alone but didn't catch the
secondary branch-id check living in the same file. Surfaced trying
to open the v1.2.0 release PR.
Tested:
bash .claude/hooks/validate-pr-create.sh against:
release/v1.2.0 → 0 (allowed, exempt)
release/v1.2.0-rc1 → 0 (allowed, RC variant)
chore/GH-1-fix → 0 (allowed, has ticket-id)
release/foo → 2 (correctly blocked)
chore/no-ticket → 2 (correctly blocked)
Refs me2resh/apexyard#168 (the parent bug) + #169 (the partial fix).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#173): sync CHANGELOG.md from main → dev (#174)
Closes me2resh/apexyard#173.
The release-cut model (AgDR-0007 / #116) squash-merges
release/vN.N.N → main, which means the v1.2.0 CHANGELOG section that
landed on main via PR #172 was never propagated back to dev. This
commit copies main's CHANGELOG.md verbatim onto dev so the v1.2.0
section is now present on both branches.
The diff is exactly the v1.2.0 entry being prepended; no other lines
change.
Without this sync, the next release PR cut from dev would build a
v1.3.0 section on top of v1.1.0, silently dropping v1.2.0 from dev's
running history. The corollary skill-level fix (option B in the
ticket) — updating /release to source the previous CHANGELOG from
upstream/main — is filed as a follow-up and out of scope for this PR.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#181): /agdr skill — searchable AgDR library (#186)
Adds /agdr — a portfolio-wide index for Agent Decision Records.
Walks apexyard.projects.yaml, reads each project's docs/agdr/*.md
(local clone if available, else gh api fallback), parses the optional
YAML frontmatter for category + projects, and answers four queries:
- /agdr browse list across the portfolio, grouped by category
- /agdr search <term> full-text grep across all bodies, returns
<project>/AgDR-NNNN paths + matching paragraph
- /agdr show <id> print a specific record, disambiguates duplicates
- /agdr stats counts per category (the marketing-slide tile,
now backed by real data)
Six-category taxonomy: architecture | tech-stack | security | patterns
| integrations | other. Legacy AgDRs without frontmatter remain
first-class — they bucket as `other` and are flagged in browse so
operators can migrate at their own pace.
Backwards-compatible template change: templates/agdr.md gains an
optional `category:` (and optional `projects:`) line in the existing
frontmatter block. Omitting the line keeps every existing AgDR valid;
the skill defaults to `other` when missing.
Doc note in workflows/sdlc.md § Phase 2 points at /agdr search for
"have we decided this before?" lookups before drafting a design.
Smoke test in .claude/hooks/tests/test_agdr_skill.sh covers the
parser the spec specifies — frontmatter extraction, category
bucketing (including the legacy default-to-other path), id reading,
stats aggregation, and search match counts. 18 assertions, all green;
12 pre-existing test suites also green (no regressions).
Closes #181
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(me2resh/apexyard#183): /launch-check trend tracking (#185)
- Persist each run as JSON under <projects_dir>/<name>/launch-check/runs/
(timestamp + branch + commit + per-dimension scores + verdict + top_risks)
- Append "Trend (last 5 runs)" section to the per-run summary when at least 2
prior runs exist — markdown table + ASCII score chart
- Add /launch-check trend mode for read-only trend rendering (no full audit)
- Auto-derive notes column from score-delta vs previous run
(e.g. "Security +12, Analytics +10")
- Opt-in commit via .launch-check-history-tracked marker; gitignored by default
- New helper script render-trend.sh + test_launch_check_trend.sh (21 cases)
- Resolve projects dir via portfolio_projects_dir helper (no hardcoded path)
- Schema is forward-compatible — extra fields preserved on framework upgrade
AgDR-0014 documents the chart format / schema / opt-in choices.
Closes me2resh/apexyard#183
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#182): /status --briefing + bin/apexyard status CLI shim (#187)
* feat(me2resh/apexyard#182): /status --briefing + bin/apexyard CLI shim
Make slide 6's `$ apexyard status` invocation real — the smallest of three
slide-reality tickets, demonstrating the marketing-to-implementation
pattern before #181 / #183 land.
- New helper at .claude/skills/status/briefing.sh — computes the 4-line
"where am I" briefing (active workspace, active ticket, branch,
role-set) so the logic is testable in isolation and runnable from a
plain shell. Workspace inference walks up from cwd to the ops-fork
root (same algorithm as _lib-portfolio-paths.sh and /start-ticket).
- Ticket reads from .claude/session/tickets/<workspace> first, falls
back to .claude/session/current-ticket. Role-set is inferred from
the active ticket's GitHub labels (v1: backend / frontend / qa /
security / platform / sre / data / ux / ui / product / tech-lead,
plus the long forms). No match emits the explicit "<none — inferred
per task>" placeholder so the four-line shape is constant.
- New CLI shim bin/apexyard delegates `apexyard status` to the same
helper after walking up to find the ops-fork root. Works from any
workspace/<name>/ clone or the fork itself; symlink onto PATH to
install (no shadowing of the `claude` binary).
- /status SKILL.md gains a "Briefing mode" section documenting the
--briefing / -b flag; default /status output is unchanged.
- docs/multi-project.md "Daily workflow" demonstrates the new
invocation alongside /inbox and /status.
- New smoke tests at .claude/hooks/tests/test_status_briefing.sh — 8
cases covering ops-root cwd, workspace cwd, unknown cwd, ops-fallback
marker, per-project marker priority, label-based role inference, the
no-matching-label path, and the constant-four-line shape.
Closes me2resh/apexyard#182
* fix(me2resh/apexyard#182): replace curios-dog with example-app in SKILL.md output sample (leak fix)
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(#178): LSP integration spike — measurement + recommendation (#184)
* docs(#178): LSP spike — token-savings measurement + integration findings
- Phase 1: estimated token cost A vs B for three representative queries on a real TS Lambda backend (~9,750 LOC); shallow semantic queries see ~3-23x input-token savings, multi-hop traces see ~1.4x
- Phase 2: verified Claude Code shipped first-party LSP support in v2.0.74 (Dec 2025), gated behind ENABLE_LSP_TOOL=1, wired in via the plugin system (.lsp.json); MCP-wrapped LSP shims (cclsp, lsp-mcp) remain as fallback
- Phase 3: recommends Option 3 — interactive clone-first prompt at end of /handover, preserving today's "never auto-clone" principle while making the deep-dive path discoverable
- Phase 4: AgDR Y-statement and option matrix sketched; full AgDR is a follow-up if the spike says go
- Recommendation: GO. Adopt the built-in tool, document the opt-in path, change /handover to offer clone-first interactively. No code changes beyond /handover SKILL.md; no novel integration to maintain
Closes me2resh/apexyard#178
* docs: fix markdownlint MD031/MD032 in spike report
Add blank lines around lists and a fenced code block to satisfy
markdownlint-cli2 in CI. Pure formatting, no content change.
Refs me2resh/apexyard#178
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(me2resh/apexyard#190): annotate LSP-aware skills with opt-in callouts (#191)
- Add identical-shape "LSP-aware (optional, recommended)" callout near
the top of each of the four code-aware SKILL.md files.
- Per-skill savings number is the only variation:
- /code-review: ~3-15× cheaper for semantic queries
- /threat-model: ~3-15× shallow, ~1.4-5× multi-hop traces
- /security-review: ~3-15× cheaper for semantic querie…
* chore(#109): project-configurable ticket / branch / commit / PR schema (#118)
* chore(#109): project-configurable ticket / branch / commit / PR schema
Lift the prefix / type whitelists hardcoded across skills, hooks, and
CI into a versioned JSON config read through a shared shell library.
Shipped defaults at .claude/project-config.defaults.json; per-fork
overrides at the optional .claude/project-config.json; one reader
(_lib-read-config.sh) that every consumer now uses.
Added:
- .claude/project-config.defaults.json (v1 schema)
- .claude/hooks/_lib-read-config.sh (shared reader)
- docs/project-config.md (schema reference + extension guide)
- docs/agdr/AgDR-0006-project-configurable-ticket-schema.md
Migrated (still pass with no config present via last-resort fallback):
- validate-branch-name.sh → .branch.type_whitelist
- validate-commit-format.sh → .commit.type_whitelist (legacy
`commit_types` top-level key honoured as backward-compat fallback)
- validate-pr-create.sh → .pr.title_type_whitelist
- /feature, /task, /bug skills reference the config in their Rules
sections; none hardcodes the list any more
Unlocks subsequent config-readers for #107 / #110 / #111 / #112 /
#113 / #114 / #115 — each extends the schema under its own subtree
without further changes to the loader.
https://github.com/me2resh/apexyard/issues/109
* fix(#109): satisfy markdownlint MD032 and MD060 on new docs
Auto-fix MD032 (blank lines around lists) in AgDR-0006 and format
table-separator rows with surrounding spaces (MD060) in both new
doc files. Content unchanged; CI green.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#110): add block-private-refs-in-public-repos.sh hook (#119)
Adds a new PreToolUse hook that blocks gh issue/PR/comment creation and
gh api .../issues|/pulls calls targeting a public framework repo
(default: me2resh/apexyard + whatever `upstream` resolves to) when the
title or body references any registered private project from
apexyard.projects.yaml (by name, repo slug, owner/repo#N ticket ref, or
workspace path).
The hook is a sibling to check-secrets.sh — both scan outgoing content
for identifiers that should never leave the local environment. Skip
marker `<!-- private-refs: allow -->` in the body lets a deliberate
reference through with a visible warning.
Files touched:
- .claude/hooks/block-private-refs-in-public-repos.sh (new)
- .claude/hooks/tests/test_block_private_refs.sh (new)
- .claude/rules/leak-protection.md (new)
- .claude/settings.json (wire PreToolUse matchers for the 5 gh shapes)
- docs/rule-audit.md (append section 10 + bump counts)
Refs: https://github.com/me2resh/apexyard/issues/110
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#115): add warn-stale-review-markers.sh PostToolUse hook (#120)
Fires after `git push` to surface review markers that have gone stale
because new commits were pushed past an existing Rex / CEO / design
approval. The merge gate already catches this at `gh pr merge` time,
but only then -- this hook closes the gap by flagging it immediately
at push-time so the author isn't surprised at merge.
- `.claude/hooks/warn-stale-review-markers.sh`
- PostToolUse, non-blocking (PostToolUse exit 2 would push noise
into the conversation; this hook is purely informational).
- Resolves the PR HEAD via `gh pr view --json headRefOid` -- same
source-of-truth as the merge-gate hooks post-apexyard#47 / #55.
Falls back to local HEAD with a visible WARN when gh is offline.
- Silent on: no PR for branch, no markers, fresh markers,
failed push (detected via `rejected` / `failed to push` /
`fatal:` / `error:` markers in tool_response.stderr).
- Modes: `warn` (default) prints one stderr line per stale marker;
`delete` opts in to auto-removal via
`.claude/project-config.json` -> `review_markers.on_stale`.
TODO(apexyard#109): switch to the shared project-config reader
once it lands.
- `.claude/settings.json`
- Wires the hook on PostToolUse / Bash / `git push *`.
- `docs/rule-audit.md`
- Adds a row under section 3 (Code review & PR quality) and
bumps the mechanized count 26 -> 27 / total 73 -> 74.
- `.claude/hooks/tests/test_warn_stale_review_markers.sh`
- 8 cases: no PR, no markers, fresh markers, stale rex / ceo /
design (warn), delete mode, failed push. All pass locally.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#111): upgrade pre-push-gate from advisory reminder to blocking check-runner (#121)
* chore(#111): upgrade pre-push-gate from reminder to blocking check-runner
Previously pre-push-gate.sh just printed a checklist of things to run
locally before pushing — it was advisory. The rule it enforces is a
HARD STOP per pr-workflow.md. That asymmetry meant agents routinely
pushed broken work and discovered it only when CI went red.
Replaces the reminder with a blocking runner that reads the list of
shell commands from project config (.pre_push.commands) and executes
them in sequence before a push is allowed through. First non-zero
exit blocks the push with exit 2 and prints the failing command plus
the last 20 lines of its output.
- Config key: .pre_push.commands[] — array of {name, run} objects.
Shipped default is an empty list (hook stays a no-op on repos that
haven't configured their checks yet, including the framework repo
itself until it wires its own CI).
- Emergency bypass: '<!-- pre-push: skip -->' in the HEAD commit
message. Grep-able on purpose so bypasses stay auditable.
- Fail-fast: once a command fails, the rest don't run. Parallel
execution is a follow-up polish.
- 7 test cases in .claude/hooks/tests/test_pre_push_gate.sh — all
pass on the shipped default + a minimal custom config.
Updates docs/rule-audit.md to flip "partial" → "yes" for the
"before git push" rule.
Integrates with the shared config reader landed in #109.
https://github.com/me2resh/apexyard/issues/111
* fix(#111): remove orphaned footnote reference from rule-audit
The previous advisory-mode footnote was superseded by pre-push-111
but its definition was accidentally kept, tripping markdownlint MD053
(unused reference definition). Drop it.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#107): add validate-issue-structure.sh PreToolUse hook (#122)
Mechanically enforces the ticket body schema when an agent files raw
`gh issue create` calls instead of going through the interactive
/feature, /task, /bug skills. Matches bracketed title prefix
([Feature] / [Chore] / [Bug] / [Docs] / etc.) against
`.ticket.required_sections` in project-config, and blocks (exit 2)
when any required section is missing or empty. Skip marker
`<!-- validate-issue-structure: skip -->` bypasses with a visible
stderr WARN for legitimate off-template tickets (epics, meta-threads).
Changes:
- .claude/hooks/validate-issue-structure.sh — the hook; reads schema
via the shared _lib-read-config.sh, with inlined defaults for bare
checkouts predating the config-schema rollout. Handles
--body / --body-file / -F path.
- .claude/project-config.defaults.json — extends .ticket with
required_sections (Feature/Chore/Refactor/Testing/CI/Docs/Bug) and
skip_marker; other .ticket fields untouched.
- .claude/settings.json — new PreToolUse matcher on Bash(gh issue
create *) alongside the existing suggest-ticket-template.sh and
block-private-refs-in-public-repos.sh hooks.
- .claude/hooks/tests/test_validate_issue_structure.sh — 15 cases
covering pass + fail paths per prefix, empty section detection,
skip marker, unknown prefix, non-gh invocation, --body-file path.
- docs/rule-audit.md — new section 11 row, mechanized count +1.
Upstream ticket: https://github.com/me2resh/apexyard/issues/107
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#112): add require-agdr-for-arch-pr.sh PreToolUse hook (#123)
Closes the asymmetry noted in .claude/rules/agdr-decisions.md: every
other HARD STOP in the ruleset (merge approval, ticket-first,
migration-first) is mechanically enforced, but the /decide HARD STOP
was prose-only. The commit-time hook require-agdr-for-arch-changes.sh
catches one architectural change at commit; this new PR-time hook
catches the cumulative diff so reviewers always have a pointer to the
decision record.
- New hook at .claude/hooks/require-agdr-for-arch-pr.sh
- Fires on Bash(gh pr create *)
- Parses --title/--body/--body-file/-F <path>
- Resolves base branch from --base, else upstream/dev, origin/dev,
upstream/main, origin/main, main, master (in that order)
- Computes `git diff <merge-base>..HEAD --name-only`
- Triggers on any changed file matching .agdr_trigger_paths[], OR any
dep-file addition (package.json via jq key-set diff; other
dep files via a commented +/- line-count heuristic — version
bumps match +/- counts and do not fire)
- Blocks (exit 2) with a helpful message naming the triggers and
pointing at /decide if the body has no `AgDR-\d+-[a-z0-9-]+`
reference
- Skip marker `<!-- agdr: not-applicable -->` bypasses with a
visible WARN on stderr
- Silent exit 0 on non-gh commands, empty diffs, unresolvable base
- Wired via .claude/settings.json PreToolUse Bash(gh pr create *)
- Adds two new top-level keys to .claude/project-config.defaults.json:
agdr_trigger_paths (shell globs — domain/, infrastructure/,
migrations/, *.tf, .github/workflows/, etc.)
agdr_trigger_dep_files (literal basenames — package.json,
pyproject.toml, Cargo.toml, go.mod, Gemfile)
Hook has inline fallback defaults kept in sync.
- Adds docs/rule-audit.md entry in the AgDR section; bumps mechanized
count 26 to 27 and total rows 73 to 74.
- Adds .claude/hooks/tests/test_require_agdr_for_arch_pr.sh (7 cases;
all green): path-triggered without AgDR (block), with AgDR (pass),
dep-file added (block), version-only bump (no fire), skip marker
(pass + warn), non-matching diff (pass), non-gh command (no-op).
Closes https://github.com/me2resh/apexyard/issues/112
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#113): require Testing section in PR body (config-driven) (#124)
Extends validate-pr-create.sh with a required-sections check that
replaces the hardcoded Glossary-only grep. The list of required H2
headings is project-configurable via `.pr.required_sections[]`.
Shipped default is ["Testing", "Glossary"], matching the canonical PR
description shape in workflows/code-review.md.
- Each entry must appear as `## <Name>` (case-insensitive).
- Empty sections are tolerated at this layer (the issue-structure hook
#107 does stricter empty-content checks for issue bodies; for PR
bodies, empty sections are left to the reviewer's judgement).
- Skip marker `<!-- pr-sections: skip -->` bypasses with a visible
stderr WARN — for trivial PRs (lint-only fixes, version bumps)
where the full template is overkill.
- Reads from project config via the shared _lib-read-config.sh (#109).
Inline fallback matches shipped defaults so bare checkouts predating
#109 keep working.
- 8 test cases cover: all-sections pass, each missing section,
missing-both (both errors printed), skip marker, case-insensitive
headings, H3 rejection.
https://github.com/me2resh/apexyard/issues/113
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#114): enforce single Closes-keyword per PR body (#125)
* chore(#114): enforce single Closes-keyword per PR body
Caps distinct auto-closing references (close/closes/closed, fix/fixes/
fixed, resolve/resolves/resolved + N or owner/repo+N) at one per PR
body. Closes the loophole where the title validator limited the title
to one ticket but multiple Closes lines in the body would still auto-
close all of them on merge.
- Scans stripped of fenced code blocks so closing keywords inside a
code sample do not count.
- Distinct counting: the same number referenced twice (e.g. via Fixes
and Closes) counts as one.
- Cross-repo refs (owner/repo+N) count normally.
- Opt-in escape hatch: pr.allow_multiple_closes=true in
project-config disables the check for teams that deliberately batch
rollbacks or dependency bumps.
- Per-PR bypass: a multi-close-approved HTML comment in the body
prints a visible stderr WARN and lets that PR through. Grep-able
trace so bypasses are auditable.
- 10 test cases cover: one close passes, no-keyword passes, two
distinct block, three mixed block, same-number-twice passes, code-
fence-ignored, skip marker, cross-ref without keyword, opt-in
config, cross-repo close.
Reads configuration via the shared _lib-read-config.sh (apexyard+109).
https://github.com/me2resh/apexyard/issues/114
* fix(#114): strip inline backticks and tilde fences from close-count scan
Rex caught a self-reflexive bug in the initial commit: documentation
mentioning closing keywords inside inline backticks (say a PR body
that explains the new hook with examples) counted as real closes, and
a skip marker inside inline backticks silently bypassed the check.
Future PRs that document the feature would trip the same trap.
Fix the code-region stripper to cover:
- Triple-backtick fences (already handled)
- Tilde fences (new)
- Inline-backtick spans (new)
Also run the skip-marker check against the stripped body, so a marker
used purely as documentation no longer activates a real bypass.
Three new test cases pin the behaviour:
- closing keywords in inline backticks are ignored
- skip marker inside inline backticks does NOT bypass
- tilde fences also get stripped
13/13 tests pass.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#108): add /tickets-batch skill for bulk-file flow (#127)
The fast happy path for filing 5–20 structured tickets in one intent
without dropping to raw `gh issue create` (non-conformant) or running
`/feature` 20 times serially (~100 turns of interview).
- .claude/skills/tickets-batch/SKILL.md — new skill spec. Asks
shared-context questions (priority, epic, area-labels, repo) ONCE
for the whole batch, then runs a ≤3-question micro-interview per
ticket (type, one-line purpose, optional clarification when the
inference is low-confidence). Confirms the full batch as a table,
then files each via specific `gh issue create` calls (never a
bulk JSON dump — the validator runs per-issue). Output conforms
to `.ticket.required_sections` by construction. Caps at 20
tickets per invocation.
- CLAUDE.md — added a row for /tickets-batch in the Available
Skills table; bumped the count references from 33 to 34.
Refs https://github.com/me2resh/apexyard/issues/108
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#117): add /fan-out skill + parallel-work rule doc (#128)
- Add `.claude/skills/fan-out/SKILL.md` — spawns N parallel Agent calls
in a single assistant message, with per-task agent type, worktree
isolation, and foreground/background mode. Caps at 5 concurrent
agents. Refuses fan-out when tasks share file write targets or have
sequential dependencies. Includes pre-spawn active-ticket safety
check and worktree merge-back flow that pauses on conflict.
- Add `.claude/rules/parallel-work.md` — trigger heuristic for when an
agent should proactively offer fan-out (>= 2 file-independent,
context-independent, individually substantial work items). Pairs
with the skill: rule says when, skill says how.
- Update `CLAUDE.md` — bump rules count to 9, skills count to 34, add
`/fan-out` row to the skills table.
Refs https://github.com/me2resh/apexyard/issues/117
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#116): adopt release-cut branch model (dev/main + tags) — framework only (#126)
* chore(#116): adopt release-cut branch model (dev/main + tags)
Formalises the dev/main split that already exists informally — dev is
the daily-work branch where every PR lands, main is release-only,
tagged with semver on each merge. Framework-only: managed projects
under apexyard governance stay trunk-based.
Added:
- AgDR-0007 — decision record (options table covers full git flow vs
trunk-only vs gitflow-lite; chose gitflow-lite)
- /release skill — diff dev against main, propose semver bump from
conventional commits, generate CHANGELOG, open release PR, tag
after merge
- docs/release-process.md — prose runbook for cutting a release
(manual fallback for the skill)
- .git.protected_branches in project-config.defaults.json
(main/master/dev/develop)
Modified:
- block-main-push.sh — now blocks direct pushes/commits to all
configured protected branches (was: hardcoded main/master). Reads
.git.protected_branches via the shared config reader (apexyard#109).
- CLAUDE.md — new section under Git Conventions explaining the
dev/main model + the framework-only scope. Skill table entry for
/release. Skills count bumped to 34.
- docs/multi-project.md — note that upstream/main is release-only
and the dev/main split is framework-only.
Non-consequences (per AgDR-0007):
- No release/* or hotfix/* branches. Hotfixes are normal patches
cut quickly. Revisit if multi-version maintenance becomes a need.
- No automatic on-merge issue closing for dev PRs. The release PR's
body aggregates all Closes references for the batch and triggers
auto-close en masse when it merges to main. Manual close in the
meantime.
- CI workflows trigger on pull_request regardless of base, so
dev-targeting PRs already get the full check matrix — no
workflow file edits needed.
https://github.com/me2resh/apexyard/issues/116
* fix(#116): satisfy markdownlint MD032 + MD060 in 116 docs
Auto-fix added blank lines around lists in AgDR-0007 (MD032) and
spaced the table separators in AgDR-0007 + multi-project.md (MD060).
Content unchanged.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* fix(#106): CHANGELOG fallback in drift hook for squash-merged forks (#129)
* fix(#106): CHANGELOG fallback in drift hook for squash-merged forks
The v1.1.0 tag-reachability check (`git tag --merged main`) misfires on
forks that sync via GitHub's default squash-merge: the squash collapses
the upstream-tag commit into a synthetic SHA, the tag stops being
reachable, and the banner keeps firing forever.
Discovered live on the first real-world `/update` flow: ops fork
squash-merged the v1.1.0 sync PR, banner kept saying "v1.1.0 available"
even though the fork was content-caught-up.
This commit adds a CHANGELOG-content fallback that fires only when the
primary tag check fails. If the fork's main has a heading
`## [X.Y.Z]` matching the upstream tag's version, treat the release as
absorbed and stay silent. Tolerant grep (matches the apexyard CHANGELOG
format from v1.1.0 onward, with leading-`v` stripping for tag→heading
conversion).
The merge-commit and rebase paths are unchanged — primary tag check
still works for them, and the fallback never fires when it shouldn't.
Test coverage (5 cases):
- squash-merge fork caught up to v1.1.0 → silent (the fix)
- merge-commit fork caught up to v1.1.0 → silent (regression check)
- fork stopped at v1.0.0 → banner fires
- fork has its own newer tag → silent
- squash-merge but no CHANGELOG on fork → banner fires (no false silence)
Records the strategy update in docs/agdr/AgDR-0008-…md (extends
AgDR-0005's tag-based-drift design).
https://github.com/me2resh/apexyard/issues/106
* fix(#106): satisfy markdownlint MD032/MD060 + shellcheck SC2164
Rex flagged two CI-blocking issues on the original 106 commit:
- AgDR-0008 had three bulleted sub-lists in the Consequences section
without surrounding blank lines (MD032). Added blanks and padded the
one tight-pipe table separator (MD060).
- The 106 test fixture had five subshell `cd "$fk"` calls without
`|| exit 1` (SC2164). Added the guard to all five.
5/5 tests still pass after the fix. No semantic change to the hook
or the test logic.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#130): add /validate-idea skill — lightweight pre-spec gate (#131)
A 10-minute, 5-question check that sits between /idea and /write-spec.
Designed for solo founders running ApexYard — not a heavyweight
methodology like event storming or Wardley mapping.
Five questions, asked one at a time:
1. Who is this specifically for?
2. What do they do today instead?
3. What's the smallest version that proves the value?
4. What would prove this is wrong? (kill criteria)
5. Build, buy, or rent?
Output: a one-page validation doc with a GREEN/YELLOW/RED verdict.
RED auto-updates the IDEA-NNN backlog row to WONTDO.
Integration:
/idea — adds an optional default-no "Validate now?" step after
capture (and after the optional GitHub Issue offer).
/handover — adds a conditional "this looks dormant, validate?"
step at the end of the integration plan, gated on the dormancy
heuristic (last commit > 90d AND zero open PRs AND no recent
issue activity). Healthy projects don't see the prompt.
CLAUDE.md skills count bumped to 35; new skills row added.
https://github.com/me2resh/apexyard/issues/130
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat: configurable voice prompts on assistant pause (AgDR-0009-voice-prompts-on-pause) (#135)
Stop hook that speaks the assistant's question aloud (Jarvis-style)
when it pauses for user input. Initial phase is macOS-only via `say`,
no voice input — user replies via keyboard.
Default OFF. Adopters opt in by overriding `voice_prompts.enabled` to
true in `.claude/project-config.json`.
Files:
- .claude/hooks/voice-prompt-on-pause.sh — Stop hook with config gate,
trigger heuristic (questions-only by default), markdown stripping,
sentence-boundary truncation, fire-and-forget say invocation
- .claude/hooks/tests/test_voice_prompt_on_pause.sh — 9 cases covering
disabled-default, enabled+question, enabled+statement, approved-pattern,
abc-menu, malformed-transcript, no-say-on-PATH, trigger-always,
markdown-stripping
- .claude/project-config.defaults.json — voice_prompts schema block
added (enabled, voice, max_chars, rate_wpm, trigger), default OFF
- .claude/settings.json — new Stop hook entry wired with the standard
ops-root resolver wrapper
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — design rationale,
options matrix (status quo / macOS say / cloud TTS / ML detection),
consequences, future phases
- docs/project-config.md — new "Voice prompts" section with override
examples and privacy notes
Test mode: hook respects VOICE_PROMPTS_SYNC=1 to run say synchronously
(test runners need this so assertions don't race against orphaned
background processes). Production invocations always run async.
Future phases (out of scope here, AgDR §"Future phases"):
- Phase 2: cross-platform TTS (Linux espeak, Windows SpeechSynthesizer)
- Phase 3: cloud TTS providers (OpenAI, ElevenLabs) — privacy AgDR-worthy
- Phase 4: voice input via Whisper-based STT
- Phase 5: per-message overrides
Refs: https://github.com/me2resh/apexyard/issues/134
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#141): add /debug skill — structured hypothesis-driven debugging (#142)
* feat(#141): add /debug skill — structured hypothesis-driven debugging
Adds a methodology skill that enforces five disciplines:
1. Capture the symptom precisely (exact URL, exact response, exact step)
2. Read the architecture before guessing (map every layer the request
touches, file by file)
3. Form a hypothesis ladder (3–5 candidates, each with an explicit
evidence test that confirms or refutes it)
4. Gather evidence first, fix second
5. Verify the fix against the original symptom evidence (re-run the
same `curl` / browser repro you used in step 4 — unit tests
verify code, not feature, correctness)
Stack appendices (Web, Desktop) carry stack-specific surface-evidence
requirements (step 1), architecture-surface maps (step 2), and
evidence-tests cookbooks (step 4). The methodology body stays portable
across stacks; appendices are where stack-specific knowledge accrues
over time.
Web appendix covers browser routing, framework configs (Next/Nuxt/Vite),
SPA-fallback layers, CDN, origin, the shared API client, backend
handlers, and auth providers. Desktop appendix covers Electron / Tauri
/ native-shell concerns: app entry points, IPC bridges, native modules,
auto-updater, sandbox / entitlements, code signing, crash reports.
Includes "When NOT to use" guidance so the methodology overhead doesn't
sandbag simple bugs (typos, off-by-ones, greenfield exploration).
Motivated by a real OAuth debug session in a managed project where
three sequential fixes chased adjacent symptoms because each was
hypothesis-then-fix without evidence in between. The skill is the
"never do that again" guardrail.
Closes #141
* fix(#141): scrub private project issue numbers from anti-pattern table
Rex review on PR #142 caught that line 155 of the skill's anti-pattern
table still named the originating PRs (#375, #377, #380) from the
private project where the methodology was first exercised. The PR body
and commit message were correctly abstracted earlier, but this in-file
reference slipped through — the leak-protection hook only scans gh
issue/pr writes, not staged file content, so mechanical enforcement
didn't catch it.
Replaced with "Three sequential PRs chasing the same symptom because
each was based on a different guess (no evidence test in between
cycles)" — same pedagogical value, zero attribution.
Refs #141
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(#143): document split-portfolio mode + add /setup privacy gate (#144)
Adopters on GitHub Free with any private project hit a silent privacy bug
following today's docs: forking apexyard makes a public fork that you cannot
later flip to private (GitHub policy), and committing `apexyard.projects.yaml`
+ `projects/<name>/` to the fork publishes private project names + handover
findings on a public GitHub repo.
This PR documents the supported workaround — split-portfolio mode — and
adds an upfront privacy gate to the /setup skill so new adopters never
hit the trip-wire silently.
docs/multi-project.md:
- New "Two setup modes — pick the one that matches your privacy needs"
section before TL;DR, with a side-by-side table and the explicit
trip-wire callout.
- Existing TL;DR retitled "TL;DR — single-fork mode (default)" with a
one-line pointer to the split-portfolio section.
- New "Split-portfolio mode — public framework + private portfolio"
section between the existing setup steps and the directory-layout
section. Includes:
- The two-repo layout (~/ops/apexyard public + ~/ops/portfolio private)
- 7-step setup walkthrough with copy-pasteable commands
- Daily workflow + upstream sync notes (both unchanged)
- Trade-offs (two repos to maintain, two clones per machine, one
upstream-sync conflict path on `projects/README.md`)
- "Migrating from single-fork to split-portfolio" recovery flow with
the explicit warning that GitHub Issue / PR edit history survives a
force-push and must be redacted separately
.claude/skills/setup/SKILL.md:
- New Step 2a: privacy gate — asks "are any projects private?" before
proposing the config. Branches on the answer:
- All public → single-fork mode
- GitHub Pro / Team / Enterprise → single-fork mode (private
forks of public repos
are supported on those
plans)
- Any private + GitHub Free → split-portfolio mode
- New Step 2b: walks through the split-portfolio setup interactively
(private repo create, sibling clone, gitignore + symlink) when the
privacy gate triggers.
- Detection: `test -L apexyard.projects.yaml` short-circuits Step 2b
for adopters already in split mode.
- Explicit "do NOT auto-migrate" rule for adopters already in single-fork
mode with private names already pushed — that path is destructive
(force-push history rewrite + redact issue/PR bodies + delete backup
branch) and warrants a deliberate, eyes-open run, not a /setup side
effect.
Out of scope for this PR (tracked separately on #143):
- `portfolio:` config block in `onboarding.yaml` schema
- Skill audit + refactor to honour configured `registry` / `projects_dir`
/ `ideas_backlog` paths instead of hardcoded fork-relative paths
- `/split-portfolio` migration helper skill that automates the recovery
flow currently documented manually
This is the docs-and-setup-question minimum-viable starter — the
framework code refactor is mechanical and lands as a follow-up.
Refs #143
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#145): portfolio config + self-healing + /split-portfolio helper (#147)
* feat(me2resh/apexyard#145): portfolio config block + self-healing + /split-portfolio helper
Closes the framework primitive deferred from me2resh/apexyard#144. Adds
first-class config-driven path resolution for the portfolio registry,
projects dir, and ideas backlog, with self-healing surfacing of broken
config at session start, plus a new /split-portfolio skill that automates
the destructive recovery flow.
Schema, helper, and hook:
- .claude/project-config.defaults.json: new portfolio: block
(registry, projects_dir, ideas_backlog) with defaults matching
today's single-fork layout
- .claude/hooks/_lib-portfolio-paths.sh: new sourceable helper exposing
portfolio_registry, portfolio_projects_dir, portfolio_ideas_backlog,
portfolio_validate, portfolio_clear_cache. Resolves relative paths
against the ops-fork root.
- .claude/hooks/check-portfolio-config.sh: new SessionStart hook —
silent on OK, one-line banner on broken config, never blocks session
- .claude/hooks/tests/test_portfolio_paths.sh: 13 cases covering
defaults, absolute/relative overrides, validate states, cache clear
Skill audit (18 SKILL.md files):
- Adds Path resolution callout pointing at the helper
- handover bash blocks now source helper and use $(portfolio_registry)
instead of literal apexyard.projects.yaml
- setup Step 2b now writes the portfolio: config block (recommended)
and validates via portfolio_validate before declaring success;
symlink approach kept as legacy fallback
New skill (.claude/skills/split-portfolio/SKILL.md):
- 10-step migration with explicit operator-confirmation gates at each
destructive step (force-push, body redaction, branch deletion)
- --verify mode: read-only state report (mode, paths, validate, drift)
- --dry-run mode: prints commands without executing
- Pre-flight refusals: already-private fork, paid GitHub plan, dirty
working tree, already-migrated state
- Step 9 writes the portfolio: config block (not symlinks) — symlink
fallback documented for adopters on older framework versions
- Step 9 surfaces the GitHub timeline-API survival caveat verbatim
- Idempotent re-runs: detects partial-migration state and resumes
Docs (docs/multi-project.md):
- Layout section describes both modes (config-block recommended,
symlink legacy) with self-healing notes
- Setup steps split into config-block mode and legacy symlink mode
- Migration section now points at /split-portfolio skill; manual
recipe preserved as fallback
AgDR (docs/agdr/AgDR-0010-portfolio-config-and-self-healing.md):
- Full Y-statement, options, decision, consequences, future phases
- Schema decision rationale: project-config.json over onboarding.yaml
because runtime path resolution belongs in project-config
Closes me2resh/apexyard#145
Refs me2resh/apexyard#146 (delivered same PR; closed manually post-merge
per the single-Closes-keyword rule in validate-pr-create.sh)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(me2resh/apexyard#145): markdownlint MD031 — blank lines around fences
CI's markdownlint-cli2 (v0.34.0) flagged the JSON + bash fenced code
blocks I added in setup/SKILL.md Step 2b without surrounding blank
lines. Added the required blanks. No content change.
Refs me2resh/apexyard#147
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>
* docs(me2resh/apexyard#148): correct privacy-gate wording — adopter action, not framework auto-publish (#149)
The privacy-gate wording introduced in PR #144 (and unchanged in PR #147)
attributed the publication to the framework rather than the adopter:
"the standard fork-and-commit setup will silently publish your private
project names on a public GitHub repo"
That's factually wrong. ApexYard never pushes anything without explicit
operator approval — the publication only happens when the adopter
themselves runs git push. The "silently publish" framing read as if the
framework auto-publishes, which is misleading and undermines trust in
the rest of the framework's safety claims.
Two prose-only edits, no code, no behavior change:
- .claude/skills/setup/SKILL.md Step 2a — replaced "will silently
publish ..." with adopter-action language ("you might accidentally
publish ... a stray git push after registering them — I won't push
without your approval, but the risk is on the adopter once the data
is committed locally")
- docs/multi-project.md trip-wire callout — replaced "silently publish
their portfolio names the moment they push" with "risk accidentally
publishing their portfolio names with a stray push (the framework
itself never pushes without operator approval, but once the registry
is committed locally the next push exposes it)"
Verified: `grep -r "silently publish" .claude/skills/ docs/` returns no
hits.
Closes me2resh/apexyard#148
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#150): bootstrap-skill exemption + Bash-write coverage (#152)
Closes the legitimate-bypass case (me2resh/apexyard#150) and the
illegitimate-bypass case (me2resh/apexyard#151) together so the
ticket-first gate is coherent. Shipping either alone would leave a
window where the framework is internally inconsistent — see AgDR-0011
for the full rationale.
Bootstrap exemption (me2resh/apexyard#150):
- .claude/session/active-bootstrap marker, written by /setup,
/handover, /update, /split-portfolio on entry; cleared on exit
- SessionStart sweep (clear-bootstrap-marker.sh) for stale markers
from interrupted sessions
- require-active-ticket.sh reads the marker and exempts skills on
the configured ticket.bootstrap_skills list
- bootstrap_skills list lives in .claude/project-config.defaults.json
(extendable per fork via .claude/project-config.json)
Bash-write coverage (me2resh/apexyard#151):
- new _lib-detect-bash-write.sh — heuristic detector for output
redirection, tee, sed -i, awk -i inplace, python/node/ruby
embedded interpreters
- require-active-ticket.sh + require-migration-ticket.sh now fire
on Bash in addition to Edit|Write|MultiEdit
- design choice: false-negatives preferred over false-positives
(the matcher errs toward "let through" rather than block legit
read-only commands)
Tests: 32 unit cases on the lib + 12 integration cases on the hook,
including the exact me2resh/apexyard#151 bypass repro from the issue
body.
Closes me2resh/apexyard#150
Will manually close me2resh/apexyard#151 post-merge per the
single-Closes-per-PR rule (precedent: AgDR-0010 / PR #147).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#153): extend Bash-write matcher beyond first-version coverage (#155)
Closes me2resh/apexyard#153.
Extends `_lib-detect-bash-write.sh` (introduced for me2resh/apexyard#151
in PR #152) with the matcher families flagged by Rex's review of #152.
AgDR-0011 already frames the matcher as a living list extended on
observation; this commit just walks the list.
New matcher families:
- File-moving builtins: `cp`, `mv`, `rm`, `dd`, `install` (anchored at
command-start; `--help`/`--version` and `git rm`/`git mv` excluded)
- Archive / network writes: `tar -x` / `tar --extract`, `curl -o` /
`--output`, `wget -O` / `--output-document`
- Additional embedded interpreters: `perl -e`, `php -r` (keyword-gated
like python/node/ruby); `go run`, `deno run`/`deno script.ts`,
`bun run`/`bun script.ts` (categorical script runners)
- Python helpers: `pathlib.Path().touch()`, `shutil.copy*`,
`shutil.move`, `os.rename` added to the `python -c` and python
heredoc keyword list
- Heredoc variants for `ruby` and `node` (previously only python
heredoc was covered)
Extractor extensions:
- `cp` / `mv`: last positional arg
- `curl -o` / `--output`: file argument
- `wget -O` / `--output-document`: file argument
- `tar -x`, `go run`, `deno`, `bun`, `perl -e`, `php -r`: return empty
(caller applies gate categorically per AgDR-0011)
Test count rose from 32 to 86. Negative-class counterexamples cover
the trickiest false-positive surfaces: `tar -t` listing, `cp --help`,
`rm --version`, `git rm`, `curl` bare URL fetch, `wget` bare URL fetch,
`deno fmt`, `deno test`, `go build`. Existing
`test_require_active_ticket_bash.sh` regression suite still passes 12/12.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(#154): mock gh in test sandboxes to remove live-tracker dependency (#156)
- add _lib-mock-gh.sh helper that installs a fake `gh` on the sandbox PATH;
intercepts `gh issue view <N> ... --json ...` and returns synthetic
`{"number":N,"state":"OPEN"}` (overridable per-num via mock_gh_set_state)
- wire the shim into test_single_closes_per_pr.sh and
test_validate_pr_required_sections.sh so the validator's CLOSED-issue
refusal no longer breaks the suite when upstream issues are closed
- both files previously failed every case (0/13 and 0/8) because their PR
titles reference #114 / #113, which are now CLOSED upstream
- post-fix: 13/13 and 8/8; full suite remains green
Closes me2resh/apexyard#154
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#132): structured CEO marker + same-turn merge in /approve-merge (#158)
* feat(#132): structured CEO marker + same-turn merge in /approve-merge
Closes me2resh/apexyard#132 (drop the "stop before merge" rule) and
me2resh/apexyard#48 (harden CEO marker against self-approval bypass)
together. The two threads compose — see AgDR-0012 for the full
rationale.
Streamline (#132):
- /approve-merge now runs `gh pr merge --squash --delete-branch`
in the same turn as the marker write, by default
- --no-merge opt-out preserves the deferred-merge case
- The discrete approval moment is the SKILL INVOCATION, not a
follow-up "now do the merge" message
Harden (#48):
- CEO marker is now a structured key/value file with required fields:
sha=<HEAD>
approved_by=user
skill_version=2
Validated by block-unreviewed-merge.sh; bare-SHA legacy markers
rejected with a clear "stale format" error pointing at /approve-merge
- The model's bare `echo SHA > <pr>-ceo.approved` bypass is now
mechanically rejected. Forging the structured fields requires a
deliberate, visible rule violation rather than a one-line accident
- Optional audit fields (approved_at, approval_summary) capture the
"what did the user say when they approved" trail
- Rex marker stays bare-SHA — different threat model (automated
reviewer, not human authorization moment)
pr-workflow.md reframed: "the load-bearing rule is explicit per-PR
approval, not two user messages." The merge is a deterministic
consequence of the approval invocation.
Tests: 12 cases on the hardened hook covering the new format end-to-end
(valid v2, missing rex/ceo, bare-SHA legacy rejected, missing
approved_by, wrong approved_by, skill_version=1, sha mismatch,
non-merge no-op, gh-api shape gated). Full suite: 205/205 across 13
test files.
Will manually close me2resh/apexyard#48 post-merge per the
single-Closes-per-PR rule (precedent: PR #152 / AgDR-0011).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#132): redact private project reference from AgDR-0012
Abstracted two references to a registered private project that named
the project's owner/repo. The leak-protection hook caught one in the
PR body; this fixup removes the matching references from the
AgDR-0012 file content (which would otherwise have shipped the names
to me2resh/apexyard public repo via the merge).
The pre-existing reference at .claude/rules/pr-workflow.md:130
(documenting #47) is untouched — it predates this PR and is already
on the public repo's history.
Refs me2resh/apexyard#132.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#132): markdownlint blanks-around-fences + typo fix
Two small fixups against red CI / Rex feedback:
- AgDR-0012 line 63: fenced code block now has a blank line before it
(MD031). markdownlint-cli2 0.13.0 was rejecting the indented fence
inside the bullet because the fence's preceding line was the bullet
text (no blank).
- approve-merge SKILL.md line 172: typo `deferes` → `defers` (Rex flag
on PR #158).
Refs me2resh/apexyard#132.
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>
* chore(#157): remove voice-prompts feature + correct hook/skill counts (#161)
* chore(#157): remove voice-prompts-on-pause feature + correct hook/skill counts
Closes me2resh/apexyard#157 (sunset the voice-prompts feature) and
me2resh/apexyard#77 (hook count off-by-one in CHANGELOG / CLAUDE.md)
in one bundled PR. AgDR-0013 supersedes AgDR-0009 with the full
rationale; both AgDRs are preserved (decision records are append-only
history).
Removed (#157):
- .claude/hooks/voice-prompt-on-pause.sh
- .claude/hooks/tests/test_voice_prompt_on_pause.sh
- Stop matcher block in .claude/settings.json (became empty after
voice removal)
- voice_prompts block in .claude/project-config.defaults.json
- "## Voice prompts" section in docs/project-config.md
- voice_prompts mention in AgDR-0010 line 32 (replaced with
leak_protection / ticket as still-current example config blocks)
Preserved:
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — historical record;
new "Superseded by: AgDR-0013" header at the top
- AgDR-0010 line 115 reference to AgDR-0009 — still accurate as a
historical pattern reference
Counts corrected (#77):
- CHANGELOG.md v0.3.0 stats: "17 hooks" → "18 hooks" (historical
fix — at v0.3.0 there were actually 18 hooks)
- CLAUDE.md table line: "18 shell scripts" → "24 shell scripts"
(current count after this removal)
- CLAUDE.md table line: "35 slash commands" → "39 slash commands"
- CLAUDE.md "Available skills (34)" → "Available skills (39)"
- CLAUDE.md quick-reference "Skills (35 slash commands)" →
"(39 slash commands)"
Why bundled: #77's correct count depends on whether voice is still in
the framework. Shipping #77 before #157 would write a number that's
wrong by one again the moment #157 lands. Same shape as previous
bundles (PR #152 / AgDR-0011, PR #158 / AgDR-0012).
Why no adopter-facing changelog mention of the voice removal: the
feature never reached a tagged release on main. v1.1.0 didn't have
it; v1.2.0 won't have it. From the adopter's perspective there's
nothing to retire. AgDR-0013 captures the framework's internal record
for future contributors. See AgDR-0013 § "No adopter-facing changelog
mention".
Tests: full hook test suite green (196 cases across 12 files —
test_voice_prompt_on_pause.sh removed). No regressions.
Will manually close me2resh/apexyard#77 post-merge per the
single-Closes-per-PR rule (precedent: PRs #152, #158).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#157): unwire voice from settings + configs + docs + AgDR-0013
Continuation of d09d7b5 (the file deletions). Squash-merge will
collapse both into one PR commit. This commit captures:
- .claude/settings.json — Stop matcher block removed (was the only
hook in it; entire matcher gone)
- .claude/project-config.defaults.json — voice_prompts block + its
_comment removed
- docs/project-config.md — "## Voice prompts" section removed
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — "Superseded by"
header added at the top, content otherwise preserved as history
- docs/agdr/AgDR-0010-portfolio-config-and-self-healing.md — line 32
example reference swapped from voice_prompts to
leak_protection / ticket (still-current config blocks)
- docs/agdr/AgDR-0013-sunset-voice-prompts.md — new supersession AgDR
- CLAUDE.md — hook count 18 → 24, skill count 35 → 39 (three
occurrences each, all aligned to current reality)
- CHANGELOG.md v0.3.0 stats — "17 hooks" → "18 hooks" historical fix
(#77 acceptance criterion 1)
Refs me2resh/apexyard#157 + me2resh/apexyard#77.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#157): markdownlint MD028 + resolve stale AgDR-numbering ref
Three small fixups against Rex CHANGES-REQUESTED on PR #161:
- AgDR-0009 line 3: promote "Superseded by:" header out of a
blockquote. The original "I decided ..." canonical blockquote at
line 5 was being merged with the new supersession blockquote
(markdownlint MD028 — "no blanks inside blockquote").
- AgDR-0013 line 3: same shape — "Supersedes:" header now a plain
bold paragraph, canonical "I decided ..." blockquote untouched.
- approve-merge SKILL.md line ~187: stale conditional reference
"AgDR-0012 (or 0013 — depends on whether voice-removal lands
first)" — order is now resolved (12 = approve-merge bundle, 13 =
voice removal). Drop the parenthetical.
Refs me2resh/apexyard#157 + me2resh/apexyard#77.
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(#160): multi-tab terminal demo on the landing site (#162)
Landing-site `site/index.html` terminal demo previously played one
canonical flow ("one ticket, start to finish") on autoplay. With the
v1.2.0 skill surface expansion (4 new skills, 8+ new hooks), one flow
no longer represents the framework's breadth.
Adds tabs to the terminal chrome — four flows visitors can either let
auto-cycle or click directly:
1. one ticket — existing flow, unchanged content
2. /handover — adopt an external repo into the portfolio
3. /setup — first-run framework bootstrap on a fresh fork
4. /fan-out — spawn 3 parallel agents on independent tickets
Auto-advance: on completion of the active tab's script, the demo
pauses ~1.8s then advances to the next tab. Loops at the end. User
can interrupt by clicking any tab or hitting Replay.
Implementation:
- HTML chrome — single title span replaced with a tablist of 4 button
tabs. ARIA `role="tablist"` / `role="tab"` / `aria-selected` so
keyboard + screen-reader users get the same semantics as sighted
ones.
- CSS — new `.shell-demo__tabs` + `.shell-demo__tab` with an accent
underline on the active tab. Tabs scroll horizontally on narrow
viewports (mobile responsive).
- JS — refactored the existing IIFE from one `script` array to an
array of four. Added `setActiveTab()` for ARIA state, `play(idx)`
takes a tab index, end-of-script auto-advances to `(idx + 1) % N`
unless the user clicked away during the pause. Adds a new `cmd`
type alongside `you` for slash-command invocations (renders with
the same `>` prompt prefix). prefers-reduced-motion still bails
early and leaves the static seed visible.
- Static seed (the non-JS fallback) still shows tab 0's content, so
reduced-motion / no-JS visitors see the one-ticket flow as before.
Hero metrics also corrected to current reality (#77 / PR #161 covers
CLAUDE.md and CHANGELOG.md; this PR catches the same numbers in the
landing site):
Skills 32 → 39
Hooks 18 → 24
The tabs ship in v1.2.0 alongside the framework changes that make
the new flows worth showcasing.
Refs me2resh/apexyard#160 (release v1.2.0 + landing-site refresh).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#163): default the split-portfolio sibling repo name to <fork>-portfolio (#164)
Closes me2resh/apexyard#163.
The split-portfolio mode docs and skills previously suggested
`your-org/ops` as the default name for the private sibling repo, and
`portfolio/` as the local clone directory. Both too generic — adopters
running multiple ops setups end up with `your-org/ops` collisions, and
a bare `portfolio/` dir gives no signal about which framework it
belongs to when it sits next to other unrelated `portfolio/` dirs.
`<fork>-portfolio` is now the default — keeps the relationship to the
public fork explicit on disk and on GitHub. If the fork is named
`your-org/apexyard`, the portfolio defaults to
`your-org/apexyard-portfolio`. If the fork was renamed (e.g. `cos`),
the portfolio defaults to `cos-portfolio`. Adopters with custom names
keep working — the `portfolio:` config block resolves whatever path
they configured.
Files updated:
docs/multi-project.md
- Layout diagrams use `apexyard-portfolio/` as the sibling
- Setup walkthrough Step 2 + Step 3 use `your-org/apexyard-portfolio`
and explain the `<fork>-portfolio` pattern
- Config-block + symlink path examples updated to
`../apexyard-portfolio/...`
- Daily workflow + cross-machine clone commands updated
- The two existing `your-org/ops` references that remain are
fork-rename examples (lines 52, 64) — kept as-is, since renaming
the fork to `ops` is still valid (the portfolio would then
default to `ops-portfolio`)
.claude/skills/setup/SKILL.md
- Step 2b's "default suggestion" for the private repo name is now
`your-org/<fork>-portfolio`, computed dynamically from the
fork's repo name via `gh repo view --json name -q .name` so the
suggestion is correct even when the fork was renamed
- Clone command no longer needs a second arg — the repo name IS
the directory name
- Config-block paths updated to `../apexyard-portfolio/...`
.claude/skills/split-portfolio/SKILL.md
- Step 3's suggested-name template is now `<account>/<fork>-portfolio`
with the same dynamic-fork-name resolution
Mechanism unchanged. The `portfolio:` config block in
`.claude/project-config.json` still takes any path; this PR is purely
default-suggestion + example prose.
No tests required (skills are markdown instructions; no automated
coverage today).
Refs `apexyard.projects.yaml.example` uses "ops repo" as a generic
term meaning "the operational management fork" — not a name — kept
as-is.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#165): skills reference page on the landing site + changelog link (#167)
Closes me2resh/apexyard#165.
Adds a public, browseable index of every apexyard slash command
alongside a one-click changelog link from the homepage nav.
site/skills.html (new):
- Lists all 39 skills currently shipping in .claude/skills/
- Each entry: slash command, argument hint, description (taken
verbatim from the SKILL.md frontmatter so the page matches the
runtime exactly)
- 10 categories: Setup & onboarding, Daily ops, Tickets & ideas,
Specs & decisions, Code review & merge, Architecture & dev tools,
Production-readiness audits, Workflow primitives, Communications,
Deprecated
- Same brutalist-terminal design tokens as the homepage — JetBrains
Mono, paper-cream background, single warning-red accent, sharp
corners. Inlined CSS to keep the static-only no-build-step
convention; design vars duplicated rather than extracted to a
shared file (~18 vars; cheap to keep in sync).
- Mobile responsive — skill grid collapses to single-column under
720px; titlebar nav hides non-CTA items on narrow viewports.
- Reduced-motion friendly (no animation in the first place).
- Internal anchor TOC at the top so the page scans in seconds.
site/index.html (nav addition):
- Added two nav links to the titlebar between "what's in the box"
and the github CTA:
• skills → ./skills.html
• changelog → https://github.com/me2resh/apexyard/releases
- The changelog link points at the GitHub releases page (not the
raw CHANGELOG.md file) so it auto-resolves to the latest tagged
release on each visit. v1.2.0 lands and the link is already there.
No new dependencies, no build step, no JS for the skills page. The
existing site convention (one-html-file-per-route, inlined CSS,
optional progressive-enhancement JS) is preserved.
Refs me2resh/apexyard#160 (release v1.2.0 + landing-site refresh) —
this is the second site-side deliverable for that ticket; the release
tag itself follows once me2resh/apexyard#159 (testing) closes.
Follow-up worth a separate ticket: a small generator script that
walks .claude/skills/*/SKILL.md, parses YAML frontmatter, and emits
the skills.html sections automatically. Out of scope for v1.2.0 —
first version is hand-curated and will need maintenance until that
generator lands.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#168): accept release/vN.N.N branches + release(...) PR titles (#169)
Closes me2resh/apexyard#168.
The /release skill prescribed `release/vA.B.C` as the source-branch
name and `release: vA.B.C` as the PR title for the dev → main release
PR (per AgDR-0007). Both were rejected by the framework's own
validators:
validate-branch-name.sh required {type}/{TICKET-ID}-{description};
release/v1.2.0 has no ticket-id portion.
validate-pr-create.sh required type(SCOPE): form with `release` not
in pr.title_type_whitelist.
The contradiction surfaced cutting v1.2.0 — the first release under
the dev/main model.
Three small changes:
1. .claude/hooks/validate-branch-name.sh — added an early-out branch
that accepts ^release/vN.N.N(-rcN)?$ as a valid name. Narrow,
intentional exception for the framework's release-cut convention;
release branches don't carry a ticket-id because the release itself
IS the ticket.
2. .claude/project-config.defaults.json — added "release" to
pr.title_type_whitelist so a title like `release(#160): v1.2.0`
passes validate-pr-create.sh's existing regex unchanged.
3. .claude/skills/release/SKILL.md step 4 — corrected the prescribed
PR title to `release(#<release-ticket>): vA.B.C` so future /release
invocations produce a title that satisfies the validators by
construction.
Tested:
bash .claude/hooks/validate-branch-name.sh against:
release/v1.2.0 → 0 (allowed, release-special-case)
release/v1.2.0-rc1 → 0 (allowed, RC variant)
release/v9.9.9 → 0 (allowed)
release/foo → 2 (correctly blocked)
release/v1 → 2 (correctly blocked)
chore/GH-168-fix → 0 (allowed, standard pattern)
feature/GH-1-x → 0 (allowed, standard pattern)
Full hook test suite: 196/196 cases green across 12 test files.
Refs: surfaced 2026-05-04 cutting the first release under AgDR-0007.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#170): exempt release/vN.N.N from validate-pr-create's branch-id check (#171)
Closes me2resh/apexyard#170.
Completes the work started in #169 (closing #168). #169 added a
release-pattern early-out to validate-branch-name.sh so release/vN.N.N
branches pass the branch-name validator. But validate-pr-create.sh has
its own independent branch-id check at line 273 that #169 didn't
touch — and it still rejects release/v1.2.0 because that name doesn't
contain a ticket-id substring.
This is the same class of contradiction #168 fixed; the fix is the
same shape. Add the same release-pattern early-out to the branch-id
check in validate-pr-create.sh:
- if the branch matches ^release/vN.N.N(-rcN)?$ → exempt (release
branches don't carry ticket-ids; the release itself is the ticket)
- otherwise → require a ticket-id substring as before
#168's acceptance criterion 3 ("validate-pr-create.sh accepts a PR
title `release(#160): v1.2.0` against the `release/v1.2.0` branch")
was checked off based on the title regex alone but didn't catch the
secondary branch-id check living in the same file. Surfaced trying
to open the v1.2.0 release PR.
Tested:
bash .claude/hooks/validate-pr-create.sh against:
release/v1.2.0 → 0 (allowed, exempt)
release/v1.2.0-rc1 → 0 (allowed, RC variant)
chore/GH-1-fix → 0 (allowed, has ticket-id)
release/foo → 2 (correctly blocked)
chore/no-ticket → 2 (correctly blocked)
Refs me2resh/apexyard#168 (the parent bug) + #169 (the partial fix).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#173): sync CHANGELOG.md from main → dev (#174)
Closes me2resh/apexyard#173.
The release-cut model (AgDR-0007 / #116) squash-merges
release/vN.N.N → main, which means the v1.2.0 CHANGELOG section that
landed on main via PR #172 was never propagated back to dev. This
commit copies main's CHANGELOG.md verbatim onto dev so the v1.2.0
section is now present on both branches.
The diff is exactly the v1.2.0 entry being prepended; no other lines
change.
Without this sync, the next release PR cut from dev would build a
v1.3.0 section on top of v1.1.0, silently dropping v1.2.0 from dev's
running history. The corollary skill-level fix (option B in the
ticket) — updating /release to source the previous CHANGELOG from
upstream/main — is filed as a follow-up and out of scope for this PR.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#181): /agdr skill — searchable AgDR library (#186)
Adds /agdr — a portfolio-wide index for Agent Decision Records.
Walks apexyard.projects.yaml, reads each project's docs/agdr/*.md
(local clone if available, else gh api fallback), parses the optional
YAML frontmatter for category + projects, and answers four queries:
- /agdr browse list across the portfolio, grouped by category
- /agdr search <term> full-text grep across all bodies, returns
<project>/AgDR-NNNN paths + matching paragraph
- /agdr show <id> print a specific record, disambiguates duplicates
- /agdr stats counts per category (the marketing-slide tile,
now backed by real data)
Six-category taxonomy: architecture | tech-stack | security | patterns
| integrations | other. Legacy AgDRs without frontmatter remain
first-class — they bucket as `other` and are flagged in browse so
operators can migrate at their own pace.
Backwards-compatible template change: templates/agdr.md gains an
optional `category:` (and optional `projects:`) line in the existing
frontmatter block. Omitting the line keeps every existing AgDR valid;
the skill defaults to `other` when missing.
Doc note in workflows/sdlc.md § Phase 2 points at /agdr search for
"have we decided this before?" lookups before drafting a design.
Smoke test in .claude/hooks/tests/test_agdr_skill.sh covers the
parser the spec specifies — frontmatter extraction, category
bucketing (including the legacy default-to-other path), id reading,
stats aggregation, and search match counts. 18 assertions, all green;
12 pre-existing test suites also green (no regressions).
Closes #181
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(me2resh/apexyard#183): /launch-check trend tracking (#185)
- Persist each run as JSON under <projects_dir>/<name>/launch-check/runs/
(timestamp + branch + commit + per-dimension scores + verdict + top_risks)
- Append "Trend (last 5 runs)" section to the per-run summary when at least 2
prior runs exist — markdown table + ASCII score chart
- Add /launch-check trend mode for read-only trend rendering (no full audit)
- Auto-derive notes column from score-delta vs previous run
(e.g. "Security +12, Analytics +10")
- Opt-in commit via .launch-check-history-tracked marker; gitignored by default
- New helper script render-trend.sh + test_launch_check_trend.sh (21 cases)
- Resolve projects dir via portfolio_projects_dir helper (no hardcoded path)
- Schema is forward-compatible — extra fields preserved on framework upgrade
AgDR-0014 documents the chart format / schema / opt-in choices.
Closes me2resh/apexyard#183
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#182): /status --briefing + bin/apexyard status CLI shim (#187)
* feat(me2resh/apexyard#182): /status --briefing + bin/apexyard CLI shim
Make slide 6's `$ apexyard status` invocation real — the smallest of three
slide-reality tickets, demonstrating the marketing-to-implementation
pattern before #181 / #183 land.
- New helper at .claude/skills/status/briefing.sh — computes the 4-line
"where am I" briefing (active workspace, active ticket, branch,
role-set) so the logic is testable in isolation and runnable from a
plain shell. Workspace inference walks up from cwd to the ops-fork
root (same algorithm as _lib-portfolio-paths.sh and /start-ticket).
- Ticket reads from .claude/session/tickets/<workspace> first, falls
back to .claude/session/current-ticket. Role-set is inferred from
the active ticket's GitHub labels (v1: backend / frontend / qa /
security / platform / sre / data / ux / ui / product / tech-lead,
plus the long forms). No match emits the explicit "<none — inferred
per task>" placeholder so the four-line shape is constant.
- New CLI shim bin/apexyard delegates `apexyard status` to the same
helper after walking up to find the ops-fork root. Works from any
workspace/<name>/ clone or the fork itself; symlink onto PATH to
install (no shadowing of the `claude` binary).
- /status SKILL.md gains a "Briefing mode" section documenting the
--briefing / -b flag; default /status output is unchanged.
- docs/multi-project.md "Daily workflow" demonstrates the new
invocation alongside /inbox and /status.
- New smoke tests at .claude/hooks/tests/test_status_briefing.sh — 8
cases covering ops-root cwd, workspace cwd, unknown cwd, ops-fallback
marker, per-project marker priority, label-based role inference, the
no-matching-label path, and the constant-four-line shape.
Closes me2resh/apexyard#182
* fix(me2resh/apexyard#182): replace curios-dog with example-app in SKILL.md output sample (leak fix)
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(#178): LSP integration spike — measurement + recommendation (#184)
* docs(#178): LSP spike — token-savings measurement + integration findings
- Phase 1: estimated token cost A vs B for three representative queries on a real TS Lambda backend (~9,750 LOC); shallow semantic queries see ~3-23x input-token savings, multi-hop traces see ~1.4x
- Phase 2: verified Claude Code shipped first-party LSP support in v2.0.74 (Dec 2025), gated behind ENABLE_LSP_TOOL=1, wired in via the plugin system (.lsp.json); MCP-wrapped LSP shims (cclsp, lsp-mcp) remain as fallback
- Phase 3: recommends Option 3 — interactive clone-first prompt at end of /handover, preserving today's "never auto-clone" principle while making the deep-dive path discoverable
- Phase 4: AgDR Y-statement and option matrix sketched; full AgDR is a follow-up if the spike says go
- Recommendation: GO. Adopt the built-in tool, document the opt-in path, change /handover to offer clone-first interactively. No code changes beyond /handover SKILL.md; no novel integration to maintain
Closes me2resh/apexyard#178
* docs: fix markdownlint MD031/MD032 in spike report
Add blank lines around lists and a fenced code block to satisfy
markdownlint-cli2 in CI. Pure formatting, no content change.
Refs me2resh/apexyard#178
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(me2resh/apexyard#190): annotate LSP-aware skills with opt-in callouts (#191)
- Add identical-shape "LSP-aware (optional, recommended)" callout near
the top of each of the four code-aware SKILL.md files.
- Per-skill savings number is the only variation:
- /code-review: ~3-15× cheaper for semantic queries
- /threat-model: ~3-15× shallow, ~1.4-5× multi-hop traces
- /security-review: ~3-15× cheaper for semantic querie…
* fix(me2resh#393): mobile UX regressions — nav main pages + eyebrow + content polish (me2resh#394) Seven findings from Iman's mobile UX audit on the v2.0.0 production site (4 iPhone screenshots from CEO at 08:59 / 09:00). Primary fix addresses a real nav regression introduced when the quickstart chip landed in PR me2resh#387 without also marking the main-page links as visible-on-mobile. Fixes (cross-page): 1. HIGH — Mobile nav was dropping the 3 main-page links on every interior page. The <700px collapse rule `.titlebar__right a:not(.is-cta):not(.always) { display: none }` was hiding everything except the current-page link + quickstart + github →. Mobile readers couldn't navigate between sections. Fix: added class="always" to the 3 main-page nav links on all 4 pages. Mobile nav now shows: how it works · quickstart · architecture · skills · github → (5 items consistently). The changelog → link stays hidden on mobile (acceptable — external one-off). 2. MEDIUM — Copy-as-Markdown-for-AI button floated orphaned on mobile because the eyebrow had no explicit flex layout (the button's inline margin-left:auto was a no-op). Fix: made .page-header__eyebrow + .hero__eyebrow flex-wrap containers with explicit gap. Added <700px override so the button drops to its own line below the pill+subtitle, sitting flush-left rather than awkward-right. 3. MEDIUM — Eyebrow subtitles too long on mobile. Trimmed: - architecture: "one fork, five layers, optional sibling repo" -> "5 layers, optional sibling repo" - skills: "53 active slash commands - grouped by what you're trying to do" -> "53 active slash commands, grouped by outcome" - how-it-works: kept ("one realistic day, in plain English") 4. MEDIUM — how-it-works.html had visible content redundancy. Tagline + ai-lead both said "a walkthrough of one realistic day". The ai-lead was added in PR me2resh#387 for /geo-audit G12 (first-500- tokens lead). Fix: CSS-hidden via visually-hidden pattern (.ai-lead-hidden class — clip + position:absolute + 1px). DOM stays for AI crawlers + screen readers (aria-hidden="false" preserves access); sighted users no longer see the duplicated prose. Homepage hero polish (LOW): 5. "Built by me2resh" moved from BETWEEN tagline and subhead to AFTER the CTAs. Previously broke the headline -> subhead reading flow; now sits as a footer-flavoured byline below the CTA block. 6. Version line ("v1.1.0 · changelog") dimmed further (opacity 0.7 -> 0.55, font-size 14px -> 13px) so it doesn't compete with the tagline for first glance. 7. Hero inline link "See what a day with it looks like ->" was wrapping mid-phrase on mobile. Shortened to "See a day with it ->" + added white-space:nowrap so it stays as one unit. Site-counts drift: PASS. (Subtitle change for skills had to keep "active" between digit and "slash commands" so the drift regex doesn't flag it as a 53-vs-54 mismatch — same pattern used since v2.0.0 prep.) Closes me2resh#393 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> * chore(me2resh#395): CHANGELOG v2.0.1 — mobile UX hotfix - Append [2.0.1] section above [2.0.0] - Site-only release; framework unchanged - 7 fixes from PR me2resh#394 (mobile nav, eyebrow stacking, content redundancy, hero polish, subtitle trims) Refs me2resh#395 --------- Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
…h#400) * fix(me2resh#399): site/ — GA4 + consent banner on all 4 pages - Copy gtag.js + Consent Mode v2 init + cookie consent banner from index.html to how-it-works.html, architecture.html, skills.html. - Wrap each block with begin/end markers so future sync edits are greppable across pages. - Remove dead `anonymize_ip: true` from gtag config (no-op in GA4 — all IPs auto-anonymized; carryover from Universal Analytics). Closes me2resh#399 * fix(me2resh#399): refresh llm:token-count + llm:doc-length meta tags The GA4 + consent block additions in the prior commit grew each page by ~5–6K chars. Refreshing the per-page LLM payload-size meta tags on all 4 site files (not just the 2 the test_site_counts.sh loop scans) so token-budget-aware clients see accurate costs. - index.html: 23189 → 23697 tok / 92757 → 94791 chars - how-it-works.html: 6572 → 8065 tok / 26291 → 32263 chars - architecture.html: 8864 → 10201 tok / 35458 → 40807 chars - skills.html: 10658 → 11920 tok / 42632 → 47681 chars Verified: test_site_counts.sh PASS — all scanned files within 5% of actual. Refs me2resh#399 --------- Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
…h#402) - Append [2.0.2] section above [2.0.1] - Bump site/index.html version pill v2.0.1 → v2.0.2 - Site-only release; framework unchanged - Underlying fix: me2resh#399 / PR me2resh#400 (GA4 + consent on all 4 pages) Refs me2resh#401 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* fix(me2resh#404): remove stale --pdf-output-folder flag from md-to-pdf dispatch (me2resh#405) md-to-pdf removed --pdf-output-folder and --dest-name in a breaking API change; convert.sh still passed both flags, causing every npx md-to-pdf invocation to abort with: ArgError: unknown or unexpected option: --pdf-output-folder Fix: stage the source into a private temp dir under the desired output stem (e.g. WORK/out.pdf → WORK/out.md), invoke `npx -y md-to-pdf <tmp>.md`, then mv the resulting <tmp>.pdf to the requested TO_ABS. md-to-pdf writes <basename>.pdf next to the source by default, so this is compatible with every currently-released version. The same fix is applied to the md-to-pdf-html branch (HTML → PDF fallback), which had identical stale flags. Adds regression test at .claude/skills/pdf/tests/test_md_to_pdf_fallback.sh covering four cases: successful conversion, absence of the stale --pdf-output-folder flag, absence of --dest-name, and custom output path resolution. All four pass locally against the current npm latest. Refs me2resh#404 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * feat(me2resh#403): /release-sync — main→dev sync after each release (me2resh#406) * feat(me2resh#403): /release-sync skill — main→dev sync after each release Introduces the /release-sync skill to address squash-merge divergence that accumulated across releases and produced 99 conflicts on the v2.0.0 release PR. After each release, /release-sync vX.Y.Z opens a sync PR that merges upstream/main into upstream/dev with -X ours (dev wins on conflicts), making the squash commit an ancestor of dev so future release PRs only show genuinely-new commits. - .claude/skills/release-sync/SKILL.md — new skill spec with pre-flight checks, idempotent no-op, edge-case table, and PR body template - .claude/skills/release/SKILL.md — adds Step 9 (mandatory invoke of /release-sync) and updates Related section - docs/agdr/AgDR-0052-release-sync-skill.md — records the design choice (explicit skill vs auto-invoke vs switch-to-merge-commit) and the merge-strategy direction reasoning (-X ours on a dev-based branch) - .claude/hooks/tests/test_release_sync.sh — 11 sandbox-based smoke tests covering already-in-sync no-op, diverged detection, branch name shape, -X ours direction, idempotence, post-sync state, and version argument validation; all 11 pass Refs me2resh#403 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(me2resh#403): refresh site skill-count (54→55) + 2 MD032 lint fixes Address red CI on Rex-approved release-sync PR: - site-counts drift: new /release-sync skill bumps total to 55. Update all 12 marketing-copy references across architecture.html, *.md.gen, llms.txt, llms-full.txt, skill.md (incl. 2 multiline / phrasing-variant references caught by the post-me2resh#377 cross-newline scanner). - markdownlint MD032: add blank lines around the bullet lists in release-sync SKILL.md (line 80) and AgDR-0052 (line 36). Refs me2resh#403 --------- Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> * chore(me2resh#408): CHANGELOG v2.1.0 + site version pill bump - Append [2.1.0] section above [2.0.2] - Bump site/index.html version pill v2.0.2 → v2.1.0 - Last cherry-pick release; /release-sync now live for v2.2.x+ Refs me2resh#408 --------- Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(#109): project-configurable ticket / branch / commit / PR schema (#118)
* chore(#109): project-configurable ticket / branch / commit / PR schema
Lift the prefix / type whitelists hardcoded across skills, hooks, and
CI into a versioned JSON config read through a shared shell library.
Shipped defaults at .claude/project-config.defaults.json; per-fork
overrides at the optional .claude/project-config.json; one reader
(_lib-read-config.sh) that every consumer now uses.
Added:
- .claude/project-config.defaults.json (v1 schema)
- .claude/hooks/_lib-read-config.sh (shared reader)
- docs/project-config.md (schema reference + extension guide)
- docs/agdr/AgDR-0006-project-configurable-ticket-schema.md
Migrated (still pass with no config present via last-resort fallback):
- validate-branch-name.sh → .branch.type_whitelist
- validate-commit-format.sh → .commit.type_whitelist (legacy
`commit_types` top-level key honoured as backward-compat fallback)
- validate-pr-create.sh → .pr.title_type_whitelist
- /feature, /task, /bug skills reference the config in their Rules
sections; none hardcodes the list any more
Unlocks subsequent config-readers for #107 / #110 / #111 / #112 /
#113 / #114 / #115 — each extends the schema under its own subtree
without further changes to the loader.
https://github.com/me2resh/apexyard/issues/109
* fix(#109): satisfy markdownlint MD032 and MD060 on new docs
Auto-fix MD032 (blank lines around lists) in AgDR-0006 and format
table-separator rows with surrounding spaces (MD060) in both new
doc files. Content unchanged; CI green.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#110): add block-private-refs-in-public-repos.sh hook (#119)
Adds a new PreToolUse hook that blocks gh issue/PR/comment creation and
gh api .../issues|/pulls calls targeting a public framework repo
(default: me2resh/apexyard + whatever `upstream` resolves to) when the
title or body references any registered private project from
apexyard.projects.yaml (by name, repo slug, owner/repo#N ticket ref, or
workspace path).
The hook is a sibling to check-secrets.sh — both scan outgoing content
for identifiers that should never leave the local environment. Skip
marker `<!-- private-refs: allow -->` in the body lets a deliberate
reference through with a visible warning.
Files touched:
- .claude/hooks/block-private-refs-in-public-repos.sh (new)
- .claude/hooks/tests/test_block_private_refs.sh (new)
- .claude/rules/leak-protection.md (new)
- .claude/settings.json (wire PreToolUse matchers for the 5 gh shapes)
- docs/rule-audit.md (append section 10 + bump counts)
Refs: https://github.com/me2resh/apexyard/issues/110
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#115): add warn-stale-review-markers.sh PostToolUse hook (#120)
Fires after `git push` to surface review markers that have gone stale
because new commits were pushed past an existing Rex / CEO / design
approval. The merge gate already catches this at `gh pr merge` time,
but only then -- this hook closes the gap by flagging it immediately
at push-time so the author isn't surprised at merge.
- `.claude/hooks/warn-stale-review-markers.sh`
- PostToolUse, non-blocking (PostToolUse exit 2 would push noise
into the conversation; this hook is purely informational).
- Resolves the PR HEAD via `gh pr view --json headRefOid` -- same
source-of-truth as the merge-gate hooks post-apexyard#47 / #55.
Falls back to local HEAD with a visible WARN when gh is offline.
- Silent on: no PR for branch, no markers, fresh markers,
failed push (detected via `rejected` / `failed to push` /
`fatal:` / `error:` markers in tool_response.stderr).
- Modes: `warn` (default) prints one stderr line per stale marker;
`delete` opts in to auto-removal via
`.claude/project-config.json` -> `review_markers.on_stale`.
TODO(apexyard#109): switch to the shared project-config reader
once it lands.
- `.claude/settings.json`
- Wires the hook on PostToolUse / Bash / `git push *`.
- `docs/rule-audit.md`
- Adds a row under section 3 (Code review & PR quality) and
bumps the mechanized count 26 -> 27 / total 73 -> 74.
- `.claude/hooks/tests/test_warn_stale_review_markers.sh`
- 8 cases: no PR, no markers, fresh markers, stale rex / ceo /
design (warn), delete mode, failed push. All pass locally.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#111): upgrade pre-push-gate from advisory reminder to blocking check-runner (#121)
* chore(#111): upgrade pre-push-gate from reminder to blocking check-runner
Previously pre-push-gate.sh just printed a checklist of things to run
locally before pushing — it was advisory. The rule it enforces is a
HARD STOP per pr-workflow.md. That asymmetry meant agents routinely
pushed broken work and discovered it only when CI went red.
Replaces the reminder with a blocking runner that reads the list of
shell commands from project config (.pre_push.commands) and executes
them in sequence before a push is allowed through. First non-zero
exit blocks the push with exit 2 and prints the failing command plus
the last 20 lines of its output.
- Config key: .pre_push.commands[] — array of {name, run} objects.
Shipped default is an empty list (hook stays a no-op on repos that
haven't configured their checks yet, including the framework repo
itself until it wires its own CI).
- Emergency bypass: '<!-- pre-push: skip -->' in the HEAD commit
message. Grep-able on purpose so bypasses stay auditable.
- Fail-fast: once a command fails, the rest don't run. Parallel
execution is a follow-up polish.
- 7 test cases in .claude/hooks/tests/test_pre_push_gate.sh — all
pass on the shipped default + a minimal custom config.
Updates docs/rule-audit.md to flip "partial" → "yes" for the
"before git push" rule.
Integrates with the shared config reader landed in #109.
https://github.com/me2resh/apexyard/issues/111
* fix(#111): remove orphaned footnote reference from rule-audit
The previous advisory-mode footnote was superseded by pre-push-111
but its definition was accidentally kept, tripping markdownlint MD053
(unused reference definition). Drop it.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#107): add validate-issue-structure.sh PreToolUse hook (#122)
Mechanically enforces the ticket body schema when an agent files raw
`gh issue create` calls instead of going through the interactive
/feature, /task, /bug skills. Matches bracketed title prefix
([Feature] / [Chore] / [Bug] / [Docs] / etc.) against
`.ticket.required_sections` in project-config, and blocks (exit 2)
when any required section is missing or empty. Skip marker
`<!-- validate-issue-structure: skip -->` bypasses with a visible
stderr WARN for legitimate off-template tickets (epics, meta-threads).
Changes:
- .claude/hooks/validate-issue-structure.sh — the hook; reads schema
via the shared _lib-read-config.sh, with inlined defaults for bare
checkouts predating the config-schema rollout. Handles
--body / --body-file / -F path.
- .claude/project-config.defaults.json — extends .ticket with
required_sections (Feature/Chore/Refactor/Testing/CI/Docs/Bug) and
skip_marker; other .ticket fields untouched.
- .claude/settings.json — new PreToolUse matcher on Bash(gh issue
create *) alongside the existing suggest-ticket-template.sh and
block-private-refs-in-public-repos.sh hooks.
- .claude/hooks/tests/test_validate_issue_structure.sh — 15 cases
covering pass + fail paths per prefix, empty section detection,
skip marker, unknown prefix, non-gh invocation, --body-file path.
- docs/rule-audit.md — new section 11 row, mechanized count +1.
Upstream ticket: https://github.com/me2resh/apexyard/issues/107
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#112): add require-agdr-for-arch-pr.sh PreToolUse hook (#123)
Closes the asymmetry noted in .claude/rules/agdr-decisions.md: every
other HARD STOP in the ruleset (merge approval, ticket-first,
migration-first) is mechanically enforced, but the /decide HARD STOP
was prose-only. The commit-time hook require-agdr-for-arch-changes.sh
catches one architectural change at commit; this new PR-time hook
catches the cumulative diff so reviewers always have a pointer to the
decision record.
- New hook at .claude/hooks/require-agdr-for-arch-pr.sh
- Fires on Bash(gh pr create *)
- Parses --title/--body/--body-file/-F <path>
- Resolves base branch from --base, else upstream/dev, origin/dev,
upstream/main, origin/main, main, master (in that order)
- Computes `git diff <merge-base>..HEAD --name-only`
- Triggers on any changed file matching .agdr_trigger_paths[], OR any
dep-file addition (package.json via jq key-set diff; other
dep files via a commented +/- line-count heuristic — version
bumps match +/- counts and do not fire)
- Blocks (exit 2) with a helpful message naming the triggers and
pointing at /decide if the body has no `AgDR-\d+-[a-z0-9-]+`
reference
- Skip marker `<!-- agdr: not-applicable -->` bypasses with a
visible WARN on stderr
- Silent exit 0 on non-gh commands, empty diffs, unresolvable base
- Wired via .claude/settings.json PreToolUse Bash(gh pr create *)
- Adds two new top-level keys to .claude/project-config.defaults.json:
agdr_trigger_paths (shell globs — domain/, infrastructure/,
migrations/, *.tf, .github/workflows/, etc.)
agdr_trigger_dep_files (literal basenames — package.json,
pyproject.toml, Cargo.toml, go.mod, Gemfile)
Hook has inline fallback defaults kept in sync.
- Adds docs/rule-audit.md entry in the AgDR section; bumps mechanized
count 26 to 27 and total rows 73 to 74.
- Adds .claude/hooks/tests/test_require_agdr_for_arch_pr.sh (7 cases;
all green): path-triggered without AgDR (block), with AgDR (pass),
dep-file added (block), version-only bump (no fire), skip marker
(pass + warn), non-matching diff (pass), non-gh command (no-op).
Closes https://github.com/me2resh/apexyard/issues/112
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#113): require Testing section in PR body (config-driven) (#124)
Extends validate-pr-create.sh with a required-sections check that
replaces the hardcoded Glossary-only grep. The list of required H2
headings is project-configurable via `.pr.required_sections[]`.
Shipped default is ["Testing", "Glossary"], matching the canonical PR
description shape in workflows/code-review.md.
- Each entry must appear as `## <Name>` (case-insensitive).
- Empty sections are tolerated at this layer (the issue-structure hook
#107 does stricter empty-content checks for issue bodies; for PR
bodies, empty sections are left to the reviewer's judgement).
- Skip marker `<!-- pr-sections: skip -->` bypasses with a visible
stderr WARN — for trivial PRs (lint-only fixes, version bumps)
where the full template is overkill.
- Reads from project config via the shared _lib-read-config.sh (#109).
Inline fallback matches shipped defaults so bare checkouts predating
#109 keep working.
- 8 test cases cover: all-sections pass, each missing section,
missing-both (both errors printed), skip marker, case-insensitive
headings, H3 rejection.
https://github.com/me2resh/apexyard/issues/113
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#114): enforce single Closes-keyword per PR body (#125)
* chore(#114): enforce single Closes-keyword per PR body
Caps distinct auto-closing references (close/closes/closed, fix/fixes/
fixed, resolve/resolves/resolved + N or owner/repo+N) at one per PR
body. Closes the loophole where the title validator limited the title
to one ticket but multiple Closes lines in the body would still auto-
close all of them on merge.
- Scans stripped of fenced code blocks so closing keywords inside a
code sample do not count.
- Distinct counting: the same number referenced twice (e.g. via Fixes
and Closes) counts as one.
- Cross-repo refs (owner/repo+N) count normally.
- Opt-in escape hatch: pr.allow_multiple_closes=true in
project-config disables the check for teams that deliberately batch
rollbacks or dependency bumps.
- Per-PR bypass: a multi-close-approved HTML comment in the body
prints a visible stderr WARN and lets that PR through. Grep-able
trace so bypasses are auditable.
- 10 test cases cover: one close passes, no-keyword passes, two
distinct block, three mixed block, same-number-twice passes, code-
fence-ignored, skip marker, cross-ref without keyword, opt-in
config, cross-repo close.
Reads configuration via the shared _lib-read-config.sh (apexyard+109).
https://github.com/me2resh/apexyard/issues/114
* fix(#114): strip inline backticks and tilde fences from close-count scan
Rex caught a self-reflexive bug in the initial commit: documentation
mentioning closing keywords inside inline backticks (say a PR body
that explains the new hook with examples) counted as real closes, and
a skip marker inside inline backticks silently bypassed the check.
Future PRs that document the feature would trip the same trap.
Fix the code-region stripper to cover:
- Triple-backtick fences (already handled)
- Tilde fences (new)
- Inline-backtick spans (new)
Also run the skip-marker check against the stripped body, so a marker
used purely as documentation no longer activates a real bypass.
Three new test cases pin the behaviour:
- closing keywords in inline backticks are ignored
- skip marker inside inline backticks does NOT bypass
- tilde fences also get stripped
13/13 tests pass.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#108): add /tickets-batch skill for bulk-file flow (#127)
The fast happy path for filing 5–20 structured tickets in one intent
without dropping to raw `gh issue create` (non-conformant) or running
`/feature` 20 times serially (~100 turns of interview).
- .claude/skills/tickets-batch/SKILL.md — new skill spec. Asks
shared-context questions (priority, epic, area-labels, repo) ONCE
for the whole batch, then runs a ≤3-question micro-interview per
ticket (type, one-line purpose, optional clarification when the
inference is low-confidence). Confirms the full batch as a table,
then files each via specific `gh issue create` calls (never a
bulk JSON dump — the validator runs per-issue). Output conforms
to `.ticket.required_sections` by construction. Caps at 20
tickets per invocation.
- CLAUDE.md — added a row for /tickets-batch in the Available
Skills table; bumped the count references from 33 to 34.
Refs https://github.com/me2resh/apexyard/issues/108
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#117): add /fan-out skill + parallel-work rule doc (#128)
- Add `.claude/skills/fan-out/SKILL.md` — spawns N parallel Agent calls
in a single assistant message, with per-task agent type, worktree
isolation, and foreground/background mode. Caps at 5 concurrent
agents. Refuses fan-out when tasks share file write targets or have
sequential dependencies. Includes pre-spawn active-ticket safety
check and worktree merge-back flow that pauses on conflict.
- Add `.claude/rules/parallel-work.md` — trigger heuristic for when an
agent should proactively offer fan-out (>= 2 file-independent,
context-independent, individually substantial work items). Pairs
with the skill: rule says when, skill says how.
- Update `CLAUDE.md` — bump rules count to 9, skills count to 34, add
`/fan-out` row to the skills table.
Refs https://github.com/me2resh/apexyard/issues/117
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#116): adopt release-cut branch model (dev/main + tags) — framework only (#126)
* chore(#116): adopt release-cut branch model (dev/main + tags)
Formalises the dev/main split that already exists informally — dev is
the daily-work branch where every PR lands, main is release-only,
tagged with semver on each merge. Framework-only: managed projects
under apexyard governance stay trunk-based.
Added:
- AgDR-0007 — decision record (options table covers full git flow vs
trunk-only vs gitflow-lite; chose gitflow-lite)
- /release skill — diff dev against main, propose semver bump from
conventional commits, generate CHANGELOG, open release PR, tag
after merge
- docs/release-process.md — prose runbook for cutting a release
(manual fallback for the skill)
- .git.protected_branches in project-config.defaults.json
(main/master/dev/develop)
Modified:
- block-main-push.sh — now blocks direct pushes/commits to all
configured protected branches (was: hardcoded main/master). Reads
.git.protected_branches via the shared config reader (apexyard#109).
- CLAUDE.md — new section under Git Conventions explaining the
dev/main model + the framework-only scope. Skill table entry for
/release. Skills count bumped to 34.
- docs/multi-project.md — note that upstream/main is release-only
and the dev/main split is framework-only.
Non-consequences (per AgDR-0007):
- No release/* or hotfix/* branches. Hotfixes are normal patches
cut quickly. Revisit if multi-version maintenance becomes a need.
- No automatic on-merge issue closing for dev PRs. The release PR's
body aggregates all Closes references for the batch and triggers
auto-close en masse when it merges to main. Manual close in the
meantime.
- CI workflows trigger on pull_request regardless of base, so
dev-targeting PRs already get the full check matrix — no
workflow file edits needed.
https://github.com/me2resh/apexyard/issues/116
* fix(#116): satisfy markdownlint MD032 + MD060 in 116 docs
Auto-fix added blank lines around lists in AgDR-0007 (MD032) and
spaced the table separators in AgDR-0007 + multi-project.md (MD060).
Content unchanged.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* fix(#106): CHANGELOG fallback in drift hook for squash-merged forks (#129)
* fix(#106): CHANGELOG fallback in drift hook for squash-merged forks
The v1.1.0 tag-reachability check (`git tag --merged main`) misfires on
forks that sync via GitHub's default squash-merge: the squash collapses
the upstream-tag commit into a synthetic SHA, the tag stops being
reachable, and the banner keeps firing forever.
Discovered live on the first real-world `/update` flow: ops fork
squash-merged the v1.1.0 sync PR, banner kept saying "v1.1.0 available"
even though the fork was content-caught-up.
This commit adds a CHANGELOG-content fallback that fires only when the
primary tag check fails. If the fork's main has a heading
`## [X.Y.Z]` matching the upstream tag's version, treat the release as
absorbed and stay silent. Tolerant grep (matches the apexyard CHANGELOG
format from v1.1.0 onward, with leading-`v` stripping for tag→heading
conversion).
The merge-commit and rebase paths are unchanged — primary tag check
still works for them, and the fallback never fires when it shouldn't.
Test coverage (5 cases):
- squash-merge fork caught up to v1.1.0 → silent (the fix)
- merge-commit fork caught up to v1.1.0 → silent (regression check)
- fork stopped at v1.0.0 → banner fires
- fork has its own newer tag → silent
- squash-merge but no CHANGELOG on fork → banner fires (no false silence)
Records the strategy update in docs/agdr/AgDR-0008-…md (extends
AgDR-0005's tag-based-drift design).
https://github.com/me2resh/apexyard/issues/106
* fix(#106): satisfy markdownlint MD032/MD060 + shellcheck SC2164
Rex flagged two CI-blocking issues on the original 106 commit:
- AgDR-0008 had three bulleted sub-lists in the Consequences section
without surrounding blank lines (MD032). Added blanks and padded the
one tight-pipe table separator (MD060).
- The 106 test fixture had five subshell `cd "$fk"` calls without
`|| exit 1` (SC2164). Added the guard to all five.
5/5 tests still pass after the fix. No semantic change to the hook
or the test logic.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#130): add /validate-idea skill — lightweight pre-spec gate (#131)
A 10-minute, 5-question check that sits between /idea and /write-spec.
Designed for solo founders running ApexYard — not a heavyweight
methodology like event storming or Wardley mapping.
Five questions, asked one at a time:
1. Who is this specifically for?
2. What do they do today instead?
3. What's the smallest version that proves the value?
4. What would prove this is wrong? (kill criteria)
5. Build, buy, or rent?
Output: a one-page validation doc with a GREEN/YELLOW/RED verdict.
RED auto-updates the IDEA-NNN backlog row to WONTDO.
Integration:
/idea — adds an optional default-no "Validate now?" step after
capture (and after the optional GitHub Issue offer).
/handover — adds a conditional "this looks dormant, validate?"
step at the end of the integration plan, gated on the dormancy
heuristic (last commit > 90d AND zero open PRs AND no recent
issue activity). Healthy projects don't see the prompt.
CLAUDE.md skills count bumped to 35; new skills row added.
https://github.com/me2resh/apexyard/issues/130
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat: configurable voice prompts on assistant pause (AgDR-0009-voice-prompts-on-pause) (#135)
Stop hook that speaks the assistant's question aloud (Jarvis-style)
when it pauses for user input. Initial phase is macOS-only via `say`,
no voice input — user replies via keyboard.
Default OFF. Adopters opt in by overriding `voice_prompts.enabled` to
true in `.claude/project-config.json`.
Files:
- .claude/hooks/voice-prompt-on-pause.sh — Stop hook with config gate,
trigger heuristic (questions-only by default), markdown stripping,
sentence-boundary truncation, fire-and-forget say invocation
- .claude/hooks/tests/test_voice_prompt_on_pause.sh — 9 cases covering
disabled-default, enabled+question, enabled+statement, approved-pattern,
abc-menu, malformed-transcript, no-say-on-PATH, trigger-always,
markdown-stripping
- .claude/project-config.defaults.json — voice_prompts schema block
added (enabled, voice, max_chars, rate_wpm, trigger), default OFF
- .claude/settings.json — new Stop hook entry wired with the standard
ops-root resolver wrapper
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — design rationale,
options matrix (status quo / macOS say / cloud TTS / ML detection),
consequences, future phases
- docs/project-config.md — new "Voice prompts" section with override
examples and privacy notes
Test mode: hook respects VOICE_PROMPTS_SYNC=1 to run say synchronously
(test runners need this so assertions don't race against orphaned
background processes). Production invocations always run async.
Future phases (out of scope here, AgDR §"Future phases"):
- Phase 2: cross-platform TTS (Linux espeak, Windows SpeechSynthesizer)
- Phase 3: cloud TTS providers (OpenAI, ElevenLabs) — privacy AgDR-worthy
- Phase 4: voice input via Whisper-based STT
- Phase 5: per-message overrides
Refs: https://github.com/me2resh/apexyard/issues/134
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#141): add /debug skill — structured hypothesis-driven debugging (#142)
* feat(#141): add /debug skill — structured hypothesis-driven debugging
Adds a methodology skill that enforces five disciplines:
1. Capture the symptom precisely (exact URL, exact response, exact step)
2. Read the architecture before guessing (map every layer the request
touches, file by file)
3. Form a hypothesis ladder (3–5 candidates, each with an explicit
evidence test that confirms or refutes it)
4. Gather evidence first, fix second
5. Verify the fix against the original symptom evidence (re-run the
same `curl` / browser repro you used in step 4 — unit tests
verify code, not feature, correctness)
Stack appendices (Web, Desktop) carry stack-specific surface-evidence
requirements (step 1), architecture-surface maps (step 2), and
evidence-tests cookbooks (step 4). The methodology body stays portable
across stacks; appendices are where stack-specific knowledge accrues
over time.
Web appendix covers browser routing, framework configs (Next/Nuxt/Vite),
SPA-fallback layers, CDN, origin, the shared API client, backend
handlers, and auth providers. Desktop appendix covers Electron / Tauri
/ native-shell concerns: app entry points, IPC bridges, native modules,
auto-updater, sandbox / entitlements, code signing, crash reports.
Includes "When NOT to use" guidance so the methodology overhead doesn't
sandbag simple bugs (typos, off-by-ones, greenfield exploration).
Motivated by a real OAuth debug session in a managed project where
three sequential fixes chased adjacent symptoms because each was
hypothesis-then-fix without evidence in between. The skill is the
"never do that again" guardrail.
Closes #141
* fix(#141): scrub private project issue numbers from anti-pattern table
Rex review on PR #142 caught that line 155 of the skill's anti-pattern
table still named the originating PRs (#375, #377, #380) from the
private project where the methodology was first exercised. The PR body
and commit message were correctly abstracted earlier, but this in-file
reference slipped through — the leak-protection hook only scans gh
issue/pr writes, not staged file content, so mechanical enforcement
didn't catch it.
Replaced with "Three sequential PRs chasing the same symptom because
each was based on a different guess (no evidence test in between
cycles)" — same pedagogical value, zero attribution.
Refs #141
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(#143): document split-portfolio mode + add /setup privacy gate (#144)
Adopters on GitHub Free with any private project hit a silent privacy bug
following today's docs: forking apexyard makes a public fork that you cannot
later flip to private (GitHub policy), and committing `apexyard.projects.yaml`
+ `projects/<name>/` to the fork publishes private project names + handover
findings on a public GitHub repo.
This PR documents the supported workaround — split-portfolio mode — and
adds an upfront privacy gate to the /setup skill so new adopters never
hit the trip-wire silently.
docs/multi-project.md:
- New "Two setup modes — pick the one that matches your privacy needs"
section before TL;DR, with a side-by-side table and the explicit
trip-wire callout.
- Existing TL;DR retitled "TL;DR — single-fork mode (default)" with a
one-line pointer to the split-portfolio section.
- New "Split-portfolio mode — public framework + private portfolio"
section between the existing setup steps and the directory-layout
section. Includes:
- The two-repo layout (~/ops/apexyard public + ~/ops/portfolio private)
- 7-step setup walkthrough with copy-pasteable commands
- Daily workflow + upstream sync notes (both unchanged)
- Trade-offs (two repos to maintain, two clones per machine, one
upstream-sync conflict path on `projects/README.md`)
- "Migrating from single-fork to split-portfolio" recovery flow with
the explicit warning that GitHub Issue / PR edit history survives a
force-push and must be redacted separately
.claude/skills/setup/SKILL.md:
- New Step 2a: privacy gate — asks "are any projects private?" before
proposing the config. Branches on the answer:
- All public → single-fork mode
- GitHub Pro / Team / Enterprise → single-fork mode (private
forks of public repos
are supported on those
plans)
- Any private + GitHub Free → split-portfolio mode
- New Step 2b: walks through the split-portfolio setup interactively
(private repo create, sibling clone, gitignore + symlink) when the
privacy gate triggers.
- Detection: `test -L apexyard.projects.yaml` short-circuits Step 2b
for adopters already in split mode.
- Explicit "do NOT auto-migrate" rule for adopters already in single-fork
mode with private names already pushed — that path is destructive
(force-push history rewrite + redact issue/PR bodies + delete backup
branch) and warrants a deliberate, eyes-open run, not a /setup side
effect.
Out of scope for this PR (tracked separately on #143):
- `portfolio:` config block in `onboarding.yaml` schema
- Skill audit + refactor to honour configured `registry` / `projects_dir`
/ `ideas_backlog` paths instead of hardcoded fork-relative paths
- `/split-portfolio` migration helper skill that automates the recovery
flow currently documented manually
This is the docs-and-setup-question minimum-viable starter — the
framework code refactor is mechanical and lands as a follow-up.
Refs #143
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#145): portfolio config + self-healing + /split-portfolio helper (#147)
* feat(me2resh/apexyard#145): portfolio config block + self-healing + /split-portfolio helper
Closes the framework primitive deferred from me2resh/apexyard#144. Adds
first-class config-driven path resolution for the portfolio registry,
projects dir, and ideas backlog, with self-healing surfacing of broken
config at session start, plus a new /split-portfolio skill that automates
the destructive recovery flow.
Schema, helper, and hook:
- .claude/project-config.defaults.json: new portfolio: block
(registry, projects_dir, ideas_backlog) with defaults matching
today's single-fork layout
- .claude/hooks/_lib-portfolio-paths.sh: new sourceable helper exposing
portfolio_registry, portfolio_projects_dir, portfolio_ideas_backlog,
portfolio_validate, portfolio_clear_cache. Resolves relative paths
against the ops-fork root.
- .claude/hooks/check-portfolio-config.sh: new SessionStart hook —
silent on OK, one-line banner on broken config, never blocks session
- .claude/hooks/tests/test_portfolio_paths.sh: 13 cases covering
defaults, absolute/relative overrides, validate states, cache clear
Skill audit (18 SKILL.md files):
- Adds Path resolution callout pointing at the helper
- handover bash blocks now source helper and use $(portfolio_registry)
instead of literal apexyard.projects.yaml
- setup Step 2b now writes the portfolio: config block (recommended)
and validates via portfolio_validate before declaring success;
symlink approach kept as legacy fallback
New skill (.claude/skills/split-portfolio/SKILL.md):
- 10-step migration with explicit operator-confirmation gates at each
destructive step (force-push, body redaction, branch deletion)
- --verify mode: read-only state report (mode, paths, validate, drift)
- --dry-run mode: prints commands without executing
- Pre-flight refusals: already-private fork, paid GitHub plan, dirty
working tree, already-migrated state
- Step 9 writes the portfolio: config block (not symlinks) — symlink
fallback documented for adopters on older framework versions
- Step 9 surfaces the GitHub timeline-API survival caveat verbatim
- Idempotent re-runs: detects partial-migration state and resumes
Docs (docs/multi-project.md):
- Layout section describes both modes (config-block recommended,
symlink legacy) with self-healing notes
- Setup steps split into config-block mode and legacy symlink mode
- Migration section now points at /split-portfolio skill; manual
recipe preserved as fallback
AgDR (docs/agdr/AgDR-0010-portfolio-config-and-self-healing.md):
- Full Y-statement, options, decision, consequences, future phases
- Schema decision rationale: project-config.json over onboarding.yaml
because runtime path resolution belongs in project-config
Closes me2resh/apexyard#145
Refs me2resh/apexyard#146 (delivered same PR; closed manually post-merge
per the single-Closes-keyword rule in validate-pr-create.sh)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(me2resh/apexyard#145): markdownlint MD031 — blank lines around fences
CI's markdownlint-cli2 (v0.34.0) flagged the JSON + bash fenced code
blocks I added in setup/SKILL.md Step 2b without surrounding blank
lines. Added the required blanks. No content change.
Refs me2resh/apexyard#147
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>
* docs(me2resh/apexyard#148): correct privacy-gate wording — adopter action, not framework auto-publish (#149)
The privacy-gate wording introduced in PR #144 (and unchanged in PR #147)
attributed the publication to the framework rather than the adopter:
"the standard fork-and-commit setup will silently publish your private
project names on a public GitHub repo"
That's factually wrong. ApexYard never pushes anything without explicit
operator approval — the publication only happens when the adopter
themselves runs git push. The "silently publish" framing read as if the
framework auto-publishes, which is misleading and undermines trust in
the rest of the framework's safety claims.
Two prose-only edits, no code, no behavior change:
- .claude/skills/setup/SKILL.md Step 2a — replaced "will silently
publish ..." with adopter-action language ("you might accidentally
publish ... a stray git push after registering them — I won't push
without your approval, but the risk is on the adopter once the data
is committed locally")
- docs/multi-project.md trip-wire callout — replaced "silently publish
their portfolio names the moment they push" with "risk accidentally
publishing their portfolio names with a stray push (the framework
itself never pushes without operator approval, but once the registry
is committed locally the next push exposes it)"
Verified: `grep -r "silently publish" .claude/skills/ docs/` returns no
hits.
Closes me2resh/apexyard#148
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#150): bootstrap-skill exemption + Bash-write coverage (#152)
Closes the legitimate-bypass case (me2resh/apexyard#150) and the
illegitimate-bypass case (me2resh/apexyard#151) together so the
ticket-first gate is coherent. Shipping either alone would leave a
window where the framework is internally inconsistent — see AgDR-0011
for the full rationale.
Bootstrap exemption (me2resh/apexyard#150):
- .claude/session/active-bootstrap marker, written by /setup,
/handover, /update, /split-portfolio on entry; cleared on exit
- SessionStart sweep (clear-bootstrap-marker.sh) for stale markers
from interrupted sessions
- require-active-ticket.sh reads the marker and exempts skills on
the configured ticket.bootstrap_skills list
- bootstrap_skills list lives in .claude/project-config.defaults.json
(extendable per fork via .claude/project-config.json)
Bash-write coverage (me2resh/apexyard#151):
- new _lib-detect-bash-write.sh — heuristic detector for output
redirection, tee, sed -i, awk -i inplace, python/node/ruby
embedded interpreters
- require-active-ticket.sh + require-migration-ticket.sh now fire
on Bash in addition to Edit|Write|MultiEdit
- design choice: false-negatives preferred over false-positives
(the matcher errs toward "let through" rather than block legit
read-only commands)
Tests: 32 unit cases on the lib + 12 integration cases on the hook,
including the exact me2resh/apexyard#151 bypass repro from the issue
body.
Closes me2resh/apexyard#150
Will manually close me2resh/apexyard#151 post-merge per the
single-Closes-per-PR rule (precedent: AgDR-0010 / PR #147).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#153): extend Bash-write matcher beyond first-version coverage (#155)
Closes me2resh/apexyard#153.
Extends `_lib-detect-bash-write.sh` (introduced for me2resh/apexyard#151
in PR #152) with the matcher families flagged by Rex's review of #152.
AgDR-0011 already frames the matcher as a living list extended on
observation; this commit just walks the list.
New matcher families:
- File-moving builtins: `cp`, `mv`, `rm`, `dd`, `install` (anchored at
command-start; `--help`/`--version` and `git rm`/`git mv` excluded)
- Archive / network writes: `tar -x` / `tar --extract`, `curl -o` /
`--output`, `wget -O` / `--output-document`
- Additional embedded interpreters: `perl -e`, `php -r` (keyword-gated
like python/node/ruby); `go run`, `deno run`/`deno script.ts`,
`bun run`/`bun script.ts` (categorical script runners)
- Python helpers: `pathlib.Path().touch()`, `shutil.copy*`,
`shutil.move`, `os.rename` added to the `python -c` and python
heredoc keyword list
- Heredoc variants for `ruby` and `node` (previously only python
heredoc was covered)
Extractor extensions:
- `cp` / `mv`: last positional arg
- `curl -o` / `--output`: file argument
- `wget -O` / `--output-document`: file argument
- `tar -x`, `go run`, `deno`, `bun`, `perl -e`, `php -r`: return empty
(caller applies gate categorically per AgDR-0011)
Test count rose from 32 to 86. Negative-class counterexamples cover
the trickiest false-positive surfaces: `tar -t` listing, `cp --help`,
`rm --version`, `git rm`, `curl` bare URL fetch, `wget` bare URL fetch,
`deno fmt`, `deno test`, `go build`. Existing
`test_require_active_ticket_bash.sh` regression suite still passes 12/12.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(#154): mock gh in test sandboxes to remove live-tracker dependency (#156)
- add _lib-mock-gh.sh helper that installs a fake `gh` on the sandbox PATH;
intercepts `gh issue view <N> ... --json ...` and returns synthetic
`{"number":N,"state":"OPEN"}` (overridable per-num via mock_gh_set_state)
- wire the shim into test_single_closes_per_pr.sh and
test_validate_pr_required_sections.sh so the validator's CLOSED-issue
refusal no longer breaks the suite when upstream issues are closed
- both files previously failed every case (0/13 and 0/8) because their PR
titles reference #114 / #113, which are now CLOSED upstream
- post-fix: 13/13 and 8/8; full suite remains green
Closes me2resh/apexyard#154
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#132): structured CEO marker + same-turn merge in /approve-merge (#158)
* feat(#132): structured CEO marker + same-turn merge in /approve-merge
Closes me2resh/apexyard#132 (drop the "stop before merge" rule) and
me2resh/apexyard#48 (harden CEO marker against self-approval bypass)
together. The two threads compose — see AgDR-0012 for the full
rationale.
Streamline (#132):
- /approve-merge now runs `gh pr merge --squash --delete-branch`
in the same turn as the marker write, by default
- --no-merge opt-out preserves the deferred-merge case
- The discrete approval moment is the SKILL INVOCATION, not a
follow-up "now do the merge" message
Harden (#48):
- CEO marker is now a structured key/value file with required fields:
sha=<HEAD>
approved_by=user
skill_version=2
Validated by block-unreviewed-merge.sh; bare-SHA legacy markers
rejected with a clear "stale format" error pointing at /approve-merge
- The model's bare `echo SHA > <pr>-ceo.approved` bypass is now
mechanically rejected. Forging the structured fields requires a
deliberate, visible rule violation rather than a one-line accident
- Optional audit fields (approved_at, approval_summary) capture the
"what did the user say when they approved" trail
- Rex marker stays bare-SHA — different threat model (automated
reviewer, not human authorization moment)
pr-workflow.md reframed: "the load-bearing rule is explicit per-PR
approval, not two user messages." The merge is a deterministic
consequence of the approval invocation.
Tests: 12 cases on the hardened hook covering the new format end-to-end
(valid v2, missing rex/ceo, bare-SHA legacy rejected, missing
approved_by, wrong approved_by, skill_version=1, sha mismatch,
non-merge no-op, gh-api shape gated). Full suite: 205/205 across 13
test files.
Will manually close me2resh/apexyard#48 post-merge per the
single-Closes-per-PR rule (precedent: PR #152 / AgDR-0011).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#132): redact private project reference from AgDR-0012
Abstracted two references to a registered private project that named
the project's owner/repo. The leak-protection hook caught one in the
PR body; this fixup removes the matching references from the
AgDR-0012 file content (which would otherwise have shipped the names
to me2resh/apexyard public repo via the merge).
The pre-existing reference at .claude/rules/pr-workflow.md:130
(documenting #47) is untouched — it predates this PR and is already
on the public repo's history.
Refs me2resh/apexyard#132.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#132): markdownlint blanks-around-fences + typo fix
Two small fixups against red CI / Rex feedback:
- AgDR-0012 line 63: fenced code block now has a blank line before it
(MD031). markdownlint-cli2 0.13.0 was rejecting the indented fence
inside the bullet because the fence's preceding line was the bullet
text (no blank).
- approve-merge SKILL.md line 172: typo `deferes` → `defers` (Rex flag
on PR #158).
Refs me2resh/apexyard#132.
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>
* chore(#157): remove voice-prompts feature + correct hook/skill counts (#161)
* chore(#157): remove voice-prompts-on-pause feature + correct hook/skill counts
Closes me2resh/apexyard#157 (sunset the voice-prompts feature) and
me2resh/apexyard#77 (hook count off-by-one in CHANGELOG / CLAUDE.md)
in one bundled PR. AgDR-0013 supersedes AgDR-0009 with the full
rationale; both AgDRs are preserved (decision records are append-only
history).
Removed (#157):
- .claude/hooks/voice-prompt-on-pause.sh
- .claude/hooks/tests/test_voice_prompt_on_pause.sh
- Stop matcher block in .claude/settings.json (became empty after
voice removal)
- voice_prompts block in .claude/project-config.defaults.json
- "## Voice prompts" section in docs/project-config.md
- voice_prompts mention in AgDR-0010 line 32 (replaced with
leak_protection / ticket as still-current example config blocks)
Preserved:
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — historical record;
new "Superseded by: AgDR-0013" header at the top
- AgDR-0010 line 115 reference to AgDR-0009 — still accurate as a
historical pattern reference
Counts corrected (#77):
- CHANGELOG.md v0.3.0 stats: "17 hooks" → "18 hooks" (historical
fix — at v0.3.0 there were actually 18 hooks)
- CLAUDE.md table line: "18 shell scripts" → "24 shell scripts"
(current count after this removal)
- CLAUDE.md table line: "35 slash commands" → "39 slash commands"
- CLAUDE.md "Available skills (34)" → "Available skills (39)"
- CLAUDE.md quick-reference "Skills (35 slash commands)" →
"(39 slash commands)"
Why bundled: #77's correct count depends on whether voice is still in
the framework. Shipping #77 before #157 would write a number that's
wrong by one again the moment #157 lands. Same shape as previous
bundles (PR #152 / AgDR-0011, PR #158 / AgDR-0012).
Why no adopter-facing changelog mention of the voice removal: the
feature never reached a tagged release on main. v1.1.0 didn't have
it; v1.2.0 won't have it. From the adopter's perspective there's
nothing to retire. AgDR-0013 captures the framework's internal record
for future contributors. See AgDR-0013 § "No adopter-facing changelog
mention".
Tests: full hook test suite green (196 cases across 12 files —
test_voice_prompt_on_pause.sh removed). No regressions.
Will manually close me2resh/apexyard#77 post-merge per the
single-Closes-per-PR rule (precedent: PRs #152, #158).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#157): unwire voice from settings + configs + docs + AgDR-0013
Continuation of d09d7b5 (the file deletions). Squash-merge will
collapse both into one PR commit. This commit captures:
- .claude/settings.json — Stop matcher block removed (was the only
hook in it; entire matcher gone)
- .claude/project-config.defaults.json — voice_prompts block + its
_comment removed
- docs/project-config.md — "## Voice prompts" section removed
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — "Superseded by"
header added at the top, content otherwise preserved as history
- docs/agdr/AgDR-0010-portfolio-config-and-self-healing.md — line 32
example reference swapped from voice_prompts to
leak_protection / ticket (still-current config blocks)
- docs/agdr/AgDR-0013-sunset-voice-prompts.md — new supersession AgDR
- CLAUDE.md — hook count 18 → 24, skill count 35 → 39 (three
occurrences each, all aligned to current reality)
- CHANGELOG.md v0.3.0 stats — "17 hooks" → "18 hooks" historical fix
(#77 acceptance criterion 1)
Refs me2resh/apexyard#157 + me2resh/apexyard#77.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#157): markdownlint MD028 + resolve stale AgDR-numbering ref
Three small fixups against Rex CHANGES-REQUESTED on PR #161:
- AgDR-0009 line 3: promote "Superseded by:" header out of a
blockquote. The original "I decided ..." canonical blockquote at
line 5 was being merged with the new supersession blockquote
(markdownlint MD028 — "no blanks inside blockquote").
- AgDR-0013 line 3: same shape — "Supersedes:" header now a plain
bold paragraph, canonical "I decided ..." blockquote untouched.
- approve-merge SKILL.md line ~187: stale conditional reference
"AgDR-0012 (or 0013 — depends on whether voice-removal lands
first)" — order is now resolved (12 = approve-merge bundle, 13 =
voice removal). Drop the parenthetical.
Refs me2resh/apexyard#157 + me2resh/apexyard#77.
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(#160): multi-tab terminal demo on the landing site (#162)
Landing-site `site/index.html` terminal demo previously played one
canonical flow ("one ticket, start to finish") on autoplay. With the
v1.2.0 skill surface expansion (4 new skills, 8+ new hooks), one flow
no longer represents the framework's breadth.
Adds tabs to the terminal chrome — four flows visitors can either let
auto-cycle or click directly:
1. one ticket — existing flow, unchanged content
2. /handover — adopt an external repo into the portfolio
3. /setup — first-run framework bootstrap on a fresh fork
4. /fan-out — spawn 3 parallel agents on independent tickets
Auto-advance: on completion of the active tab's script, the demo
pauses ~1.8s then advances to the next tab. Loops at the end. User
can interrupt by clicking any tab or hitting Replay.
Implementation:
- HTML chrome — single title span replaced with a tablist of 4 button
tabs. ARIA `role="tablist"` / `role="tab"` / `aria-selected` so
keyboard + screen-reader users get the same semantics as sighted
ones.
- CSS — new `.shell-demo__tabs` + `.shell-demo__tab` with an accent
underline on the active tab. Tabs scroll horizontally on narrow
viewports (mobile responsive).
- JS — refactored the existing IIFE from one `script` array to an
array of four. Added `setActiveTab()` for ARIA state, `play(idx)`
takes a tab index, end-of-script auto-advances to `(idx + 1) % N`
unless the user clicked away during the pause. Adds a new `cmd`
type alongside `you` for slash-command invocations (renders with
the same `>` prompt prefix). prefers-reduced-motion still bails
early and leaves the static seed visible.
- Static seed (the non-JS fallback) still shows tab 0's content, so
reduced-motion / no-JS visitors see the one-ticket flow as before.
Hero metrics also corrected to current reality (#77 / PR #161 covers
CLAUDE.md and CHANGELOG.md; this PR catches the same numbers in the
landing site):
Skills 32 → 39
Hooks 18 → 24
The tabs ship in v1.2.0 alongside the framework changes that make
the new flows worth showcasing.
Refs me2resh/apexyard#160 (release v1.2.0 + landing-site refresh).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#163): default the split-portfolio sibling repo name to <fork>-portfolio (#164)
Closes me2resh/apexyard#163.
The split-portfolio mode docs and skills previously suggested
`your-org/ops` as the default name for the private sibling repo, and
`portfolio/` as the local clone directory. Both too generic — adopters
running multiple ops setups end up with `your-org/ops` collisions, and
a bare `portfolio/` dir gives no signal about which framework it
belongs to when it sits next to other unrelated `portfolio/` dirs.
`<fork>-portfolio` is now the default — keeps the relationship to the
public fork explicit on disk and on GitHub. If the fork is named
`your-org/apexyard`, the portfolio defaults to
`your-org/apexyard-portfolio`. If the fork was renamed (e.g. `cos`),
the portfolio defaults to `cos-portfolio`. Adopters with custom names
keep working — the `portfolio:` config block resolves whatever path
they configured.
Files updated:
docs/multi-project.md
- Layout diagrams use `apexyard-portfolio/` as the sibling
- Setup walkthrough Step 2 + Step 3 use `your-org/apexyard-portfolio`
and explain the `<fork>-portfolio` pattern
- Config-block + symlink path examples updated to
`../apexyard-portfolio/...`
- Daily workflow + cross-machine clone commands updated
- The two existing `your-org/ops` references that remain are
fork-rename examples (lines 52, 64) — kept as-is, since renaming
the fork to `ops` is still valid (the portfolio would then
default to `ops-portfolio`)
.claude/skills/setup/SKILL.md
- Step 2b's "default suggestion" for the private repo name is now
`your-org/<fork>-portfolio`, computed dynamically from the
fork's repo name via `gh repo view --json name -q .name` so the
suggestion is correct even when the fork was renamed
- Clone command no longer needs a second arg — the repo name IS
the directory name
- Config-block paths updated to `../apexyard-portfolio/...`
.claude/skills/split-portfolio/SKILL.md
- Step 3's suggested-name template is now `<account>/<fork>-portfolio`
with the same dynamic-fork-name resolution
Mechanism unchanged. The `portfolio:` config block in
`.claude/project-config.json` still takes any path; this PR is purely
default-suggestion + example prose.
No tests required (skills are markdown instructions; no automated
coverage today).
Refs `apexyard.projects.yaml.example` uses "ops repo" as a generic
term meaning "the operational management fork" — not a name — kept
as-is.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#165): skills reference page on the landing site + changelog link (#167)
Closes me2resh/apexyard#165.
Adds a public, browseable index of every apexyard slash command
alongside a one-click changelog link from the homepage nav.
site/skills.html (new):
- Lists all 39 skills currently shipping in .claude/skills/
- Each entry: slash command, argument hint, description (taken
verbatim from the SKILL.md frontmatter so the page matches the
runtime exactly)
- 10 categories: Setup & onboarding, Daily ops, Tickets & ideas,
Specs & decisions, Code review & merge, Architecture & dev tools,
Production-readiness audits, Workflow primitives, Communications,
Deprecated
- Same brutalist-terminal design tokens as the homepage — JetBrains
Mono, paper-cream background, single warning-red accent, sharp
corners. Inlined CSS to keep the static-only no-build-step
convention; design vars duplicated rather than extracted to a
shared file (~18 vars; cheap to keep in sync).
- Mobile responsive — skill grid collapses to single-column under
720px; titlebar nav hides non-CTA items on narrow viewports.
- Reduced-motion friendly (no animation in the first place).
- Internal anchor TOC at the top so the page scans in seconds.
site/index.html (nav addition):
- Added two nav links to the titlebar between "what's in the box"
and the github CTA:
• skills → ./skills.html
• changelog → https://github.com/me2resh/apexyard/releases
- The changelog link points at the GitHub releases page (not the
raw CHANGELOG.md file) so it auto-resolves to the latest tagged
release on each visit. v1.2.0 lands and the link is already there.
No new dependencies, no build step, no JS for the skills page. The
existing site convention (one-html-file-per-route, inlined CSS,
optional progressive-enhancement JS) is preserved.
Refs me2resh/apexyard#160 (release v1.2.0 + landing-site refresh) —
this is the second site-side deliverable for that ticket; the release
tag itself follows once me2resh/apexyard#159 (testing) closes.
Follow-up worth a separate ticket: a small generator script that
walks .claude/skills/*/SKILL.md, parses YAML frontmatter, and emits
the skills.html sections automatically. Out of scope for v1.2.0 —
first version is hand-curated and will need maintenance until that
generator lands.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#168): accept release/vN.N.N branches + release(...) PR titles (#169)
Closes me2resh/apexyard#168.
The /release skill prescribed `release/vA.B.C` as the source-branch
name and `release: vA.B.C` as the PR title for the dev → main release
PR (per AgDR-0007). Both were rejected by the framework's own
validators:
validate-branch-name.sh required {type}/{TICKET-ID}-{description};
release/v1.2.0 has no ticket-id portion.
validate-pr-create.sh required type(SCOPE): form with `release` not
in pr.title_type_whitelist.
The contradiction surfaced cutting v1.2.0 — the first release under
the dev/main model.
Three small changes:
1. .claude/hooks/validate-branch-name.sh — added an early-out branch
that accepts ^release/vN.N.N(-rcN)?$ as a valid name. Narrow,
intentional exception for the framework's release-cut convention;
release branches don't carry a ticket-id because the release itself
IS the ticket.
2. .claude/project-config.defaults.json — added "release" to
pr.title_type_whitelist so a title like `release(#160): v1.2.0`
passes validate-pr-create.sh's existing regex unchanged.
3. .claude/skills/release/SKILL.md step 4 — corrected the prescribed
PR title to `release(#<release-ticket>): vA.B.C` so future /release
invocations produce a title that satisfies the validators by
construction.
Tested:
bash .claude/hooks/validate-branch-name.sh against:
release/v1.2.0 → 0 (allowed, release-special-case)
release/v1.2.0-rc1 → 0 (allowed, RC variant)
release/v9.9.9 → 0 (allowed)
release/foo → 2 (correctly blocked)
release/v1 → 2 (correctly blocked)
chore/GH-168-fix → 0 (allowed, standard pattern)
feature/GH-1-x → 0 (allowed, standard pattern)
Full hook test suite: 196/196 cases green across 12 test files.
Refs: surfaced 2026-05-04 cutting the first release under AgDR-0007.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#170): exempt release/vN.N.N from validate-pr-create's branch-id check (#171)
Closes me2resh/apexyard#170.
Completes the work started in #169 (closing #168). #169 added a
release-pattern early-out to validate-branch-name.sh so release/vN.N.N
branches pass the branch-name validator. But validate-pr-create.sh has
its own independent branch-id check at line 273 that #169 didn't
touch — and it still rejects release/v1.2.0 because that name doesn't
contain a ticket-id substring.
This is the same class of contradiction #168 fixed; the fix is the
same shape. Add the same release-pattern early-out to the branch-id
check in validate-pr-create.sh:
- if the branch matches ^release/vN.N.N(-rcN)?$ → exempt (release
branches don't carry ticket-ids; the release itself is the ticket)
- otherwise → require a ticket-id substring as before
#168's acceptance criterion 3 ("validate-pr-create.sh accepts a PR
title `release(#160): v1.2.0` against the `release/v1.2.0` branch")
was checked off based on the title regex alone but didn't catch the
secondary branch-id check living in the same file. Surfaced trying
to open the v1.2.0 release PR.
Tested:
bash .claude/hooks/validate-pr-create.sh against:
release/v1.2.0 → 0 (allowed, exempt)
release/v1.2.0-rc1 → 0 (allowed, RC variant)
chore/GH-1-fix → 0 (allowed, has ticket-id)
release/foo → 2 (correctly blocked)
chore/no-ticket → 2 (correctly blocked)
Refs me2resh/apexyard#168 (the parent bug) + #169 (the partial fix).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#173): sync CHANGELOG.md from main → dev (#174)
Closes me2resh/apexyard#173.
The release-cut model (AgDR-0007 / #116) squash-merges
release/vN.N.N → main, which means the v1.2.0 CHANGELOG section that
landed on main via PR #172 was never propagated back to dev. This
commit copies main's CHANGELOG.md verbatim onto dev so the v1.2.0
section is now present on both branches.
The diff is exactly the v1.2.0 entry being prepended; no other lines
change.
Without this sync, the next release PR cut from dev would build a
v1.3.0 section on top of v1.1.0, silently dropping v1.2.0 from dev's
running history. The corollary skill-level fix (option B in the
ticket) — updating /release to source the previous CHANGELOG from
upstream/main — is filed as a follow-up and out of scope for this PR.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#181): /agdr skill — searchable AgDR library (#186)
Adds /agdr — a portfolio-wide index for Agent Decision Records.
Walks apexyard.projects.yaml, reads each project's docs/agdr/*.md
(local clone if available, else gh api fallback), parses the optional
YAML frontmatter for category + projects, and answers four queries:
- /agdr browse list across the portfolio, grouped by category
- /agdr search <term> full-text grep across all bodies, returns
<project>/AgDR-NNNN paths + matching paragraph
- /agdr show <id> print a specific record, disambiguates duplicates
- /agdr stats counts per category (the marketing-slide tile,
now backed by real data)
Six-category taxonomy: architecture | tech-stack | security | patterns
| integrations | other. Legacy AgDRs without frontmatter remain
first-class — they bucket as `other` and are flagged in browse so
operators can migrate at their own pace.
Backwards-compatible template change: templates/agdr.md gains an
optional `category:` (and optional `projects:`) line in the existing
frontmatter block. Omitting the line keeps every existing AgDR valid;
the skill defaults to `other` when missing.
Doc note in workflows/sdlc.md § Phase 2 points at /agdr search for
"have we decided this before?" lookups before drafting a design.
Smoke test in .claude/hooks/tests/test_agdr_skill.sh covers the
parser the spec specifies — frontmatter extraction, category
bucketing (including the legacy default-to-other path), id reading,
stats aggregation, and search match counts. 18 assertions, all green;
12 pre-existing test suites also green (no regressions).
Closes #181
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(me2resh/apexyard#183): /launch-check trend tracking (#185)
- Persist each run as JSON under <projects_dir>/<name>/launch-check/runs/
(timestamp + branch + commit + per-dimension scores + verdict + top_risks)
- Append "Trend (last 5 runs)" section to the per-run summary when at least 2
prior runs exist — markdown table + ASCII score chart
- Add /launch-check trend mode for read-only trend rendering (no full audit)
- Auto-derive notes column from score-delta vs previous run
(e.g. "Security +12, Analytics +10")
- Opt-in commit via .launch-check-history-tracked marker; gitignored by default
- New helper script render-trend.sh + test_launch_check_trend.sh (21 cases)
- Resolve projects dir via portfolio_projects_dir helper (no hardcoded path)
- Schema is forward-compatible — extra fields preserved on framework upgrade
AgDR-0014 documents the chart format / schema / opt-in choices.
Closes me2resh/apexyard#183
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#182): /status --briefing + bin/apexyard status CLI shim (#187)
* feat(me2resh/apexyard#182): /status --briefing + bin/apexyard CLI shim
Make slide 6's `$ apexyard status` invocation real — the smallest of three
slide-reality tickets, demonstrating the marketing-to-implementation
pattern before #181 / #183 land.
- New helper at .claude/skills/status/briefing.sh — computes the 4-line
"where am I" briefing (active workspace, active ticket, branch,
role-set) so the logic is testable in isolation and runnable from a
plain shell. Workspace inference walks up from cwd to the ops-fork
root (same algorithm as _lib-portfolio-paths.sh and /start-ticket).
- Ticket reads from .claude/session/tickets/<workspace> first, falls
back to .claude/session/current-ticket. Role-set is inferred from
the active ticket's GitHub labels (v1: backend / frontend / qa /
security / platform / sre / data / ux / ui / product / tech-lead,
plus the long forms). No match emits the explicit "<none — inferred
per task>" placeholder so the four-line shape is constant.
- New CLI shim bin/apexyard delegates `apexyard status` to the same
helper after walking up to find the ops-fork root. Works from any
workspace/<name>/ clone or the fork itself; symlink onto PATH to
install (no shadowing of the `claude` binary).
- /status SKILL.md gains a "Briefing mode" section documenting the
--briefing / -b flag; default /status output is unchanged.
- docs/multi-project.md "Daily workflow" demonstrates the new
invocation alongside /inbox and /status.
- New smoke tests at .claude/hooks/tests/test_status_briefing.sh — 8
cases covering ops-root cwd, workspace cwd, unknown cwd, ops-fallback
marker, per-project marker priority, label-based role inference, the
no-matching-label path, and the constant-four-line shape.
Closes me2resh/apexyard#182
* fix(me2resh/apexyard#182): replace curios-dog with example-app in SKILL.md output sample (leak fix)
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(#178): LSP integration spike — measurement + recommendation (#184)
* docs(#178): LSP spike — token-savings measurement + integration findings
- Phase 1: estimated token cost A vs B for three representative queries on a real TS Lambda backend (~9,750 LOC); shallow semantic queries see ~3-23x input-token savings, multi-hop traces see ~1.4x
- Phase 2: verified Claude Code shipped first-party LSP support in v2.0.74 (Dec 2025), gated behind ENABLE_LSP_TOOL=1, wired in via the plugin system (.lsp.json); MCP-wrapped LSP shims (cclsp, lsp-mcp) remain as fallback
- Phase 3: recommends Option 3 — interactive clone-first prompt at end of /handover, preserving today's "never auto-clone" principle while making the deep-dive path discoverable
- Phase 4: AgDR Y-statement and option matrix sketched; full AgDR is a follow-up if the spike says go
- Recommendation: GO. Adopt the built-in tool, document the opt-in path, change /handover to offer clone-first interactively. No code changes beyond /handover SKILL.md; no novel integration to maintain
Closes me2resh/apexyard#178
* docs: fix markdownlint MD031/MD032 in spike report
Add blank lines around lists and a fenced code block to satisfy
markdownlint-cli2 in CI. Pure formatting, no content change.
Refs me2resh/apexyard#178
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(me2resh/apexyard#190): annotate LSP-aware skills with opt-in callouts (#191)
- Add identical-shape "LSP-aware (optional, recommended)" callout near
the top of each of the four code-aware SKILL.md files.
- Per-skill savings number is the only variation:
- /code-review: ~3-15× cheaper for semantic queries
- /threat-model: ~3-15× shallow, ~1.4-5× multi-hop traces
- /security-review: ~3-15× cheaper for semantic querie…
* chore(#109): project-configurable ticket / branch / commit / PR schema (#118)
* chore(#109): project-configurable ticket / branch / commit / PR schema
Lift the prefix / type whitelists hardcoded across skills, hooks, and
CI into a versioned JSON config read through a shared shell library.
Shipped defaults at .claude/project-config.defaults.json; per-fork
overrides at the optional .claude/project-config.json; one reader
(_lib-read-config.sh) that every consumer now uses.
Added:
- .claude/project-config.defaults.json (v1 schema)
- .claude/hooks/_lib-read-config.sh (shared reader)
- docs/project-config.md (schema reference + extension guide)
- docs/agdr/AgDR-0006-project-configurable-ticket-schema.md
Migrated (still pass with no config present via last-resort fallback):
- validate-branch-name.sh → .branch.type_whitelist
- validate-commit-format.sh → .commit.type_whitelist (legacy
`commit_types` top-level key honoured as backward-compat fallback)
- validate-pr-create.sh → .pr.title_type_whitelist
- /feature, /task, /bug skills reference the config in their Rules
sections; none hardcodes the list any more
Unlocks subsequent config-readers for #107 / #110 / #111 / #112 /
#113 / #114 / #115 — each extends the schema under its own subtree
without further changes to the loader.
https://github.com/me2resh/apexyard/issues/109
* fix(#109): satisfy markdownlint MD032 and MD060 on new docs
Auto-fix MD032 (blank lines around lists) in AgDR-0006 and format
table-separator rows with surrounding spaces (MD060) in both new
doc files. Content unchanged; CI green.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#110): add block-private-refs-in-public-repos.sh hook (#119)
Adds a new PreToolUse hook that blocks gh issue/PR/comment creation and
gh api .../issues|/pulls calls targeting a public framework repo
(default: me2resh/apexyard + whatever `upstream` resolves to) when the
title or body references any registered private project from
apexyard.projects.yaml (by name, repo slug, owner/repo#N ticket ref, or
workspace path).
The hook is a sibling to check-secrets.sh — both scan outgoing content
for identifiers that should never leave the local environment. Skip
marker `<!-- private-refs: allow -->` in the body lets a deliberate
reference through with a visible warning.
Files touched:
- .claude/hooks/block-private-refs-in-public-repos.sh (new)
- .claude/hooks/tests/test_block_private_refs.sh (new)
- .claude/rules/leak-protection.md (new)
- .claude/settings.json (wire PreToolUse matchers for the 5 gh shapes)
- docs/rule-audit.md (append section 10 + bump counts)
Refs: https://github.com/me2resh/apexyard/issues/110
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#115): add warn-stale-review-markers.sh PostToolUse hook (#120)
Fires after `git push` to surface review markers that have gone stale
because new commits were pushed past an existing Rex / CEO / design
approval. The merge gate already catches this at `gh pr merge` time,
but only then -- this hook closes the gap by flagging it immediately
at push-time so the author isn't surprised at merge.
- `.claude/hooks/warn-stale-review-markers.sh`
- PostToolUse, non-blocking (PostToolUse exit 2 would push noise
into the conversation; this hook is purely informational).
- Resolves the PR HEAD via `gh pr view --json headRefOid` -- same
source-of-truth as the merge-gate hooks post-apexyard#47 / #55.
Falls back to local HEAD with a visible WARN when gh is offline.
- Silent on: no PR for branch, no markers, fresh markers,
failed push (detected via `rejected` / `failed to push` /
`fatal:` / `error:` markers in tool_response.stderr).
- Modes: `warn` (default) prints one stderr line per stale marker;
`delete` opts in to auto-removal via
`.claude/project-config.json` -> `review_markers.on_stale`.
TODO(apexyard#109): switch to the shared project-config reader
once it lands.
- `.claude/settings.json`
- Wires the hook on PostToolUse / Bash / `git push *`.
- `docs/rule-audit.md`
- Adds a row under section 3 (Code review & PR quality) and
bumps the mechanized count 26 -> 27 / total 73 -> 74.
- `.claude/hooks/tests/test_warn_stale_review_markers.sh`
- 8 cases: no PR, no markers, fresh markers, stale rex / ceo /
design (warn), delete mode, failed push. All pass locally.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#111): upgrade pre-push-gate from advisory reminder to blocking check-runner (#121)
* chore(#111): upgrade pre-push-gate from reminder to blocking check-runner
Previously pre-push-gate.sh just printed a checklist of things to run
locally before pushing — it was advisory. The rule it enforces is a
HARD STOP per pr-workflow.md. That asymmetry meant agents routinely
pushed broken work and discovered it only when CI went red.
Replaces the reminder with a blocking runner that reads the list of
shell commands from project config (.pre_push.commands) and executes
them in sequence before a push is allowed through. First non-zero
exit blocks the push with exit 2 and prints the failing command plus
the last 20 lines of its output.
- Config key: .pre_push.commands[] — array of {name, run} objects.
Shipped default is an empty list (hook stays a no-op on repos that
haven't configured their checks yet, including the framework repo
itself until it wires its own CI).
- Emergency bypass: '<!-- pre-push: skip -->' in the HEAD commit
message. Grep-able on purpose so bypasses stay auditable.
- Fail-fast: once a command fails, the rest don't run. Parallel
execution is a follow-up polish.
- 7 test cases in .claude/hooks/tests/test_pre_push_gate.sh — all
pass on the shipped default + a minimal custom config.
Updates docs/rule-audit.md to flip "partial" → "yes" for the
"before git push" rule.
Integrates with the shared config reader landed in #109.
https://github.com/me2resh/apexyard/issues/111
* fix(#111): remove orphaned footnote reference from rule-audit
The previous advisory-mode footnote was superseded by pre-push-111
but its definition was accidentally kept, tripping markdownlint MD053
(unused reference definition). Drop it.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#107): add validate-issue-structure.sh PreToolUse hook (#122)
Mechanically enforces the ticket body schema when an agent files raw
`gh issue create` calls instead of going through the interactive
/feature, /task, /bug skills. Matches bracketed title prefix
([Feature] / [Chore] / [Bug] / [Docs] / etc.) against
`.ticket.required_sections` in project-config, and blocks (exit 2)
when any required section is missing or empty. Skip marker
`<!-- validate-issue-structure: skip -->` bypasses with a visible
stderr WARN for legitimate off-template tickets (epics, meta-threads).
Changes:
- .claude/hooks/validate-issue-structure.sh — the hook; reads schema
via the shared _lib-read-config.sh, with inlined defaults for bare
checkouts predating the config-schema rollout. Handles
--body / --body-file / -F path.
- .claude/project-config.defaults.json — extends .ticket with
required_sections (Feature/Chore/Refactor/Testing/CI/Docs/Bug) and
skip_marker; other .ticket fields untouched.
- .claude/settings.json — new PreToolUse matcher on Bash(gh issue
create *) alongside the existing suggest-ticket-template.sh and
block-private-refs-in-public-repos.sh hooks.
- .claude/hooks/tests/test_validate_issue_structure.sh — 15 cases
covering pass + fail paths per prefix, empty section detection,
skip marker, unknown prefix, non-gh invocation, --body-file path.
- docs/rule-audit.md — new section 11 row, mechanized count +1.
Upstream ticket: https://github.com/me2resh/apexyard/issues/107
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#112): add require-agdr-for-arch-pr.sh PreToolUse hook (#123)
Closes the asymmetry noted in .claude/rules/agdr-decisions.md: every
other HARD STOP in the ruleset (merge approval, ticket-first,
migration-first) is mechanically enforced, but the /decide HARD STOP
was prose-only. The commit-time hook require-agdr-for-arch-changes.sh
catches one architectural change at commit; this new PR-time hook
catches the cumulative diff so reviewers always have a pointer to the
decision record.
- New hook at .claude/hooks/require-agdr-for-arch-pr.sh
- Fires on Bash(gh pr create *)
- Parses --title/--body/--body-file/-F <path>
- Resolves base branch from --base, else upstream/dev, origin/dev,
upstream/main, origin/main, main, master (in that order)
- Computes `git diff <merge-base>..HEAD --name-only`
- Triggers on any changed file matching .agdr_trigger_paths[], OR any
dep-file addition (package.json via jq key-set diff; other
dep files via a commented +/- line-count heuristic — version
bumps match +/- counts and do not fire)
- Blocks (exit 2) with a helpful message naming the triggers and
pointing at /decide if the body has no `AgDR-\d+-[a-z0-9-]+`
reference
- Skip marker `<!-- agdr: not-applicable -->` bypasses with a
visible WARN on stderr
- Silent exit 0 on non-gh commands, empty diffs, unresolvable base
- Wired via .claude/settings.json PreToolUse Bash(gh pr create *)
- Adds two new top-level keys to .claude/project-config.defaults.json:
agdr_trigger_paths (shell globs — domain/, infrastructure/,
migrations/, *.tf, .github/workflows/, etc.)
agdr_trigger_dep_files (literal basenames — package.json,
pyproject.toml, Cargo.toml, go.mod, Gemfile)
Hook has inline fallback defaults kept in sync.
- Adds docs/rule-audit.md entry in the AgDR section; bumps mechanized
count 26 to 27 and total rows 73 to 74.
- Adds .claude/hooks/tests/test_require_agdr_for_arch_pr.sh (7 cases;
all green): path-triggered without AgDR (block), with AgDR (pass),
dep-file added (block), version-only bump (no fire), skip marker
(pass + warn), non-matching diff (pass), non-gh command (no-op).
Closes https://github.com/me2resh/apexyard/issues/112
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#113): require Testing section in PR body (config-driven) (#124)
Extends validate-pr-create.sh with a required-sections check that
replaces the hardcoded Glossary-only grep. The list of required H2
headings is project-configurable via `.pr.required_sections[]`.
Shipped default is ["Testing", "Glossary"], matching the canonical PR
description shape in workflows/code-review.md.
- Each entry must appear as `## <Name>` (case-insensitive).
- Empty sections are tolerated at this layer (the issue-structure hook
#107 does stricter empty-content checks for issue bodies; for PR
bodies, empty sections are left to the reviewer's judgement).
- Skip marker `<!-- pr-sections: skip -->` bypasses with a visible
stderr WARN — for trivial PRs (lint-only fixes, version bumps)
where the full template is overkill.
- Reads from project config via the shared _lib-read-config.sh (#109).
Inline fallback matches shipped defaults so bare checkouts predating
#109 keep working.
- 8 test cases cover: all-sections pass, each missing section,
missing-both (both errors printed), skip marker, case-insensitive
headings, H3 rejection.
https://github.com/me2resh/apexyard/issues/113
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#114): enforce single Closes-keyword per PR body (#125)
* chore(#114): enforce single Closes-keyword per PR body
Caps distinct auto-closing references (close/closes/closed, fix/fixes/
fixed, resolve/resolves/resolved + N or owner/repo+N) at one per PR
body. Closes the loophole where the title validator limited the title
to one ticket but multiple Closes lines in the body would still auto-
close all of them on merge.
- Scans stripped of fenced code blocks so closing keywords inside a
code sample do not count.
- Distinct counting: the same number referenced twice (e.g. via Fixes
and Closes) counts as one.
- Cross-repo refs (owner/repo+N) count normally.
- Opt-in escape hatch: pr.allow_multiple_closes=true in
project-config disables the check for teams that deliberately batch
rollbacks or dependency bumps.
- Per-PR bypass: a multi-close-approved HTML comment in the body
prints a visible stderr WARN and lets that PR through. Grep-able
trace so bypasses are auditable.
- 10 test cases cover: one close passes, no-keyword passes, two
distinct block, three mixed block, same-number-twice passes, code-
fence-ignored, skip marker, cross-ref without keyword, opt-in
config, cross-repo close.
Reads configuration via the shared _lib-read-config.sh (apexyard+109).
https://github.com/me2resh/apexyard/issues/114
* fix(#114): strip inline backticks and tilde fences from close-count scan
Rex caught a self-reflexive bug in the initial commit: documentation
mentioning closing keywords inside inline backticks (say a PR body
that explains the new hook with examples) counted as real closes, and
a skip marker inside inline backticks silently bypassed the check.
Future PRs that document the feature would trip the same trap.
Fix the code-region stripper to cover:
- Triple-backtick fences (already handled)
- Tilde fences (new)
- Inline-backtick spans (new)
Also run the skip-marker check against the stripped body, so a marker
used purely as documentation no longer activates a real bypass.
Three new test cases pin the behaviour:
- closing keywords in inline backticks are ignored
- skip marker inside inline backticks does NOT bypass
- tilde fences also get stripped
13/13 tests pass.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#108): add /tickets-batch skill for bulk-file flow (#127)
The fast happy path for filing 5–20 structured tickets in one intent
without dropping to raw `gh issue create` (non-conformant) or running
`/feature` 20 times serially (~100 turns of interview).
- .claude/skills/tickets-batch/SKILL.md — new skill spec. Asks
shared-context questions (priority, epic, area-labels, repo) ONCE
for the whole batch, then runs a ≤3-question micro-interview per
ticket (type, one-line purpose, optional clarification when the
inference is low-confidence). Confirms the full batch as a table,
then files each via specific `gh issue create` calls (never a
bulk JSON dump — the validator runs per-issue). Output conforms
to `.ticket.required_sections` by construction. Caps at 20
tickets per invocation.
- CLAUDE.md — added a row for /tickets-batch in the Available
Skills table; bumped the count references from 33 to 34.
Refs https://github.com/me2resh/apexyard/issues/108
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#117): add /fan-out skill + parallel-work rule doc (#128)
- Add `.claude/skills/fan-out/SKILL.md` — spawns N parallel Agent calls
in a single assistant message, with per-task agent type, worktree
isolation, and foreground/background mode. Caps at 5 concurrent
agents. Refuses fan-out when tasks share file write targets or have
sequential dependencies. Includes pre-spawn active-ticket safety
check and worktree merge-back flow that pauses on conflict.
- Add `.claude/rules/parallel-work.md` — trigger heuristic for when an
agent should proactively offer fan-out (>= 2 file-independent,
context-independent, individually substantial work items). Pairs
with the skill: rule says when, skill says how.
- Update `CLAUDE.md` — bump rules count to 9, skills count to 34, add
`/fan-out` row to the skills table.
Refs https://github.com/me2resh/apexyard/issues/117
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#116): adopt release-cut branch model (dev/main + tags) — framework only (#126)
* chore(#116): adopt release-cut branch model (dev/main + tags)
Formalises the dev/main split that already exists informally — dev is
the daily-work branch where every PR lands, main is release-only,
tagged with semver on each merge. Framework-only: managed projects
under apexyard governance stay trunk-based.
Added:
- AgDR-0007 — decision record (options table covers full git flow vs
trunk-only vs gitflow-lite; chose gitflow-lite)
- /release skill — diff dev against main, propose semver bump from
conventional commits, generate CHANGELOG, open release PR, tag
after merge
- docs/release-process.md — prose runbook for cutting a release
(manual fallback for the skill)
- .git.protected_branches in project-config.defaults.json
(main/master/dev/develop)
Modified:
- block-main-push.sh — now blocks direct pushes/commits to all
configured protected branches (was: hardcoded main/master). Reads
.git.protected_branches via the shared config reader (apexyard#109).
- CLAUDE.md — new section under Git Conventions explaining the
dev/main model + the framework-only scope. Skill table entry for
/release. Skills count bumped to 34.
- docs/multi-project.md — note that upstream/main is release-only
and the dev/main split is framework-only.
Non-consequences (per AgDR-0007):
- No release/* or hotfix/* branches. Hotfixes are normal patches
cut quickly. Revisit if multi-version maintenance becomes a need.
- No automatic on-merge issue closing for dev PRs. The release PR's
body aggregates all Closes references for the batch and triggers
auto-close en masse when it merges to main. Manual close in the
meantime.
- CI workflows trigger on pull_request regardless of base, so
dev-targeting PRs already get the full check matrix — no
workflow file edits needed.
https://github.com/me2resh/apexyard/issues/116
* fix(#116): satisfy markdownlint MD032 + MD060 in 116 docs
Auto-fix added blank lines around lists in AgDR-0007 (MD032) and
spaced the table separators in AgDR-0007 + multi-project.md (MD060).
Content unchanged.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* fix(#106): CHANGELOG fallback in drift hook for squash-merged forks (#129)
* fix(#106): CHANGELOG fallback in drift hook for squash-merged forks
The v1.1.0 tag-reachability check (`git tag --merged main`) misfires on
forks that sync via GitHub's default squash-merge: the squash collapses
the upstream-tag commit into a synthetic SHA, the tag stops being
reachable, and the banner keeps firing forever.
Discovered live on the first real-world `/update` flow: ops fork
squash-merged the v1.1.0 sync PR, banner kept saying "v1.1.0 available"
even though the fork was content-caught-up.
This commit adds a CHANGELOG-content fallback that fires only when the
primary tag check fails. If the fork's main has a heading
`## [X.Y.Z]` matching the upstream tag's version, treat the release as
absorbed and stay silent. Tolerant grep (matches the apexyard CHANGELOG
format from v1.1.0 onward, with leading-`v` stripping for tag→heading
conversion).
The merge-commit and rebase paths are unchanged — primary tag check
still works for them, and the fallback never fires when it shouldn't.
Test coverage (5 cases):
- squash-merge fork caught up to v1.1.0 → silent (the fix)
- merge-commit fork caught up to v1.1.0 → silent (regression check)
- fork stopped at v1.0.0 → banner fires
- fork has its own newer tag → silent
- squash-merge but no CHANGELOG on fork → banner fires (no false silence)
Records the strategy update in docs/agdr/AgDR-0008-…md (extends
AgDR-0005's tag-based-drift design).
https://github.com/me2resh/apexyard/issues/106
* fix(#106): satisfy markdownlint MD032/MD060 + shellcheck SC2164
Rex flagged two CI-blocking issues on the original 106 commit:
- AgDR-0008 had three bulleted sub-lists in the Consequences section
without surrounding blank lines (MD032). Added blanks and padded the
one tight-pipe table separator (MD060).
- The 106 test fixture had five subshell `cd "$fk"` calls without
`|| exit 1` (SC2164). Added the guard to all five.
5/5 tests still pass after the fix. No semantic change to the hook
or the test logic.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#130): add /validate-idea skill — lightweight pre-spec gate (#131)
A 10-minute, 5-question check that sits between /idea and /write-spec.
Designed for solo founders running ApexYard — not a heavyweight
methodology like event storming or Wardley mapping.
Five questions, asked one at a time:
1. Who is this specifically for?
2. What do they do today instead?
3. What's the smallest version that proves the value?
4. What would prove this is wrong? (kill criteria)
5. Build, buy, or rent?
Output: a one-page validation doc with a GREEN/YELLOW/RED verdict.
RED auto-updates the IDEA-NNN backlog row to WONTDO.
Integration:
/idea — adds an optional default-no "Validate now?" step after
capture (and after the optional GitHub Issue offer).
/handover — adds a conditional "this looks dormant, validate?"
step at the end of the integration plan, gated on the dormancy
heuristic (last commit > 90d AND zero open PRs AND no recent
issue activity). Healthy projects don't see the prompt.
CLAUDE.md skills count bumped to 35; new skills row added.
https://github.com/me2resh/apexyard/issues/130
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat: configurable voice prompts on assistant pause (AgDR-0009-voice-prompts-on-pause) (#135)
Stop hook that speaks the assistant's question aloud (Jarvis-style)
when it pauses for user input. Initial phase is macOS-only via `say`,
no voice input — user replies via keyboard.
Default OFF. Adopters opt in by overriding `voice_prompts.enabled` to
true in `.claude/project-config.json`.
Files:
- .claude/hooks/voice-prompt-on-pause.sh — Stop hook with config gate,
trigger heuristic (questions-only by default), markdown stripping,
sentence-boundary truncation, fire-and-forget say invocation
- .claude/hooks/tests/test_voice_prompt_on_pause.sh — 9 cases covering
disabled-default, enabled+question, enabled+statement, approved-pattern,
abc-menu, malformed-transcript, no-say-on-PATH, trigger-always,
markdown-stripping
- .claude/project-config.defaults.json — voice_prompts schema block
added (enabled, voice, max_chars, rate_wpm, trigger), default OFF
- .claude/settings.json — new Stop hook entry wired with the standard
ops-root resolver wrapper
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — design rationale,
options matrix (status quo / macOS say / cloud TTS / ML detection),
consequences, future phases
- docs/project-config.md — new "Voice prompts" section with override
examples and privacy notes
Test mode: hook respects VOICE_PROMPTS_SYNC=1 to run say synchronously
(test runners need this so assertions don't race against orphaned
background processes). Production invocations always run async.
Future phases (out of scope here, AgDR §"Future phases"):
- Phase 2: cross-platform TTS (Linux espeak, Windows SpeechSynthesizer)
- Phase 3: cloud TTS providers (OpenAI, ElevenLabs) — privacy AgDR-worthy
- Phase 4: voice input via Whisper-based STT
- Phase 5: per-message overrides
Refs: https://github.com/me2resh/apexyard/issues/134
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#141): add /debug skill — structured hypothesis-driven debugging (#142)
* feat(#141): add /debug skill — structured hypothesis-driven debugging
Adds a methodology skill that enforces five disciplines:
1. Capture the symptom precisely (exact URL, exact response, exact step)
2. Read the architecture before guessing (map every layer the request
touches, file by file)
3. Form a hypothesis ladder (3–5 candidates, each with an explicit
evidence test that confirms or refutes it)
4. Gather evidence first, fix second
5. Verify the fix against the original symptom evidence (re-run the
same `curl` / browser repro you used in step 4 — unit tests
verify code, not feature, correctness)
Stack appendices (Web, Desktop) carry stack-specific surface-evidence
requirements (step 1), architecture-surface maps (step 2), and
evidence-tests cookbooks (step 4). The methodology body stays portable
across stacks; appendices are where stack-specific knowledge accrues
over time.
Web appendix covers browser routing, framework configs (Next/Nuxt/Vite),
SPA-fallback layers, CDN, origin, the shared API client, backend
handlers, and auth providers. Desktop appendix covers Electron / Tauri
/ native-shell concerns: app entry points, IPC bridges, native modules,
auto-updater, sandbox / entitlements, code signing, crash reports.
Includes "When NOT to use" guidance so the methodology overhead doesn't
sandbag simple bugs (typos, off-by-ones, greenfield exploration).
Motivated by a real OAuth debug session in a managed project where
three sequential fixes chased adjacent symptoms because each was
hypothesis-then-fix without evidence in between. The skill is the
"never do that again" guardrail.
Closes #141
* fix(#141): scrub private project issue numbers from anti-pattern table
Rex review on PR #142 caught that line 155 of the skill's anti-pattern
table still named the originating PRs (#375, #377, #380) from the
private project where the methodology was first exercised. The PR body
and commit message were correctly abstracted earlier, but this in-file
reference slipped through — the leak-protection hook only scans gh
issue/pr writes, not staged file content, so mechanical enforcement
didn't catch it.
Replaced with "Three sequential PRs chasing the same symptom because
each was based on a different guess (no evidence test in between
cycles)" — same pedagogical value, zero attribution.
Refs #141
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(#143): document split-portfolio mode + add /setup privacy gate (#144)
Adopters on GitHub Free with any private project hit a silent privacy bug
following today's docs: forking apexyard makes a public fork that you cannot
later flip to private (GitHub policy), and committing `apexyard.projects.yaml`
+ `projects/<name>/` to the fork publishes private project names + handover
findings on a public GitHub repo.
This PR documents the supported workaround — split-portfolio mode — and
adds an upfront privacy gate to the /setup skill so new adopters never
hit the trip-wire silently.
docs/multi-project.md:
- New "Two setup modes — pick the one that matches your privacy needs"
section before TL;DR, with a side-by-side table and the explicit
trip-wire callout.
- Existing TL;DR retitled "TL;DR — single-fork mode (default)" with a
one-line pointer to the split-portfolio section.
- New "Split-portfolio mode — public framework + private portfolio"
section between the existing setup steps and the directory-layout
section. Includes:
- The two-repo layout (~/ops/apexyard public + ~/ops/portfolio private)
- 7-step setup walkthrough with copy-pasteable commands
- Daily workflow + upstream sync notes (both unchanged)
- Trade-offs (two repos to maintain, two clones per machine, one
upstream-sync conflict path on `projects/README.md`)
- "Migrating from single-fork to split-portfolio" recovery flow with
the explicit warning that GitHub Issue / PR edit history survives a
force-push and must be redacted separately
.claude/skills/setup/SKILL.md:
- New Step 2a: privacy gate — asks "are any projects private?" before
proposing the config. Branches on the answer:
- All public → single-fork mode
- GitHub Pro / Team / Enterprise → single-fork mode (private
forks of public repos
are supported on those
plans)
- Any private + GitHub Free → split-portfolio mode
- New Step 2b: walks through the split-portfolio setup interactively
(private repo create, sibling clone, gitignore + symlink) when the
privacy gate triggers.
- Detection: `test -L apexyard.projects.yaml` short-circuits Step 2b
for adopters already in split mode.
- Explicit "do NOT auto-migrate" rule for adopters already in single-fork
mode with private names already pushed — that path is destructive
(force-push history rewrite + redact issue/PR bodies + delete backup
branch) and warrants a deliberate, eyes-open run, not a /setup side
effect.
Out of scope for this PR (tracked separately on #143):
- `portfolio:` config block in `onboarding.yaml` schema
- Skill audit + refactor to honour configured `registry` / `projects_dir`
/ `ideas_backlog` paths instead of hardcoded fork-relative paths
- `/split-portfolio` migration helper skill that automates the recovery
flow currently documented manually
This is the docs-and-setup-question minimum-viable starter — the
framework code refactor is mechanical and lands as a follow-up.
Refs #143
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#145): portfolio config + self-healing + /split-portfolio helper (#147)
* feat(me2resh/apexyard#145): portfolio config block + self-healing + /split-portfolio helper
Closes the framework primitive deferred from me2resh/apexyard#144. Adds
first-class config-driven path resolution for the portfolio registry,
projects dir, and ideas backlog, with self-healing surfacing of broken
config at session start, plus a new /split-portfolio skill that automates
the destructive recovery flow.
Schema, helper, and hook:
- .claude/project-config.defaults.json: new portfolio: block
(registry, projects_dir, ideas_backlog) with defaults matching
today's single-fork layout
- .claude/hooks/_lib-portfolio-paths.sh: new sourceable helper exposing
portfolio_registry, portfolio_projects_dir, portfolio_ideas_backlog,
portfolio_validate, portfolio_clear_cache. Resolves relative paths
against the ops-fork root.
- .claude/hooks/check-portfolio-config.sh: new SessionStart hook —
silent on OK, one-line banner on broken config, never blocks session
- .claude/hooks/tests/test_portfolio_paths.sh: 13 cases covering
defaults, absolute/relative overrides, validate states, cache clear
Skill audit (18 SKILL.md files):
- Adds Path resolution callout pointing at the helper
- handover bash blocks now source helper and use $(portfolio_registry)
instead of literal apexyard.projects.yaml
- setup Step 2b now writes the portfolio: config block (recommended)
and validates via portfolio_validate before declaring success;
symlink approach kept as legacy fallback
New skill (.claude/skills/split-portfolio/SKILL.md):
- 10-step migration with explicit operator-confirmation gates at each
destructive step (force-push, body redaction, branch deletion)
- --verify mode: read-only state report (mode, paths, validate, drift)
- --dry-run mode: prints commands without executing
- Pre-flight refusals: already-private fork, paid GitHub plan, dirty
working tree, already-migrated state
- Step 9 writes the portfolio: config block (not symlinks) — symlink
fallback documented for adopters on older framework versions
- Step 9 surfaces the GitHub timeline-API survival caveat verbatim
- Idempotent re-runs: detects partial-migration state and resumes
Docs (docs/multi-project.md):
- Layout section describes both modes (config-block recommended,
symlink legacy) with self-healing notes
- Setup steps split into config-block mode and legacy symlink mode
- Migration section now points at /split-portfolio skill; manual
recipe preserved as fallback
AgDR (docs/agdr/AgDR-0010-portfolio-config-and-self-healing.md):
- Full Y-statement, options, decision, consequences, future phases
- Schema decision rationale: project-config.json over onboarding.yaml
because runtime path resolution belongs in project-config
Closes me2resh/apexyard#145
Refs me2resh/apexyard#146 (delivered same PR; closed manually post-merge
per the single-Closes-keyword rule in validate-pr-create.sh)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(me2resh/apexyard#145): markdownlint MD031 — blank lines around fences
CI's markdownlint-cli2 (v0.34.0) flagged the JSON + bash fenced code
blocks I added in setup/SKILL.md Step 2b without surrounding blank
lines. Added the required blanks. No content change.
Refs me2resh/apexyard#147
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>
* docs(me2resh/apexyard#148): correct privacy-gate wording — adopter action, not framework auto-publish (#149)
The privacy-gate wording introduced in PR #144 (and unchanged in PR #147)
attributed the publication to the framework rather than the adopter:
"the standard fork-and-commit setup will silently publish your private
project names on a public GitHub repo"
That's factually wrong. ApexYard never pushes anything without explicit
operator approval — the publication only happens when the adopter
themselves runs git push. The "silently publish" framing read as if the
framework auto-publishes, which is misleading and undermines trust in
the rest of the framework's safety claims.
Two prose-only edits, no code, no behavior change:
- .claude/skills/setup/SKILL.md Step 2a — replaced "will silently
publish ..." with adopter-action language ("you might accidentally
publish ... a stray git push after registering them — I won't push
without your approval, but the risk is on the adopter once the data
is committed locally")
- docs/multi-project.md trip-wire callout — replaced "silently publish
their portfolio names the moment they push" with "risk accidentally
publishing their portfolio names with a stray push (the framework
itself never pushes without operator approval, but once the registry
is committed locally the next push exposes it)"
Verified: `grep -r "silently publish" .claude/skills/ docs/` returns no
hits.
Closes me2resh/apexyard#148
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#150): bootstrap-skill exemption + Bash-write coverage (#152)
Closes the legitimate-bypass case (me2resh/apexyard#150) and the
illegitimate-bypass case (me2resh/apexyard#151) together so the
ticket-first gate is coherent. Shipping either alone would leave a
window where the framework is internally inconsistent — see AgDR-0011
for the full rationale.
Bootstrap exemption (me2resh/apexyard#150):
- .claude/session/active-bootstrap marker, written by /setup,
/handover, /update, /split-portfolio on entry; cleared on exit
- SessionStart sweep (clear-bootstrap-marker.sh) for stale markers
from interrupted sessions
- require-active-ticket.sh reads the marker and exempts skills on
the configured ticket.bootstrap_skills list
- bootstrap_skills list lives in .claude/project-config.defaults.json
(extendable per fork via .claude/project-config.json)
Bash-write coverage (me2resh/apexyard#151):
- new _lib-detect-bash-write.sh — heuristic detector for output
redirection, tee, sed -i, awk -i inplace, python/node/ruby
embedded interpreters
- require-active-ticket.sh + require-migration-ticket.sh now fire
on Bash in addition to Edit|Write|MultiEdit
- design choice: false-negatives preferred over false-positives
(the matcher errs toward "let through" rather than block legit
read-only commands)
Tests: 32 unit cases on the lib + 12 integration cases on the hook,
including the exact me2resh/apexyard#151 bypass repro from the issue
body.
Closes me2resh/apexyard#150
Will manually close me2resh/apexyard#151 post-merge per the
single-Closes-per-PR rule (precedent: AgDR-0010 / PR #147).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#153): extend Bash-write matcher beyond first-version coverage (#155)
Closes me2resh/apexyard#153.
Extends `_lib-detect-bash-write.sh` (introduced for me2resh/apexyard#151
in PR #152) with the matcher families flagged by Rex's review of #152.
AgDR-0011 already frames the matcher as a living list extended on
observation; this commit just walks the list.
New matcher families:
- File-moving builtins: `cp`, `mv`, `rm`, `dd`, `install` (anchored at
command-start; `--help`/`--version` and `git rm`/`git mv` excluded)
- Archive / network writes: `tar -x` / `tar --extract`, `curl -o` /
`--output`, `wget -O` / `--output-document`
- Additional embedded interpreters: `perl -e`, `php -r` (keyword-gated
like python/node/ruby); `go run`, `deno run`/`deno script.ts`,
`bun run`/`bun script.ts` (categorical script runners)
- Python helpers: `pathlib.Path().touch()`, `shutil.copy*`,
`shutil.move`, `os.rename` added to the `python -c` and python
heredoc keyword list
- Heredoc variants for `ruby` and `node` (previously only python
heredoc was covered)
Extractor extensions:
- `cp` / `mv`: last positional arg
- `curl -o` / `--output`: file argument
- `wget -O` / `--output-document`: file argument
- `tar -x`, `go run`, `deno`, `bun`, `perl -e`, `php -r`: return empty
(caller applies gate categorically per AgDR-0011)
Test count rose from 32 to 86. Negative-class counterexamples cover
the trickiest false-positive surfaces: `tar -t` listing, `cp --help`,
`rm --version`, `git rm`, `curl` bare URL fetch, `wget` bare URL fetch,
`deno fmt`, `deno test`, `go build`. Existing
`test_require_active_ticket_bash.sh` regression suite still passes 12/12.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(#154): mock gh in test sandboxes to remove live-tracker dependency (#156)
- add _lib-mock-gh.sh helper that installs a fake `gh` on the sandbox PATH;
intercepts `gh issue view <N> ... --json ...` and returns synthetic
`{"number":N,"state":"OPEN"}` (overridable per-num via mock_gh_set_state)
- wire the shim into test_single_closes_per_pr.sh and
test_validate_pr_required_sections.sh so the validator's CLOSED-issue
refusal no longer breaks the suite when upstream issues are closed
- both files previously failed every case (0/13 and 0/8) because their PR
titles reference #114 / #113, which are now CLOSED upstream
- post-fix: 13/13 and 8/8; full suite remains green
Closes me2resh/apexyard#154
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#132): structured CEO marker + same-turn merge in /approve-merge (#158)
* feat(#132): structured CEO marker + same-turn merge in /approve-merge
Closes me2resh/apexyard#132 (drop the "stop before merge" rule) and
me2resh/apexyard#48 (harden CEO marker against self-approval bypass)
together. The two threads compose — see AgDR-0012 for the full
rationale.
Streamline (#132):
- /approve-merge now runs `gh pr merge --squash --delete-branch`
in the same turn as the marker write, by default
- --no-merge opt-out preserves the deferred-merge case
- The discrete approval moment is the SKILL INVOCATION, not a
follow-up "now do the merge" message
Harden (#48):
- CEO marker is now a structured key/value file with required fields:
sha=<HEAD>
approved_by=user
skill_version=2
Validated by block-unreviewed-merge.sh; bare-SHA legacy markers
rejected with a clear "stale format" error pointing at /approve-merge
- The model's bare `echo SHA > <pr>-ceo.approved` bypass is now
mechanically rejected. Forging the structured fields requires a
deliberate, visible rule violation rather than a one-line accident
- Optional audit fields (approved_at, approval_summary) capture the
"what did the user say when they approved" trail
- Rex marker stays bare-SHA — different threat model (automated
reviewer, not human authorization moment)
pr-workflow.md reframed: "the load-bearing rule is explicit per-PR
approval, not two user messages." The merge is a deterministic
consequence of the approval invocation.
Tests: 12 cases on the hardened hook covering the new format end-to-end
(valid v2, missing rex/ceo, bare-SHA legacy rejected, missing
approved_by, wrong approved_by, skill_version=1, sha mismatch,
non-merge no-op, gh-api shape gated). Full suite: 205/205 across 13
test files.
Will manually close me2resh/apexyard#48 post-merge per the
single-Closes-per-PR rule (precedent: PR #152 / AgDR-0011).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#132): redact private project reference from AgDR-0012
Abstracted two references to a registered private project that named
the project's owner/repo. The leak-protection hook caught one in the
PR body; this fixup removes the matching references from the
AgDR-0012 file content (which would otherwise have shipped the names
to me2resh/apexyard public repo via the merge).
The pre-existing reference at .claude/rules/pr-workflow.md:130
(documenting #47) is untouched — it predates this PR and is already
on the public repo's history.
Refs me2resh/apexyard#132.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#132): markdownlint blanks-around-fences + typo fix
Two small fixups against red CI / Rex feedback:
- AgDR-0012 line 63: fenced code block now has a blank line before it
(MD031). markdownlint-cli2 0.13.0 was rejecting the indented fence
inside the bullet because the fence's preceding line was the bullet
text (no blank).
- approve-merge SKILL.md line 172: typo `deferes` → `defers` (Rex flag
on PR #158).
Refs me2resh/apexyard#132.
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>
* chore(#157): remove voice-prompts feature + correct hook/skill counts (#161)
* chore(#157): remove voice-prompts-on-pause feature + correct hook/skill counts
Closes me2resh/apexyard#157 (sunset the voice-prompts feature) and
me2resh/apexyard#77 (hook count off-by-one in CHANGELOG / CLAUDE.md)
in one bundled PR. AgDR-0013 supersedes AgDR-0009 with the full
rationale; both AgDRs are preserved (decision records are append-only
history).
Removed (#157):
- .claude/hooks/voice-prompt-on-pause.sh
- .claude/hooks/tests/test_voice_prompt_on_pause.sh
- Stop matcher block in .claude/settings.json (became empty after
voice removal)
- voice_prompts block in .claude/project-config.defaults.json
- "## Voice prompts" section in docs/project-config.md
- voice_prompts mention in AgDR-0010 line 32 (replaced with
leak_protection / ticket as still-current example config blocks)
Preserved:
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — historical record;
new "Superseded by: AgDR-0013" header at the top
- AgDR-0010 line 115 reference to AgDR-0009 — still accurate as a
historical pattern reference
Counts corrected (#77):
- CHANGELOG.md v0.3.0 stats: "17 hooks" → "18 hooks" (historical
fix — at v0.3.0 there were actually 18 hooks)
- CLAUDE.md table line: "18 shell scripts" → "24 shell scripts"
(current count after this removal)
- CLAUDE.md table line: "35 slash commands" → "39 slash commands"
- CLAUDE.md "Available skills (34)" → "Available skills (39)"
- CLAUDE.md quick-reference "Skills (35 slash commands)" →
"(39 slash commands)"
Why bundled: #77's correct count depends on whether voice is still in
the framework. Shipping #77 before #157 would write a number that's
wrong by one again the moment #157 lands. Same shape as previous
bundles (PR #152 / AgDR-0011, PR #158 / AgDR-0012).
Why no adopter-facing changelog mention of the voice removal: the
feature never reached a tagged release on main. v1.1.0 didn't have
it; v1.2.0 won't have it. From the adopter's perspective there's
nothing to retire. AgDR-0013 captures the framework's internal record
for future contributors. See AgDR-0013 § "No adopter-facing changelog
mention".
Tests: full hook test suite green (196 cases across 12 files —
test_voice_prompt_on_pause.sh removed). No regressions.
Will manually close me2resh/apexyard#77 post-merge per the
single-Closes-per-PR rule (precedent: PRs #152, #158).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#157): unwire voice from settings + configs + docs + AgDR-0013
Continuation of d09d7b5 (the file deletions). Squash-merge will
collapse both into one PR commit. This commit captures:
- .claude/settings.json — Stop matcher block removed (was the only
hook in it; entire matcher gone)
- .claude/project-config.defaults.json — voice_prompts block + its
_comment removed
- docs/project-config.md — "## Voice prompts" section removed
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — "Superseded by"
header added at the top, content otherwise preserved as history
- docs/agdr/AgDR-0010-portfolio-config-and-self-healing.md — line 32
example reference swapped from voice_prompts to
leak_protection / ticket (still-current config blocks)
- docs/agdr/AgDR-0013-sunset-voice-prompts.md — new supersession AgDR
- CLAUDE.md — hook count 18 → 24, skill count 35 → 39 (three
occurrences each, all aligned to current reality)
- CHANGELOG.md v0.3.0 stats — "17 hooks" → "18 hooks" historical fix
(#77 acceptance criterion 1)
Refs me2resh/apexyard#157 + me2resh/apexyard#77.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#157): markdownlint MD028 + resolve stale AgDR-numbering ref
Three small fixups against Rex CHANGES-REQUESTED on PR #161:
- AgDR-0009 line 3: promote "Superseded by:" header out of a
blockquote. The original "I decided ..." canonical blockquote at
line 5 was being merged with the new supersession blockquote
(markdownlint MD028 — "no blanks inside blockquote").
- AgDR-0013 line 3: same shape — "Supersedes:" header now a plain
bold paragraph, canonical "I decided ..." blockquote untouched.
- approve-merge SKILL.md line ~187: stale conditional reference
"AgDR-0012 (or 0013 — depends on whether voice-removal lands
first)" — order is now resolved (12 = approve-merge bundle, 13 =
voice removal). Drop the parenthetical.
Refs me2resh/apexyard#157 + me2resh/apexyard#77.
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(#160): multi-tab terminal demo on the landing site (#162)
Landing-site `site/index.html` terminal demo previously played one
canonical flow ("one ticket, start to finish") on autoplay. With the
v1.2.0 skill surface expansion (4 new skills, 8+ new hooks), one flow
no longer represents the framework's breadth.
Adds tabs to the terminal chrome — four flows visitors can either let
auto-cycle or click directly:
1. one ticket — existing flow, unchanged content
2. /handover — adopt an external repo into the portfolio
3. /setup — first-run framework bootstrap on a fresh fork
4. /fan-out — spawn 3 parallel agents on independent tickets
Auto-advance: on completion of the active tab's script, the demo
pauses ~1.8s then advances to the next tab. Loops at the end. User
can interrupt by clicking any tab or hitting Replay.
Implementation:
- HTML chrome — single title span replaced with a tablist of 4 button
tabs. ARIA `role="tablist"` / `role="tab"` / `aria-selected` so
keyboard + screen-reader users get the same semantics as sighted
ones.
- CSS — new `.shell-demo__tabs` + `.shell-demo__tab` with an accent
underline on the active tab. Tabs scroll horizontally on narrow
viewports (mobile responsive).
- JS — refactored the existing IIFE from one `script` array to an
array of four. Added `setActiveTab()` for ARIA state, `play(idx)`
takes a tab index, end-of-script auto-advances to `(idx + 1) % N`
unless the user clicked away during the pause. Adds a new `cmd`
type alongside `you` for slash-command invocations (renders with
the same `>` prompt prefix). prefers-reduced-motion still bails
early and leaves the static seed visible.
- Static seed (the non-JS fallback) still shows tab 0's content, so
reduced-motion / no-JS visitors see the one-ticket flow as before.
Hero metrics also corrected to current reality (#77 / PR #161 covers
CLAUDE.md and CHANGELOG.md; this PR catches the same numbers in the
landing site):
Skills 32 → 39
Hooks 18 → 24
The tabs ship in v1.2.0 alongside the framework changes that make
the new flows worth showcasing.
Refs me2resh/apexyard#160 (release v1.2.0 + landing-site refresh).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#163): default the split-portfolio sibling repo name to <fork>-portfolio (#164)
Closes me2resh/apexyard#163.
The split-portfolio mode docs and skills previously suggested
`your-org/ops` as the default name for the private sibling repo, and
`portfolio/` as the local clone directory. Both too generic — adopters
running multiple ops setups end up with `your-org/ops` collisions, and
a bare `portfolio/` dir gives no signal about which framework it
belongs to when it sits next to other unrelated `portfolio/` dirs.
`<fork>-portfolio` is now the default — keeps the relationship to the
public fork explicit on disk and on GitHub. If the fork is named
`your-org/apexyard`, the portfolio defaults to
`your-org/apexyard-portfolio`. If the fork was renamed (e.g. `cos`),
the portfolio defaults to `cos-portfolio`. Adopters with custom names
keep working — the `portfolio:` config block resolves whatever path
they configured.
Files updated:
docs/multi-project.md
- Layout diagrams use `apexyard-portfolio/` as the sibling
- Setup walkthrough Step 2 + Step 3 use `your-org/apexyard-portfolio`
and explain the `<fork>-portfolio` pattern
- Config-block + symlink path examples updated to
`../apexyard-portfolio/...`
- Daily workflow + cross-machine clone commands updated
- The two existing `your-org/ops` references that remain are
fork-rename examples (lines 52, 64) — kept as-is, since renaming
the fork to `ops` is still valid (the portfolio would then
default to `ops-portfolio`)
.claude/skills/setup/SKILL.md
- Step 2b's "default suggestion" for the private repo name is now
`your-org/<fork>-portfolio`, computed dynamically from the
fork's repo name via `gh repo view --json name -q .name` so the
suggestion is correct even when the fork was renamed
- Clone command no longer needs a second arg — the repo name IS
the directory name
- Config-block paths updated to `../apexyard-portfolio/...`
.claude/skills/split-portfolio/SKILL.md
- Step 3's suggested-name template is now `<account>/<fork>-portfolio`
with the same dynamic-fork-name resolution
Mechanism unchanged. The `portfolio:` config block in
`.claude/project-config.json` still takes any path; this PR is purely
default-suggestion + example prose.
No tests required (skills are markdown instructions; no automated
coverage today).
Refs `apexyard.projects.yaml.example` uses "ops repo" as a generic
term meaning "the operational management fork" — not a name — kept
as-is.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#165): skills reference page on the landing site + changelog link (#167)
Closes me2resh/apexyard#165.
Adds a public, browseable index of every apexyard slash command
alongside a one-click changelog link from the homepage nav.
site/skills.html (new):
- Lists all 39 skills currently shipping in .claude/skills/
- Each entry: slash command, argument hint, description (taken
verbatim from the SKILL.md frontmatter so the page matches the
runtime exactly)
- 10 categories: Setup & onboarding, Daily ops, Tickets & ideas,
Specs & decisions, Code review & merge, Architecture & dev tools,
Production-readiness audits, Workflow primitives, Communications,
Deprecated
- Same brutalist-terminal design tokens as the homepage — JetBrains
Mono, paper-cream background, single warning-red accent, sharp
corners. Inlined CSS to keep the static-only no-build-step
convention; design vars duplicated rather than extracted to a
shared file (~18 vars; cheap to keep in sync).
- Mobile responsive — skill grid collapses to single-column under
720px; titlebar nav hides non-CTA items on narrow viewports.
- Reduced-motion friendly (no animation in the first place).
- Internal anchor TOC at the top so the page scans in seconds.
site/index.html (nav addition):
- Added two nav links to the titlebar between "what's in the box"
and the github CTA:
• skills → ./skills.html
• changelog → https://github.com/me2resh/apexyard/releases
- The changelog link points at the GitHub releases page (not the
raw CHANGELOG.md file) so it auto-resolves to the latest tagged
release on each visit. v1.2.0 lands and the link is already there.
No new dependencies, no build step, no JS for the skills page. The
existing site convention (one-html-file-per-route, inlined CSS,
optional progressive-enhancement JS) is preserved.
Refs me2resh/apexyard#160 (release v1.2.0 + landing-site refresh) —
this is the second site-side deliverable for that ticket; the release
tag itself follows once me2resh/apexyard#159 (testing) closes.
Follow-up worth a separate ticket: a small generator script that
walks .claude/skills/*/SKILL.md, parses YAML frontmatter, and emits
the skills.html sections automatically. Out of scope for v1.2.0 —
first version is hand-curated and will need maintenance until that
generator lands.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#168): accept release/vN.N.N branches + release(...) PR titles (#169)
Closes me2resh/apexyard#168.
The /release skill prescribed `release/vA.B.C` as the source-branch
name and `release: vA.B.C` as the PR title for the dev → main release
PR (per AgDR-0007). Both were rejected by the framework's own
validators:
validate-branch-name.sh required {type}/{TICKET-ID}-{description};
release/v1.2.0 has no ticket-id portion.
validate-pr-create.sh required type(SCOPE): form with `release` not
in pr.title_type_whitelist.
The contradiction surfaced cutting v1.2.0 — the first release under
the dev/main model.
Three small changes:
1. .claude/hooks/validate-branch-name.sh — added an early-out branch
that accepts ^release/vN.N.N(-rcN)?$ as a valid name. Narrow,
intentional exception for the framework's release-cut convention;
release branches don't carry a ticket-id because the release itself
IS the ticket.
2. .claude/project-config.defaults.json — added "release" to
pr.title_type_whitelist so a title like `release(#160): v1.2.0`
passes validate-pr-create.sh's existing regex unchanged.
3. .claude/skills/release/SKILL.md step 4 — corrected the prescribed
PR title to `release(#<release-ticket>): vA.B.C` so future /release
invocations produce a title that satisfies the validators by
construction.
Tested:
bash .claude/hooks/validate-branch-name.sh against:
release/v1.2.0 → 0 (allowed, release-special-case)
release/v1.2.0-rc1 → 0 (allowed, RC variant)
release/v9.9.9 → 0 (allowed)
release/foo → 2 (correctly blocked)
release/v1 → 2 (correctly blocked)
chore/GH-168-fix → 0 (allowed, standard pattern)
feature/GH-1-x → 0 (allowed, standard pattern)
Full hook test suite: 196/196 cases green across 12 test files.
Refs: surfaced 2026-05-04 cutting the first release under AgDR-0007.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#170): exempt release/vN.N.N from validate-pr-create's branch-id check (#171)
Closes me2resh/apexyard#170.
Completes the work started in #169 (closing #168). #169 added a
release-pattern early-out to validate-branch-name.sh so release/vN.N.N
branches pass the branch-name validator. But validate-pr-create.sh has
its own independent branch-id check at line 273 that #169 didn't
touch — and it still rejects release/v1.2.0 because that name doesn't
contain a ticket-id substring.
This is the same class of contradiction #168 fixed; the fix is the
same shape. Add the same release-pattern early-out to the branch-id
check in validate-pr-create.sh:
- if the branch matches ^release/vN.N.N(-rcN)?$ → exempt (release
branches don't carry ticket-ids; the release itself is the ticket)
- otherwise → require a ticket-id substring as before
#168's acceptance criterion 3 ("validate-pr-create.sh accepts a PR
title `release(#160): v1.2.0` against the `release/v1.2.0` branch")
was checked off based on the title regex alone but didn't catch the
secondary branch-id check living in the same file. Surfaced trying
to open the v1.2.0 release PR.
Tested:
bash .claude/hooks/validate-pr-create.sh against:
release/v1.2.0 → 0 (allowed, exempt)
release/v1.2.0-rc1 → 0 (allowed, RC variant)
chore/GH-1-fix → 0 (allowed, has ticket-id)
release/foo → 2 (correctly blocked)
chore/no-ticket → 2 (correctly blocked)
Refs me2resh/apexyard#168 (the parent bug) + #169 (the partial fix).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#173): sync CHANGELOG.md from main → dev (#174)
Closes me2resh/apexyard#173.
The release-cut model (AgDR-0007 / #116) squash-merges
release/vN.N.N → main, which means the v1.2.0 CHANGELOG section that
landed on main via PR #172 was never propagated back to dev. This
commit copies main's CHANGELOG.md verbatim onto dev so the v1.2.0
section is now present on both branches.
The diff is exactly the v1.2.0 entry being prepended; no other lines
change.
Without this sync, the next release PR cut from dev would build a
v1.3.0 section on top of v1.1.0, silently dropping v1.2.0 from dev's
running history. The corollary skill-level fix (option B in the
ticket) — updating /release to source the previous CHANGELOG from
upstream/main — is filed as a follow-up and out of scope for this PR.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#181): /agdr skill — searchable AgDR library (#186)
Adds /agdr — a portfolio-wide index for Agent Decision Records.
Walks apexyard.projects.yaml, reads each project's docs/agdr/*.md
(local clone if available, else gh api fallback), parses the optional
YAML frontmatter for category + projects, and answers four queries:
- /agdr browse list across the portfolio, grouped by category
- /agdr search <term> full-text grep across all bodies, returns
<project>/AgDR-NNNN paths + matching paragraph
- /agdr show <id> print a specific record, disambiguates duplicates
- /agdr stats counts per category (the marketing-slide tile,
now backed by real data)
Six-category taxonomy: architecture | tech-stack | security | patterns
| integrations | other. Legacy AgDRs without frontmatter remain
first-class — they bucket as `other` and are flagged in browse so
operators can migrate at their own pace.
Backwards-compatible template change: templates/agdr.md gains an
optional `category:` (and optional `projects:`) line in the existing
frontmatter block. Omitting the line keeps every existing AgDR valid;
the skill defaults to `other` when missing.
Doc note in workflows/sdlc.md § Phase 2 points at /agdr search for
"have we decided this before?" lookups before drafting a design.
Smoke test in .claude/hooks/tests/test_agdr_skill.sh covers the
parser the spec specifies — frontmatter extraction, category
bucketing (including the legacy default-to-other path), id reading,
stats aggregation, and search match counts. 18 assertions, all green;
12 pre-existing test suites also green (no regressions).
Closes #181
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(me2resh/apexyard#183): /launch-check trend tracking (#185)
- Persist each run as JSON under <projects_dir>/<name>/launch-check/runs/
(timestamp + branch + commit + per-dimension scores + verdict + top_risks)
- Append "Trend (last 5 runs)" section to the per-run summary when at least 2
prior runs exist — markdown table + ASCII score chart
- Add /launch-check trend mode for read-only trend rendering (no full audit)
- Auto-derive notes column from score-delta vs previous run
(e.g. "Security +12, Analytics +10")
- Opt-in commit via .launch-check-history-tracked marker; gitignored by default
- New helper script render-trend.sh + test_launch_check_trend.sh (21 cases)
- Resolve projects dir via portfolio_projects_dir helper (no hardcoded path)
- Schema is forward-compatible — extra fields preserved on framework upgrade
AgDR-0014 documents the chart format / schema / opt-in choices.
Closes me2resh/apexyard#183
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#182): /status --briefing + bin/apexyard status CLI shim (#187)
* feat(me2resh/apexyard#182): /status --briefing + bin/apexyard CLI shim
Make slide 6's `$ apexyard status` invocation real — the smallest of three
slide-reality tickets, demonstrating the marketing-to-implementation
pattern before #181 / #183 land.
- New helper at .claude/skills/status/briefing.sh — computes the 4-line
"where am I" briefing (active workspace, active ticket, branch,
role-set) so the logic is testable in isolation and runnable from a
plain shell. Workspace inference walks up from cwd to the ops-fork
root (same algorithm as _lib-portfolio-paths.sh and /start-ticket).
- Ticket reads from .claude/session/tickets/<workspace> first, falls
back to .claude/session/current-ticket. Role-set is inferred from
the active ticket's GitHub labels (v1: backend / frontend / qa /
security / platform / sre / data / ux / ui / product / tech-lead,
plus the long forms). No match emits the explicit "<none — inferred
per task>" placeholder so the four-line shape is constant.
- New CLI shim bin/apexyard delegates `apexyard status` to the same
helper after walking up to find the ops-fork root. Works from any
workspace/<name>/ clone or the fork itself; symlink onto PATH to
install (no shadowing of the `claude` binary).
- /status SKILL.md gains a "Briefing mode" section documenting the
--briefing / -b flag; default /status output is unchanged.
- docs/multi-project.md "Daily workflow" demonstrates the new
invocation alongside /inbox and /status.
- New smoke tests at .claude/hooks/tests/test_status_briefing.sh — 8
cases covering ops-root cwd, workspace cwd, unknown cwd, ops-fallback
marker, per-project marker priority, label-based role inference, the
no-matching-label path, and the constant-four-line shape.
Closes me2resh/apexyard#182
* fix(me2resh/apexyard#182): replace curios-dog with example-app in SKILL.md output sample (leak fix)
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(#178): LSP integration spike — measurement + recommendation (#184)
* docs(#178): LSP spike — token-savings measurement + integration findings
- Phase 1: estimated token cost A vs B for three representative queries on a real TS Lambda backend (~9,750 LOC); shallow semantic queries see ~3-23x input-token savings, multi-hop traces see ~1.4x
- Phase 2: verified Claude Code shipped first-party LSP support in v2.0.74 (Dec 2025), gated behind ENABLE_LSP_TOOL=1, wired in via the plugin system (.lsp.json); MCP-wrapped LSP shims (cclsp, lsp-mcp) remain as fallback
- Phase 3: recommends Option 3 — interactive clone-first prompt at end of /handover, preserving today's "never auto-clone" principle while making the deep-dive path discoverable
- Phase 4: AgDR Y-statement and option matrix sketched; full AgDR is a follow-up if the spike says go
- Recommendation: GO. Adopt the built-in tool, document the opt-in path, change /handover to offer clone-first interactively. No code changes beyond /handover SKILL.md; no novel integration to maintain
Closes me2resh/apexyard#178
* docs: fix markdownlint MD031/MD032 in spike report
Add blank lines around lists and a fenced code block to satisfy
markdownlint-cli2 in CI. Pure formatting, no content change.
Refs me2resh/apexyard#178
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(me2resh/apexyard#190): annotate LSP-aware skills with opt-in callouts (#191)
- Add identical-shape "LSP-aware (optional, recommended)" callout near
the top of each of the four code-aware SKILL.md files.
- Per-skill savings number is the only variation:
- /code-review: ~3-15× cheaper for semantic queries
- /threat-model: ~3-15× shallow, ~1.4-5× multi-hop traces
- /security-review: ~3-15× cheaper for semantic querie…
- Add .apexyard-fork ops-fork anchor marker - Gitignore apexyard.projects.yaml, onboarding.yaml, projects/, workspace/ so private portfolio data cannot be accidentally staged in the public fork - Remove upstream-default onboarding.yaml and projects/README.md (both now live in apexyard-portfolio) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* chore(#109): project-configurable ticket / branch / commit / PR schema (#118)
* chore(#109): project-configurable ticket / branch / commit / PR schema
Lift the prefix / type whitelists hardcoded across skills, hooks, and
CI into a versioned JSON config read through a shared shell library.
Shipped defaults at .claude/project-config.defaults.json; per-fork
overrides at the optional .claude/project-config.json; one reader
(_lib-read-config.sh) that every consumer now uses.
Added:
- .claude/project-config.defaults.json (v1 schema)
- .claude/hooks/_lib-read-config.sh (shared reader)
- docs/project-config.md (schema reference + extension guide)
- docs/agdr/AgDR-0006-project-configurable-ticket-schema.md
Migrated (still pass with no config present via last-resort fallback):
- validate-branch-name.sh → .branch.type_whitelist
- validate-commit-format.sh → .commit.type_whitelist (legacy
`commit_types` top-level key honoured as backward-compat fallback)
- validate-pr-create.sh → .pr.title_type_whitelist
- /feature, /task, /bug skills reference the config in their Rules
sections; none hardcodes the list any more
Unlocks subsequent config-readers for #107 / #110 / #111 / #112 /
#113 / #114 / #115 — each extends the schema under its own subtree
without further changes to the loader.
https://github.com/me2resh/apexyard/issues/109
* fix(#109): satisfy markdownlint MD032 and MD060 on new docs
Auto-fix MD032 (blank lines around lists) in AgDR-0006 and format
table-separator rows with surrounding spaces (MD060) in both new
doc files. Content unchanged; CI green.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#110): add block-private-refs-in-public-repos.sh hook (#119)
Adds a new PreToolUse hook that blocks gh issue/PR/comment creation and
gh api .../issues|/pulls calls targeting a public framework repo
(default: me2resh/apexyard + whatever `upstream` resolves to) when the
title or body references any registered private project from
apexyard.projects.yaml (by name, repo slug, owner/repo#N ticket ref, or
workspace path).
The hook is a sibling to check-secrets.sh — both scan outgoing content
for identifiers that should never leave the local environment. Skip
marker `<!-- private-refs: allow -->` in the body lets a deliberate
reference through with a visible warning.
Files touched:
- .claude/hooks/block-private-refs-in-public-repos.sh (new)
- .claude/hooks/tests/test_block_private_refs.sh (new)
- .claude/rules/leak-protection.md (new)
- .claude/settings.json (wire PreToolUse matchers for the 5 gh shapes)
- docs/rule-audit.md (append section 10 + bump counts)
Refs: https://github.com/me2resh/apexyard/issues/110
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#115): add warn-stale-review-markers.sh PostToolUse hook (#120)
Fires after `git push` to surface review markers that have gone stale
because new commits were pushed past an existing Rex / CEO / design
approval. The merge gate already catches this at `gh pr merge` time,
but only then -- this hook closes the gap by flagging it immediately
at push-time so the author isn't surprised at merge.
- `.claude/hooks/warn-stale-review-markers.sh`
- PostToolUse, non-blocking (PostToolUse exit 2 would push noise
into the conversation; this hook is purely informational).
- Resolves the PR HEAD via `gh pr view --json headRefOid` -- same
source-of-truth as the merge-gate hooks post-apexyard#47 / #55.
Falls back to local HEAD with a visible WARN when gh is offline.
- Silent on: no PR for branch, no markers, fresh markers,
failed push (detected via `rejected` / `failed to push` /
`fatal:` / `error:` markers in tool_response.stderr).
- Modes: `warn` (default) prints one stderr line per stale marker;
`delete` opts in to auto-removal via
`.claude/project-config.json` -> `review_markers.on_stale`.
TODO(apexyard#109): switch to the shared project-config reader
once it lands.
- `.claude/settings.json`
- Wires the hook on PostToolUse / Bash / `git push *`.
- `docs/rule-audit.md`
- Adds a row under section 3 (Code review & PR quality) and
bumps the mechanized count 26 -> 27 / total 73 -> 74.
- `.claude/hooks/tests/test_warn_stale_review_markers.sh`
- 8 cases: no PR, no markers, fresh markers, stale rex / ceo /
design (warn), delete mode, failed push. All pass locally.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#111): upgrade pre-push-gate from advisory reminder to blocking check-runner (#121)
* chore(#111): upgrade pre-push-gate from reminder to blocking check-runner
Previously pre-push-gate.sh just printed a checklist of things to run
locally before pushing — it was advisory. The rule it enforces is a
HARD STOP per pr-workflow.md. That asymmetry meant agents routinely
pushed broken work and discovered it only when CI went red.
Replaces the reminder with a blocking runner that reads the list of
shell commands from project config (.pre_push.commands) and executes
them in sequence before a push is allowed through. First non-zero
exit blocks the push with exit 2 and prints the failing command plus
the last 20 lines of its output.
- Config key: .pre_push.commands[] — array of {name, run} objects.
Shipped default is an empty list (hook stays a no-op on repos that
haven't configured their checks yet, including the framework repo
itself until it wires its own CI).
- Emergency bypass: '<!-- pre-push: skip -->' in the HEAD commit
message. Grep-able on purpose so bypasses stay auditable.
- Fail-fast: once a command fails, the rest don't run. Parallel
execution is a follow-up polish.
- 7 test cases in .claude/hooks/tests/test_pre_push_gate.sh — all
pass on the shipped default + a minimal custom config.
Updates docs/rule-audit.md to flip "partial" → "yes" for the
"before git push" rule.
Integrates with the shared config reader landed in #109.
https://github.com/me2resh/apexyard/issues/111
* fix(#111): remove orphaned footnote reference from rule-audit
The previous advisory-mode footnote was superseded by pre-push-111
but its definition was accidentally kept, tripping markdownlint MD053
(unused reference definition). Drop it.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#107): add validate-issue-structure.sh PreToolUse hook (#122)
Mechanically enforces the ticket body schema when an agent files raw
`gh issue create` calls instead of going through the interactive
/feature, /task, /bug skills. Matches bracketed title prefix
([Feature] / [Chore] / [Bug] / [Docs] / etc.) against
`.ticket.required_sections` in project-config, and blocks (exit 2)
when any required section is missing or empty. Skip marker
`<!-- validate-issue-structure: skip -->` bypasses with a visible
stderr WARN for legitimate off-template tickets (epics, meta-threads).
Changes:
- .claude/hooks/validate-issue-structure.sh — the hook; reads schema
via the shared _lib-read-config.sh, with inlined defaults for bare
checkouts predating the config-schema rollout. Handles
--body / --body-file / -F path.
- .claude/project-config.defaults.json — extends .ticket with
required_sections (Feature/Chore/Refactor/Testing/CI/Docs/Bug) and
skip_marker; other .ticket fields untouched.
- .claude/settings.json — new PreToolUse matcher on Bash(gh issue
create *) alongside the existing suggest-ticket-template.sh and
block-private-refs-in-public-repos.sh hooks.
- .claude/hooks/tests/test_validate_issue_structure.sh — 15 cases
covering pass + fail paths per prefix, empty section detection,
skip marker, unknown prefix, non-gh invocation, --body-file path.
- docs/rule-audit.md — new section 11 row, mechanized count +1.
Upstream ticket: https://github.com/me2resh/apexyard/issues/107
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#112): add require-agdr-for-arch-pr.sh PreToolUse hook (#123)
Closes the asymmetry noted in .claude/rules/agdr-decisions.md: every
other HARD STOP in the ruleset (merge approval, ticket-first,
migration-first) is mechanically enforced, but the /decide HARD STOP
was prose-only. The commit-time hook require-agdr-for-arch-changes.sh
catches one architectural change at commit; this new PR-time hook
catches the cumulative diff so reviewers always have a pointer to the
decision record.
- New hook at .claude/hooks/require-agdr-for-arch-pr.sh
- Fires on Bash(gh pr create *)
- Parses --title/--body/--body-file/-F <path>
- Resolves base branch from --base, else upstream/dev, origin/dev,
upstream/main, origin/main, main, master (in that order)
- Computes `git diff <merge-base>..HEAD --name-only`
- Triggers on any changed file matching .agdr_trigger_paths[], OR any
dep-file addition (package.json via jq key-set diff; other
dep files via a commented +/- line-count heuristic — version
bumps match +/- counts and do not fire)
- Blocks (exit 2) with a helpful message naming the triggers and
pointing at /decide if the body has no `AgDR-\d+-[a-z0-9-]+`
reference
- Skip marker `<!-- agdr: not-applicable -->` bypasses with a
visible WARN on stderr
- Silent exit 0 on non-gh commands, empty diffs, unresolvable base
- Wired via .claude/settings.json PreToolUse Bash(gh pr create *)
- Adds two new top-level keys to .claude/project-config.defaults.json:
agdr_trigger_paths (shell globs — domain/, infrastructure/,
migrations/, *.tf, .github/workflows/, etc.)
agdr_trigger_dep_files (literal basenames — package.json,
pyproject.toml, Cargo.toml, go.mod, Gemfile)
Hook has inline fallback defaults kept in sync.
- Adds docs/rule-audit.md entry in the AgDR section; bumps mechanized
count 26 to 27 and total rows 73 to 74.
- Adds .claude/hooks/tests/test_require_agdr_for_arch_pr.sh (7 cases;
all green): path-triggered without AgDR (block), with AgDR (pass),
dep-file added (block), version-only bump (no fire), skip marker
(pass + warn), non-matching diff (pass), non-gh command (no-op).
Closes https://github.com/me2resh/apexyard/issues/112
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#113): require Testing section in PR body (config-driven) (#124)
Extends validate-pr-create.sh with a required-sections check that
replaces the hardcoded Glossary-only grep. The list of required H2
headings is project-configurable via `.pr.required_sections[]`.
Shipped default is ["Testing", "Glossary"], matching the canonical PR
description shape in workflows/code-review.md.
- Each entry must appear as `## <Name>` (case-insensitive).
- Empty sections are tolerated at this layer (the issue-structure hook
#107 does stricter empty-content checks for issue bodies; for PR
bodies, empty sections are left to the reviewer's judgement).
- Skip marker `<!-- pr-sections: skip -->` bypasses with a visible
stderr WARN — for trivial PRs (lint-only fixes, version bumps)
where the full template is overkill.
- Reads from project config via the shared _lib-read-config.sh (#109).
Inline fallback matches shipped defaults so bare checkouts predating
#109 keep working.
- 8 test cases cover: all-sections pass, each missing section,
missing-both (both errors printed), skip marker, case-insensitive
headings, H3 rejection.
https://github.com/me2resh/apexyard/issues/113
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#114): enforce single Closes-keyword per PR body (#125)
* chore(#114): enforce single Closes-keyword per PR body
Caps distinct auto-closing references (close/closes/closed, fix/fixes/
fixed, resolve/resolves/resolved + N or owner/repo+N) at one per PR
body. Closes the loophole where the title validator limited the title
to one ticket but multiple Closes lines in the body would still auto-
close all of them on merge.
- Scans stripped of fenced code blocks so closing keywords inside a
code sample do not count.
- Distinct counting: the same number referenced twice (e.g. via Fixes
and Closes) counts as one.
- Cross-repo refs (owner/repo+N) count normally.
- Opt-in escape hatch: pr.allow_multiple_closes=true in
project-config disables the check for teams that deliberately batch
rollbacks or dependency bumps.
- Per-PR bypass: a multi-close-approved HTML comment in the body
prints a visible stderr WARN and lets that PR through. Grep-able
trace so bypasses are auditable.
- 10 test cases cover: one close passes, no-keyword passes, two
distinct block, three mixed block, same-number-twice passes, code-
fence-ignored, skip marker, cross-ref without keyword, opt-in
config, cross-repo close.
Reads configuration via the shared _lib-read-config.sh (apexyard+109).
https://github.com/me2resh/apexyard/issues/114
* fix(#114): strip inline backticks and tilde fences from close-count scan
Rex caught a self-reflexive bug in the initial commit: documentation
mentioning closing keywords inside inline backticks (say a PR body
that explains the new hook with examples) counted as real closes, and
a skip marker inside inline backticks silently bypassed the check.
Future PRs that document the feature would trip the same trap.
Fix the code-region stripper to cover:
- Triple-backtick fences (already handled)
- Tilde fences (new)
- Inline-backtick spans (new)
Also run the skip-marker check against the stripped body, so a marker
used purely as documentation no longer activates a real bypass.
Three new test cases pin the behaviour:
- closing keywords in inline backticks are ignored
- skip marker inside inline backticks does NOT bypass
- tilde fences also get stripped
13/13 tests pass.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#108): add /tickets-batch skill for bulk-file flow (#127)
The fast happy path for filing 5–20 structured tickets in one intent
without dropping to raw `gh issue create` (non-conformant) or running
`/feature` 20 times serially (~100 turns of interview).
- .claude/skills/tickets-batch/SKILL.md — new skill spec. Asks
shared-context questions (priority, epic, area-labels, repo) ONCE
for the whole batch, then runs a ≤3-question micro-interview per
ticket (type, one-line purpose, optional clarification when the
inference is low-confidence). Confirms the full batch as a table,
then files each via specific `gh issue create` calls (never a
bulk JSON dump — the validator runs per-issue). Output conforms
to `.ticket.required_sections` by construction. Caps at 20
tickets per invocation.
- CLAUDE.md — added a row for /tickets-batch in the Available
Skills table; bumped the count references from 33 to 34.
Refs https://github.com/me2resh/apexyard/issues/108
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#117): add /fan-out skill + parallel-work rule doc (#128)
- Add `.claude/skills/fan-out/SKILL.md` — spawns N parallel Agent calls
in a single assistant message, with per-task agent type, worktree
isolation, and foreground/background mode. Caps at 5 concurrent
agents. Refuses fan-out when tasks share file write targets or have
sequential dependencies. Includes pre-spawn active-ticket safety
check and worktree merge-back flow that pauses on conflict.
- Add `.claude/rules/parallel-work.md` — trigger heuristic for when an
agent should proactively offer fan-out (>= 2 file-independent,
context-independent, individually substantial work items). Pairs
with the skill: rule says when, skill says how.
- Update `CLAUDE.md` — bump rules count to 9, skills count to 34, add
`/fan-out` row to the skills table.
Refs https://github.com/me2resh/apexyard/issues/117
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#116): adopt release-cut branch model (dev/main + tags) — framework only (#126)
* chore(#116): adopt release-cut branch model (dev/main + tags)
Formalises the dev/main split that already exists informally — dev is
the daily-work branch where every PR lands, main is release-only,
tagged with semver on each merge. Framework-only: managed projects
under apexyard governance stay trunk-based.
Added:
- AgDR-0007 — decision record (options table covers full git flow vs
trunk-only vs gitflow-lite; chose gitflow-lite)
- /release skill — diff dev against main, propose semver bump from
conventional commits, generate CHANGELOG, open release PR, tag
after merge
- docs/release-process.md — prose runbook for cutting a release
(manual fallback for the skill)
- .git.protected_branches in project-config.defaults.json
(main/master/dev/develop)
Modified:
- block-main-push.sh — now blocks direct pushes/commits to all
configured protected branches (was: hardcoded main/master). Reads
.git.protected_branches via the shared config reader (apexyard#109).
- CLAUDE.md — new section under Git Conventions explaining the
dev/main model + the framework-only scope. Skill table entry for
/release. Skills count bumped to 34.
- docs/multi-project.md — note that upstream/main is release-only
and the dev/main split is framework-only.
Non-consequences (per AgDR-0007):
- No release/* or hotfix/* branches. Hotfixes are normal patches
cut quickly. Revisit if multi-version maintenance becomes a need.
- No automatic on-merge issue closing for dev PRs. The release PR's
body aggregates all Closes references for the batch and triggers
auto-close en masse when it merges to main. Manual close in the
meantime.
- CI workflows trigger on pull_request regardless of base, so
dev-targeting PRs already get the full check matrix — no
workflow file edits needed.
https://github.com/me2resh/apexyard/issues/116
* fix(#116): satisfy markdownlint MD032 + MD060 in 116 docs
Auto-fix added blank lines around lists in AgDR-0007 (MD032) and
spaced the table separators in AgDR-0007 + multi-project.md (MD060).
Content unchanged.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* fix(#106): CHANGELOG fallback in drift hook for squash-merged forks (#129)
* fix(#106): CHANGELOG fallback in drift hook for squash-merged forks
The v1.1.0 tag-reachability check (`git tag --merged main`) misfires on
forks that sync via GitHub's default squash-merge: the squash collapses
the upstream-tag commit into a synthetic SHA, the tag stops being
reachable, and the banner keeps firing forever.
Discovered live on the first real-world `/update` flow: ops fork
squash-merged the v1.1.0 sync PR, banner kept saying "v1.1.0 available"
even though the fork was content-caught-up.
This commit adds a CHANGELOG-content fallback that fires only when the
primary tag check fails. If the fork's main has a heading
`## [X.Y.Z]` matching the upstream tag's version, treat the release as
absorbed and stay silent. Tolerant grep (matches the apexyard CHANGELOG
format from v1.1.0 onward, with leading-`v` stripping for tag→heading
conversion).
The merge-commit and rebase paths are unchanged — primary tag check
still works for them, and the fallback never fires when it shouldn't.
Test coverage (5 cases):
- squash-merge fork caught up to v1.1.0 → silent (the fix)
- merge-commit fork caught up to v1.1.0 → silent (regression check)
- fork stopped at v1.0.0 → banner fires
- fork has its own newer tag → silent
- squash-merge but no CHANGELOG on fork → banner fires (no false silence)
Records the strategy update in docs/agdr/AgDR-0008-…md (extends
AgDR-0005's tag-based-drift design).
https://github.com/me2resh/apexyard/issues/106
* fix(#106): satisfy markdownlint MD032/MD060 + shellcheck SC2164
Rex flagged two CI-blocking issues on the original 106 commit:
- AgDR-0008 had three bulleted sub-lists in the Consequences section
without surrounding blank lines (MD032). Added blanks and padded the
one tight-pipe table separator (MD060).
- The 106 test fixture had five subshell `cd "$fk"` calls without
`|| exit 1` (SC2164). Added the guard to all five.
5/5 tests still pass after the fix. No semantic change to the hook
or the test logic.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#130): add /validate-idea skill — lightweight pre-spec gate (#131)
A 10-minute, 5-question check that sits between /idea and /write-spec.
Designed for solo founders running ApexYard — not a heavyweight
methodology like event storming or Wardley mapping.
Five questions, asked one at a time:
1. Who is this specifically for?
2. What do they do today instead?
3. What's the smallest version that proves the value?
4. What would prove this is wrong? (kill criteria)
5. Build, buy, or rent?
Output: a one-page validation doc with a GREEN/YELLOW/RED verdict.
RED auto-updates the IDEA-NNN backlog row to WONTDO.
Integration:
/idea — adds an optional default-no "Validate now?" step after
capture (and after the optional GitHub Issue offer).
/handover — adds a conditional "this looks dormant, validate?"
step at the end of the integration plan, gated on the dormancy
heuristic (last commit > 90d AND zero open PRs AND no recent
issue activity). Healthy projects don't see the prompt.
CLAUDE.md skills count bumped to 35; new skills row added.
https://github.com/me2resh/apexyard/issues/130
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat: configurable voice prompts on assistant pause (AgDR-0009-voice-prompts-on-pause) (#135)
Stop hook that speaks the assistant's question aloud (Jarvis-style)
when it pauses for user input. Initial phase is macOS-only via `say`,
no voice input — user replies via keyboard.
Default OFF. Adopters opt in by overriding `voice_prompts.enabled` to
true in `.claude/project-config.json`.
Files:
- .claude/hooks/voice-prompt-on-pause.sh — Stop hook with config gate,
trigger heuristic (questions-only by default), markdown stripping,
sentence-boundary truncation, fire-and-forget say invocation
- .claude/hooks/tests/test_voice_prompt_on_pause.sh — 9 cases covering
disabled-default, enabled+question, enabled+statement, approved-pattern,
abc-menu, malformed-transcript, no-say-on-PATH, trigger-always,
markdown-stripping
- .claude/project-config.defaults.json — voice_prompts schema block
added (enabled, voice, max_chars, rate_wpm, trigger), default OFF
- .claude/settings.json — new Stop hook entry wired with the standard
ops-root resolver wrapper
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — design rationale,
options matrix (status quo / macOS say / cloud TTS / ML detection),
consequences, future phases
- docs/project-config.md — new "Voice prompts" section with override
examples and privacy notes
Test mode: hook respects VOICE_PROMPTS_SYNC=1 to run say synchronously
(test runners need this so assertions don't race against orphaned
background processes). Production invocations always run async.
Future phases (out of scope here, AgDR §"Future phases"):
- Phase 2: cross-platform TTS (Linux espeak, Windows SpeechSynthesizer)
- Phase 3: cloud TTS providers (OpenAI, ElevenLabs) — privacy AgDR-worthy
- Phase 4: voice input via Whisper-based STT
- Phase 5: per-message overrides
Refs: https://github.com/me2resh/apexyard/issues/134
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#141): add /debug skill — structured hypothesis-driven debugging (#142)
* feat(#141): add /debug skill — structured hypothesis-driven debugging
Adds a methodology skill that enforces five disciplines:
1. Capture the symptom precisely (exact URL, exact response, exact step)
2. Read the architecture before guessing (map every layer the request
touches, file by file)
3. Form a hypothesis ladder (3–5 candidates, each with an explicit
evidence test that confirms or refutes it)
4. Gather evidence first, fix second
5. Verify the fix against the original symptom evidence (re-run the
same `curl` / browser repro you used in step 4 — unit tests
verify code, not feature, correctness)
Stack appendices (Web, Desktop) carry stack-specific surface-evidence
requirements (step 1), architecture-surface maps (step 2), and
evidence-tests cookbooks (step 4). The methodology body stays portable
across stacks; appendices are where stack-specific knowledge accrues
over time.
Web appendix covers browser routing, framework configs (Next/Nuxt/Vite),
SPA-fallback layers, CDN, origin, the shared API client, backend
handlers, and auth providers. Desktop appendix covers Electron / Tauri
/ native-shell concerns: app entry points, IPC bridges, native modules,
auto-updater, sandbox / entitlements, code signing, crash reports.
Includes "When NOT to use" guidance so the methodology overhead doesn't
sandbag simple bugs (typos, off-by-ones, greenfield exploration).
Motivated by a real OAuth debug session in a managed project where
three sequential fixes chased adjacent symptoms because each was
hypothesis-then-fix without evidence in between. The skill is the
"never do that again" guardrail.
Closes #141
* fix(#141): scrub private project issue numbers from anti-pattern table
Rex review on PR #142 caught that line 155 of the skill's anti-pattern
table still named the originating PRs (#375, #377, #380) from the
private project where the methodology was first exercised. The PR body
and commit message were correctly abstracted earlier, but this in-file
reference slipped through — the leak-protection hook only scans gh
issue/pr writes, not staged file content, so mechanical enforcement
didn't catch it.
Replaced with "Three sequential PRs chasing the same symptom because
each was based on a different guess (no evidence test in between
cycles)" — same pedagogical value, zero attribution.
Refs #141
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(#143): document split-portfolio mode + add /setup privacy gate (#144)
Adopters on GitHub Free with any private project hit a silent privacy bug
following today's docs: forking apexyard makes a public fork that you cannot
later flip to private (GitHub policy), and committing `apexyard.projects.yaml`
+ `projects/<name>/` to the fork publishes private project names + handover
findings on a public GitHub repo.
This PR documents the supported workaround — split-portfolio mode — and
adds an upfront privacy gate to the /setup skill so new adopters never
hit the trip-wire silently.
docs/multi-project.md:
- New "Two setup modes — pick the one that matches your privacy needs"
section before TL;DR, with a side-by-side table and the explicit
trip-wire callout.
- Existing TL;DR retitled "TL;DR — single-fork mode (default)" with a
one-line pointer to the split-portfolio section.
- New "Split-portfolio mode — public framework + private portfolio"
section between the existing setup steps and the directory-layout
section. Includes:
- The two-repo layout (~/ops/apexyard public + ~/ops/portfolio private)
- 7-step setup walkthrough with copy-pasteable commands
- Daily workflow + upstream sync notes (both unchanged)
- Trade-offs (two repos to maintain, two clones per machine, one
upstream-sync conflict path on `projects/README.md`)
- "Migrating from single-fork to split-portfolio" recovery flow with
the explicit warning that GitHub Issue / PR edit history survives a
force-push and must be redacted separately
.claude/skills/setup/SKILL.md:
- New Step 2a: privacy gate — asks "are any projects private?" before
proposing the config. Branches on the answer:
- All public → single-fork mode
- GitHub Pro / Team / Enterprise → single-fork mode (private
forks of public repos
are supported on those
plans)
- Any private + GitHub Free → split-portfolio mode
- New Step 2b: walks through the split-portfolio setup interactively
(private repo create, sibling clone, gitignore + symlink) when the
privacy gate triggers.
- Detection: `test -L apexyard.projects.yaml` short-circuits Step 2b
for adopters already in split mode.
- Explicit "do NOT auto-migrate" rule for adopters already in single-fork
mode with private names already pushed — that path is destructive
(force-push history rewrite + redact issue/PR bodies + delete backup
branch) and warrants a deliberate, eyes-open run, not a /setup side
effect.
Out of scope for this PR (tracked separately on #143):
- `portfolio:` config block in `onboarding.yaml` schema
- Skill audit + refactor to honour configured `registry` / `projects_dir`
/ `ideas_backlog` paths instead of hardcoded fork-relative paths
- `/split-portfolio` migration helper skill that automates the recovery
flow currently documented manually
This is the docs-and-setup-question minimum-viable starter — the
framework code refactor is mechanical and lands as a follow-up.
Refs #143
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#145): portfolio config + self-healing + /split-portfolio helper (#147)
* feat(me2resh/apexyard#145): portfolio config block + self-healing + /split-portfolio helper
Closes the framework primitive deferred from me2resh/apexyard#144. Adds
first-class config-driven path resolution for the portfolio registry,
projects dir, and ideas backlog, with self-healing surfacing of broken
config at session start, plus a new /split-portfolio skill that automates
the destructive recovery flow.
Schema, helper, and hook:
- .claude/project-config.defaults.json: new portfolio: block
(registry, projects_dir, ideas_backlog) with defaults matching
today's single-fork layout
- .claude/hooks/_lib-portfolio-paths.sh: new sourceable helper exposing
portfolio_registry, portfolio_projects_dir, portfolio_ideas_backlog,
portfolio_validate, portfolio_clear_cache. Resolves relative paths
against the ops-fork root.
- .claude/hooks/check-portfolio-config.sh: new SessionStart hook —
silent on OK, one-line banner on broken config, never blocks session
- .claude/hooks/tests/test_portfolio_paths.sh: 13 cases covering
defaults, absolute/relative overrides, validate states, cache clear
Skill audit (18 SKILL.md files):
- Adds Path resolution callout pointing at the helper
- handover bash blocks now source helper and use $(portfolio_registry)
instead of literal apexyard.projects.yaml
- setup Step 2b now writes the portfolio: config block (recommended)
and validates via portfolio_validate before declaring success;
symlink approach kept as legacy fallback
New skill (.claude/skills/split-portfolio/SKILL.md):
- 10-step migration with explicit operator-confirmation gates at each
destructive step (force-push, body redaction, branch deletion)
- --verify mode: read-only state report (mode, paths, validate, drift)
- --dry-run mode: prints commands without executing
- Pre-flight refusals: already-private fork, paid GitHub plan, dirty
working tree, already-migrated state
- Step 9 writes the portfolio: config block (not symlinks) — symlink
fallback documented for adopters on older framework versions
- Step 9 surfaces the GitHub timeline-API survival caveat verbatim
- Idempotent re-runs: detects partial-migration state and resumes
Docs (docs/multi-project.md):
- Layout section describes both modes (config-block recommended,
symlink legacy) with self-healing notes
- Setup steps split into config-block mode and legacy symlink mode
- Migration section now points at /split-portfolio skill; manual
recipe preserved as fallback
AgDR (docs/agdr/AgDR-0010-portfolio-config-and-self-healing.md):
- Full Y-statement, options, decision, consequences, future phases
- Schema decision rationale: project-config.json over onboarding.yaml
because runtime path resolution belongs in project-config
Closes me2resh/apexyard#145
Refs me2resh/apexyard#146 (delivered same PR; closed manually post-merge
per the single-Closes-keyword rule in validate-pr-create.sh)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(me2resh/apexyard#145): markdownlint MD031 — blank lines around fences
CI's markdownlint-cli2 (v0.34.0) flagged the JSON + bash fenced code
blocks I added in setup/SKILL.md Step 2b without surrounding blank
lines. Added the required blanks. No content change.
Refs me2resh/apexyard#147
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>
* docs(me2resh/apexyard#148): correct privacy-gate wording — adopter action, not framework auto-publish (#149)
The privacy-gate wording introduced in PR #144 (and unchanged in PR #147)
attributed the publication to the framework rather than the adopter:
"the standard fork-and-commit setup will silently publish your private
project names on a public GitHub repo"
That's factually wrong. ApexYard never pushes anything without explicit
operator approval — the publication only happens when the adopter
themselves runs git push. The "silently publish" framing read as if the
framework auto-publishes, which is misleading and undermines trust in
the rest of the framework's safety claims.
Two prose-only edits, no code, no behavior change:
- .claude/skills/setup/SKILL.md Step 2a — replaced "will silently
publish ..." with adopter-action language ("you might accidentally
publish ... a stray git push after registering them — I won't push
without your approval, but the risk is on the adopter once the data
is committed locally")
- docs/multi-project.md trip-wire callout — replaced "silently publish
their portfolio names the moment they push" with "risk accidentally
publishing their portfolio names with a stray push (the framework
itself never pushes without operator approval, but once the registry
is committed locally the next push exposes it)"
Verified: `grep -r "silently publish" .claude/skills/ docs/` returns no
hits.
Closes me2resh/apexyard#148
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#150): bootstrap-skill exemption + Bash-write coverage (#152)
Closes the legitimate-bypass case (me2resh/apexyard#150) and the
illegitimate-bypass case (me2resh/apexyard#151) together so the
ticket-first gate is coherent. Shipping either alone would leave a
window where the framework is internally inconsistent — see AgDR-0011
for the full rationale.
Bootstrap exemption (me2resh/apexyard#150):
- .claude/session/active-bootstrap marker, written by /setup,
/handover, /update, /split-portfolio on entry; cleared on exit
- SessionStart sweep (clear-bootstrap-marker.sh) for stale markers
from interrupted sessions
- require-active-ticket.sh reads the marker and exempts skills on
the configured ticket.bootstrap_skills list
- bootstrap_skills list lives in .claude/project-config.defaults.json
(extendable per fork via .claude/project-config.json)
Bash-write coverage (me2resh/apexyard#151):
- new _lib-detect-bash-write.sh — heuristic detector for output
redirection, tee, sed -i, awk -i inplace, python/node/ruby
embedded interpreters
- require-active-ticket.sh + require-migration-ticket.sh now fire
on Bash in addition to Edit|Write|MultiEdit
- design choice: false-negatives preferred over false-positives
(the matcher errs toward "let through" rather than block legit
read-only commands)
Tests: 32 unit cases on the lib + 12 integration cases on the hook,
including the exact me2resh/apexyard#151 bypass repro from the issue
body.
Closes me2resh/apexyard#150
Will manually close me2resh/apexyard#151 post-merge per the
single-Closes-per-PR rule (precedent: AgDR-0010 / PR #147).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#153): extend Bash-write matcher beyond first-version coverage (#155)
Closes me2resh/apexyard#153.
Extends `_lib-detect-bash-write.sh` (introduced for me2resh/apexyard#151
in PR #152) with the matcher families flagged by Rex's review of #152.
AgDR-0011 already frames the matcher as a living list extended on
observation; this commit just walks the list.
New matcher families:
- File-moving builtins: `cp`, `mv`, `rm`, `dd`, `install` (anchored at
command-start; `--help`/`--version` and `git rm`/`git mv` excluded)
- Archive / network writes: `tar -x` / `tar --extract`, `curl -o` /
`--output`, `wget -O` / `--output-document`
- Additional embedded interpreters: `perl -e`, `php -r` (keyword-gated
like python/node/ruby); `go run`, `deno run`/`deno script.ts`,
`bun run`/`bun script.ts` (categorical script runners)
- Python helpers: `pathlib.Path().touch()`, `shutil.copy*`,
`shutil.move`, `os.rename` added to the `python -c` and python
heredoc keyword list
- Heredoc variants for `ruby` and `node` (previously only python
heredoc was covered)
Extractor extensions:
- `cp` / `mv`: last positional arg
- `curl -o` / `--output`: file argument
- `wget -O` / `--output-document`: file argument
- `tar -x`, `go run`, `deno`, `bun`, `perl -e`, `php -r`: return empty
(caller applies gate categorically per AgDR-0011)
Test count rose from 32 to 86. Negative-class counterexamples cover
the trickiest false-positive surfaces: `tar -t` listing, `cp --help`,
`rm --version`, `git rm`, `curl` bare URL fetch, `wget` bare URL fetch,
`deno fmt`, `deno test`, `go build`. Existing
`test_require_active_ticket_bash.sh` regression suite still passes 12/12.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(#154): mock gh in test sandboxes to remove live-tracker dependency (#156)
- add _lib-mock-gh.sh helper that installs a fake `gh` on the sandbox PATH;
intercepts `gh issue view <N> ... --json ...` and returns synthetic
`{"number":N,"state":"OPEN"}` (overridable per-num via mock_gh_set_state)
- wire the shim into test_single_closes_per_pr.sh and
test_validate_pr_required_sections.sh so the validator's CLOSED-issue
refusal no longer breaks the suite when upstream issues are closed
- both files previously failed every case (0/13 and 0/8) because their PR
titles reference #114 / #113, which are now CLOSED upstream
- post-fix: 13/13 and 8/8; full suite remains green
Closes me2resh/apexyard#154
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#132): structured CEO marker + same-turn merge in /approve-merge (#158)
* feat(#132): structured CEO marker + same-turn merge in /approve-merge
Closes me2resh/apexyard#132 (drop the "stop before merge" rule) and
me2resh/apexyard#48 (harden CEO marker against self-approval bypass)
together. The two threads compose — see AgDR-0012 for the full
rationale.
Streamline (#132):
- /approve-merge now runs `gh pr merge --squash --delete-branch`
in the same turn as the marker write, by default
- --no-merge opt-out preserves the deferred-merge case
- The discrete approval moment is the SKILL INVOCATION, not a
follow-up "now do the merge" message
Harden (#48):
- CEO marker is now a structured key/value file with required fields:
sha=<HEAD>
approved_by=user
skill_version=2
Validated by block-unreviewed-merge.sh; bare-SHA legacy markers
rejected with a clear "stale format" error pointing at /approve-merge
- The model's bare `echo SHA > <pr>-ceo.approved` bypass is now
mechanically rejected. Forging the structured fields requires a
deliberate, visible rule violation rather than a one-line accident
- Optional audit fields (approved_at, approval_summary) capture the
"what did the user say when they approved" trail
- Rex marker stays bare-SHA — different threat model (automated
reviewer, not human authorization moment)
pr-workflow.md reframed: "the load-bearing rule is explicit per-PR
approval, not two user messages." The merge is a deterministic
consequence of the approval invocation.
Tests: 12 cases on the hardened hook covering the new format end-to-end
(valid v2, missing rex/ceo, bare-SHA legacy rejected, missing
approved_by, wrong approved_by, skill_version=1, sha mismatch,
non-merge no-op, gh-api shape gated). Full suite: 205/205 across 13
test files.
Will manually close me2resh/apexyard#48 post-merge per the
single-Closes-per-PR rule (precedent: PR #152 / AgDR-0011).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#132): redact private project reference from AgDR-0012
Abstracted two references to a registered private project that named
the project's owner/repo. The leak-protection hook caught one in the
PR body; this fixup removes the matching references from the
AgDR-0012 file content (which would otherwise have shipped the names
to me2resh/apexyard public repo via the merge).
The pre-existing reference at .claude/rules/pr-workflow.md:130
(documenting #47) is untouched — it predates this PR and is already
on the public repo's history.
Refs me2resh/apexyard#132.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#132): markdownlint blanks-around-fences + typo fix
Two small fixups against red CI / Rex feedback:
- AgDR-0012 line 63: fenced code block now has a blank line before it
(MD031). markdownlint-cli2 0.13.0 was rejecting the indented fence
inside the bullet because the fence's preceding line was the bullet
text (no blank).
- approve-merge SKILL.md line 172: typo `deferes` → `defers` (Rex flag
on PR #158).
Refs me2resh/apexyard#132.
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>
* chore(#157): remove voice-prompts feature + correct hook/skill counts (#161)
* chore(#157): remove voice-prompts-on-pause feature + correct hook/skill counts
Closes me2resh/apexyard#157 (sunset the voice-prompts feature) and
me2resh/apexyard#77 (hook count off-by-one in CHANGELOG / CLAUDE.md)
in one bundled PR. AgDR-0013 supersedes AgDR-0009 with the full
rationale; both AgDRs are preserved (decision records are append-only
history).
Removed (#157):
- .claude/hooks/voice-prompt-on-pause.sh
- .claude/hooks/tests/test_voice_prompt_on_pause.sh
- Stop matcher block in .claude/settings.json (became empty after
voice removal)
- voice_prompts block in .claude/project-config.defaults.json
- "## Voice prompts" section in docs/project-config.md
- voice_prompts mention in AgDR-0010 line 32 (replaced with
leak_protection / ticket as still-current example config blocks)
Preserved:
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — historical record;
new "Superseded by: AgDR-0013" header at the top
- AgDR-0010 line 115 reference to AgDR-0009 — still accurate as a
historical pattern reference
Counts corrected (#77):
- CHANGELOG.md v0.3.0 stats: "17 hooks" → "18 hooks" (historical
fix — at v0.3.0 there were actually 18 hooks)
- CLAUDE.md table line: "18 shell scripts" → "24 shell scripts"
(current count after this removal)
- CLAUDE.md table line: "35 slash commands" → "39 slash commands"
- CLAUDE.md "Available skills (34)" → "Available skills (39)"
- CLAUDE.md quick-reference "Skills (35 slash commands)" →
"(39 slash commands)"
Why bundled: #77's correct count depends on whether voice is still in
the framework. Shipping #77 before #157 would write a number that's
wrong by one again the moment #157 lands. Same shape as previous
bundles (PR #152 / AgDR-0011, PR #158 / AgDR-0012).
Why no adopter-facing changelog mention of the voice removal: the
feature never reached a tagged release on main. v1.1.0 didn't have
it; v1.2.0 won't have it. From the adopter's perspective there's
nothing to retire. AgDR-0013 captures the framework's internal record
for future contributors. See AgDR-0013 § "No adopter-facing changelog
mention".
Tests: full hook test suite green (196 cases across 12 files —
test_voice_prompt_on_pause.sh removed). No regressions.
Will manually close me2resh/apexyard#77 post-merge per the
single-Closes-per-PR rule (precedent: PRs #152, #158).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#157): unwire voice from settings + configs + docs + AgDR-0013
Continuation of d09d7b5 (the file deletions). Squash-merge will
collapse both into one PR commit. This commit captures:
- .claude/settings.json — Stop matcher block removed (was the only
hook in it; entire matcher gone)
- .claude/project-config.defaults.json — voice_prompts block + its
_comment removed
- docs/project-config.md — "## Voice prompts" section removed
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — "Superseded by"
header added at the top, content otherwise preserved as history
- docs/agdr/AgDR-0010-portfolio-config-and-self-healing.md — line 32
example reference swapped from voice_prompts to
leak_protection / ticket (still-current config blocks)
- docs/agdr/AgDR-0013-sunset-voice-prompts.md — new supersession AgDR
- CLAUDE.md — hook count 18 → 24, skill count 35 → 39 (three
occurrences each, all aligned to current reality)
- CHANGELOG.md v0.3.0 stats — "17 hooks" → "18 hooks" historical fix
(#77 acceptance criterion 1)
Refs me2resh/apexyard#157 + me2resh/apexyard#77.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#157): markdownlint MD028 + resolve stale AgDR-numbering ref
Three small fixups against Rex CHANGES-REQUESTED on PR #161:
- AgDR-0009 line 3: promote "Superseded by:" header out of a
blockquote. The original "I decided ..." canonical blockquote at
line 5 was being merged with the new supersession blockquote
(markdownlint MD028 — "no blanks inside blockquote").
- AgDR-0013 line 3: same shape — "Supersedes:" header now a plain
bold paragraph, canonical "I decided ..." blockquote untouched.
- approve-merge SKILL.md line ~187: stale conditional reference
"AgDR-0012 (or 0013 — depends on whether voice-removal lands
first)" — order is now resolved (12 = approve-merge bundle, 13 =
voice removal). Drop the parenthetical.
Refs me2resh/apexyard#157 + me2resh/apexyard#77.
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(#160): multi-tab terminal demo on the landing site (#162)
Landing-site `site/index.html` terminal demo previously played one
canonical flow ("one ticket, start to finish") on autoplay. With the
v1.2.0 skill surface expansion (4 new skills, 8+ new hooks), one flow
no longer represents the framework's breadth.
Adds tabs to the terminal chrome — four flows visitors can either let
auto-cycle or click directly:
1. one ticket — existing flow, unchanged content
2. /handover — adopt an external repo into the portfolio
3. /setup — first-run framework bootstrap on a fresh fork
4. /fan-out — spawn 3 parallel agents on independent tickets
Auto-advance: on completion of the active tab's script, the demo
pauses ~1.8s then advances to the next tab. Loops at the end. User
can interrupt by clicking any tab or hitting Replay.
Implementation:
- HTML chrome — single title span replaced with a tablist of 4 button
tabs. ARIA `role="tablist"` / `role="tab"` / `aria-selected` so
keyboard + screen-reader users get the same semantics as sighted
ones.
- CSS — new `.shell-demo__tabs` + `.shell-demo__tab` with an accent
underline on the active tab. Tabs scroll horizontally on narrow
viewports (mobile responsive).
- JS — refactored the existing IIFE from one `script` array to an
array of four. Added `setActiveTab()` for ARIA state, `play(idx)`
takes a tab index, end-of-script auto-advances to `(idx + 1) % N`
unless the user clicked away during the pause. Adds a new `cmd`
type alongside `you` for slash-command invocations (renders with
the same `>` prompt prefix). prefers-reduced-motion still bails
early and leaves the static seed visible.
- Static seed (the non-JS fallback) still shows tab 0's content, so
reduced-motion / no-JS visitors see the one-ticket flow as before.
Hero metrics also corrected to current reality (#77 / PR #161 covers
CLAUDE.md and CHANGELOG.md; this PR catches the same numbers in the
landing site):
Skills 32 → 39
Hooks 18 → 24
The tabs ship in v1.2.0 alongside the framework changes that make
the new flows worth showcasing.
Refs me2resh/apexyard#160 (release v1.2.0 + landing-site refresh).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#163): default the split-portfolio sibling repo name to <fork>-portfolio (#164)
Closes me2resh/apexyard#163.
The split-portfolio mode docs and skills previously suggested
`your-org/ops` as the default name for the private sibling repo, and
`portfolio/` as the local clone directory. Both too generic — adopters
running multiple ops setups end up with `your-org/ops` collisions, and
a bare `portfolio/` dir gives no signal about which framework it
belongs to when it sits next to other unrelated `portfolio/` dirs.
`<fork>-portfolio` is now the default — keeps the relationship to the
public fork explicit on disk and on GitHub. If the fork is named
`your-org/apexyard`, the portfolio defaults to
`your-org/apexyard-portfolio`. If the fork was renamed (e.g. `cos`),
the portfolio defaults to `cos-portfolio`. Adopters with custom names
keep working — the `portfolio:` config block resolves whatever path
they configured.
Files updated:
docs/multi-project.md
- Layout diagrams use `apexyard-portfolio/` as the sibling
- Setup walkthrough Step 2 + Step 3 use `your-org/apexyard-portfolio`
and explain the `<fork>-portfolio` pattern
- Config-block + symlink path examples updated to
`../apexyard-portfolio/...`
- Daily workflow + cross-machine clone commands updated
- The two existing `your-org/ops` references that remain are
fork-rename examples (lines 52, 64) — kept as-is, since renaming
the fork to `ops` is still valid (the portfolio would then
default to `ops-portfolio`)
.claude/skills/setup/SKILL.md
- Step 2b's "default suggestion" for the private repo name is now
`your-org/<fork>-portfolio`, computed dynamically from the
fork's repo name via `gh repo view --json name -q .name` so the
suggestion is correct even when the fork was renamed
- Clone command no longer needs a second arg — the repo name IS
the directory name
- Config-block paths updated to `../apexyard-portfolio/...`
.claude/skills/split-portfolio/SKILL.md
- Step 3's suggested-name template is now `<account>/<fork>-portfolio`
with the same dynamic-fork-name resolution
Mechanism unchanged. The `portfolio:` config block in
`.claude/project-config.json` still takes any path; this PR is purely
default-suggestion + example prose.
No tests required (skills are markdown instructions; no automated
coverage today).
Refs `apexyard.projects.yaml.example` uses "ops repo" as a generic
term meaning "the operational management fork" — not a name — kept
as-is.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#165): skills reference page on the landing site + changelog link (#167)
Closes me2resh/apexyard#165.
Adds a public, browseable index of every apexyard slash command
alongside a one-click changelog link from the homepage nav.
site/skills.html (new):
- Lists all 39 skills currently shipping in .claude/skills/
- Each entry: slash command, argument hint, description (taken
verbatim from the SKILL.md frontmatter so the page matches the
runtime exactly)
- 10 categories: Setup & onboarding, Daily ops, Tickets & ideas,
Specs & decisions, Code review & merge, Architecture & dev tools,
Production-readiness audits, Workflow primitives, Communications,
Deprecated
- Same brutalist-terminal design tokens as the homepage — JetBrains
Mono, paper-cream background, single warning-red accent, sharp
corners. Inlined CSS to keep the static-only no-build-step
convention; design vars duplicated rather than extracted to a
shared file (~18 vars; cheap to keep in sync).
- Mobile responsive — skill grid collapses to single-column under
720px; titlebar nav hides non-CTA items on narrow viewports.
- Reduced-motion friendly (no animation in the first place).
- Internal anchor TOC at the top so the page scans in seconds.
site/index.html (nav addition):
- Added two nav links to the titlebar between "what's in the box"
and the github CTA:
• skills → ./skills.html
• changelog → https://github.com/me2resh/apexyard/releases
- The changelog link points at the GitHub releases page (not the
raw CHANGELOG.md file) so it auto-resolves to the latest tagged
release on each visit. v1.2.0 lands and the link is already there.
No new dependencies, no build step, no JS for the skills page. The
existing site convention (one-html-file-per-route, inlined CSS,
optional progressive-enhancement JS) is preserved.
Refs me2resh/apexyard#160 (release v1.2.0 + landing-site refresh) —
this is the second site-side deliverable for that ticket; the release
tag itself follows once me2resh/apexyard#159 (testing) closes.
Follow-up worth a separate ticket: a small generator script that
walks .claude/skills/*/SKILL.md, parses YAML frontmatter, and emits
the skills.html sections automatically. Out of scope for v1.2.0 —
first version is hand-curated and will need maintenance until that
generator lands.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#168): accept release/vN.N.N branches + release(...) PR titles (#169)
Closes me2resh/apexyard#168.
The /release skill prescribed `release/vA.B.C` as the source-branch
name and `release: vA.B.C` as the PR title for the dev → main release
PR (per AgDR-0007). Both were rejected by the framework's own
validators:
validate-branch-name.sh required {type}/{TICKET-ID}-{description};
release/v1.2.0 has no ticket-id portion.
validate-pr-create.sh required type(SCOPE): form with `release` not
in pr.title_type_whitelist.
The contradiction surfaced cutting v1.2.0 — the first release under
the dev/main model.
Three small changes:
1. .claude/hooks/validate-branch-name.sh — added an early-out branch
that accepts ^release/vN.N.N(-rcN)?$ as a valid name. Narrow,
intentional exception for the framework's release-cut convention;
release branches don't carry a ticket-id because the release itself
IS the ticket.
2. .claude/project-config.defaults.json — added "release" to
pr.title_type_whitelist so a title like `release(#160): v1.2.0`
passes validate-pr-create.sh's existing regex unchanged.
3. .claude/skills/release/SKILL.md step 4 — corrected the prescribed
PR title to `release(#<release-ticket>): vA.B.C` so future /release
invocations produce a title that satisfies the validators by
construction.
Tested:
bash .claude/hooks/validate-branch-name.sh against:
release/v1.2.0 → 0 (allowed, release-special-case)
release/v1.2.0-rc1 → 0 (allowed, RC variant)
release/v9.9.9 → 0 (allowed)
release/foo → 2 (correctly blocked)
release/v1 → 2 (correctly blocked)
chore/GH-168-fix → 0 (allowed, standard pattern)
feature/GH-1-x → 0 (allowed, standard pattern)
Full hook test suite: 196/196 cases green across 12 test files.
Refs: surfaced 2026-05-04 cutting the first release under AgDR-0007.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#170): exempt release/vN.N.N from validate-pr-create's branch-id check (#171)
Closes me2resh/apexyard#170.
Completes the work started in #169 (closing #168). #169 added a
release-pattern early-out to validate-branch-name.sh so release/vN.N.N
branches pass the branch-name validator. But validate-pr-create.sh has
its own independent branch-id check at line 273 that #169 didn't
touch — and it still rejects release/v1.2.0 because that name doesn't
contain a ticket-id substring.
This is the same class of contradiction #168 fixed; the fix is the
same shape. Add the same release-pattern early-out to the branch-id
check in validate-pr-create.sh:
- if the branch matches ^release/vN.N.N(-rcN)?$ → exempt (release
branches don't carry ticket-ids; the release itself is the ticket)
- otherwise → require a ticket-id substring as before
#168's acceptance criterion 3 ("validate-pr-create.sh accepts a PR
title `release(#160): v1.2.0` against the `release/v1.2.0` branch")
was checked off based on the title regex alone but didn't catch the
secondary branch-id check living in the same file. Surfaced trying
to open the v1.2.0 release PR.
Tested:
bash .claude/hooks/validate-pr-create.sh against:
release/v1.2.0 → 0 (allowed, exempt)
release/v1.2.0-rc1 → 0 (allowed, RC variant)
chore/GH-1-fix → 0 (allowed, has ticket-id)
release/foo → 2 (correctly blocked)
chore/no-ticket → 2 (correctly blocked)
Refs me2resh/apexyard#168 (the parent bug) + #169 (the partial fix).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#173): sync CHANGELOG.md from main → dev (#174)
Closes me2resh/apexyard#173.
The release-cut model (AgDR-0007 / #116) squash-merges
release/vN.N.N → main, which means the v1.2.0 CHANGELOG section that
landed on main via PR #172 was never propagated back to dev. This
commit copies main's CHANGELOG.md verbatim onto dev so the v1.2.0
section is now present on both branches.
The diff is exactly the v1.2.0 entry being prepended; no other lines
change.
Without this sync, the next release PR cut from dev would build a
v1.3.0 section on top of v1.1.0, silently dropping v1.2.0 from dev's
running history. The corollary skill-level fix (option B in the
ticket) — updating /release to source the previous CHANGELOG from
upstream/main — is filed as a follow-up and out of scope for this PR.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#181): /agdr skill — searchable AgDR library (#186)
Adds /agdr — a portfolio-wide index for Agent Decision Records.
Walks apexyard.projects.yaml, reads each project's docs/agdr/*.md
(local clone if available, else gh api fallback), parses the optional
YAML frontmatter for category + projects, and answers four queries:
- /agdr browse list across the portfolio, grouped by category
- /agdr search <term> full-text grep across all bodies, returns
<project>/AgDR-NNNN paths + matching paragraph
- /agdr show <id> print a specific record, disambiguates duplicates
- /agdr stats counts per category (the marketing-slide tile,
now backed by real data)
Six-category taxonomy: architecture | tech-stack | security | patterns
| integrations | other. Legacy AgDRs without frontmatter remain
first-class — they bucket as `other` and are flagged in browse so
operators can migrate at their own pace.
Backwards-compatible template change: templates/agdr.md gains an
optional `category:` (and optional `projects:`) line in the existing
frontmatter block. Omitting the line keeps every existing AgDR valid;
the skill defaults to `other` when missing.
Doc note in workflows/sdlc.md § Phase 2 points at /agdr search for
"have we decided this before?" lookups before drafting a design.
Smoke test in .claude/hooks/tests/test_agdr_skill.sh covers the
parser the spec specifies — frontmatter extraction, category
bucketing (including the legacy default-to-other path), id reading,
stats aggregation, and search match counts. 18 assertions, all green;
12 pre-existing test suites also green (no regressions).
Closes #181
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(me2resh/apexyard#183): /launch-check trend tracking (#185)
- Persist each run as JSON under <projects_dir>/<name>/launch-check/runs/
(timestamp + branch + commit + per-dimension scores + verdict + top_risks)
- Append "Trend (last 5 runs)" section to the per-run summary when at least 2
prior runs exist — markdown table + ASCII score chart
- Add /launch-check trend mode for read-only trend rendering (no full audit)
- Auto-derive notes column from score-delta vs previous run
(e.g. "Security +12, Analytics +10")
- Opt-in commit via .launch-check-history-tracked marker; gitignored by default
- New helper script render-trend.sh + test_launch_check_trend.sh (21 cases)
- Resolve projects dir via portfolio_projects_dir helper (no hardcoded path)
- Schema is forward-compatible — extra fields preserved on framework upgrade
AgDR-0014 documents the chart format / schema / opt-in choices.
Closes me2resh/apexyard#183
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#182): /status --briefing + bin/apexyard status CLI shim (#187)
* feat(me2resh/apexyard#182): /status --briefing + bin/apexyard CLI shim
Make slide 6's `$ apexyard status` invocation real — the smallest of three
slide-reality tickets, demonstrating the marketing-to-implementation
pattern before #181 / #183 land.
- New helper at .claude/skills/status/briefing.sh — computes the 4-line
"where am I" briefing (active workspace, active ticket, branch,
role-set) so the logic is testable in isolation and runnable from a
plain shell. Workspace inference walks up from cwd to the ops-fork
root (same algorithm as _lib-portfolio-paths.sh and /start-ticket).
- Ticket reads from .claude/session/tickets/<workspace> first, falls
back to .claude/session/current-ticket. Role-set is inferred from
the active ticket's GitHub labels (v1: backend / frontend / qa /
security / platform / sre / data / ux / ui / product / tech-lead,
plus the long forms). No match emits the explicit "<none — inferred
per task>" placeholder so the four-line shape is constant.
- New CLI shim bin/apexyard delegates `apexyard status` to the same
helper after walking up to find the ops-fork root. Works from any
workspace/<name>/ clone or the fork itself; symlink onto PATH to
install (no shadowing of the `claude` binary).
- /status SKILL.md gains a "Briefing mode" section documenting the
--briefing / -b flag; default /status output is unchanged.
- docs/multi-project.md "Daily workflow" demonstrates the new
invocation alongside /inbox and /status.
- New smoke tests at .claude/hooks/tests/test_status_briefing.sh — 8
cases covering ops-root cwd, workspace cwd, unknown cwd, ops-fallback
marker, per-project marker priority, label-based role inference, the
no-matching-label path, and the constant-four-line shape.
Closes me2resh/apexyard#182
* fix(me2resh/apexyard#182): replace curios-dog with example-app in SKILL.md output sample (leak fix)
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(#178): LSP integration spike — measurement + recommendation (#184)
* docs(#178): LSP spike — token-savings measurement + integration findings
- Phase 1: estimated token cost A vs B for three representative queries on a real TS Lambda backend (~9,750 LOC); shallow semantic queries see ~3-23x input-token savings, multi-hop traces see ~1.4x
- Phase 2: verified Claude Code shipped first-party LSP support in v2.0.74 (Dec 2025), gated behind ENABLE_LSP_TOOL=1, wired in via the plugin system (.lsp.json); MCP-wrapped LSP shims (cclsp, lsp-mcp) remain as fallback
- Phase 3: recommends Option 3 — interactive clone-first prompt at end of /handover, preserving today's "never auto-clone" principle while making the deep-dive path discoverable
- Phase 4: AgDR Y-statement and option matrix sketched; full AgDR is a follow-up if the spike says go
- Recommendation: GO. Adopt the built-in tool, document the opt-in path, change /handover to offer clone-first interactively. No code changes beyond /handover SKILL.md; no novel integration to maintain
Closes me2resh/apexyard#178
* docs: fix markdownlint MD031/MD032 in spike report
Add blank lines around lists and a fenced code block to satisfy
markdownlint-cli2 in CI. Pure formatting, no content change.
Refs me2resh/apexyard#178
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(me2resh/apexyard#190): annotate LSP-aware skills with opt-in callouts (#191)
- Add identical-shape "LSP-aware (optional, recommended)" callout near
the top of each of the four code-aware SKILL.md files.
- Per-skill savings number is the only variation:
- /code-review: ~3-15× cheaper for semantic queries
- /threat-model: ~3-15× shallow, ~1.4-5× multi-hop traces
- /security-review: ~3-15× cheaper for semantic querie…
* chore(#109): project-configurable ticket / branch / commit / PR schema (#118)
* chore(#109): project-configurable ticket / branch / commit / PR schema
Lift the prefix / type whitelists hardcoded across skills, hooks, and
CI into a versioned JSON config read through a shared shell library.
Shipped defaults at .claude/project-config.defaults.json; per-fork
overrides at the optional .claude/project-config.json; one reader
(_lib-read-config.sh) that every consumer now uses.
Added:
- .claude/project-config.defaults.json (v1 schema)
- .claude/hooks/_lib-read-config.sh (shared reader)
- docs/project-config.md (schema reference + extension guide)
- docs/agdr/AgDR-0006-project-configurable-ticket-schema.md
Migrated (still pass with no config present via last-resort fallback):
- validate-branch-name.sh → .branch.type_whitelist
- validate-commit-format.sh → .commit.type_whitelist (legacy
`commit_types` top-level key honoured as backward-compat fallback)
- validate-pr-create.sh → .pr.title_type_whitelist
- /feature, /task, /bug skills reference the config in their Rules
sections; none hardcodes the list any more
Unlocks subsequent config-readers for #107 / #110 / #111 / #112 /
#113 / #114 / #115 — each extends the schema under its own subtree
without further changes to the loader.
https://github.com/me2resh/apexyard/issues/109
* fix(#109): satisfy markdownlint MD032 and MD060 on new docs
Auto-fix MD032 (blank lines around lists) in AgDR-0006 and format
table-separator rows with surrounding spaces (MD060) in both new
doc files. Content unchanged; CI green.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#110): add block-private-refs-in-public-repos.sh hook (#119)
Adds a new PreToolUse hook that blocks gh issue/PR/comment creation and
gh api .../issues|/pulls calls targeting a public framework repo
(default: me2resh/apexyard + whatever `upstream` resolves to) when the
title or body references any registered private project from
apexyard.projects.yaml (by name, repo slug, owner/repo#N ticket ref, or
workspace path).
The hook is a sibling to check-secrets.sh — both scan outgoing content
for identifiers that should never leave the local environment. Skip
marker `<!-- private-refs: allow -->` in the body lets a deliberate
reference through with a visible warning.
Files touched:
- .claude/hooks/block-private-refs-in-public-repos.sh (new)
- .claude/hooks/tests/test_block_private_refs.sh (new)
- .claude/rules/leak-protection.md (new)
- .claude/settings.json (wire PreToolUse matchers for the 5 gh shapes)
- docs/rule-audit.md (append section 10 + bump counts)
Refs: https://github.com/me2resh/apexyard/issues/110
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#115): add warn-stale-review-markers.sh PostToolUse hook (#120)
Fires after `git push` to surface review markers that have gone stale
because new commits were pushed past an existing Rex / CEO / design
approval. The merge gate already catches this at `gh pr merge` time,
but only then -- this hook closes the gap by flagging it immediately
at push-time so the author isn't surprised at merge.
- `.claude/hooks/warn-stale-review-markers.sh`
- PostToolUse, non-blocking (PostToolUse exit 2 would push noise
into the conversation; this hook is purely informational).
- Resolves the PR HEAD via `gh pr view --json headRefOid` -- same
source-of-truth as the merge-gate hooks post-apexyard#47 / #55.
Falls back to local HEAD with a visible WARN when gh is offline.
- Silent on: no PR for branch, no markers, fresh markers,
failed push (detected via `rejected` / `failed to push` /
`fatal:` / `error:` markers in tool_response.stderr).
- Modes: `warn` (default) prints one stderr line per stale marker;
`delete` opts in to auto-removal via
`.claude/project-config.json` -> `review_markers.on_stale`.
TODO(apexyard#109): switch to the shared project-config reader
once it lands.
- `.claude/settings.json`
- Wires the hook on PostToolUse / Bash / `git push *`.
- `docs/rule-audit.md`
- Adds a row under section 3 (Code review & PR quality) and
bumps the mechanized count 26 -> 27 / total 73 -> 74.
- `.claude/hooks/tests/test_warn_stale_review_markers.sh`
- 8 cases: no PR, no markers, fresh markers, stale rex / ceo /
design (warn), delete mode, failed push. All pass locally.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#111): upgrade pre-push-gate from advisory reminder to blocking check-runner (#121)
* chore(#111): upgrade pre-push-gate from reminder to blocking check-runner
Previously pre-push-gate.sh just printed a checklist of things to run
locally before pushing — it was advisory. The rule it enforces is a
HARD STOP per pr-workflow.md. That asymmetry meant agents routinely
pushed broken work and discovered it only when CI went red.
Replaces the reminder with a blocking runner that reads the list of
shell commands from project config (.pre_push.commands) and executes
them in sequence before a push is allowed through. First non-zero
exit blocks the push with exit 2 and prints the failing command plus
the last 20 lines of its output.
- Config key: .pre_push.commands[] — array of {name, run} objects.
Shipped default is an empty list (hook stays a no-op on repos that
haven't configured their checks yet, including the framework repo
itself until it wires its own CI).
- Emergency bypass: '<!-- pre-push: skip -->' in the HEAD commit
message. Grep-able on purpose so bypasses stay auditable.
- Fail-fast: once a command fails, the rest don't run. Parallel
execution is a follow-up polish.
- 7 test cases in .claude/hooks/tests/test_pre_push_gate.sh — all
pass on the shipped default + a minimal custom config.
Updates docs/rule-audit.md to flip "partial" → "yes" for the
"before git push" rule.
Integrates with the shared config reader landed in #109.
https://github.com/me2resh/apexyard/issues/111
* fix(#111): remove orphaned footnote reference from rule-audit
The previous advisory-mode footnote was superseded by pre-push-111
but its definition was accidentally kept, tripping markdownlint MD053
(unused reference definition). Drop it.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#107): add validate-issue-structure.sh PreToolUse hook (#122)
Mechanically enforces the ticket body schema when an agent files raw
`gh issue create` calls instead of going through the interactive
/feature, /task, /bug skills. Matches bracketed title prefix
([Feature] / [Chore] / [Bug] / [Docs] / etc.) against
`.ticket.required_sections` in project-config, and blocks (exit 2)
when any required section is missing or empty. Skip marker
`<!-- validate-issue-structure: skip -->` bypasses with a visible
stderr WARN for legitimate off-template tickets (epics, meta-threads).
Changes:
- .claude/hooks/validate-issue-structure.sh — the hook; reads schema
via the shared _lib-read-config.sh, with inlined defaults for bare
checkouts predating the config-schema rollout. Handles
--body / --body-file / -F path.
- .claude/project-config.defaults.json — extends .ticket with
required_sections (Feature/Chore/Refactor/Testing/CI/Docs/Bug) and
skip_marker; other .ticket fields untouched.
- .claude/settings.json — new PreToolUse matcher on Bash(gh issue
create *) alongside the existing suggest-ticket-template.sh and
block-private-refs-in-public-repos.sh hooks.
- .claude/hooks/tests/test_validate_issue_structure.sh — 15 cases
covering pass + fail paths per prefix, empty section detection,
skip marker, unknown prefix, non-gh invocation, --body-file path.
- docs/rule-audit.md — new section 11 row, mechanized count +1.
Upstream ticket: https://github.com/me2resh/apexyard/issues/107
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#112): add require-agdr-for-arch-pr.sh PreToolUse hook (#123)
Closes the asymmetry noted in .claude/rules/agdr-decisions.md: every
other HARD STOP in the ruleset (merge approval, ticket-first,
migration-first) is mechanically enforced, but the /decide HARD STOP
was prose-only. The commit-time hook require-agdr-for-arch-changes.sh
catches one architectural change at commit; this new PR-time hook
catches the cumulative diff so reviewers always have a pointer to the
decision record.
- New hook at .claude/hooks/require-agdr-for-arch-pr.sh
- Fires on Bash(gh pr create *)
- Parses --title/--body/--body-file/-F <path>
- Resolves base branch from --base, else upstream/dev, origin/dev,
upstream/main, origin/main, main, master (in that order)
- Computes `git diff <merge-base>..HEAD --name-only`
- Triggers on any changed file matching .agdr_trigger_paths[], OR any
dep-file addition (package.json via jq key-set diff; other
dep files via a commented +/- line-count heuristic — version
bumps match +/- counts and do not fire)
- Blocks (exit 2) with a helpful message naming the triggers and
pointing at /decide if the body has no `AgDR-\d+-[a-z0-9-]+`
reference
- Skip marker `<!-- agdr: not-applicable -->` bypasses with a
visible WARN on stderr
- Silent exit 0 on non-gh commands, empty diffs, unresolvable base
- Wired via .claude/settings.json PreToolUse Bash(gh pr create *)
- Adds two new top-level keys to .claude/project-config.defaults.json:
agdr_trigger_paths (shell globs — domain/, infrastructure/,
migrations/, *.tf, .github/workflows/, etc.)
agdr_trigger_dep_files (literal basenames — package.json,
pyproject.toml, Cargo.toml, go.mod, Gemfile)
Hook has inline fallback defaults kept in sync.
- Adds docs/rule-audit.md entry in the AgDR section; bumps mechanized
count 26 to 27 and total rows 73 to 74.
- Adds .claude/hooks/tests/test_require_agdr_for_arch_pr.sh (7 cases;
all green): path-triggered without AgDR (block), with AgDR (pass),
dep-file added (block), version-only bump (no fire), skip marker
(pass + warn), non-matching diff (pass), non-gh command (no-op).
Closes https://github.com/me2resh/apexyard/issues/112
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#113): require Testing section in PR body (config-driven) (#124)
Extends validate-pr-create.sh with a required-sections check that
replaces the hardcoded Glossary-only grep. The list of required H2
headings is project-configurable via `.pr.required_sections[]`.
Shipped default is ["Testing", "Glossary"], matching the canonical PR
description shape in workflows/code-review.md.
- Each entry must appear as `## <Name>` (case-insensitive).
- Empty sections are tolerated at this layer (the issue-structure hook
#107 does stricter empty-content checks for issue bodies; for PR
bodies, empty sections are left to the reviewer's judgement).
- Skip marker `<!-- pr-sections: skip -->` bypasses with a visible
stderr WARN — for trivial PRs (lint-only fixes, version bumps)
where the full template is overkill.
- Reads from project config via the shared _lib-read-config.sh (#109).
Inline fallback matches shipped defaults so bare checkouts predating
#109 keep working.
- 8 test cases cover: all-sections pass, each missing section,
missing-both (both errors printed), skip marker, case-insensitive
headings, H3 rejection.
https://github.com/me2resh/apexyard/issues/113
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#114): enforce single Closes-keyword per PR body (#125)
* chore(#114): enforce single Closes-keyword per PR body
Caps distinct auto-closing references (close/closes/closed, fix/fixes/
fixed, resolve/resolves/resolved + N or owner/repo+N) at one per PR
body. Closes the loophole where the title validator limited the title
to one ticket but multiple Closes lines in the body would still auto-
close all of them on merge.
- Scans stripped of fenced code blocks so closing keywords inside a
code sample do not count.
- Distinct counting: the same number referenced twice (e.g. via Fixes
and Closes) counts as one.
- Cross-repo refs (owner/repo+N) count normally.
- Opt-in escape hatch: pr.allow_multiple_closes=true in
project-config disables the check for teams that deliberately batch
rollbacks or dependency bumps.
- Per-PR bypass: a multi-close-approved HTML comment in the body
prints a visible stderr WARN and lets that PR through. Grep-able
trace so bypasses are auditable.
- 10 test cases cover: one close passes, no-keyword passes, two
distinct block, three mixed block, same-number-twice passes, code-
fence-ignored, skip marker, cross-ref without keyword, opt-in
config, cross-repo close.
Reads configuration via the shared _lib-read-config.sh (apexyard+109).
https://github.com/me2resh/apexyard/issues/114
* fix(#114): strip inline backticks and tilde fences from close-count scan
Rex caught a self-reflexive bug in the initial commit: documentation
mentioning closing keywords inside inline backticks (say a PR body
that explains the new hook with examples) counted as real closes, and
a skip marker inside inline backticks silently bypassed the check.
Future PRs that document the feature would trip the same trap.
Fix the code-region stripper to cover:
- Triple-backtick fences (already handled)
- Tilde fences (new)
- Inline-backtick spans (new)
Also run the skip-marker check against the stripped body, so a marker
used purely as documentation no longer activates a real bypass.
Three new test cases pin the behaviour:
- closing keywords in inline backticks are ignored
- skip marker inside inline backticks does NOT bypass
- tilde fences also get stripped
13/13 tests pass.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#108): add /tickets-batch skill for bulk-file flow (#127)
The fast happy path for filing 5–20 structured tickets in one intent
without dropping to raw `gh issue create` (non-conformant) or running
`/feature` 20 times serially (~100 turns of interview).
- .claude/skills/tickets-batch/SKILL.md — new skill spec. Asks
shared-context questions (priority, epic, area-labels, repo) ONCE
for the whole batch, then runs a ≤3-question micro-interview per
ticket (type, one-line purpose, optional clarification when the
inference is low-confidence). Confirms the full batch as a table,
then files each via specific `gh issue create` calls (never a
bulk JSON dump — the validator runs per-issue). Output conforms
to `.ticket.required_sections` by construction. Caps at 20
tickets per invocation.
- CLAUDE.md — added a row for /tickets-batch in the Available
Skills table; bumped the count references from 33 to 34.
Refs https://github.com/me2resh/apexyard/issues/108
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#117): add /fan-out skill + parallel-work rule doc (#128)
- Add `.claude/skills/fan-out/SKILL.md` — spawns N parallel Agent calls
in a single assistant message, with per-task agent type, worktree
isolation, and foreground/background mode. Caps at 5 concurrent
agents. Refuses fan-out when tasks share file write targets or have
sequential dependencies. Includes pre-spawn active-ticket safety
check and worktree merge-back flow that pauses on conflict.
- Add `.claude/rules/parallel-work.md` — trigger heuristic for when an
agent should proactively offer fan-out (>= 2 file-independent,
context-independent, individually substantial work items). Pairs
with the skill: rule says when, skill says how.
- Update `CLAUDE.md` — bump rules count to 9, skills count to 34, add
`/fan-out` row to the skills table.
Refs https://github.com/me2resh/apexyard/issues/117
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#116): adopt release-cut branch model (dev/main + tags) — framework only (#126)
* chore(#116): adopt release-cut branch model (dev/main + tags)
Formalises the dev/main split that already exists informally — dev is
the daily-work branch where every PR lands, main is release-only,
tagged with semver on each merge. Framework-only: managed projects
under apexyard governance stay trunk-based.
Added:
- AgDR-0007 — decision record (options table covers full git flow vs
trunk-only vs gitflow-lite; chose gitflow-lite)
- /release skill — diff dev against main, propose semver bump from
conventional commits, generate CHANGELOG, open release PR, tag
after merge
- docs/release-process.md — prose runbook for cutting a release
(manual fallback for the skill)
- .git.protected_branches in project-config.defaults.json
(main/master/dev/develop)
Modified:
- block-main-push.sh — now blocks direct pushes/commits to all
configured protected branches (was: hardcoded main/master). Reads
.git.protected_branches via the shared config reader (apexyard#109).
- CLAUDE.md — new section under Git Conventions explaining the
dev/main model + the framework-only scope. Skill table entry for
/release. Skills count bumped to 34.
- docs/multi-project.md — note that upstream/main is release-only
and the dev/main split is framework-only.
Non-consequences (per AgDR-0007):
- No release/* or hotfix/* branches. Hotfixes are normal patches
cut quickly. Revisit if multi-version maintenance becomes a need.
- No automatic on-merge issue closing for dev PRs. The release PR's
body aggregates all Closes references for the batch and triggers
auto-close en masse when it merges to main. Manual close in the
meantime.
- CI workflows trigger on pull_request regardless of base, so
dev-targeting PRs already get the full check matrix — no
workflow file edits needed.
https://github.com/me2resh/apexyard/issues/116
* fix(#116): satisfy markdownlint MD032 + MD060 in 116 docs
Auto-fix added blank lines around lists in AgDR-0007 (MD032) and
spaced the table separators in AgDR-0007 + multi-project.md (MD060).
Content unchanged.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* fix(#106): CHANGELOG fallback in drift hook for squash-merged forks (#129)
* fix(#106): CHANGELOG fallback in drift hook for squash-merged forks
The v1.1.0 tag-reachability check (`git tag --merged main`) misfires on
forks that sync via GitHub's default squash-merge: the squash collapses
the upstream-tag commit into a synthetic SHA, the tag stops being
reachable, and the banner keeps firing forever.
Discovered live on the first real-world `/update` flow: ops fork
squash-merged the v1.1.0 sync PR, banner kept saying "v1.1.0 available"
even though the fork was content-caught-up.
This commit adds a CHANGELOG-content fallback that fires only when the
primary tag check fails. If the fork's main has a heading
`## [X.Y.Z]` matching the upstream tag's version, treat the release as
absorbed and stay silent. Tolerant grep (matches the apexyard CHANGELOG
format from v1.1.0 onward, with leading-`v` stripping for tag→heading
conversion).
The merge-commit and rebase paths are unchanged — primary tag check
still works for them, and the fallback never fires when it shouldn't.
Test coverage (5 cases):
- squash-merge fork caught up to v1.1.0 → silent (the fix)
- merge-commit fork caught up to v1.1.0 → silent (regression check)
- fork stopped at v1.0.0 → banner fires
- fork has its own newer tag → silent
- squash-merge but no CHANGELOG on fork → banner fires (no false silence)
Records the strategy update in docs/agdr/AgDR-0008-…md (extends
AgDR-0005's tag-based-drift design).
https://github.com/me2resh/apexyard/issues/106
* fix(#106): satisfy markdownlint MD032/MD060 + shellcheck SC2164
Rex flagged two CI-blocking issues on the original 106 commit:
- AgDR-0008 had three bulleted sub-lists in the Consequences section
without surrounding blank lines (MD032). Added blanks and padded the
one tight-pipe table separator (MD060).
- The 106 test fixture had five subshell `cd "$fk"` calls without
`|| exit 1` (SC2164). Added the guard to all five.
5/5 tests still pass after the fix. No semantic change to the hook
or the test logic.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#130): add /validate-idea skill — lightweight pre-spec gate (#131)
A 10-minute, 5-question check that sits between /idea and /write-spec.
Designed for solo founders running ApexYard — not a heavyweight
methodology like event storming or Wardley mapping.
Five questions, asked one at a time:
1. Who is this specifically for?
2. What do they do today instead?
3. What's the smallest version that proves the value?
4. What would prove this is wrong? (kill criteria)
5. Build, buy, or rent?
Output: a one-page validation doc with a GREEN/YELLOW/RED verdict.
RED auto-updates the IDEA-NNN backlog row to WONTDO.
Integration:
/idea — adds an optional default-no "Validate now?" step after
capture (and after the optional GitHub Issue offer).
/handover — adds a conditional "this looks dormant, validate?"
step at the end of the integration plan, gated on the dormancy
heuristic (last commit > 90d AND zero open PRs AND no recent
issue activity). Healthy projects don't see the prompt.
CLAUDE.md skills count bumped to 35; new skills row added.
https://github.com/me2resh/apexyard/issues/130
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat: configurable voice prompts on assistant pause (AgDR-0009-voice-prompts-on-pause) (#135)
Stop hook that speaks the assistant's question aloud (Jarvis-style)
when it pauses for user input. Initial phase is macOS-only via `say`,
no voice input — user replies via keyboard.
Default OFF. Adopters opt in by overriding `voice_prompts.enabled` to
true in `.claude/project-config.json`.
Files:
- .claude/hooks/voice-prompt-on-pause.sh — Stop hook with config gate,
trigger heuristic (questions-only by default), markdown stripping,
sentence-boundary truncation, fire-and-forget say invocation
- .claude/hooks/tests/test_voice_prompt_on_pause.sh — 9 cases covering
disabled-default, enabled+question, enabled+statement, approved-pattern,
abc-menu, malformed-transcript, no-say-on-PATH, trigger-always,
markdown-stripping
- .claude/project-config.defaults.json — voice_prompts schema block
added (enabled, voice, max_chars, rate_wpm, trigger), default OFF
- .claude/settings.json — new Stop hook entry wired with the standard
ops-root resolver wrapper
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — design rationale,
options matrix (status quo / macOS say / cloud TTS / ML detection),
consequences, future phases
- docs/project-config.md — new "Voice prompts" section with override
examples and privacy notes
Test mode: hook respects VOICE_PROMPTS_SYNC=1 to run say synchronously
(test runners need this so assertions don't race against orphaned
background processes). Production invocations always run async.
Future phases (out of scope here, AgDR §"Future phases"):
- Phase 2: cross-platform TTS (Linux espeak, Windows SpeechSynthesizer)
- Phase 3: cloud TTS providers (OpenAI, ElevenLabs) — privacy AgDR-worthy
- Phase 4: voice input via Whisper-based STT
- Phase 5: per-message overrides
Refs: https://github.com/me2resh/apexyard/issues/134
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#141): add /debug skill — structured hypothesis-driven debugging (#142)
* feat(#141): add /debug skill — structured hypothesis-driven debugging
Adds a methodology skill that enforces five disciplines:
1. Capture the symptom precisely (exact URL, exact response, exact step)
2. Read the architecture before guessing (map every layer the request
touches, file by file)
3. Form a hypothesis ladder (3–5 candidates, each with an explicit
evidence test that confirms or refutes it)
4. Gather evidence first, fix second
5. Verify the fix against the original symptom evidence (re-run the
same `curl` / browser repro you used in step 4 — unit tests
verify code, not feature, correctness)
Stack appendices (Web, Desktop) carry stack-specific surface-evidence
requirements (step 1), architecture-surface maps (step 2), and
evidence-tests cookbooks (step 4). The methodology body stays portable
across stacks; appendices are where stack-specific knowledge accrues
over time.
Web appendix covers browser routing, framework configs (Next/Nuxt/Vite),
SPA-fallback layers, CDN, origin, the shared API client, backend
handlers, and auth providers. Desktop appendix covers Electron / Tauri
/ native-shell concerns: app entry points, IPC bridges, native modules,
auto-updater, sandbox / entitlements, code signing, crash reports.
Includes "When NOT to use" guidance so the methodology overhead doesn't
sandbag simple bugs (typos, off-by-ones, greenfield exploration).
Motivated by a real OAuth debug session in a managed project where
three sequential fixes chased adjacent symptoms because each was
hypothesis-then-fix without evidence in between. The skill is the
"never do that again" guardrail.
Closes #141
* fix(#141): scrub private project issue numbers from anti-pattern table
Rex review on PR #142 caught that line 155 of the skill's anti-pattern
table still named the originating PRs (#375, #377, #380) from the
private project where the methodology was first exercised. The PR body
and commit message were correctly abstracted earlier, but this in-file
reference slipped through — the leak-protection hook only scans gh
issue/pr writes, not staged file content, so mechanical enforcement
didn't catch it.
Replaced with "Three sequential PRs chasing the same symptom because
each was based on a different guess (no evidence test in between
cycles)" — same pedagogical value, zero attribution.
Refs #141
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(#143): document split-portfolio mode + add /setup privacy gate (#144)
Adopters on GitHub Free with any private project hit a silent privacy bug
following today's docs: forking apexyard makes a public fork that you cannot
later flip to private (GitHub policy), and committing `apexyard.projects.yaml`
+ `projects/<name>/` to the fork publishes private project names + handover
findings on a public GitHub repo.
This PR documents the supported workaround — split-portfolio mode — and
adds an upfront privacy gate to the /setup skill so new adopters never
hit the trip-wire silently.
docs/multi-project.md:
- New "Two setup modes — pick the one that matches your privacy needs"
section before TL;DR, with a side-by-side table and the explicit
trip-wire callout.
- Existing TL;DR retitled "TL;DR — single-fork mode (default)" with a
one-line pointer to the split-portfolio section.
- New "Split-portfolio mode — public framework + private portfolio"
section between the existing setup steps and the directory-layout
section. Includes:
- The two-repo layout (~/ops/apexyard public + ~/ops/portfolio private)
- 7-step setup walkthrough with copy-pasteable commands
- Daily workflow + upstream sync notes (both unchanged)
- Trade-offs (two repos to maintain, two clones per machine, one
upstream-sync conflict path on `projects/README.md`)
- "Migrating from single-fork to split-portfolio" recovery flow with
the explicit warning that GitHub Issue / PR edit history survives a
force-push and must be redacted separately
.claude/skills/setup/SKILL.md:
- New Step 2a: privacy gate — asks "are any projects private?" before
proposing the config. Branches on the answer:
- All public → single-fork mode
- GitHub Pro / Team / Enterprise → single-fork mode (private
forks of public repos
are supported on those
plans)
- Any private + GitHub Free → split-portfolio mode
- New Step 2b: walks through the split-portfolio setup interactively
(private repo create, sibling clone, gitignore + symlink) when the
privacy gate triggers.
- Detection: `test -L apexyard.projects.yaml` short-circuits Step 2b
for adopters already in split mode.
- Explicit "do NOT auto-migrate" rule for adopters already in single-fork
mode with private names already pushed — that path is destructive
(force-push history rewrite + redact issue/PR bodies + delete backup
branch) and warrants a deliberate, eyes-open run, not a /setup side
effect.
Out of scope for this PR (tracked separately on #143):
- `portfolio:` config block in `onboarding.yaml` schema
- Skill audit + refactor to honour configured `registry` / `projects_dir`
/ `ideas_backlog` paths instead of hardcoded fork-relative paths
- `/split-portfolio` migration helper skill that automates the recovery
flow currently documented manually
This is the docs-and-setup-question minimum-viable starter — the
framework code refactor is mechanical and lands as a follow-up.
Refs #143
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#145): portfolio config + self-healing + /split-portfolio helper (#147)
* feat(me2resh/apexyard#145): portfolio config block + self-healing + /split-portfolio helper
Closes the framework primitive deferred from me2resh/apexyard#144. Adds
first-class config-driven path resolution for the portfolio registry,
projects dir, and ideas backlog, with self-healing surfacing of broken
config at session start, plus a new /split-portfolio skill that automates
the destructive recovery flow.
Schema, helper, and hook:
- .claude/project-config.defaults.json: new portfolio: block
(registry, projects_dir, ideas_backlog) with defaults matching
today's single-fork layout
- .claude/hooks/_lib-portfolio-paths.sh: new sourceable helper exposing
portfolio_registry, portfolio_projects_dir, portfolio_ideas_backlog,
portfolio_validate, portfolio_clear_cache. Resolves relative paths
against the ops-fork root.
- .claude/hooks/check-portfolio-config.sh: new SessionStart hook —
silent on OK, one-line banner on broken config, never blocks session
- .claude/hooks/tests/test_portfolio_paths.sh: 13 cases covering
defaults, absolute/relative overrides, validate states, cache clear
Skill audit (18 SKILL.md files):
- Adds Path resolution callout pointing at the helper
- handover bash blocks now source helper and use $(portfolio_registry)
instead of literal apexyard.projects.yaml
- setup Step 2b now writes the portfolio: config block (recommended)
and validates via portfolio_validate before declaring success;
symlink approach kept as legacy fallback
New skill (.claude/skills/split-portfolio/SKILL.md):
- 10-step migration with explicit operator-confirmation gates at each
destructive step (force-push, body redaction, branch deletion)
- --verify mode: read-only state report (mode, paths, validate, drift)
- --dry-run mode: prints commands without executing
- Pre-flight refusals: already-private fork, paid GitHub plan, dirty
working tree, already-migrated state
- Step 9 writes the portfolio: config block (not symlinks) — symlink
fallback documented for adopters on older framework versions
- Step 9 surfaces the GitHub timeline-API survival caveat verbatim
- Idempotent re-runs: detects partial-migration state and resumes
Docs (docs/multi-project.md):
- Layout section describes both modes (config-block recommended,
symlink legacy) with self-healing notes
- Setup steps split into config-block mode and legacy symlink mode
- Migration section now points at /split-portfolio skill; manual
recipe preserved as fallback
AgDR (docs/agdr/AgDR-0010-portfolio-config-and-self-healing.md):
- Full Y-statement, options, decision, consequences, future phases
- Schema decision rationale: project-config.json over onboarding.yaml
because runtime path resolution belongs in project-config
Closes me2resh/apexyard#145
Refs me2resh/apexyard#146 (delivered same PR; closed manually post-merge
per the single-Closes-keyword rule in validate-pr-create.sh)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(me2resh/apexyard#145): markdownlint MD031 — blank lines around fences
CI's markdownlint-cli2 (v0.34.0) flagged the JSON + bash fenced code
blocks I added in setup/SKILL.md Step 2b without surrounding blank
lines. Added the required blanks. No content change.
Refs me2resh/apexyard#147
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>
* docs(me2resh/apexyard#148): correct privacy-gate wording — adopter action, not framework auto-publish (#149)
The privacy-gate wording introduced in PR #144 (and unchanged in PR #147)
attributed the publication to the framework rather than the adopter:
"the standard fork-and-commit setup will silently publish your private
project names on a public GitHub repo"
That's factually wrong. ApexYard never pushes anything without explicit
operator approval — the publication only happens when the adopter
themselves runs git push. The "silently publish" framing read as if the
framework auto-publishes, which is misleading and undermines trust in
the rest of the framework's safety claims.
Two prose-only edits, no code, no behavior change:
- .claude/skills/setup/SKILL.md Step 2a — replaced "will silently
publish ..." with adopter-action language ("you might accidentally
publish ... a stray git push after registering them — I won't push
without your approval, but the risk is on the adopter once the data
is committed locally")
- docs/multi-project.md trip-wire callout — replaced "silently publish
their portfolio names the moment they push" with "risk accidentally
publishing their portfolio names with a stray push (the framework
itself never pushes without operator approval, but once the registry
is committed locally the next push exposes it)"
Verified: `grep -r "silently publish" .claude/skills/ docs/` returns no
hits.
Closes me2resh/apexyard#148
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#150): bootstrap-skill exemption + Bash-write coverage (#152)
Closes the legitimate-bypass case (me2resh/apexyard#150) and the
illegitimate-bypass case (me2resh/apexyard#151) together so the
ticket-first gate is coherent. Shipping either alone would leave a
window where the framework is internally inconsistent — see AgDR-0011
for the full rationale.
Bootstrap exemption (me2resh/apexyard#150):
- .claude/session/active-bootstrap marker, written by /setup,
/handover, /update, /split-portfolio on entry; cleared on exit
- SessionStart sweep (clear-bootstrap-marker.sh) for stale markers
from interrupted sessions
- require-active-ticket.sh reads the marker and exempts skills on
the configured ticket.bootstrap_skills list
- bootstrap_skills list lives in .claude/project-config.defaults.json
(extendable per fork via .claude/project-config.json)
Bash-write coverage (me2resh/apexyard#151):
- new _lib-detect-bash-write.sh — heuristic detector for output
redirection, tee, sed -i, awk -i inplace, python/node/ruby
embedded interpreters
- require-active-ticket.sh + require-migration-ticket.sh now fire
on Bash in addition to Edit|Write|MultiEdit
- design choice: false-negatives preferred over false-positives
(the matcher errs toward "let through" rather than block legit
read-only commands)
Tests: 32 unit cases on the lib + 12 integration cases on the hook,
including the exact me2resh/apexyard#151 bypass repro from the issue
body.
Closes me2resh/apexyard#150
Will manually close me2resh/apexyard#151 post-merge per the
single-Closes-per-PR rule (precedent: AgDR-0010 / PR #147).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#153): extend Bash-write matcher beyond first-version coverage (#155)
Closes me2resh/apexyard#153.
Extends `_lib-detect-bash-write.sh` (introduced for me2resh/apexyard#151
in PR #152) with the matcher families flagged by Rex's review of #152.
AgDR-0011 already frames the matcher as a living list extended on
observation; this commit just walks the list.
New matcher families:
- File-moving builtins: `cp`, `mv`, `rm`, `dd`, `install` (anchored at
command-start; `--help`/`--version` and `git rm`/`git mv` excluded)
- Archive / network writes: `tar -x` / `tar --extract`, `curl -o` /
`--output`, `wget -O` / `--output-document`
- Additional embedded interpreters: `perl -e`, `php -r` (keyword-gated
like python/node/ruby); `go run`, `deno run`/`deno script.ts`,
`bun run`/`bun script.ts` (categorical script runners)
- Python helpers: `pathlib.Path().touch()`, `shutil.copy*`,
`shutil.move`, `os.rename` added to the `python -c` and python
heredoc keyword list
- Heredoc variants for `ruby` and `node` (previously only python
heredoc was covered)
Extractor extensions:
- `cp` / `mv`: last positional arg
- `curl -o` / `--output`: file argument
- `wget -O` / `--output-document`: file argument
- `tar -x`, `go run`, `deno`, `bun`, `perl -e`, `php -r`: return empty
(caller applies gate categorically per AgDR-0011)
Test count rose from 32 to 86. Negative-class counterexamples cover
the trickiest false-positive surfaces: `tar -t` listing, `cp --help`,
`rm --version`, `git rm`, `curl` bare URL fetch, `wget` bare URL fetch,
`deno fmt`, `deno test`, `go build`. Existing
`test_require_active_ticket_bash.sh` regression suite still passes 12/12.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(#154): mock gh in test sandboxes to remove live-tracker dependency (#156)
- add _lib-mock-gh.sh helper that installs a fake `gh` on the sandbox PATH;
intercepts `gh issue view <N> ... --json ...` and returns synthetic
`{"number":N,"state":"OPEN"}` (overridable per-num via mock_gh_set_state)
- wire the shim into test_single_closes_per_pr.sh and
test_validate_pr_required_sections.sh so the validator's CLOSED-issue
refusal no longer breaks the suite when upstream issues are closed
- both files previously failed every case (0/13 and 0/8) because their PR
titles reference #114 / #113, which are now CLOSED upstream
- post-fix: 13/13 and 8/8; full suite remains green
Closes me2resh/apexyard#154
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#132): structured CEO marker + same-turn merge in /approve-merge (#158)
* feat(#132): structured CEO marker + same-turn merge in /approve-merge
Closes me2resh/apexyard#132 (drop the "stop before merge" rule) and
me2resh/apexyard#48 (harden CEO marker against self-approval bypass)
together. The two threads compose — see AgDR-0012 for the full
rationale.
Streamline (#132):
- /approve-merge now runs `gh pr merge --squash --delete-branch`
in the same turn as the marker write, by default
- --no-merge opt-out preserves the deferred-merge case
- The discrete approval moment is the SKILL INVOCATION, not a
follow-up "now do the merge" message
Harden (#48):
- CEO marker is now a structured key/value file with required fields:
sha=<HEAD>
approved_by=user
skill_version=2
Validated by block-unreviewed-merge.sh; bare-SHA legacy markers
rejected with a clear "stale format" error pointing at /approve-merge
- The model's bare `echo SHA > <pr>-ceo.approved` bypass is now
mechanically rejected. Forging the structured fields requires a
deliberate, visible rule violation rather than a one-line accident
- Optional audit fields (approved_at, approval_summary) capture the
"what did the user say when they approved" trail
- Rex marker stays bare-SHA — different threat model (automated
reviewer, not human authorization moment)
pr-workflow.md reframed: "the load-bearing rule is explicit per-PR
approval, not two user messages." The merge is a deterministic
consequence of the approval invocation.
Tests: 12 cases on the hardened hook covering the new format end-to-end
(valid v2, missing rex/ceo, bare-SHA legacy rejected, missing
approved_by, wrong approved_by, skill_version=1, sha mismatch,
non-merge no-op, gh-api shape gated). Full suite: 205/205 across 13
test files.
Will manually close me2resh/apexyard#48 post-merge per the
single-Closes-per-PR rule (precedent: PR #152 / AgDR-0011).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#132): redact private project reference from AgDR-0012
Abstracted two references to a registered private project that named
the project's owner/repo. The leak-protection hook caught one in the
PR body; this fixup removes the matching references from the
AgDR-0012 file content (which would otherwise have shipped the names
to me2resh/apexyard public repo via the merge).
The pre-existing reference at .claude/rules/pr-workflow.md:130
(documenting #47) is untouched — it predates this PR and is already
on the public repo's history.
Refs me2resh/apexyard#132.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#132): markdownlint blanks-around-fences + typo fix
Two small fixups against red CI / Rex feedback:
- AgDR-0012 line 63: fenced code block now has a blank line before it
(MD031). markdownlint-cli2 0.13.0 was rejecting the indented fence
inside the bullet because the fence's preceding line was the bullet
text (no blank).
- approve-merge SKILL.md line 172: typo `deferes` → `defers` (Rex flag
on PR #158).
Refs me2resh/apexyard#132.
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>
* chore(#157): remove voice-prompts feature + correct hook/skill counts (#161)
* chore(#157): remove voice-prompts-on-pause feature + correct hook/skill counts
Closes me2resh/apexyard#157 (sunset the voice-prompts feature) and
me2resh/apexyard#77 (hook count off-by-one in CHANGELOG / CLAUDE.md)
in one bundled PR. AgDR-0013 supersedes AgDR-0009 with the full
rationale; both AgDRs are preserved (decision records are append-only
history).
Removed (#157):
- .claude/hooks/voice-prompt-on-pause.sh
- .claude/hooks/tests/test_voice_prompt_on_pause.sh
- Stop matcher block in .claude/settings.json (became empty after
voice removal)
- voice_prompts block in .claude/project-config.defaults.json
- "## Voice prompts" section in docs/project-config.md
- voice_prompts mention in AgDR-0010 line 32 (replaced with
leak_protection / ticket as still-current example config blocks)
Preserved:
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — historical record;
new "Superseded by: AgDR-0013" header at the top
- AgDR-0010 line 115 reference to AgDR-0009 — still accurate as a
historical pattern reference
Counts corrected (#77):
- CHANGELOG.md v0.3.0 stats: "17 hooks" → "18 hooks" (historical
fix — at v0.3.0 there were actually 18 hooks)
- CLAUDE.md table line: "18 shell scripts" → "24 shell scripts"
(current count after this removal)
- CLAUDE.md table line: "35 slash commands" → "39 slash commands"
- CLAUDE.md "Available skills (34)" → "Available skills (39)"
- CLAUDE.md quick-reference "Skills (35 slash commands)" →
"(39 slash commands)"
Why bundled: #77's correct count depends on whether voice is still in
the framework. Shipping #77 before #157 would write a number that's
wrong by one again the moment #157 lands. Same shape as previous
bundles (PR #152 / AgDR-0011, PR #158 / AgDR-0012).
Why no adopter-facing changelog mention of the voice removal: the
feature never reached a tagged release on main. v1.1.0 didn't have
it; v1.2.0 won't have it. From the adopter's perspective there's
nothing to retire. AgDR-0013 captures the framework's internal record
for future contributors. See AgDR-0013 § "No adopter-facing changelog
mention".
Tests: full hook test suite green (196 cases across 12 files —
test_voice_prompt_on_pause.sh removed). No regressions.
Will manually close me2resh/apexyard#77 post-merge per the
single-Closes-per-PR rule (precedent: PRs #152, #158).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#157): unwire voice from settings + configs + docs + AgDR-0013
Continuation of d09d7b5 (the file deletions). Squash-merge will
collapse both into one PR commit. This commit captures:
- .claude/settings.json — Stop matcher block removed (was the only
hook in it; entire matcher gone)
- .claude/project-config.defaults.json — voice_prompts block + its
_comment removed
- docs/project-config.md — "## Voice prompts" section removed
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — "Superseded by"
header added at the top, content otherwise preserved as history
- docs/agdr/AgDR-0010-portfolio-config-and-self-healing.md — line 32
example reference swapped from voice_prompts to
leak_protection / ticket (still-current config blocks)
- docs/agdr/AgDR-0013-sunset-voice-prompts.md — new supersession AgDR
- CLAUDE.md — hook count 18 → 24, skill count 35 → 39 (three
occurrences each, all aligned to current reality)
- CHANGELOG.md v0.3.0 stats — "17 hooks" → "18 hooks" historical fix
(#77 acceptance criterion 1)
Refs me2resh/apexyard#157 + me2resh/apexyard#77.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#157): markdownlint MD028 + resolve stale AgDR-numbering ref
Three small fixups against Rex CHANGES-REQUESTED on PR #161:
- AgDR-0009 line 3: promote "Superseded by:" header out of a
blockquote. The original "I decided ..." canonical blockquote at
line 5 was being merged with the new supersession blockquote
(markdownlint MD028 — "no blanks inside blockquote").
- AgDR-0013 line 3: same shape — "Supersedes:" header now a plain
bold paragraph, canonical "I decided ..." blockquote untouched.
- approve-merge SKILL.md line ~187: stale conditional reference
"AgDR-0012 (or 0013 — depends on whether voice-removal lands
first)" — order is now resolved (12 = approve-merge bundle, 13 =
voice removal). Drop the parenthetical.
Refs me2resh/apexyard#157 + me2resh/apexyard#77.
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(#160): multi-tab terminal demo on the landing site (#162)
Landing-site `site/index.html` terminal demo previously played one
canonical flow ("one ticket, start to finish") on autoplay. With the
v1.2.0 skill surface expansion (4 new skills, 8+ new hooks), one flow
no longer represents the framework's breadth.
Adds tabs to the terminal chrome — four flows visitors can either let
auto-cycle or click directly:
1. one ticket — existing flow, unchanged content
2. /handover — adopt an external repo into the portfolio
3. /setup — first-run framework bootstrap on a fresh fork
4. /fan-out — spawn 3 parallel agents on independent tickets
Auto-advance: on completion of the active tab's script, the demo
pauses ~1.8s then advances to the next tab. Loops at the end. User
can interrupt by clicking any tab or hitting Replay.
Implementation:
- HTML chrome — single title span replaced with a tablist of 4 button
tabs. ARIA `role="tablist"` / `role="tab"` / `aria-selected` so
keyboard + screen-reader users get the same semantics as sighted
ones.
- CSS — new `.shell-demo__tabs` + `.shell-demo__tab` with an accent
underline on the active tab. Tabs scroll horizontally on narrow
viewports (mobile responsive).
- JS — refactored the existing IIFE from one `script` array to an
array of four. Added `setActiveTab()` for ARIA state, `play(idx)`
takes a tab index, end-of-script auto-advances to `(idx + 1) % N`
unless the user clicked away during the pause. Adds a new `cmd`
type alongside `you` for slash-command invocations (renders with
the same `>` prompt prefix). prefers-reduced-motion still bails
early and leaves the static seed visible.
- Static seed (the non-JS fallback) still shows tab 0's content, so
reduced-motion / no-JS visitors see the one-ticket flow as before.
Hero metrics also corrected to current reality (#77 / PR #161 covers
CLAUDE.md and CHANGELOG.md; this PR catches the same numbers in the
landing site):
Skills 32 → 39
Hooks 18 → 24
The tabs ship in v1.2.0 alongside the framework changes that make
the new flows worth showcasing.
Refs me2resh/apexyard#160 (release v1.2.0 + landing-site refresh).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#163): default the split-portfolio sibling repo name to <fork>-portfolio (#164)
Closes me2resh/apexyard#163.
The split-portfolio mode docs and skills previously suggested
`your-org/ops` as the default name for the private sibling repo, and
`portfolio/` as the local clone directory. Both too generic — adopters
running multiple ops setups end up with `your-org/ops` collisions, and
a bare `portfolio/` dir gives no signal about which framework it
belongs to when it sits next to other unrelated `portfolio/` dirs.
`<fork>-portfolio` is now the default — keeps the relationship to the
public fork explicit on disk and on GitHub. If the fork is named
`your-org/apexyard`, the portfolio defaults to
`your-org/apexyard-portfolio`. If the fork was renamed (e.g. `cos`),
the portfolio defaults to `cos-portfolio`. Adopters with custom names
keep working — the `portfolio:` config block resolves whatever path
they configured.
Files updated:
docs/multi-project.md
- Layout diagrams use `apexyard-portfolio/` as the sibling
- Setup walkthrough Step 2 + Step 3 use `your-org/apexyard-portfolio`
and explain the `<fork>-portfolio` pattern
- Config-block + symlink path examples updated to
`../apexyard-portfolio/...`
- Daily workflow + cross-machine clone commands updated
- The two existing `your-org/ops` references that remain are
fork-rename examples (lines 52, 64) — kept as-is, since renaming
the fork to `ops` is still valid (the portfolio would then
default to `ops-portfolio`)
.claude/skills/setup/SKILL.md
- Step 2b's "default suggestion" for the private repo name is now
`your-org/<fork>-portfolio`, computed dynamically from the
fork's repo name via `gh repo view --json name -q .name` so the
suggestion is correct even when the fork was renamed
- Clone command no longer needs a second arg — the repo name IS
the directory name
- Config-block paths updated to `../apexyard-portfolio/...`
.claude/skills/split-portfolio/SKILL.md
- Step 3's suggested-name template is now `<account>/<fork>-portfolio`
with the same dynamic-fork-name resolution
Mechanism unchanged. The `portfolio:` config block in
`.claude/project-config.json` still takes any path; this PR is purely
default-suggestion + example prose.
No tests required (skills are markdown instructions; no automated
coverage today).
Refs `apexyard.projects.yaml.example` uses "ops repo" as a generic
term meaning "the operational management fork" — not a name — kept
as-is.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#165): skills reference page on the landing site + changelog link (#167)
Closes me2resh/apexyard#165.
Adds a public, browseable index of every apexyard slash command
alongside a one-click changelog link from the homepage nav.
site/skills.html (new):
- Lists all 39 skills currently shipping in .claude/skills/
- Each entry: slash command, argument hint, description (taken
verbatim from the SKILL.md frontmatter so the page matches the
runtime exactly)
- 10 categories: Setup & onboarding, Daily ops, Tickets & ideas,
Specs & decisions, Code review & merge, Architecture & dev tools,
Production-readiness audits, Workflow primitives, Communications,
Deprecated
- Same brutalist-terminal design tokens as the homepage — JetBrains
Mono, paper-cream background, single warning-red accent, sharp
corners. Inlined CSS to keep the static-only no-build-step
convention; design vars duplicated rather than extracted to a
shared file (~18 vars; cheap to keep in sync).
- Mobile responsive — skill grid collapses to single-column under
720px; titlebar nav hides non-CTA items on narrow viewports.
- Reduced-motion friendly (no animation in the first place).
- Internal anchor TOC at the top so the page scans in seconds.
site/index.html (nav addition):
- Added two nav links to the titlebar between "what's in the box"
and the github CTA:
• skills → ./skills.html
• changelog → https://github.com/me2resh/apexyard/releases
- The changelog link points at the GitHub releases page (not the
raw CHANGELOG.md file) so it auto-resolves to the latest tagged
release on each visit. v1.2.0 lands and the link is already there.
No new dependencies, no build step, no JS for the skills page. The
existing site convention (one-html-file-per-route, inlined CSS,
optional progressive-enhancement JS) is preserved.
Refs me2resh/apexyard#160 (release v1.2.0 + landing-site refresh) —
this is the second site-side deliverable for that ticket; the release
tag itself follows once me2resh/apexyard#159 (testing) closes.
Follow-up worth a separate ticket: a small generator script that
walks .claude/skills/*/SKILL.md, parses YAML frontmatter, and emits
the skills.html sections automatically. Out of scope for v1.2.0 —
first version is hand-curated and will need maintenance until that
generator lands.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#168): accept release/vN.N.N branches + release(...) PR titles (#169)
Closes me2resh/apexyard#168.
The /release skill prescribed `release/vA.B.C` as the source-branch
name and `release: vA.B.C` as the PR title for the dev → main release
PR (per AgDR-0007). Both were rejected by the framework's own
validators:
validate-branch-name.sh required {type}/{TICKET-ID}-{description};
release/v1.2.0 has no ticket-id portion.
validate-pr-create.sh required type(SCOPE): form with `release` not
in pr.title_type_whitelist.
The contradiction surfaced cutting v1.2.0 — the first release under
the dev/main model.
Three small changes:
1. .claude/hooks/validate-branch-name.sh — added an early-out branch
that accepts ^release/vN.N.N(-rcN)?$ as a valid name. Narrow,
intentional exception for the framework's release-cut convention;
release branches don't carry a ticket-id because the release itself
IS the ticket.
2. .claude/project-config.defaults.json — added "release" to
pr.title_type_whitelist so a title like `release(#160): v1.2.0`
passes validate-pr-create.sh's existing regex unchanged.
3. .claude/skills/release/SKILL.md step 4 — corrected the prescribed
PR title to `release(#<release-ticket>): vA.B.C` so future /release
invocations produce a title that satisfies the validators by
construction.
Tested:
bash .claude/hooks/validate-branch-name.sh against:
release/v1.2.0 → 0 (allowed, release-special-case)
release/v1.2.0-rc1 → 0 (allowed, RC variant)
release/v9.9.9 → 0 (allowed)
release/foo → 2 (correctly blocked)
release/v1 → 2 (correctly blocked)
chore/GH-168-fix → 0 (allowed, standard pattern)
feature/GH-1-x → 0 (allowed, standard pattern)
Full hook test suite: 196/196 cases green across 12 test files.
Refs: surfaced 2026-05-04 cutting the first release under AgDR-0007.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#170): exempt release/vN.N.N from validate-pr-create's branch-id check (#171)
Closes me2resh/apexyard#170.
Completes the work started in #169 (closing #168). #169 added a
release-pattern early-out to validate-branch-name.sh so release/vN.N.N
branches pass the branch-name validator. But validate-pr-create.sh has
its own independent branch-id check at line 273 that #169 didn't
touch — and it still rejects release/v1.2.0 because that name doesn't
contain a ticket-id substring.
This is the same class of contradiction #168 fixed; the fix is the
same shape. Add the same release-pattern early-out to the branch-id
check in validate-pr-create.sh:
- if the branch matches ^release/vN.N.N(-rcN)?$ → exempt (release
branches don't carry ticket-ids; the release itself is the ticket)
- otherwise → require a ticket-id substring as before
#168's acceptance criterion 3 ("validate-pr-create.sh accepts a PR
title `release(#160): v1.2.0` against the `release/v1.2.0` branch")
was checked off based on the title regex alone but didn't catch the
secondary branch-id check living in the same file. Surfaced trying
to open the v1.2.0 release PR.
Tested:
bash .claude/hooks/validate-pr-create.sh against:
release/v1.2.0 → 0 (allowed, exempt)
release/v1.2.0-rc1 → 0 (allowed, RC variant)
chore/GH-1-fix → 0 (allowed, has ticket-id)
release/foo → 2 (correctly blocked)
chore/no-ticket → 2 (correctly blocked)
Refs me2resh/apexyard#168 (the parent bug) + #169 (the partial fix).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#173): sync CHANGELOG.md from main → dev (#174)
Closes me2resh/apexyard#173.
The release-cut model (AgDR-0007 / #116) squash-merges
release/vN.N.N → main, which means the v1.2.0 CHANGELOG section that
landed on main via PR #172 was never propagated back to dev. This
commit copies main's CHANGELOG.md verbatim onto dev so the v1.2.0
section is now present on both branches.
The diff is exactly the v1.2.0 entry being prepended; no other lines
change.
Without this sync, the next release PR cut from dev would build a
v1.3.0 section on top of v1.1.0, silently dropping v1.2.0 from dev's
running history. The corollary skill-level fix (option B in the
ticket) — updating /release to source the previous CHANGELOG from
upstream/main — is filed as a follow-up and out of scope for this PR.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#181): /agdr skill — searchable AgDR library (#186)
Adds /agdr — a portfolio-wide index for Agent Decision Records.
Walks apexyard.projects.yaml, reads each project's docs/agdr/*.md
(local clone if available, else gh api fallback), parses the optional
YAML frontmatter for category + projects, and answers four queries:
- /agdr browse list across the portfolio, grouped by category
- /agdr search <term> full-text grep across all bodies, returns
<project>/AgDR-NNNN paths + matching paragraph
- /agdr show <id> print a specific record, disambiguates duplicates
- /agdr stats counts per category (the marketing-slide tile,
now backed by real data)
Six-category taxonomy: architecture | tech-stack | security | patterns
| integrations | other. Legacy AgDRs without frontmatter remain
first-class — they bucket as `other` and are flagged in browse so
operators can migrate at their own pace.
Backwards-compatible template change: templates/agdr.md gains an
optional `category:` (and optional `projects:`) line in the existing
frontmatter block. Omitting the line keeps every existing AgDR valid;
the skill defaults to `other` when missing.
Doc note in workflows/sdlc.md § Phase 2 points at /agdr search for
"have we decided this before?" lookups before drafting a design.
Smoke test in .claude/hooks/tests/test_agdr_skill.sh covers the
parser the spec specifies — frontmatter extraction, category
bucketing (including the legacy default-to-other path), id reading,
stats aggregation, and search match counts. 18 assertions, all green;
12 pre-existing test suites also green (no regressions).
Closes #181
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(me2resh/apexyard#183): /launch-check trend tracking (#185)
- Persist each run as JSON under <projects_dir>/<name>/launch-check/runs/
(timestamp + branch + commit + per-dimension scores + verdict + top_risks)
- Append "Trend (last 5 runs)" section to the per-run summary when at least 2
prior runs exist — markdown table + ASCII score chart
- Add /launch-check trend mode for read-only trend rendering (no full audit)
- Auto-derive notes column from score-delta vs previous run
(e.g. "Security +12, Analytics +10")
- Opt-in commit via .launch-check-history-tracked marker; gitignored by default
- New helper script render-trend.sh + test_launch_check_trend.sh (21 cases)
- Resolve projects dir via portfolio_projects_dir helper (no hardcoded path)
- Schema is forward-compatible — extra fields preserved on framework upgrade
AgDR-0014 documents the chart format / schema / opt-in choices.
Closes me2resh/apexyard#183
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#182): /status --briefing + bin/apexyard status CLI shim (#187)
* feat(me2resh/apexyard#182): /status --briefing + bin/apexyard CLI shim
Make slide 6's `$ apexyard status` invocation real — the smallest of three
slide-reality tickets, demonstrating the marketing-to-implementation
pattern before #181 / #183 land.
- New helper at .claude/skills/status/briefing.sh — computes the 4-line
"where am I" briefing (active workspace, active ticket, branch,
role-set) so the logic is testable in isolation and runnable from a
plain shell. Workspace inference walks up from cwd to the ops-fork
root (same algorithm as _lib-portfolio-paths.sh and /start-ticket).
- Ticket reads from .claude/session/tickets/<workspace> first, falls
back to .claude/session/current-ticket. Role-set is inferred from
the active ticket's GitHub labels (v1: backend / frontend / qa /
security / platform / sre / data / ux / ui / product / tech-lead,
plus the long forms). No match emits the explicit "<none — inferred
per task>" placeholder so the four-line shape is constant.
- New CLI shim bin/apexyard delegates `apexyard status` to the same
helper after walking up to find the ops-fork root. Works from any
workspace/<name>/ clone or the fork itself; symlink onto PATH to
install (no shadowing of the `claude` binary).
- /status SKILL.md gains a "Briefing mode" section documenting the
--briefing / -b flag; default /status output is unchanged.
- docs/multi-project.md "Daily workflow" demonstrates the new
invocation alongside /inbox and /status.
- New smoke tests at .claude/hooks/tests/test_status_briefing.sh — 8
cases covering ops-root cwd, workspace cwd, unknown cwd, ops-fallback
marker, per-project marker priority, label-based role inference, the
no-matching-label path, and the constant-four-line shape.
Closes me2resh/apexyard#182
* fix(me2resh/apexyard#182): replace curios-dog with example-app in SKILL.md output sample (leak fix)
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(#178): LSP integration spike — measurement + recommendation (#184)
* docs(#178): LSP spike — token-savings measurement + integration findings
- Phase 1: estimated token cost A vs B for three representative queries on a real TS Lambda backend (~9,750 LOC); shallow semantic queries see ~3-23x input-token savings, multi-hop traces see ~1.4x
- Phase 2: verified Claude Code shipped first-party LSP support in v2.0.74 (Dec 2025), gated behind ENABLE_LSP_TOOL=1, wired in via the plugin system (.lsp.json); MCP-wrapped LSP shims (cclsp, lsp-mcp) remain as fallback
- Phase 3: recommends Option 3 — interactive clone-first prompt at end of /handover, preserving today's "never auto-clone" principle while making the deep-dive path discoverable
- Phase 4: AgDR Y-statement and option matrix sketched; full AgDR is a follow-up if the spike says go
- Recommendation: GO. Adopt the built-in tool, document the opt-in path, change /handover to offer clone-first interactively. No code changes beyond /handover SKILL.md; no novel integration to maintain
Closes me2resh/apexyard#178
* docs: fix markdownlint MD031/MD032 in spike report
Add blank lines around lists and a fenced code block to satisfy
markdownlint-cli2 in CI. Pure formatting, no content change.
Refs me2resh/apexyard#178
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(me2resh/apexyard#190): annotate LSP-aware skills with opt-in callouts (#191)
- Add identical-shape "LSP-aware (optional, recommended)" callout near
the top of each of the four code-aware SKILL.md files.
- Per-skill savings number is the only variation:
- /code-review: ~3-15× cheaper for semantic queries
- /threat-model: ~3-15× shallow, ~1.4-5× multi-hop traces
- /security-review: ~3-15× cheaper for semantic querie…
* chore(#109): project-configurable ticket / branch / commit / PR schema (#118)
* chore(#109): project-configurable ticket / branch / commit / PR schema
Lift the prefix / type whitelists hardcoded across skills, hooks, and
CI into a versioned JSON config read through a shared shell library.
Shipped defaults at .claude/project-config.defaults.json; per-fork
overrides at the optional .claude/project-config.json; one reader
(_lib-read-config.sh) that every consumer now uses.
Added:
- .claude/project-config.defaults.json (v1 schema)
- .claude/hooks/_lib-read-config.sh (shared reader)
- docs/project-config.md (schema reference + extension guide)
- docs/agdr/AgDR-0006-project-configurable-ticket-schema.md
Migrated (still pass with no config present via last-resort fallback):
- validate-branch-name.sh → .branch.type_whitelist
- validate-commit-format.sh → .commit.type_whitelist (legacy
`commit_types` top-level key honoured as backward-compat fallback)
- validate-pr-create.sh → .pr.title_type_whitelist
- /feature, /task, /bug skills reference the config in their Rules
sections; none hardcodes the list any more
Unlocks subsequent config-readers for #107 / #110 / #111 / #112 /
#113 / #114 / #115 — each extends the schema under its own subtree
without further changes to the loader.
https://github.com/me2resh/apexyard/issues/109
* fix(#109): satisfy markdownlint MD032 and MD060 on new docs
Auto-fix MD032 (blank lines around lists) in AgDR-0006 and format
table-separator rows with surrounding spaces (MD060) in both new
doc files. Content unchanged; CI green.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#110): add block-private-refs-in-public-repos.sh hook (#119)
Adds a new PreToolUse hook that blocks gh issue/PR/comment creation and
gh api .../issues|/pulls calls targeting a public framework repo
(default: me2resh/apexyard + whatever `upstream` resolves to) when the
title or body references any registered private project from
apexyard.projects.yaml (by name, repo slug, owner/repo#N ticket ref, or
workspace path).
The hook is a sibling to check-secrets.sh — both scan outgoing content
for identifiers that should never leave the local environment. Skip
marker `<!-- private-refs: allow -->` in the body lets a deliberate
reference through with a visible warning.
Files touched:
- .claude/hooks/block-private-refs-in-public-repos.sh (new)
- .claude/hooks/tests/test_block_private_refs.sh (new)
- .claude/rules/leak-protection.md (new)
- .claude/settings.json (wire PreToolUse matchers for the 5 gh shapes)
- docs/rule-audit.md (append section 10 + bump counts)
Refs: https://github.com/me2resh/apexyard/issues/110
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#115): add warn-stale-review-markers.sh PostToolUse hook (#120)
Fires after `git push` to surface review markers that have gone stale
because new commits were pushed past an existing Rex / CEO / design
approval. The merge gate already catches this at `gh pr merge` time,
but only then -- this hook closes the gap by flagging it immediately
at push-time so the author isn't surprised at merge.
- `.claude/hooks/warn-stale-review-markers.sh`
- PostToolUse, non-blocking (PostToolUse exit 2 would push noise
into the conversation; this hook is purely informational).
- Resolves the PR HEAD via `gh pr view --json headRefOid` -- same
source-of-truth as the merge-gate hooks post-apexyard#47 / #55.
Falls back to local HEAD with a visible WARN when gh is offline.
- Silent on: no PR for branch, no markers, fresh markers,
failed push (detected via `rejected` / `failed to push` /
`fatal:` / `error:` markers in tool_response.stderr).
- Modes: `warn` (default) prints one stderr line per stale marker;
`delete` opts in to auto-removal via
`.claude/project-config.json` -> `review_markers.on_stale`.
TODO(apexyard#109): switch to the shared project-config reader
once it lands.
- `.claude/settings.json`
- Wires the hook on PostToolUse / Bash / `git push *`.
- `docs/rule-audit.md`
- Adds a row under section 3 (Code review & PR quality) and
bumps the mechanized count 26 -> 27 / total 73 -> 74.
- `.claude/hooks/tests/test_warn_stale_review_markers.sh`
- 8 cases: no PR, no markers, fresh markers, stale rex / ceo /
design (warn), delete mode, failed push. All pass locally.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#111): upgrade pre-push-gate from advisory reminder to blocking check-runner (#121)
* chore(#111): upgrade pre-push-gate from reminder to blocking check-runner
Previously pre-push-gate.sh just printed a checklist of things to run
locally before pushing — it was advisory. The rule it enforces is a
HARD STOP per pr-workflow.md. That asymmetry meant agents routinely
pushed broken work and discovered it only when CI went red.
Replaces the reminder with a blocking runner that reads the list of
shell commands from project config (.pre_push.commands) and executes
them in sequence before a push is allowed through. First non-zero
exit blocks the push with exit 2 and prints the failing command plus
the last 20 lines of its output.
- Config key: .pre_push.commands[] — array of {name, run} objects.
Shipped default is an empty list (hook stays a no-op on repos that
haven't configured their checks yet, including the framework repo
itself until it wires its own CI).
- Emergency bypass: '<!-- pre-push: skip -->' in the HEAD commit
message. Grep-able on purpose so bypasses stay auditable.
- Fail-fast: once a command fails, the rest don't run. Parallel
execution is a follow-up polish.
- 7 test cases in .claude/hooks/tests/test_pre_push_gate.sh — all
pass on the shipped default + a minimal custom config.
Updates docs/rule-audit.md to flip "partial" → "yes" for the
"before git push" rule.
Integrates with the shared config reader landed in #109.
https://github.com/me2resh/apexyard/issues/111
* fix(#111): remove orphaned footnote reference from rule-audit
The previous advisory-mode footnote was superseded by pre-push-111
but its definition was accidentally kept, tripping markdownlint MD053
(unused reference definition). Drop it.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#107): add validate-issue-structure.sh PreToolUse hook (#122)
Mechanically enforces the ticket body schema when an agent files raw
`gh issue create` calls instead of going through the interactive
/feature, /task, /bug skills. Matches bracketed title prefix
([Feature] / [Chore] / [Bug] / [Docs] / etc.) against
`.ticket.required_sections` in project-config, and blocks (exit 2)
when any required section is missing or empty. Skip marker
`<!-- validate-issue-structure: skip -->` bypasses with a visible
stderr WARN for legitimate off-template tickets (epics, meta-threads).
Changes:
- .claude/hooks/validate-issue-structure.sh — the hook; reads schema
via the shared _lib-read-config.sh, with inlined defaults for bare
checkouts predating the config-schema rollout. Handles
--body / --body-file / -F path.
- .claude/project-config.defaults.json — extends .ticket with
required_sections (Feature/Chore/Refactor/Testing/CI/Docs/Bug) and
skip_marker; other .ticket fields untouched.
- .claude/settings.json — new PreToolUse matcher on Bash(gh issue
create *) alongside the existing suggest-ticket-template.sh and
block-private-refs-in-public-repos.sh hooks.
- .claude/hooks/tests/test_validate_issue_structure.sh — 15 cases
covering pass + fail paths per prefix, empty section detection,
skip marker, unknown prefix, non-gh invocation, --body-file path.
- docs/rule-audit.md — new section 11 row, mechanized count +1.
Upstream ticket: https://github.com/me2resh/apexyard/issues/107
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#112): add require-agdr-for-arch-pr.sh PreToolUse hook (#123)
Closes the asymmetry noted in .claude/rules/agdr-decisions.md: every
other HARD STOP in the ruleset (merge approval, ticket-first,
migration-first) is mechanically enforced, but the /decide HARD STOP
was prose-only. The commit-time hook require-agdr-for-arch-changes.sh
catches one architectural change at commit; this new PR-time hook
catches the cumulative diff so reviewers always have a pointer to the
decision record.
- New hook at .claude/hooks/require-agdr-for-arch-pr.sh
- Fires on Bash(gh pr create *)
- Parses --title/--body/--body-file/-F <path>
- Resolves base branch from --base, else upstream/dev, origin/dev,
upstream/main, origin/main, main, master (in that order)
- Computes `git diff <merge-base>..HEAD --name-only`
- Triggers on any changed file matching .agdr_trigger_paths[], OR any
dep-file addition (package.json via jq key-set diff; other
dep files via a commented +/- line-count heuristic — version
bumps match +/- counts and do not fire)
- Blocks (exit 2) with a helpful message naming the triggers and
pointing at /decide if the body has no `AgDR-\d+-[a-z0-9-]+`
reference
- Skip marker `<!-- agdr: not-applicable -->` bypasses with a
visible WARN on stderr
- Silent exit 0 on non-gh commands, empty diffs, unresolvable base
- Wired via .claude/settings.json PreToolUse Bash(gh pr create *)
- Adds two new top-level keys to .claude/project-config.defaults.json:
agdr_trigger_paths (shell globs — domain/, infrastructure/,
migrations/, *.tf, .github/workflows/, etc.)
agdr_trigger_dep_files (literal basenames — package.json,
pyproject.toml, Cargo.toml, go.mod, Gemfile)
Hook has inline fallback defaults kept in sync.
- Adds docs/rule-audit.md entry in the AgDR section; bumps mechanized
count 26 to 27 and total rows 73 to 74.
- Adds .claude/hooks/tests/test_require_agdr_for_arch_pr.sh (7 cases;
all green): path-triggered without AgDR (block), with AgDR (pass),
dep-file added (block), version-only bump (no fire), skip marker
(pass + warn), non-matching diff (pass), non-gh command (no-op).
Closes https://github.com/me2resh/apexyard/issues/112
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#113): require Testing section in PR body (config-driven) (#124)
Extends validate-pr-create.sh with a required-sections check that
replaces the hardcoded Glossary-only grep. The list of required H2
headings is project-configurable via `.pr.required_sections[]`.
Shipped default is ["Testing", "Glossary"], matching the canonical PR
description shape in workflows/code-review.md.
- Each entry must appear as `## <Name>` (case-insensitive).
- Empty sections are tolerated at this layer (the issue-structure hook
#107 does stricter empty-content checks for issue bodies; for PR
bodies, empty sections are left to the reviewer's judgement).
- Skip marker `<!-- pr-sections: skip -->` bypasses with a visible
stderr WARN — for trivial PRs (lint-only fixes, version bumps)
where the full template is overkill.
- Reads from project config via the shared _lib-read-config.sh (#109).
Inline fallback matches shipped defaults so bare checkouts predating
#109 keep working.
- 8 test cases cover: all-sections pass, each missing section,
missing-both (both errors printed), skip marker, case-insensitive
headings, H3 rejection.
https://github.com/me2resh/apexyard/issues/113
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#114): enforce single Closes-keyword per PR body (#125)
* chore(#114): enforce single Closes-keyword per PR body
Caps distinct auto-closing references (close/closes/closed, fix/fixes/
fixed, resolve/resolves/resolved + N or owner/repo+N) at one per PR
body. Closes the loophole where the title validator limited the title
to one ticket but multiple Closes lines in the body would still auto-
close all of them on merge.
- Scans stripped of fenced code blocks so closing keywords inside a
code sample do not count.
- Distinct counting: the same number referenced twice (e.g. via Fixes
and Closes) counts as one.
- Cross-repo refs (owner/repo+N) count normally.
- Opt-in escape hatch: pr.allow_multiple_closes=true in
project-config disables the check for teams that deliberately batch
rollbacks or dependency bumps.
- Per-PR bypass: a multi-close-approved HTML comment in the body
prints a visible stderr WARN and lets that PR through. Grep-able
trace so bypasses are auditable.
- 10 test cases cover: one close passes, no-keyword passes, two
distinct block, three mixed block, same-number-twice passes, code-
fence-ignored, skip marker, cross-ref without keyword, opt-in
config, cross-repo close.
Reads configuration via the shared _lib-read-config.sh (apexyard+109).
https://github.com/me2resh/apexyard/issues/114
* fix(#114): strip inline backticks and tilde fences from close-count scan
Rex caught a self-reflexive bug in the initial commit: documentation
mentioning closing keywords inside inline backticks (say a PR body
that explains the new hook with examples) counted as real closes, and
a skip marker inside inline backticks silently bypassed the check.
Future PRs that document the feature would trip the same trap.
Fix the code-region stripper to cover:
- Triple-backtick fences (already handled)
- Tilde fences (new)
- Inline-backtick spans (new)
Also run the skip-marker check against the stripped body, so a marker
used purely as documentation no longer activates a real bypass.
Three new test cases pin the behaviour:
- closing keywords in inline backticks are ignored
- skip marker inside inline backticks does NOT bypass
- tilde fences also get stripped
13/13 tests pass.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#108): add /tickets-batch skill for bulk-file flow (#127)
The fast happy path for filing 5–20 structured tickets in one intent
without dropping to raw `gh issue create` (non-conformant) or running
`/feature` 20 times serially (~100 turns of interview).
- .claude/skills/tickets-batch/SKILL.md — new skill spec. Asks
shared-context questions (priority, epic, area-labels, repo) ONCE
for the whole batch, then runs a ≤3-question micro-interview per
ticket (type, one-line purpose, optional clarification when the
inference is low-confidence). Confirms the full batch as a table,
then files each via specific `gh issue create` calls (never a
bulk JSON dump — the validator runs per-issue). Output conforms
to `.ticket.required_sections` by construction. Caps at 20
tickets per invocation.
- CLAUDE.md — added a row for /tickets-batch in the Available
Skills table; bumped the count references from 33 to 34.
Refs https://github.com/me2resh/apexyard/issues/108
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#117): add /fan-out skill + parallel-work rule doc (#128)
- Add `.claude/skills/fan-out/SKILL.md` — spawns N parallel Agent calls
in a single assistant message, with per-task agent type, worktree
isolation, and foreground/background mode. Caps at 5 concurrent
agents. Refuses fan-out when tasks share file write targets or have
sequential dependencies. Includes pre-spawn active-ticket safety
check and worktree merge-back flow that pauses on conflict.
- Add `.claude/rules/parallel-work.md` — trigger heuristic for when an
agent should proactively offer fan-out (>= 2 file-independent,
context-independent, individually substantial work items). Pairs
with the skill: rule says when, skill says how.
- Update `CLAUDE.md` — bump rules count to 9, skills count to 34, add
`/fan-out` row to the skills table.
Refs https://github.com/me2resh/apexyard/issues/117
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#116): adopt release-cut branch model (dev/main + tags) — framework only (#126)
* chore(#116): adopt release-cut branch model (dev/main + tags)
Formalises the dev/main split that already exists informally — dev is
the daily-work branch where every PR lands, main is release-only,
tagged with semver on each merge. Framework-only: managed projects
under apexyard governance stay trunk-based.
Added:
- AgDR-0007 — decision record (options table covers full git flow vs
trunk-only vs gitflow-lite; chose gitflow-lite)
- /release skill — diff dev against main, propose semver bump from
conventional commits, generate CHANGELOG, open release PR, tag
after merge
- docs/release-process.md — prose runbook for cutting a release
(manual fallback for the skill)
- .git.protected_branches in project-config.defaults.json
(main/master/dev/develop)
Modified:
- block-main-push.sh — now blocks direct pushes/commits to all
configured protected branches (was: hardcoded main/master). Reads
.git.protected_branches via the shared config reader (apexyard#109).
- CLAUDE.md — new section under Git Conventions explaining the
dev/main model + the framework-only scope. Skill table entry for
/release. Skills count bumped to 34.
- docs/multi-project.md — note that upstream/main is release-only
and the dev/main split is framework-only.
Non-consequences (per AgDR-0007):
- No release/* or hotfix/* branches. Hotfixes are normal patches
cut quickly. Revisit if multi-version maintenance becomes a need.
- No automatic on-merge issue closing for dev PRs. The release PR's
body aggregates all Closes references for the batch and triggers
auto-close en masse when it merges to main. Manual close in the
meantime.
- CI workflows trigger on pull_request regardless of base, so
dev-targeting PRs already get the full check matrix — no
workflow file edits needed.
https://github.com/me2resh/apexyard/issues/116
* fix(#116): satisfy markdownlint MD032 + MD060 in 116 docs
Auto-fix added blank lines around lists in AgDR-0007 (MD032) and
spaced the table separators in AgDR-0007 + multi-project.md (MD060).
Content unchanged.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* fix(#106): CHANGELOG fallback in drift hook for squash-merged forks (#129)
* fix(#106): CHANGELOG fallback in drift hook for squash-merged forks
The v1.1.0 tag-reachability check (`git tag --merged main`) misfires on
forks that sync via GitHub's default squash-merge: the squash collapses
the upstream-tag commit into a synthetic SHA, the tag stops being
reachable, and the banner keeps firing forever.
Discovered live on the first real-world `/update` flow: ops fork
squash-merged the v1.1.0 sync PR, banner kept saying "v1.1.0 available"
even though the fork was content-caught-up.
This commit adds a CHANGELOG-content fallback that fires only when the
primary tag check fails. If the fork's main has a heading
`## [X.Y.Z]` matching the upstream tag's version, treat the release as
absorbed and stay silent. Tolerant grep (matches the apexyard CHANGELOG
format from v1.1.0 onward, with leading-`v` stripping for tag→heading
conversion).
The merge-commit and rebase paths are unchanged — primary tag check
still works for them, and the fallback never fires when it shouldn't.
Test coverage (5 cases):
- squash-merge fork caught up to v1.1.0 → silent (the fix)
- merge-commit fork caught up to v1.1.0 → silent (regression check)
- fork stopped at v1.0.0 → banner fires
- fork has its own newer tag → silent
- squash-merge but no CHANGELOG on fork → banner fires (no false silence)
Records the strategy update in docs/agdr/AgDR-0008-…md (extends
AgDR-0005's tag-based-drift design).
https://github.com/me2resh/apexyard/issues/106
* fix(#106): satisfy markdownlint MD032/MD060 + shellcheck SC2164
Rex flagged two CI-blocking issues on the original 106 commit:
- AgDR-0008 had three bulleted sub-lists in the Consequences section
without surrounding blank lines (MD032). Added blanks and padded the
one tight-pipe table separator (MD060).
- The 106 test fixture had five subshell `cd "$fk"` calls without
`|| exit 1` (SC2164). Added the guard to all five.
5/5 tests still pass after the fix. No semantic change to the hook
or the test logic.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#130): add /validate-idea skill — lightweight pre-spec gate (#131)
A 10-minute, 5-question check that sits between /idea and /write-spec.
Designed for solo founders running ApexYard — not a heavyweight
methodology like event storming or Wardley mapping.
Five questions, asked one at a time:
1. Who is this specifically for?
2. What do they do today instead?
3. What's the smallest version that proves the value?
4. What would prove this is wrong? (kill criteria)
5. Build, buy, or rent?
Output: a one-page validation doc with a GREEN/YELLOW/RED verdict.
RED auto-updates the IDEA-NNN backlog row to WONTDO.
Integration:
/idea — adds an optional default-no "Validate now?" step after
capture (and after the optional GitHub Issue offer).
/handover — adds a conditional "this looks dormant, validate?"
step at the end of the integration plan, gated on the dormancy
heuristic (last commit > 90d AND zero open PRs AND no recent
issue activity). Healthy projects don't see the prompt.
CLAUDE.md skills count bumped to 35; new skills row added.
https://github.com/me2resh/apexyard/issues/130
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat: configurable voice prompts on assistant pause (AgDR-0009-voice-prompts-on-pause) (#135)
Stop hook that speaks the assistant's question aloud (Jarvis-style)
when it pauses for user input. Initial phase is macOS-only via `say`,
no voice input — user replies via keyboard.
Default OFF. Adopters opt in by overriding `voice_prompts.enabled` to
true in `.claude/project-config.json`.
Files:
- .claude/hooks/voice-prompt-on-pause.sh — Stop hook with config gate,
trigger heuristic (questions-only by default), markdown stripping,
sentence-boundary truncation, fire-and-forget say invocation
- .claude/hooks/tests/test_voice_prompt_on_pause.sh — 9 cases covering
disabled-default, enabled+question, enabled+statement, approved-pattern,
abc-menu, malformed-transcript, no-say-on-PATH, trigger-always,
markdown-stripping
- .claude/project-config.defaults.json — voice_prompts schema block
added (enabled, voice, max_chars, rate_wpm, trigger), default OFF
- .claude/settings.json — new Stop hook entry wired with the standard
ops-root resolver wrapper
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — design rationale,
options matrix (status quo / macOS say / cloud TTS / ML detection),
consequences, future phases
- docs/project-config.md — new "Voice prompts" section with override
examples and privacy notes
Test mode: hook respects VOICE_PROMPTS_SYNC=1 to run say synchronously
(test runners need this so assertions don't race against orphaned
background processes). Production invocations always run async.
Future phases (out of scope here, AgDR §"Future phases"):
- Phase 2: cross-platform TTS (Linux espeak, Windows SpeechSynthesizer)
- Phase 3: cloud TTS providers (OpenAI, ElevenLabs) — privacy AgDR-worthy
- Phase 4: voice input via Whisper-based STT
- Phase 5: per-message overrides
Refs: https://github.com/me2resh/apexyard/issues/134
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#141): add /debug skill — structured hypothesis-driven debugging (#142)
* feat(#141): add /debug skill — structured hypothesis-driven debugging
Adds a methodology skill that enforces five disciplines:
1. Capture the symptom precisely (exact URL, exact response, exact step)
2. Read the architecture before guessing (map every layer the request
touches, file by file)
3. Form a hypothesis ladder (3–5 candidates, each with an explicit
evidence test that confirms or refutes it)
4. Gather evidence first, fix second
5. Verify the fix against the original symptom evidence (re-run the
same `curl` / browser repro you used in step 4 — unit tests
verify code, not feature, correctness)
Stack appendices (Web, Desktop) carry stack-specific surface-evidence
requirements (step 1), architecture-surface maps (step 2), and
evidence-tests cookbooks (step 4). The methodology body stays portable
across stacks; appendices are where stack-specific knowledge accrues
over time.
Web appendix covers browser routing, framework configs (Next/Nuxt/Vite),
SPA-fallback layers, CDN, origin, the shared API client, backend
handlers, and auth providers. Desktop appendix covers Electron / Tauri
/ native-shell concerns: app entry points, IPC bridges, native modules,
auto-updater, sandbox / entitlements, code signing, crash reports.
Includes "When NOT to use" guidance so the methodology overhead doesn't
sandbag simple bugs (typos, off-by-ones, greenfield exploration).
Motivated by a real OAuth debug session in a managed project where
three sequential fixes chased adjacent symptoms because each was
hypothesis-then-fix without evidence in between. The skill is the
"never do that again" guardrail.
Closes #141
* fix(#141): scrub private project issue numbers from anti-pattern table
Rex review on PR #142 caught that line 155 of the skill's anti-pattern
table still named the originating PRs (#375, #377, #380) from the
private project where the methodology was first exercised. The PR body
and commit message were correctly abstracted earlier, but this in-file
reference slipped through — the leak-protection hook only scans gh
issue/pr writes, not staged file content, so mechanical enforcement
didn't catch it.
Replaced with "Three sequential PRs chasing the same symptom because
each was based on a different guess (no evidence test in between
cycles)" — same pedagogical value, zero attribution.
Refs #141
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(#143): document split-portfolio mode + add /setup privacy gate (#144)
Adopters on GitHub Free with any private project hit a silent privacy bug
following today's docs: forking apexyard makes a public fork that you cannot
later flip to private (GitHub policy), and committing `apexyard.projects.yaml`
+ `projects/<name>/` to the fork publishes private project names + handover
findings on a public GitHub repo.
This PR documents the supported workaround — split-portfolio mode — and
adds an upfront privacy gate to the /setup skill so new adopters never
hit the trip-wire silently.
docs/multi-project.md:
- New "Two setup modes — pick the one that matches your privacy needs"
section before TL;DR, with a side-by-side table and the explicit
trip-wire callout.
- Existing TL;DR retitled "TL;DR — single-fork mode (default)" with a
one-line pointer to the split-portfolio section.
- New "Split-portfolio mode — public framework + private portfolio"
section between the existing setup steps and the directory-layout
section. Includes:
- The two-repo layout (~/ops/apexyard public + ~/ops/portfolio private)
- 7-step setup walkthrough with copy-pasteable commands
- Daily workflow + upstream sync notes (both unchanged)
- Trade-offs (two repos to maintain, two clones per machine, one
upstream-sync conflict path on `projects/README.md`)
- "Migrating from single-fork to split-portfolio" recovery flow with
the explicit warning that GitHub Issue / PR edit history survives a
force-push and must be redacted separately
.claude/skills/setup/SKILL.md:
- New Step 2a: privacy gate — asks "are any projects private?" before
proposing the config. Branches on the answer:
- All public → single-fork mode
- GitHub Pro / Team / Enterprise → single-fork mode (private
forks of public repos
are supported on those
plans)
- Any private + GitHub Free → split-portfolio mode
- New Step 2b: walks through the split-portfolio setup interactively
(private repo create, sibling clone, gitignore + symlink) when the
privacy gate triggers.
- Detection: `test -L apexyard.projects.yaml` short-circuits Step 2b
for adopters already in split mode.
- Explicit "do NOT auto-migrate" rule for adopters already in single-fork
mode with private names already pushed — that path is destructive
(force-push history rewrite + redact issue/PR bodies + delete backup
branch) and warrants a deliberate, eyes-open run, not a /setup side
effect.
Out of scope for this PR (tracked separately on #143):
- `portfolio:` config block in `onboarding.yaml` schema
- Skill audit + refactor to honour configured `registry` / `projects_dir`
/ `ideas_backlog` paths instead of hardcoded fork-relative paths
- `/split-portfolio` migration helper skill that automates the recovery
flow currently documented manually
This is the docs-and-setup-question minimum-viable starter — the
framework code refactor is mechanical and lands as a follow-up.
Refs #143
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#145): portfolio config + self-healing + /split-portfolio helper (#147)
* feat(me2resh/apexyard#145): portfolio config block + self-healing + /split-portfolio helper
Closes the framework primitive deferred from me2resh/apexyard#144. Adds
first-class config-driven path resolution for the portfolio registry,
projects dir, and ideas backlog, with self-healing surfacing of broken
config at session start, plus a new /split-portfolio skill that automates
the destructive recovery flow.
Schema, helper, and hook:
- .claude/project-config.defaults.json: new portfolio: block
(registry, projects_dir, ideas_backlog) with defaults matching
today's single-fork layout
- .claude/hooks/_lib-portfolio-paths.sh: new sourceable helper exposing
portfolio_registry, portfolio_projects_dir, portfolio_ideas_backlog,
portfolio_validate, portfolio_clear_cache. Resolves relative paths
against the ops-fork root.
- .claude/hooks/check-portfolio-config.sh: new SessionStart hook —
silent on OK, one-line banner on broken config, never blocks session
- .claude/hooks/tests/test_portfolio_paths.sh: 13 cases covering
defaults, absolute/relative overrides, validate states, cache clear
Skill audit (18 SKILL.md files):
- Adds Path resolution callout pointing at the helper
- handover bash blocks now source helper and use $(portfolio_registry)
instead of literal apexyard.projects.yaml
- setup Step 2b now writes the portfolio: config block (recommended)
and validates via portfolio_validate before declaring success;
symlink approach kept as legacy fallback
New skill (.claude/skills/split-portfolio/SKILL.md):
- 10-step migration with explicit operator-confirmation gates at each
destructive step (force-push, body redaction, branch deletion)
- --verify mode: read-only state report (mode, paths, validate, drift)
- --dry-run mode: prints commands without executing
- Pre-flight refusals: already-private fork, paid GitHub plan, dirty
working tree, already-migrated state
- Step 9 writes the portfolio: config block (not symlinks) — symlink
fallback documented for adopters on older framework versions
- Step 9 surfaces the GitHub timeline-API survival caveat verbatim
- Idempotent re-runs: detects partial-migration state and resumes
Docs (docs/multi-project.md):
- Layout section describes both modes (config-block recommended,
symlink legacy) with self-healing notes
- Setup steps split into config-block mode and legacy symlink mode
- Migration section now points at /split-portfolio skill; manual
recipe preserved as fallback
AgDR (docs/agdr/AgDR-0010-portfolio-config-and-self-healing.md):
- Full Y-statement, options, decision, consequences, future phases
- Schema decision rationale: project-config.json over onboarding.yaml
because runtime path resolution belongs in project-config
Closes me2resh/apexyard#145
Refs me2resh/apexyard#146 (delivered same PR; closed manually post-merge
per the single-Closes-keyword rule in validate-pr-create.sh)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(me2resh/apexyard#145): markdownlint MD031 — blank lines around fences
CI's markdownlint-cli2 (v0.34.0) flagged the JSON + bash fenced code
blocks I added in setup/SKILL.md Step 2b without surrounding blank
lines. Added the required blanks. No content change.
Refs me2resh/apexyard#147
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>
* docs(me2resh/apexyard#148): correct privacy-gate wording — adopter action, not framework auto-publish (#149)
The privacy-gate wording introduced in PR #144 (and unchanged in PR #147)
attributed the publication to the framework rather than the adopter:
"the standard fork-and-commit setup will silently publish your private
project names on a public GitHub repo"
That's factually wrong. ApexYard never pushes anything without explicit
operator approval — the publication only happens when the adopter
themselves runs git push. The "silently publish" framing read as if the
framework auto-publishes, which is misleading and undermines trust in
the rest of the framework's safety claims.
Two prose-only edits, no code, no behavior change:
- .claude/skills/setup/SKILL.md Step 2a — replaced "will silently
publish ..." with adopter-action language ("you might accidentally
publish ... a stray git push after registering them — I won't push
without your approval, but the risk is on the adopter once the data
is committed locally")
- docs/multi-project.md trip-wire callout — replaced "silently publish
their portfolio names the moment they push" with "risk accidentally
publishing their portfolio names with a stray push (the framework
itself never pushes without operator approval, but once the registry
is committed locally the next push exposes it)"
Verified: `grep -r "silently publish" .claude/skills/ docs/` returns no
hits.
Closes me2resh/apexyard#148
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#150): bootstrap-skill exemption + Bash-write coverage (#152)
Closes the legitimate-bypass case (me2resh/apexyard#150) and the
illegitimate-bypass case (me2resh/apexyard#151) together so the
ticket-first gate is coherent. Shipping either alone would leave a
window where the framework is internally inconsistent — see AgDR-0011
for the full rationale.
Bootstrap exemption (me2resh/apexyard#150):
- .claude/session/active-bootstrap marker, written by /setup,
/handover, /update, /split-portfolio on entry; cleared on exit
- SessionStart sweep (clear-bootstrap-marker.sh) for stale markers
from interrupted sessions
- require-active-ticket.sh reads the marker and exempts skills on
the configured ticket.bootstrap_skills list
- bootstrap_skills list lives in .claude/project-config.defaults.json
(extendable per fork via .claude/project-config.json)
Bash-write coverage (me2resh/apexyard#151):
- new _lib-detect-bash-write.sh — heuristic detector for output
redirection, tee, sed -i, awk -i inplace, python/node/ruby
embedded interpreters
- require-active-ticket.sh + require-migration-ticket.sh now fire
on Bash in addition to Edit|Write|MultiEdit
- design choice: false-negatives preferred over false-positives
(the matcher errs toward "let through" rather than block legit
read-only commands)
Tests: 32 unit cases on the lib + 12 integration cases on the hook,
including the exact me2resh/apexyard#151 bypass repro from the issue
body.
Closes me2resh/apexyard#150
Will manually close me2resh/apexyard#151 post-merge per the
single-Closes-per-PR rule (precedent: AgDR-0010 / PR #147).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#153): extend Bash-write matcher beyond first-version coverage (#155)
Closes me2resh/apexyard#153.
Extends `_lib-detect-bash-write.sh` (introduced for me2resh/apexyard#151
in PR #152) with the matcher families flagged by Rex's review of #152.
AgDR-0011 already frames the matcher as a living list extended on
observation; this commit just walks the list.
New matcher families:
- File-moving builtins: `cp`, `mv`, `rm`, `dd`, `install` (anchored at
command-start; `--help`/`--version` and `git rm`/`git mv` excluded)
- Archive / network writes: `tar -x` / `tar --extract`, `curl -o` /
`--output`, `wget -O` / `--output-document`
- Additional embedded interpreters: `perl -e`, `php -r` (keyword-gated
like python/node/ruby); `go run`, `deno run`/`deno script.ts`,
`bun run`/`bun script.ts` (categorical script runners)
- Python helpers: `pathlib.Path().touch()`, `shutil.copy*`,
`shutil.move`, `os.rename` added to the `python -c` and python
heredoc keyword list
- Heredoc variants for `ruby` and `node` (previously only python
heredoc was covered)
Extractor extensions:
- `cp` / `mv`: last positional arg
- `curl -o` / `--output`: file argument
- `wget -O` / `--output-document`: file argument
- `tar -x`, `go run`, `deno`, `bun`, `perl -e`, `php -r`: return empty
(caller applies gate categorically per AgDR-0011)
Test count rose from 32 to 86. Negative-class counterexamples cover
the trickiest false-positive surfaces: `tar -t` listing, `cp --help`,
`rm --version`, `git rm`, `curl` bare URL fetch, `wget` bare URL fetch,
`deno fmt`, `deno test`, `go build`. Existing
`test_require_active_ticket_bash.sh` regression suite still passes 12/12.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(#154): mock gh in test sandboxes to remove live-tracker dependency (#156)
- add _lib-mock-gh.sh helper that installs a fake `gh` on the sandbox PATH;
intercepts `gh issue view <N> ... --json ...` and returns synthetic
`{"number":N,"state":"OPEN"}` (overridable per-num via mock_gh_set_state)
- wire the shim into test_single_closes_per_pr.sh and
test_validate_pr_required_sections.sh so the validator's CLOSED-issue
refusal no longer breaks the suite when upstream issues are closed
- both files previously failed every case (0/13 and 0/8) because their PR
titles reference #114 / #113, which are now CLOSED upstream
- post-fix: 13/13 and 8/8; full suite remains green
Closes me2resh/apexyard#154
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#132): structured CEO marker + same-turn merge in /approve-merge (#158)
* feat(#132): structured CEO marker + same-turn merge in /approve-merge
Closes me2resh/apexyard#132 (drop the "stop before merge" rule) and
me2resh/apexyard#48 (harden CEO marker against self-approval bypass)
together. The two threads compose — see AgDR-0012 for the full
rationale.
Streamline (#132):
- /approve-merge now runs `gh pr merge --squash --delete-branch`
in the same turn as the marker write, by default
- --no-merge opt-out preserves the deferred-merge case
- The discrete approval moment is the SKILL INVOCATION, not a
follow-up "now do the merge" message
Harden (#48):
- CEO marker is now a structured key/value file with required fields:
sha=<HEAD>
approved_by=user
skill_version=2
Validated by block-unreviewed-merge.sh; bare-SHA legacy markers
rejected with a clear "stale format" error pointing at /approve-merge
- The model's bare `echo SHA > <pr>-ceo.approved` bypass is now
mechanically rejected. Forging the structured fields requires a
deliberate, visible rule violation rather than a one-line accident
- Optional audit fields (approved_at, approval_summary) capture the
"what did the user say when they approved" trail
- Rex marker stays bare-SHA — different threat model (automated
reviewer, not human authorization moment)
pr-workflow.md reframed: "the load-bearing rule is explicit per-PR
approval, not two user messages." The merge is a deterministic
consequence of the approval invocation.
Tests: 12 cases on the hardened hook covering the new format end-to-end
(valid v2, missing rex/ceo, bare-SHA legacy rejected, missing
approved_by, wrong approved_by, skill_version=1, sha mismatch,
non-merge no-op, gh-api shape gated). Full suite: 205/205 across 13
test files.
Will manually close me2resh/apexyard#48 post-merge per the
single-Closes-per-PR rule (precedent: PR #152 / AgDR-0011).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#132): redact private project reference from AgDR-0012
Abstracted two references to a registered private project that named
the project's owner/repo. The leak-protection hook caught one in the
PR body; this fixup removes the matching references from the
AgDR-0012 file content (which would otherwise have shipped the names
to me2resh/apexyard public repo via the merge).
The pre-existing reference at .claude/rules/pr-workflow.md:130
(documenting #47) is untouched — it predates this PR and is already
on the public repo's history.
Refs me2resh/apexyard#132.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#132): markdownlint blanks-around-fences + typo fix
Two small fixups against red CI / Rex feedback:
- AgDR-0012 line 63: fenced code block now has a blank line before it
(MD031). markdownlint-cli2 0.13.0 was rejecting the indented fence
inside the bullet because the fence's preceding line was the bullet
text (no blank).
- approve-merge SKILL.md line 172: typo `deferes` → `defers` (Rex flag
on PR #158).
Refs me2resh/apexyard#132.
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>
* chore(#157): remove voice-prompts feature + correct hook/skill counts (#161)
* chore(#157): remove voice-prompts-on-pause feature + correct hook/skill counts
Closes me2resh/apexyard#157 (sunset the voice-prompts feature) and
me2resh/apexyard#77 (hook count off-by-one in CHANGELOG / CLAUDE.md)
in one bundled PR. AgDR-0013 supersedes AgDR-0009 with the full
rationale; both AgDRs are preserved (decision records are append-only
history).
Removed (#157):
- .claude/hooks/voice-prompt-on-pause.sh
- .claude/hooks/tests/test_voice_prompt_on_pause.sh
- Stop matcher block in .claude/settings.json (became empty after
voice removal)
- voice_prompts block in .claude/project-config.defaults.json
- "## Voice prompts" section in docs/project-config.md
- voice_prompts mention in AgDR-0010 line 32 (replaced with
leak_protection / ticket as still-current example config blocks)
Preserved:
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — historical record;
new "Superseded by: AgDR-0013" header at the top
- AgDR-0010 line 115 reference to AgDR-0009 — still accurate as a
historical pattern reference
Counts corrected (#77):
- CHANGELOG.md v0.3.0 stats: "17 hooks" → "18 hooks" (historical
fix — at v0.3.0 there were actually 18 hooks)
- CLAUDE.md table line: "18 shell scripts" → "24 shell scripts"
(current count after this removal)
- CLAUDE.md table line: "35 slash commands" → "39 slash commands"
- CLAUDE.md "Available skills (34)" → "Available skills (39)"
- CLAUDE.md quick-reference "Skills (35 slash commands)" →
"(39 slash commands)"
Why bundled: #77's correct count depends on whether voice is still in
the framework. Shipping #77 before #157 would write a number that's
wrong by one again the moment #157 lands. Same shape as previous
bundles (PR #152 / AgDR-0011, PR #158 / AgDR-0012).
Why no adopter-facing changelog mention of the voice removal: the
feature never reached a tagged release on main. v1.1.0 didn't have
it; v1.2.0 won't have it. From the adopter's perspective there's
nothing to retire. AgDR-0013 captures the framework's internal record
for future contributors. See AgDR-0013 § "No adopter-facing changelog
mention".
Tests: full hook test suite green (196 cases across 12 files —
test_voice_prompt_on_pause.sh removed). No regressions.
Will manually close me2resh/apexyard#77 post-merge per the
single-Closes-per-PR rule (precedent: PRs #152, #158).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#157): unwire voice from settings + configs + docs + AgDR-0013
Continuation of d09d7b5 (the file deletions). Squash-merge will
collapse both into one PR commit. This commit captures:
- .claude/settings.json — Stop matcher block removed (was the only
hook in it; entire matcher gone)
- .claude/project-config.defaults.json — voice_prompts block + its
_comment removed
- docs/project-config.md — "## Voice prompts" section removed
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — "Superseded by"
header added at the top, content otherwise preserved as history
- docs/agdr/AgDR-0010-portfolio-config-and-self-healing.md — line 32
example reference swapped from voice_prompts to
leak_protection / ticket (still-current config blocks)
- docs/agdr/AgDR-0013-sunset-voice-prompts.md — new supersession AgDR
- CLAUDE.md — hook count 18 → 24, skill count 35 → 39 (three
occurrences each, all aligned to current reality)
- CHANGELOG.md v0.3.0 stats — "17 hooks" → "18 hooks" historical fix
(#77 acceptance criterion 1)
Refs me2resh/apexyard#157 + me2resh/apexyard#77.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#157): markdownlint MD028 + resolve stale AgDR-numbering ref
Three small fixups against Rex CHANGES-REQUESTED on PR #161:
- AgDR-0009 line 3: promote "Superseded by:" header out of a
blockquote. The original "I decided ..." canonical blockquote at
line 5 was being merged with the new supersession blockquote
(markdownlint MD028 — "no blanks inside blockquote").
- AgDR-0013 line 3: same shape — "Supersedes:" header now a plain
bold paragraph, canonical "I decided ..." blockquote untouched.
- approve-merge SKILL.md line ~187: stale conditional reference
"AgDR-0012 (or 0013 — depends on whether voice-removal lands
first)" — order is now resolved (12 = approve-merge bundle, 13 =
voice removal). Drop the parenthetical.
Refs me2resh/apexyard#157 + me2resh/apexyard#77.
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(#160): multi-tab terminal demo on the landing site (#162)
Landing-site `site/index.html` terminal demo previously played one
canonical flow ("one ticket, start to finish") on autoplay. With the
v1.2.0 skill surface expansion (4 new skills, 8+ new hooks), one flow
no longer represents the framework's breadth.
Adds tabs to the terminal chrome — four flows visitors can either let
auto-cycle or click directly:
1. one ticket — existing flow, unchanged content
2. /handover — adopt an external repo into the portfolio
3. /setup — first-run framework bootstrap on a fresh fork
4. /fan-out — spawn 3 parallel agents on independent tickets
Auto-advance: on completion of the active tab's script, the demo
pauses ~1.8s then advances to the next tab. Loops at the end. User
can interrupt by clicking any tab or hitting Replay.
Implementation:
- HTML chrome — single title span replaced with a tablist of 4 button
tabs. ARIA `role="tablist"` / `role="tab"` / `aria-selected` so
keyboard + screen-reader users get the same semantics as sighted
ones.
- CSS — new `.shell-demo__tabs` + `.shell-demo__tab` with an accent
underline on the active tab. Tabs scroll horizontally on narrow
viewports (mobile responsive).
- JS — refactored the existing IIFE from one `script` array to an
array of four. Added `setActiveTab()` for ARIA state, `play(idx)`
takes a tab index, end-of-script auto-advances to `(idx + 1) % N`
unless the user clicked away during the pause. Adds a new `cmd`
type alongside `you` for slash-command invocations (renders with
the same `>` prompt prefix). prefers-reduced-motion still bails
early and leaves the static seed visible.
- Static seed (the non-JS fallback) still shows tab 0's content, so
reduced-motion / no-JS visitors see the one-ticket flow as before.
Hero metrics also corrected to current reality (#77 / PR #161 covers
CLAUDE.md and CHANGELOG.md; this PR catches the same numbers in the
landing site):
Skills 32 → 39
Hooks 18 → 24
The tabs ship in v1.2.0 alongside the framework changes that make
the new flows worth showcasing.
Refs me2resh/apexyard#160 (release v1.2.0 + landing-site refresh).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#163): default the split-portfolio sibling repo name to <fork>-portfolio (#164)
Closes me2resh/apexyard#163.
The split-portfolio mode docs and skills previously suggested
`your-org/ops` as the default name for the private sibling repo, and
`portfolio/` as the local clone directory. Both too generic — adopters
running multiple ops setups end up with `your-org/ops` collisions, and
a bare `portfolio/` dir gives no signal about which framework it
belongs to when it sits next to other unrelated `portfolio/` dirs.
`<fork>-portfolio` is now the default — keeps the relationship to the
public fork explicit on disk and on GitHub. If the fork is named
`your-org/apexyard`, the portfolio defaults to
`your-org/apexyard-portfolio`. If the fork was renamed (e.g. `cos`),
the portfolio defaults to `cos-portfolio`. Adopters with custom names
keep working — the `portfolio:` config block resolves whatever path
they configured.
Files updated:
docs/multi-project.md
- Layout diagrams use `apexyard-portfolio/` as the sibling
- Setup walkthrough Step 2 + Step 3 use `your-org/apexyard-portfolio`
and explain the `<fork>-portfolio` pattern
- Config-block + symlink path examples updated to
`../apexyard-portfolio/...`
- Daily workflow + cross-machine clone commands updated
- The two existing `your-org/ops` references that remain are
fork-rename examples (lines 52, 64) — kept as-is, since renaming
the fork to `ops` is still valid (the portfolio would then
default to `ops-portfolio`)
.claude/skills/setup/SKILL.md
- Step 2b's "default suggestion" for the private repo name is now
`your-org/<fork>-portfolio`, computed dynamically from the
fork's repo name via `gh repo view --json name -q .name` so the
suggestion is correct even when the fork was renamed
- Clone command no longer needs a second arg — the repo name IS
the directory name
- Config-block paths updated to `../apexyard-portfolio/...`
.claude/skills/split-portfolio/SKILL.md
- Step 3's suggested-name template is now `<account>/<fork>-portfolio`
with the same dynamic-fork-name resolution
Mechanism unchanged. The `portfolio:` config block in
`.claude/project-config.json` still takes any path; this PR is purely
default-suggestion + example prose.
No tests required (skills are markdown instructions; no automated
coverage today).
Refs `apexyard.projects.yaml.example` uses "ops repo" as a generic
term meaning "the operational management fork" — not a name — kept
as-is.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#165): skills reference page on the landing site + changelog link (#167)
Closes me2resh/apexyard#165.
Adds a public, browseable index of every apexyard slash command
alongside a one-click changelog link from the homepage nav.
site/skills.html (new):
- Lists all 39 skills currently shipping in .claude/skills/
- Each entry: slash command, argument hint, description (taken
verbatim from the SKILL.md frontmatter so the page matches the
runtime exactly)
- 10 categories: Setup & onboarding, Daily ops, Tickets & ideas,
Specs & decisions, Code review & merge, Architecture & dev tools,
Production-readiness audits, Workflow primitives, Communications,
Deprecated
- Same brutalist-terminal design tokens as the homepage — JetBrains
Mono, paper-cream background, single warning-red accent, sharp
corners. Inlined CSS to keep the static-only no-build-step
convention; design vars duplicated rather than extracted to a
shared file (~18 vars; cheap to keep in sync).
- Mobile responsive — skill grid collapses to single-column under
720px; titlebar nav hides non-CTA items on narrow viewports.
- Reduced-motion friendly (no animation in the first place).
- Internal anchor TOC at the top so the page scans in seconds.
site/index.html (nav addition):
- Added two nav links to the titlebar between "what's in the box"
and the github CTA:
• skills → ./skills.html
• changelog → https://github.com/me2resh/apexyard/releases
- The changelog link points at the GitHub releases page (not the
raw CHANGELOG.md file) so it auto-resolves to the latest tagged
release on each visit. v1.2.0 lands and the link is already there.
No new dependencies, no build step, no JS for the skills page. The
existing site convention (one-html-file-per-route, inlined CSS,
optional progressive-enhancement JS) is preserved.
Refs me2resh/apexyard#160 (release v1.2.0 + landing-site refresh) —
this is the second site-side deliverable for that ticket; the release
tag itself follows once me2resh/apexyard#159 (testing) closes.
Follow-up worth a separate ticket: a small generator script that
walks .claude/skills/*/SKILL.md, parses YAML frontmatter, and emits
the skills.html sections automatically. Out of scope for v1.2.0 —
first version is hand-curated and will need maintenance until that
generator lands.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#168): accept release/vN.N.N branches + release(...) PR titles (#169)
Closes me2resh/apexyard#168.
The /release skill prescribed `release/vA.B.C` as the source-branch
name and `release: vA.B.C` as the PR title for the dev → main release
PR (per AgDR-0007). Both were rejected by the framework's own
validators:
validate-branch-name.sh required {type}/{TICKET-ID}-{description};
release/v1.2.0 has no ticket-id portion.
validate-pr-create.sh required type(SCOPE): form with `release` not
in pr.title_type_whitelist.
The contradiction surfaced cutting v1.2.0 — the first release under
the dev/main model.
Three small changes:
1. .claude/hooks/validate-branch-name.sh — added an early-out branch
that accepts ^release/vN.N.N(-rcN)?$ as a valid name. Narrow,
intentional exception for the framework's release-cut convention;
release branches don't carry a ticket-id because the release itself
IS the ticket.
2. .claude/project-config.defaults.json — added "release" to
pr.title_type_whitelist so a title like `release(#160): v1.2.0`
passes validate-pr-create.sh's existing regex unchanged.
3. .claude/skills/release/SKILL.md step 4 — corrected the prescribed
PR title to `release(#<release-ticket>): vA.B.C` so future /release
invocations produce a title that satisfies the validators by
construction.
Tested:
bash .claude/hooks/validate-branch-name.sh against:
release/v1.2.0 → 0 (allowed, release-special-case)
release/v1.2.0-rc1 → 0 (allowed, RC variant)
release/v9.9.9 → 0 (allowed)
release/foo → 2 (correctly blocked)
release/v1 → 2 (correctly blocked)
chore/GH-168-fix → 0 (allowed, standard pattern)
feature/GH-1-x → 0 (allowed, standard pattern)
Full hook test suite: 196/196 cases green across 12 test files.
Refs: surfaced 2026-05-04 cutting the first release under AgDR-0007.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#170): exempt release/vN.N.N from validate-pr-create's branch-id check (#171)
Closes me2resh/apexyard#170.
Completes the work started in #169 (closing #168). #169 added a
release-pattern early-out to validate-branch-name.sh so release/vN.N.N
branches pass the branch-name validator. But validate-pr-create.sh has
its own independent branch-id check at line 273 that #169 didn't
touch — and it still rejects release/v1.2.0 because that name doesn't
contain a ticket-id substring.
This is the same class of contradiction #168 fixed; the fix is the
same shape. Add the same release-pattern early-out to the branch-id
check in validate-pr-create.sh:
- if the branch matches ^release/vN.N.N(-rcN)?$ → exempt (release
branches don't carry ticket-ids; the release itself is the ticket)
- otherwise → require a ticket-id substring as before
#168's acceptance criterion 3 ("validate-pr-create.sh accepts a PR
title `release(#160): v1.2.0` against the `release/v1.2.0` branch")
was checked off based on the title regex alone but didn't catch the
secondary branch-id check living in the same file. Surfaced trying
to open the v1.2.0 release PR.
Tested:
bash .claude/hooks/validate-pr-create.sh against:
release/v1.2.0 → 0 (allowed, exempt)
release/v1.2.0-rc1 → 0 (allowed, RC variant)
chore/GH-1-fix → 0 (allowed, has ticket-id)
release/foo → 2 (correctly blocked)
chore/no-ticket → 2 (correctly blocked)
Refs me2resh/apexyard#168 (the parent bug) + #169 (the partial fix).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#173): sync CHANGELOG.md from main → dev (#174)
Closes me2resh/apexyard#173.
The release-cut model (AgDR-0007 / #116) squash-merges
release/vN.N.N → main, which means the v1.2.0 CHANGELOG section that
landed on main via PR #172 was never propagated back to dev. This
commit copies main's CHANGELOG.md verbatim onto dev so the v1.2.0
section is now present on both branches.
The diff is exactly the v1.2.0 entry being prepended; no other lines
change.
Without this sync, the next release PR cut from dev would build a
v1.3.0 section on top of v1.1.0, silently dropping v1.2.0 from dev's
running history. The corollary skill-level fix (option B in the
ticket) — updating /release to source the previous CHANGELOG from
upstream/main — is filed as a follow-up and out of scope for this PR.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#181): /agdr skill — searchable AgDR library (#186)
Adds /agdr — a portfolio-wide index for Agent Decision Records.
Walks apexyard.projects.yaml, reads each project's docs/agdr/*.md
(local clone if available, else gh api fallback), parses the optional
YAML frontmatter for category + projects, and answers four queries:
- /agdr browse list across the portfolio, grouped by category
- /agdr search <term> full-text grep across all bodies, returns
<project>/AgDR-NNNN paths + matching paragraph
- /agdr show <id> print a specific record, disambiguates duplicates
- /agdr stats counts per category (the marketing-slide tile,
now backed by real data)
Six-category taxonomy: architecture | tech-stack | security | patterns
| integrations | other. Legacy AgDRs without frontmatter remain
first-class — they bucket as `other` and are flagged in browse so
operators can migrate at their own pace.
Backwards-compatible template change: templates/agdr.md gains an
optional `category:` (and optional `projects:`) line in the existing
frontmatter block. Omitting the line keeps every existing AgDR valid;
the skill defaults to `other` when missing.
Doc note in workflows/sdlc.md § Phase 2 points at /agdr search for
"have we decided this before?" lookups before drafting a design.
Smoke test in .claude/hooks/tests/test_agdr_skill.sh covers the
parser the spec specifies — frontmatter extraction, category
bucketing (including the legacy default-to-other path), id reading,
stats aggregation, and search match counts. 18 assertions, all green;
12 pre-existing test suites also green (no regressions).
Closes #181
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(me2resh/apexyard#183): /launch-check trend tracking (#185)
- Persist each run as JSON under <projects_dir>/<name>/launch-check/runs/
(timestamp + branch + commit + per-dimension scores + verdict + top_risks)
- Append "Trend (last 5 runs)" section to the per-run summary when at least 2
prior runs exist — markdown table + ASCII score chart
- Add /launch-check trend mode for read-only trend rendering (no full audit)
- Auto-derive notes column from score-delta vs previous run
(e.g. "Security +12, Analytics +10")
- Opt-in commit via .launch-check-history-tracked marker; gitignored by default
- New helper script render-trend.sh + test_launch_check_trend.sh (21 cases)
- Resolve projects dir via portfolio_projects_dir helper (no hardcoded path)
- Schema is forward-compatible — extra fields preserved on framework upgrade
AgDR-0014 documents the chart format / schema / opt-in choices.
Closes me2resh/apexyard#183
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#182): /status --briefing + bin/apexyard status CLI shim (#187)
* feat(me2resh/apexyard#182): /status --briefing + bin/apexyard CLI shim
Make slide 6's `$ apexyard status` invocation real — the smallest of three
slide-reality tickets, demonstrating the marketing-to-implementation
pattern before #181 / #183 land.
- New helper at .claude/skills/status/briefing.sh — computes the 4-line
"where am I" briefing (active workspace, active ticket, branch,
role-set) so the logic is testable in isolation and runnable from a
plain shell. Workspace inference walks up from cwd to the ops-fork
root (same algorithm as _lib-portfolio-paths.sh and /start-ticket).
- Ticket reads from .claude/session/tickets/<workspace> first, falls
back to .claude/session/current-ticket. Role-set is inferred from
the active ticket's GitHub labels (v1: backend / frontend / qa /
security / platform / sre / data / ux / ui / product / tech-lead,
plus the long forms). No match emits the explicit "<none — inferred
per task>" placeholder so the four-line shape is constant.
- New CLI shim bin/apexyard delegates `apexyard status` to the same
helper after walking up to find the ops-fork root. Works from any
workspace/<name>/ clone or the fork itself; symlink onto PATH to
install (no shadowing of the `claude` binary).
- /status SKILL.md gains a "Briefing mode" section documenting the
--briefing / -b flag; default /status output is unchanged.
- docs/multi-project.md "Daily workflow" demonstrates the new
invocation alongside /inbox and /status.
- New smoke tests at .claude/hooks/tests/test_status_briefing.sh — 8
cases covering ops-root cwd, workspace cwd, unknown cwd, ops-fallback
marker, per-project marker priority, label-based role inference, the
no-matching-label path, and the constant-four-line shape.
Closes me2resh/apexyard#182
* fix(me2resh/apexyard#182): replace curios-dog with example-app in SKILL.md output sample (leak fix)
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(#178): LSP integration spike — measurement + recommendation (#184)
* docs(#178): LSP spike — token-savings measurement + integration findings
- Phase 1: estimated token cost A vs B for three representative queries on a real TS Lambda backend (~9,750 LOC); shallow semantic queries see ~3-23x input-token savings, multi-hop traces see ~1.4x
- Phase 2: verified Claude Code shipped first-party LSP support in v2.0.74 (Dec 2025), gated behind ENABLE_LSP_TOOL=1, wired in via the plugin system (.lsp.json); MCP-wrapped LSP shims (cclsp, lsp-mcp) remain as fallback
- Phase 3: recommends Option 3 — interactive clone-first prompt at end of /handover, preserving today's "never auto-clone" principle while making the deep-dive path discoverable
- Phase 4: AgDR Y-statement and option matrix sketched; full AgDR is a follow-up if the spike says go
- Recommendation: GO. Adopt the built-in tool, document the opt-in path, change /handover to offer clone-first interactively. No code changes beyond /handover SKILL.md; no novel integration to maintain
Closes me2resh/apexyard#178
* docs: fix markdownlint MD031/MD032 in spike report
Add blank lines around lists and a fenced code block to satisfy
markdownlint-cli2 in CI. Pure formatting, no content change.
Refs me2resh/apexyard#178
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(me2resh/apexyard#190): annotate LSP-aware skills with opt-in callouts (#191)
- Add identical-shape "LSP-aware (optional, recommended)" callout near
the top of each of the four code-aware SKILL.md files.
- Per-skill savings number is the only variation:
- /code-review: ~3-15× cheaper for semantic queries
- /threat-model: ~3-15× shallow, ~1.4-5× multi-hop traces
- /security-review: ~3-15× cheaper for semantic querie…
* chore(#109): project-configurable ticket / branch / commit / PR schema (#118)
* chore(#109): project-configurable ticket / branch / commit / PR schema
Lift the prefix / type whitelists hardcoded across skills, hooks, and
CI into a versioned JSON config read through a shared shell library.
Shipped defaults at .claude/project-config.defaults.json; per-fork
overrides at the optional .claude/project-config.json; one reader
(_lib-read-config.sh) that every consumer now uses.
Added:
- .claude/project-config.defaults.json (v1 schema)
- .claude/hooks/_lib-read-config.sh (shared reader)
- docs/project-config.md (schema reference + extension guide)
- docs/agdr/AgDR-0006-project-configurable-ticket-schema.md
Migrated (still pass with no config present via last-resort fallback):
- validate-branch-name.sh → .branch.type_whitelist
- validate-commit-format.sh → .commit.type_whitelist (legacy
`commit_types` top-level key honoured as backward-compat fallback)
- validate-pr-create.sh → .pr.title_type_whitelist
- /feature, /task, /bug skills reference the config in their Rules
sections; none hardcodes the list any more
Unlocks subsequent config-readers for #107 / #110 / #111 / #112 /
#113 / #114 / #115 — each extends the schema under its own subtree
without further changes to the loader.
https://github.com/me2resh/apexyard/issues/109
* fix(#109): satisfy markdownlint MD032 and MD060 on new docs
Auto-fix MD032 (blank lines around lists) in AgDR-0006 and format
table-separator rows with surrounding spaces (MD060) in both new
doc files. Content unchanged; CI green.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#110): add block-private-refs-in-public-repos.sh hook (#119)
Adds a new PreToolUse hook that blocks gh issue/PR/comment creation and
gh api .../issues|/pulls calls targeting a public framework repo
(default: me2resh/apexyard + whatever `upstream` resolves to) when the
title or body references any registered private project from
apexyard.projects.yaml (by name, repo slug, owner/repo#N ticket ref, or
workspace path).
The hook is a sibling to check-secrets.sh — both scan outgoing content
for identifiers that should never leave the local environment. Skip
marker `<!-- private-refs: allow -->` in the body lets a deliberate
reference through with a visible warning.
Files touched:
- .claude/hooks/block-private-refs-in-public-repos.sh (new)
- .claude/hooks/tests/test_block_private_refs.sh (new)
- .claude/rules/leak-protection.md (new)
- .claude/settings.json (wire PreToolUse matchers for the 5 gh shapes)
- docs/rule-audit.md (append section 10 + bump counts)
Refs: https://github.com/me2resh/apexyard/issues/110
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#115): add warn-stale-review-markers.sh PostToolUse hook (#120)
Fires after `git push` to surface review markers that have gone stale
because new commits were pushed past an existing Rex / CEO / design
approval. The merge gate already catches this at `gh pr merge` time,
but only then -- this hook closes the gap by flagging it immediately
at push-time so the author isn't surprised at merge.
- `.claude/hooks/warn-stale-review-markers.sh`
- PostToolUse, non-blocking (PostToolUse exit 2 would push noise
into the conversation; this hook is purely informational).
- Resolves the PR HEAD via `gh pr view --json headRefOid` -- same
source-of-truth as the merge-gate hooks post-apexyard#47 / #55.
Falls back to local HEAD with a visible WARN when gh is offline.
- Silent on: no PR for branch, no markers, fresh markers,
failed push (detected via `rejected` / `failed to push` /
`fatal:` / `error:` markers in tool_response.stderr).
- Modes: `warn` (default) prints one stderr line per stale marker;
`delete` opts in to auto-removal via
`.claude/project-config.json` -> `review_markers.on_stale`.
TODO(apexyard#109): switch to the shared project-config reader
once it lands.
- `.claude/settings.json`
- Wires the hook on PostToolUse / Bash / `git push *`.
- `docs/rule-audit.md`
- Adds a row under section 3 (Code review & PR quality) and
bumps the mechanized count 26 -> 27 / total 73 -> 74.
- `.claude/hooks/tests/test_warn_stale_review_markers.sh`
- 8 cases: no PR, no markers, fresh markers, stale rex / ceo /
design (warn), delete mode, failed push. All pass locally.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#111): upgrade pre-push-gate from advisory reminder to blocking check-runner (#121)
* chore(#111): upgrade pre-push-gate from reminder to blocking check-runner
Previously pre-push-gate.sh just printed a checklist of things to run
locally before pushing — it was advisory. The rule it enforces is a
HARD STOP per pr-workflow.md. That asymmetry meant agents routinely
pushed broken work and discovered it only when CI went red.
Replaces the reminder with a blocking runner that reads the list of
shell commands from project config (.pre_push.commands) and executes
them in sequence before a push is allowed through. First non-zero
exit blocks the push with exit 2 and prints the failing command plus
the last 20 lines of its output.
- Config key: .pre_push.commands[] — array of {name, run} objects.
Shipped default is an empty list (hook stays a no-op on repos that
haven't configured their checks yet, including the framework repo
itself until it wires its own CI).
- Emergency bypass: '<!-- pre-push: skip -->' in the HEAD commit
message. Grep-able on purpose so bypasses stay auditable.
- Fail-fast: once a command fails, the rest don't run. Parallel
execution is a follow-up polish.
- 7 test cases in .claude/hooks/tests/test_pre_push_gate.sh — all
pass on the shipped default + a minimal custom config.
Updates docs/rule-audit.md to flip "partial" → "yes" for the
"before git push" rule.
Integrates with the shared config reader landed in #109.
https://github.com/me2resh/apexyard/issues/111
* fix(#111): remove orphaned footnote reference from rule-audit
The previous advisory-mode footnote was superseded by pre-push-111
but its definition was accidentally kept, tripping markdownlint MD053
(unused reference definition). Drop it.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#107): add validate-issue-structure.sh PreToolUse hook (#122)
Mechanically enforces the ticket body schema when an agent files raw
`gh issue create` calls instead of going through the interactive
/feature, /task, /bug skills. Matches bracketed title prefix
([Feature] / [Chore] / [Bug] / [Docs] / etc.) against
`.ticket.required_sections` in project-config, and blocks (exit 2)
when any required section is missing or empty. Skip marker
`<!-- validate-issue-structure: skip -->` bypasses with a visible
stderr WARN for legitimate off-template tickets (epics, meta-threads).
Changes:
- .claude/hooks/validate-issue-structure.sh — the hook; reads schema
via the shared _lib-read-config.sh, with inlined defaults for bare
checkouts predating the config-schema rollout. Handles
--body / --body-file / -F path.
- .claude/project-config.defaults.json — extends .ticket with
required_sections (Feature/Chore/Refactor/Testing/CI/Docs/Bug) and
skip_marker; other .ticket fields untouched.
- .claude/settings.json — new PreToolUse matcher on Bash(gh issue
create *) alongside the existing suggest-ticket-template.sh and
block-private-refs-in-public-repos.sh hooks.
- .claude/hooks/tests/test_validate_issue_structure.sh — 15 cases
covering pass + fail paths per prefix, empty section detection,
skip marker, unknown prefix, non-gh invocation, --body-file path.
- docs/rule-audit.md — new section 11 row, mechanized count +1.
Upstream ticket: https://github.com/me2resh/apexyard/issues/107
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#112): add require-agdr-for-arch-pr.sh PreToolUse hook (#123)
Closes the asymmetry noted in .claude/rules/agdr-decisions.md: every
other HARD STOP in the ruleset (merge approval, ticket-first,
migration-first) is mechanically enforced, but the /decide HARD STOP
was prose-only. The commit-time hook require-agdr-for-arch-changes.sh
catches one architectural change at commit; this new PR-time hook
catches the cumulative diff so reviewers always have a pointer to the
decision record.
- New hook at .claude/hooks/require-agdr-for-arch-pr.sh
- Fires on Bash(gh pr create *)
- Parses --title/--body/--body-file/-F <path>
- Resolves base branch from --base, else upstream/dev, origin/dev,
upstream/main, origin/main, main, master (in that order)
- Computes `git diff <merge-base>..HEAD --name-only`
- Triggers on any changed file matching .agdr_trigger_paths[], OR any
dep-file addition (package.json via jq key-set diff; other
dep files via a commented +/- line-count heuristic — version
bumps match +/- counts and do not fire)
- Blocks (exit 2) with a helpful message naming the triggers and
pointing at /decide if the body has no `AgDR-\d+-[a-z0-9-]+`
reference
- Skip marker `<!-- agdr: not-applicable -->` bypasses with a
visible WARN on stderr
- Silent exit 0 on non-gh commands, empty diffs, unresolvable base
- Wired via .claude/settings.json PreToolUse Bash(gh pr create *)
- Adds two new top-level keys to .claude/project-config.defaults.json:
agdr_trigger_paths (shell globs — domain/, infrastructure/,
migrations/, *.tf, .github/workflows/, etc.)
agdr_trigger_dep_files (literal basenames — package.json,
pyproject.toml, Cargo.toml, go.mod, Gemfile)
Hook has inline fallback defaults kept in sync.
- Adds docs/rule-audit.md entry in the AgDR section; bumps mechanized
count 26 to 27 and total rows 73 to 74.
- Adds .claude/hooks/tests/test_require_agdr_for_arch_pr.sh (7 cases;
all green): path-triggered without AgDR (block), with AgDR (pass),
dep-file added (block), version-only bump (no fire), skip marker
(pass + warn), non-matching diff (pass), non-gh command (no-op).
Closes https://github.com/me2resh/apexyard/issues/112
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#113): require Testing section in PR body (config-driven) (#124)
Extends validate-pr-create.sh with a required-sections check that
replaces the hardcoded Glossary-only grep. The list of required H2
headings is project-configurable via `.pr.required_sections[]`.
Shipped default is ["Testing", "Glossary"], matching the canonical PR
description shape in workflows/code-review.md.
- Each entry must appear as `## <Name>` (case-insensitive).
- Empty sections are tolerated at this layer (the issue-structure hook
#107 does stricter empty-content checks for issue bodies; for PR
bodies, empty sections are left to the reviewer's judgement).
- Skip marker `<!-- pr-sections: skip -->` bypasses with a visible
stderr WARN — for trivial PRs (lint-only fixes, version bumps)
where the full template is overkill.
- Reads from project config via the shared _lib-read-config.sh (#109).
Inline fallback matches shipped defaults so bare checkouts predating
#109 keep working.
- 8 test cases cover: all-sections pass, each missing section,
missing-both (both errors printed), skip marker, case-insensitive
headings, H3 rejection.
https://github.com/me2resh/apexyard/issues/113
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#114): enforce single Closes-keyword per PR body (#125)
* chore(#114): enforce single Closes-keyword per PR body
Caps distinct auto-closing references (close/closes/closed, fix/fixes/
fixed, resolve/resolves/resolved + N or owner/repo+N) at one per PR
body. Closes the loophole where the title validator limited the title
to one ticket but multiple Closes lines in the body would still auto-
close all of them on merge.
- Scans stripped of fenced code blocks so closing keywords inside a
code sample do not count.
- Distinct counting: the same number referenced twice (e.g. via Fixes
and Closes) counts as one.
- Cross-repo refs (owner/repo+N) count normally.
- Opt-in escape hatch: pr.allow_multiple_closes=true in
project-config disables the check for teams that deliberately batch
rollbacks or dependency bumps.
- Per-PR bypass: a multi-close-approved HTML comment in the body
prints a visible stderr WARN and lets that PR through. Grep-able
trace so bypasses are auditable.
- 10 test cases cover: one close passes, no-keyword passes, two
distinct block, three mixed block, same-number-twice passes, code-
fence-ignored, skip marker, cross-ref without keyword, opt-in
config, cross-repo close.
Reads configuration via the shared _lib-read-config.sh (apexyard+109).
https://github.com/me2resh/apexyard/issues/114
* fix(#114): strip inline backticks and tilde fences from close-count scan
Rex caught a self-reflexive bug in the initial commit: documentation
mentioning closing keywords inside inline backticks (say a PR body
that explains the new hook with examples) counted as real closes, and
a skip marker inside inline backticks silently bypassed the check.
Future PRs that document the feature would trip the same trap.
Fix the code-region stripper to cover:
- Triple-backtick fences (already handled)
- Tilde fences (new)
- Inline-backtick spans (new)
Also run the skip-marker check against the stripped body, so a marker
used purely as documentation no longer activates a real bypass.
Three new test cases pin the behaviour:
- closing keywords in inline backticks are ignored
- skip marker inside inline backticks does NOT bypass
- tilde fences also get stripped
13/13 tests pass.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#108): add /tickets-batch skill for bulk-file flow (#127)
The fast happy path for filing 5–20 structured tickets in one intent
without dropping to raw `gh issue create` (non-conformant) or running
`/feature` 20 times serially (~100 turns of interview).
- .claude/skills/tickets-batch/SKILL.md — new skill spec. Asks
shared-context questions (priority, epic, area-labels, repo) ONCE
for the whole batch, then runs a ≤3-question micro-interview per
ticket (type, one-line purpose, optional clarification when the
inference is low-confidence). Confirms the full batch as a table,
then files each via specific `gh issue create` calls (never a
bulk JSON dump — the validator runs per-issue). Output conforms
to `.ticket.required_sections` by construction. Caps at 20
tickets per invocation.
- CLAUDE.md — added a row for /tickets-batch in the Available
Skills table; bumped the count references from 33 to 34.
Refs https://github.com/me2resh/apexyard/issues/108
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#117): add /fan-out skill + parallel-work rule doc (#128)
- Add `.claude/skills/fan-out/SKILL.md` — spawns N parallel Agent calls
in a single assistant message, with per-task agent type, worktree
isolation, and foreground/background mode. Caps at 5 concurrent
agents. Refuses fan-out when tasks share file write targets or have
sequential dependencies. Includes pre-spawn active-ticket safety
check and worktree merge-back flow that pauses on conflict.
- Add `.claude/rules/parallel-work.md` — trigger heuristic for when an
agent should proactively offer fan-out (>= 2 file-independent,
context-independent, individually substantial work items). Pairs
with the skill: rule says when, skill says how.
- Update `CLAUDE.md` — bump rules count to 9, skills count to 34, add
`/fan-out` row to the skills table.
Refs https://github.com/me2resh/apexyard/issues/117
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#116): adopt release-cut branch model (dev/main + tags) — framework only (#126)
* chore(#116): adopt release-cut branch model (dev/main + tags)
Formalises the dev/main split that already exists informally — dev is
the daily-work branch where every PR lands, main is release-only,
tagged with semver on each merge. Framework-only: managed projects
under apexyard governance stay trunk-based.
Added:
- AgDR-0007 — decision record (options table covers full git flow vs
trunk-only vs gitflow-lite; chose gitflow-lite)
- /release skill — diff dev against main, propose semver bump from
conventional commits, generate CHANGELOG, open release PR, tag
after merge
- docs/release-process.md — prose runbook for cutting a release
(manual fallback for the skill)
- .git.protected_branches in project-config.defaults.json
(main/master/dev/develop)
Modified:
- block-main-push.sh — now blocks direct pushes/commits to all
configured protected branches (was: hardcoded main/master). Reads
.git.protected_branches via the shared config reader (apexyard#109).
- CLAUDE.md — new section under Git Conventions explaining the
dev/main model + the framework-only scope. Skill table entry for
/release. Skills count bumped to 34.
- docs/multi-project.md — note that upstream/main is release-only
and the dev/main split is framework-only.
Non-consequences (per AgDR-0007):
- No release/* or hotfix/* branches. Hotfixes are normal patches
cut quickly. Revisit if multi-version maintenance becomes a need.
- No automatic on-merge issue closing for dev PRs. The release PR's
body aggregates all Closes references for the batch and triggers
auto-close en masse when it merges to main. Manual close in the
meantime.
- CI workflows trigger on pull_request regardless of base, so
dev-targeting PRs already get the full check matrix — no
workflow file edits needed.
https://github.com/me2resh/apexyard/issues/116
* fix(#116): satisfy markdownlint MD032 + MD060 in 116 docs
Auto-fix added blank lines around lists in AgDR-0007 (MD032) and
spaced the table separators in AgDR-0007 + multi-project.md (MD060).
Content unchanged.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* fix(#106): CHANGELOG fallback in drift hook for squash-merged forks (#129)
* fix(#106): CHANGELOG fallback in drift hook for squash-merged forks
The v1.1.0 tag-reachability check (`git tag --merged main`) misfires on
forks that sync via GitHub's default squash-merge: the squash collapses
the upstream-tag commit into a synthetic SHA, the tag stops being
reachable, and the banner keeps firing forever.
Discovered live on the first real-world `/update` flow: ops fork
squash-merged the v1.1.0 sync PR, banner kept saying "v1.1.0 available"
even though the fork was content-caught-up.
This commit adds a CHANGELOG-content fallback that fires only when the
primary tag check fails. If the fork's main has a heading
`## [X.Y.Z]` matching the upstream tag's version, treat the release as
absorbed and stay silent. Tolerant grep (matches the apexyard CHANGELOG
format from v1.1.0 onward, with leading-`v` stripping for tag→heading
conversion).
The merge-commit and rebase paths are unchanged — primary tag check
still works for them, and the fallback never fires when it shouldn't.
Test coverage (5 cases):
- squash-merge fork caught up to v1.1.0 → silent (the fix)
- merge-commit fork caught up to v1.1.0 → silent (regression check)
- fork stopped at v1.0.0 → banner fires
- fork has its own newer tag → silent
- squash-merge but no CHANGELOG on fork → banner fires (no false silence)
Records the strategy update in docs/agdr/AgDR-0008-…md (extends
AgDR-0005's tag-based-drift design).
https://github.com/me2resh/apexyard/issues/106
* fix(#106): satisfy markdownlint MD032/MD060 + shellcheck SC2164
Rex flagged two CI-blocking issues on the original 106 commit:
- AgDR-0008 had three bulleted sub-lists in the Consequences section
without surrounding blank lines (MD032). Added blanks and padded the
one tight-pipe table separator (MD060).
- The 106 test fixture had five subshell `cd "$fk"` calls without
`|| exit 1` (SC2164). Added the guard to all five.
5/5 tests still pass after the fix. No semantic change to the hook
or the test logic.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#130): add /validate-idea skill — lightweight pre-spec gate (#131)
A 10-minute, 5-question check that sits between /idea and /write-spec.
Designed for solo founders running ApexYard — not a heavyweight
methodology like event storming or Wardley mapping.
Five questions, asked one at a time:
1. Who is this specifically for?
2. What do they do today instead?
3. What's the smallest version that proves the value?
4. What would prove this is wrong? (kill criteria)
5. Build, buy, or rent?
Output: a one-page validation doc with a GREEN/YELLOW/RED verdict.
RED auto-updates the IDEA-NNN backlog row to WONTDO.
Integration:
/idea — adds an optional default-no "Validate now?" step after
capture (and after the optional GitHub Issue offer).
/handover — adds a conditional "this looks dormant, validate?"
step at the end of the integration plan, gated on the dormancy
heuristic (last commit > 90d AND zero open PRs AND no recent
issue activity). Healthy projects don't see the prompt.
CLAUDE.md skills count bumped to 35; new skills row added.
https://github.com/me2resh/apexyard/issues/130
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat: configurable voice prompts on assistant pause (AgDR-0009-voice-prompts-on-pause) (#135)
Stop hook that speaks the assistant's question aloud (Jarvis-style)
when it pauses for user input. Initial phase is macOS-only via `say`,
no voice input — user replies via keyboard.
Default OFF. Adopters opt in by overriding `voice_prompts.enabled` to
true in `.claude/project-config.json`.
Files:
- .claude/hooks/voice-prompt-on-pause.sh — Stop hook with config gate,
trigger heuristic (questions-only by default), markdown stripping,
sentence-boundary truncation, fire-and-forget say invocation
- .claude/hooks/tests/test_voice_prompt_on_pause.sh — 9 cases covering
disabled-default, enabled+question, enabled+statement, approved-pattern,
abc-menu, malformed-transcript, no-say-on-PATH, trigger-always,
markdown-stripping
- .claude/project-config.defaults.json — voice_prompts schema block
added (enabled, voice, max_chars, rate_wpm, trigger), default OFF
- .claude/settings.json — new Stop hook entry wired with the standard
ops-root resolver wrapper
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — design rationale,
options matrix (status quo / macOS say / cloud TTS / ML detection),
consequences, future phases
- docs/project-config.md — new "Voice prompts" section with override
examples and privacy notes
Test mode: hook respects VOICE_PROMPTS_SYNC=1 to run say synchronously
(test runners need this so assertions don't race against orphaned
background processes). Production invocations always run async.
Future phases (out of scope here, AgDR §"Future phases"):
- Phase 2: cross-platform TTS (Linux espeak, Windows SpeechSynthesizer)
- Phase 3: cloud TTS providers (OpenAI, ElevenLabs) — privacy AgDR-worthy
- Phase 4: voice input via Whisper-based STT
- Phase 5: per-message overrides
Refs: https://github.com/me2resh/apexyard/issues/134
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#141): add /debug skill — structured hypothesis-driven debugging (#142)
* feat(#141): add /debug skill — structured hypothesis-driven debugging
Adds a methodology skill that enforces five disciplines:
1. Capture the symptom precisely (exact URL, exact response, exact step)
2. Read the architecture before guessing (map every layer the request
touches, file by file)
3. Form a hypothesis ladder (3–5 candidates, each with an explicit
evidence test that confirms or refutes it)
4. Gather evidence first, fix second
5. Verify the fix against the original symptom evidence (re-run the
same `curl` / browser repro you used in step 4 — unit tests
verify code, not feature, correctness)
Stack appendices (Web, Desktop) carry stack-specific surface-evidence
requirements (step 1), architecture-surface maps (step 2), and
evidence-tests cookbooks (step 4). The methodology body stays portable
across stacks; appendices are where stack-specific knowledge accrues
over time.
Web appendix covers browser routing, framework configs (Next/Nuxt/Vite),
SPA-fallback layers, CDN, origin, the shared API client, backend
handlers, and auth providers. Desktop appendix covers Electron / Tauri
/ native-shell concerns: app entry points, IPC bridges, native modules,
auto-updater, sandbox / entitlements, code signing, crash reports.
Includes "When NOT to use" guidance so the methodology overhead doesn't
sandbag simple bugs (typos, off-by-ones, greenfield exploration).
Motivated by a real OAuth debug session in a managed project where
three sequential fixes chased adjacent symptoms because each was
hypothesis-then-fix without evidence in between. The skill is the
"never do that again" guardrail.
Closes #141
* fix(#141): scrub private project issue numbers from anti-pattern table
Rex review on PR #142 caught that line 155 of the skill's anti-pattern
table still named the originating PRs (#375, #377, #380) from the
private project where the methodology was first exercised. The PR body
and commit message were correctly abstracted earlier, but this in-file
reference slipped through — the leak-protection hook only scans gh
issue/pr writes, not staged file content, so mechanical enforcement
didn't catch it.
Replaced with "Three sequential PRs chasing the same symptom because
each was based on a different guess (no evidence test in between
cycles)" — same pedagogical value, zero attribution.
Refs #141
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(#143): document split-portfolio mode + add /setup privacy gate (#144)
Adopters on GitHub Free with any private project hit a silent privacy bug
following today's docs: forking apexyard makes a public fork that you cannot
later flip to private (GitHub policy), and committing `apexyard.projects.yaml`
+ `projects/<name>/` to the fork publishes private project names + handover
findings on a public GitHub repo.
This PR documents the supported workaround — split-portfolio mode — and
adds an upfront privacy gate to the /setup skill so new adopters never
hit the trip-wire silently.
docs/multi-project.md:
- New "Two setup modes — pick the one that matches your privacy needs"
section before TL;DR, with a side-by-side table and the explicit
trip-wire callout.
- Existing TL;DR retitled "TL;DR — single-fork mode (default)" with a
one-line pointer to the split-portfolio section.
- New "Split-portfolio mode — public framework + private portfolio"
section between the existing setup steps and the directory-layout
section. Includes:
- The two-repo layout (~/ops/apexyard public + ~/ops/portfolio private)
- 7-step setup walkthrough with copy-pasteable commands
- Daily workflow + upstream sync notes (both unchanged)
- Trade-offs (two repos to maintain, two clones per machine, one
upstream-sync conflict path on `projects/README.md`)
- "Migrating from single-fork to split-portfolio" recovery flow with
the explicit warning that GitHub Issue / PR edit history survives a
force-push and must be redacted separately
.claude/skills/setup/SKILL.md:
- New Step 2a: privacy gate — asks "are any projects private?" before
proposing the config. Branches on the answer:
- All public → single-fork mode
- GitHub Pro / Team / Enterprise → single-fork mode (private
forks of public repos
are supported on those
plans)
- Any private + GitHub Free → split-portfolio mode
- New Step 2b: walks through the split-portfolio setup interactively
(private repo create, sibling clone, gitignore + symlink) when the
privacy gate triggers.
- Detection: `test -L apexyard.projects.yaml` short-circuits Step 2b
for adopters already in split mode.
- Explicit "do NOT auto-migrate" rule for adopters already in single-fork
mode with private names already pushed — that path is destructive
(force-push history rewrite + redact issue/PR bodies + delete backup
branch) and warrants a deliberate, eyes-open run, not a /setup side
effect.
Out of scope for this PR (tracked separately on #143):
- `portfolio:` config block in `onboarding.yaml` schema
- Skill audit + refactor to honour configured `registry` / `projects_dir`
/ `ideas_backlog` paths instead of hardcoded fork-relative paths
- `/split-portfolio` migration helper skill that automates the recovery
flow currently documented manually
This is the docs-and-setup-question minimum-viable starter — the
framework code refactor is mechanical and lands as a follow-up.
Refs #143
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#145): portfolio config + self-healing + /split-portfolio helper (#147)
* feat(me2resh/apexyard#145): portfolio config block + self-healing + /split-portfolio helper
Closes the framework primitive deferred from me2resh/apexyard#144. Adds
first-class config-driven path resolution for the portfolio registry,
projects dir, and ideas backlog, with self-healing surfacing of broken
config at session start, plus a new /split-portfolio skill that automates
the destructive recovery flow.
Schema, helper, and hook:
- .claude/project-config.defaults.json: new portfolio: block
(registry, projects_dir, ideas_backlog) with defaults matching
today's single-fork layout
- .claude/hooks/_lib-portfolio-paths.sh: new sourceable helper exposing
portfolio_registry, portfolio_projects_dir, portfolio_ideas_backlog,
portfolio_validate, portfolio_clear_cache. Resolves relative paths
against the ops-fork root.
- .claude/hooks/check-portfolio-config.sh: new SessionStart hook —
silent on OK, one-line banner on broken config, never blocks session
- .claude/hooks/tests/test_portfolio_paths.sh: 13 cases covering
defaults, absolute/relative overrides, validate states, cache clear
Skill audit (18 SKILL.md files):
- Adds Path resolution callout pointing at the helper
- handover bash blocks now source helper and use $(portfolio_registry)
instead of literal apexyard.projects.yaml
- setup Step 2b now writes the portfolio: config block (recommended)
and validates via portfolio_validate before declaring success;
symlink approach kept as legacy fallback
New skill (.claude/skills/split-portfolio/SKILL.md):
- 10-step migration with explicit operator-confirmation gates at each
destructive step (force-push, body redaction, branch deletion)
- --verify mode: read-only state report (mode, paths, validate, drift)
- --dry-run mode: prints commands without executing
- Pre-flight refusals: already-private fork, paid GitHub plan, dirty
working tree, already-migrated state
- Step 9 writes the portfolio: config block (not symlinks) — symlink
fallback documented for adopters on older framework versions
- Step 9 surfaces the GitHub timeline-API survival caveat verbatim
- Idempotent re-runs: detects partial-migration state and resumes
Docs (docs/multi-project.md):
- Layout section describes both modes (config-block recommended,
symlink legacy) with self-healing notes
- Setup steps split into config-block mode and legacy symlink mode
- Migration section now points at /split-portfolio skill; manual
recipe preserved as fallback
AgDR (docs/agdr/AgDR-0010-portfolio-config-and-self-healing.md):
- Full Y-statement, options, decision, consequences, future phases
- Schema decision rationale: project-config.json over onboarding.yaml
because runtime path resolution belongs in project-config
Closes me2resh/apexyard#145
Refs me2resh/apexyard#146 (delivered same PR; closed manually post-merge
per the single-Closes-keyword rule in validate-pr-create.sh)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(me2resh/apexyard#145): markdownlint MD031 — blank lines around fences
CI's markdownlint-cli2 (v0.34.0) flagged the JSON + bash fenced code
blocks I added in setup/SKILL.md Step 2b without surrounding blank
lines. Added the required blanks. No content change.
Refs me2resh/apexyard#147
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>
* docs(me2resh/apexyard#148): correct privacy-gate wording — adopter action, not framework auto-publish (#149)
The privacy-gate wording introduced in PR #144 (and unchanged in PR #147)
attributed the publication to the framework rather than the adopter:
"the standard fork-and-commit setup will silently publish your private
project names on a public GitHub repo"
That's factually wrong. ApexYard never pushes anything without explicit
operator approval — the publication only happens when the adopter
themselves runs git push. The "silently publish" framing read as if the
framework auto-publishes, which is misleading and undermines trust in
the rest of the framework's safety claims.
Two prose-only edits, no code, no behavior change:
- .claude/skills/setup/SKILL.md Step 2a — replaced "will silently
publish ..." with adopter-action language ("you might accidentally
publish ... a stray git push after registering them — I won't push
without your approval, but the risk is on the adopter once the data
is committed locally")
- docs/multi-project.md trip-wire callout — replaced "silently publish
their portfolio names the moment they push" with "risk accidentally
publishing their portfolio names with a stray push (the framework
itself never pushes without operator approval, but once the registry
is committed locally the next push exposes it)"
Verified: `grep -r "silently publish" .claude/skills/ docs/` returns no
hits.
Closes me2resh/apexyard#148
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#150): bootstrap-skill exemption + Bash-write coverage (#152)
Closes the legitimate-bypass case (me2resh/apexyard#150) and the
illegitimate-bypass case (me2resh/apexyard#151) together so the
ticket-first gate is coherent. Shipping either alone would leave a
window where the framework is internally inconsistent — see AgDR-0011
for the full rationale.
Bootstrap exemption (me2resh/apexyard#150):
- .claude/session/active-bootstrap marker, written by /setup,
/handover, /update, /split-portfolio on entry; cleared on exit
- SessionStart sweep (clear-bootstrap-marker.sh) for stale markers
from interrupted sessions
- require-active-ticket.sh reads the marker and exempts skills on
the configured ticket.bootstrap_skills list
- bootstrap_skills list lives in .claude/project-config.defaults.json
(extendable per fork via .claude/project-config.json)
Bash-write coverage (me2resh/apexyard#151):
- new _lib-detect-bash-write.sh — heuristic detector for output
redirection, tee, sed -i, awk -i inplace, python/node/ruby
embedded interpreters
- require-active-ticket.sh + require-migration-ticket.sh now fire
on Bash in addition to Edit|Write|MultiEdit
- design choice: false-negatives preferred over false-positives
(the matcher errs toward "let through" rather than block legit
read-only commands)
Tests: 32 unit cases on the lib + 12 integration cases on the hook,
including the exact me2resh/apexyard#151 bypass repro from the issue
body.
Closes me2resh/apexyard#150
Will manually close me2resh/apexyard#151 post-merge per the
single-Closes-per-PR rule (precedent: AgDR-0010 / PR #147).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#153): extend Bash-write matcher beyond first-version coverage (#155)
Closes me2resh/apexyard#153.
Extends `_lib-detect-bash-write.sh` (introduced for me2resh/apexyard#151
in PR #152) with the matcher families flagged by Rex's review of #152.
AgDR-0011 already frames the matcher as a living list extended on
observation; this commit just walks the list.
New matcher families:
- File-moving builtins: `cp`, `mv`, `rm`, `dd`, `install` (anchored at
command-start; `--help`/`--version` and `git rm`/`git mv` excluded)
- Archive / network writes: `tar -x` / `tar --extract`, `curl -o` /
`--output`, `wget -O` / `--output-document`
- Additional embedded interpreters: `perl -e`, `php -r` (keyword-gated
like python/node/ruby); `go run`, `deno run`/`deno script.ts`,
`bun run`/`bun script.ts` (categorical script runners)
- Python helpers: `pathlib.Path().touch()`, `shutil.copy*`,
`shutil.move`, `os.rename` added to the `python -c` and python
heredoc keyword list
- Heredoc variants for `ruby` and `node` (previously only python
heredoc was covered)
Extractor extensions:
- `cp` / `mv`: last positional arg
- `curl -o` / `--output`: file argument
- `wget -O` / `--output-document`: file argument
- `tar -x`, `go run`, `deno`, `bun`, `perl -e`, `php -r`: return empty
(caller applies gate categorically per AgDR-0011)
Test count rose from 32 to 86. Negative-class counterexamples cover
the trickiest false-positive surfaces: `tar -t` listing, `cp --help`,
`rm --version`, `git rm`, `curl` bare URL fetch, `wget` bare URL fetch,
`deno fmt`, `deno test`, `go build`. Existing
`test_require_active_ticket_bash.sh` regression suite still passes 12/12.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(#154): mock gh in test sandboxes to remove live-tracker dependency (#156)
- add _lib-mock-gh.sh helper that installs a fake `gh` on the sandbox PATH;
intercepts `gh issue view <N> ... --json ...` and returns synthetic
`{"number":N,"state":"OPEN"}` (overridable per-num via mock_gh_set_state)
- wire the shim into test_single_closes_per_pr.sh and
test_validate_pr_required_sections.sh so the validator's CLOSED-issue
refusal no longer breaks the suite when upstream issues are closed
- both files previously failed every case (0/13 and 0/8) because their PR
titles reference #114 / #113, which are now CLOSED upstream
- post-fix: 13/13 and 8/8; full suite remains green
Closes me2resh/apexyard#154
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#132): structured CEO marker + same-turn merge in /approve-merge (#158)
* feat(#132): structured CEO marker + same-turn merge in /approve-merge
Closes me2resh/apexyard#132 (drop the "stop before merge" rule) and
me2resh/apexyard#48 (harden CEO marker against self-approval bypass)
together. The two threads compose — see AgDR-0012 for the full
rationale.
Streamline (#132):
- /approve-merge now runs `gh pr merge --squash --delete-branch`
in the same turn as the marker write, by default
- --no-merge opt-out preserves the deferred-merge case
- The discrete approval moment is the SKILL INVOCATION, not a
follow-up "now do the merge" message
Harden (#48):
- CEO marker is now a structured key/value file with required fields:
sha=<HEAD>
approved_by=user
skill_version=2
Validated by block-unreviewed-merge.sh; bare-SHA legacy markers
rejected with a clear "stale format" error pointing at /approve-merge
- The model's bare `echo SHA > <pr>-ceo.approved` bypass is now
mechanically rejected. Forging the structured fields requires a
deliberate, visible rule violation rather than a one-line accident
- Optional audit fields (approved_at, approval_summary) capture the
"what did the user say when they approved" trail
- Rex marker stays bare-SHA — different threat model (automated
reviewer, not human authorization moment)
pr-workflow.md reframed: "the load-bearing rule is explicit per-PR
approval, not two user messages." The merge is a deterministic
consequence of the approval invocation.
Tests: 12 cases on the hardened hook covering the new format end-to-end
(valid v2, missing rex/ceo, bare-SHA legacy rejected, missing
approved_by, wrong approved_by, skill_version=1, sha mismatch,
non-merge no-op, gh-api shape gated). Full suite: 205/205 across 13
test files.
Will manually close me2resh/apexyard#48 post-merge per the
single-Closes-per-PR rule (precedent: PR #152 / AgDR-0011).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#132): redact private project reference from AgDR-0012
Abstracted two references to a registered private project that named
the project's owner/repo. The leak-protection hook caught one in the
PR body; this fixup removes the matching references from the
AgDR-0012 file content (which would otherwise have shipped the names
to me2resh/apexyard public repo via the merge).
The pre-existing reference at .claude/rules/pr-workflow.md:130
(documenting #47) is untouched — it predates this PR and is already
on the public repo's history.
Refs me2resh/apexyard#132.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#132): markdownlint blanks-around-fences + typo fix
Two small fixups against red CI / Rex feedback:
- AgDR-0012 line 63: fenced code block now has a blank line before it
(MD031). markdownlint-cli2 0.13.0 was rejecting the indented fence
inside the bullet because the fence's preceding line was the bullet
text (no blank).
- approve-merge SKILL.md line 172: typo `deferes` → `defers` (Rex flag
on PR #158).
Refs me2resh/apexyard#132.
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>
* chore(#157): remove voice-prompts feature + correct hook/skill counts (#161)
* chore(#157): remove voice-prompts-on-pause feature + correct hook/skill counts
Closes me2resh/apexyard#157 (sunset the voice-prompts feature) and
me2resh/apexyard#77 (hook count off-by-one in CHANGELOG / CLAUDE.md)
in one bundled PR. AgDR-0013 supersedes AgDR-0009 with the full
rationale; both AgDRs are preserved (decision records are append-only
history).
Removed (#157):
- .claude/hooks/voice-prompt-on-pause.sh
- .claude/hooks/tests/test_voice_prompt_on_pause.sh
- Stop matcher block in .claude/settings.json (became empty after
voice removal)
- voice_prompts block in .claude/project-config.defaults.json
- "## Voice prompts" section in docs/project-config.md
- voice_prompts mention in AgDR-0010 line 32 (replaced with
leak_protection / ticket as still-current example config blocks)
Preserved:
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — historical record;
new "Superseded by: AgDR-0013" header at the top
- AgDR-0010 line 115 reference to AgDR-0009 — still accurate as a
historical pattern reference
Counts corrected (#77):
- CHANGELOG.md v0.3.0 stats: "17 hooks" → "18 hooks" (historical
fix — at v0.3.0 there were actually 18 hooks)
- CLAUDE.md table line: "18 shell scripts" → "24 shell scripts"
(current count after this removal)
- CLAUDE.md table line: "35 slash commands" → "39 slash commands"
- CLAUDE.md "Available skills (34)" → "Available skills (39)"
- CLAUDE.md quick-reference "Skills (35 slash commands)" →
"(39 slash commands)"
Why bundled: #77's correct count depends on whether voice is still in
the framework. Shipping #77 before #157 would write a number that's
wrong by one again the moment #157 lands. Same shape as previous
bundles (PR #152 / AgDR-0011, PR #158 / AgDR-0012).
Why no adopter-facing changelog mention of the voice removal: the
feature never reached a tagged release on main. v1.1.0 didn't have
it; v1.2.0 won't have it. From the adopter's perspective there's
nothing to retire. AgDR-0013 captures the framework's internal record
for future contributors. See AgDR-0013 § "No adopter-facing changelog
mention".
Tests: full hook test suite green (196 cases across 12 files —
test_voice_prompt_on_pause.sh removed). No regressions.
Will manually close me2resh/apexyard#77 post-merge per the
single-Closes-per-PR rule (precedent: PRs #152, #158).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#157): unwire voice from settings + configs + docs + AgDR-0013
Continuation of d09d7b5 (the file deletions). Squash-merge will
collapse both into one PR commit. This commit captures:
- .claude/settings.json — Stop matcher block removed (was the only
hook in it; entire matcher gone)
- .claude/project-config.defaults.json — voice_prompts block + its
_comment removed
- docs/project-config.md — "## Voice prompts" section removed
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — "Superseded by"
header added at the top, content otherwise preserved as history
- docs/agdr/AgDR-0010-portfolio-config-and-self-healing.md — line 32
example reference swapped from voice_prompts to
leak_protection / ticket (still-current config blocks)
- docs/agdr/AgDR-0013-sunset-voice-prompts.md — new supersession AgDR
- CLAUDE.md — hook count 18 → 24, skill count 35 → 39 (three
occurrences each, all aligned to current reality)
- CHANGELOG.md v0.3.0 stats — "17 hooks" → "18 hooks" historical fix
(#77 acceptance criterion 1)
Refs me2resh/apexyard#157 + me2resh/apexyard#77.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#157): markdownlint MD028 + resolve stale AgDR-numbering ref
Three small fixups against Rex CHANGES-REQUESTED on PR #161:
- AgDR-0009 line 3: promote "Superseded by:" header out of a
blockquote. The original "I decided ..." canonical blockquote at
line 5 was being merged with the new supersession blockquote
(markdownlint MD028 — "no blanks inside blockquote").
- AgDR-0013 line 3: same shape — "Supersedes:" header now a plain
bold paragraph, canonical "I decided ..." blockquote untouched.
- approve-merge SKILL.md line ~187: stale conditional reference
"AgDR-0012 (or 0013 — depends on whether voice-removal lands
first)" — order is now resolved (12 = approve-merge bundle, 13 =
voice removal). Drop the parenthetical.
Refs me2resh/apexyard#157 + me2resh/apexyard#77.
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(#160): multi-tab terminal demo on the landing site (#162)
Landing-site `site/index.html` terminal demo previously played one
canonical flow ("one ticket, start to finish") on autoplay. With the
v1.2.0 skill surface expansion (4 new skills, 8+ new hooks), one flow
no longer represents the framework's breadth.
Adds tabs to the terminal chrome — four flows visitors can either let
auto-cycle or click directly:
1. one ticket — existing flow, unchanged content
2. /handover — adopt an external repo into the portfolio
3. /setup — first-run framework bootstrap on a fresh fork
4. /fan-out — spawn 3 parallel agents on independent tickets
Auto-advance: on completion of the active tab's script, the demo
pauses ~1.8s then advances to the next tab. Loops at the end. User
can interrupt by clicking any tab or hitting Replay.
Implementation:
- HTML chrome — single title span replaced with a tablist of 4 button
tabs. ARIA `role="tablist"` / `role="tab"` / `aria-selected` so
keyboard + screen-reader users get the same semantics as sighted
ones.
- CSS — new `.shell-demo__tabs` + `.shell-demo__tab` with an accent
underline on the active tab. Tabs scroll horizontally on narrow
viewports (mobile responsive).
- JS — refactored the existing IIFE from one `script` array to an
array of four. Added `setActiveTab()` for ARIA state, `play(idx)`
takes a tab index, end-of-script auto-advances to `(idx + 1) % N`
unless the user clicked away during the pause. Adds a new `cmd`
type alongside `you` for slash-command invocations (renders with
the same `>` prompt prefix). prefers-reduced-motion still bails
early and leaves the static seed visible.
- Static seed (the non-JS fallback) still shows tab 0's content, so
reduced-motion / no-JS visitors see the one-ticket flow as before.
Hero metrics also corrected to current reality (#77 / PR #161 covers
CLAUDE.md and CHANGELOG.md; this PR catches the same numbers in the
landing site):
Skills 32 → 39
Hooks 18 → 24
The tabs ship in v1.2.0 alongside the framework changes that make
the new flows worth showcasing.
Refs me2resh/apexyard#160 (release v1.2.0 + landing-site refresh).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#163): default the split-portfolio sibling repo name to <fork>-portfolio (#164)
Closes me2resh/apexyard#163.
The split-portfolio mode docs and skills previously suggested
`your-org/ops` as the default name for the private sibling repo, and
`portfolio/` as the local clone directory. Both too generic — adopters
running multiple ops setups end up with `your-org/ops` collisions, and
a bare `portfolio/` dir gives no signal about which framework it
belongs to when it sits next to other unrelated `portfolio/` dirs.
`<fork>-portfolio` is now the default — keeps the relationship to the
public fork explicit on disk and on GitHub. If the fork is named
`your-org/apexyard`, the portfolio defaults to
`your-org/apexyard-portfolio`. If the fork was renamed (e.g. `cos`),
the portfolio defaults to `cos-portfolio`. Adopters with custom names
keep working — the `portfolio:` config block resolves whatever path
they configured.
Files updated:
docs/multi-project.md
- Layout diagrams use `apexyard-portfolio/` as the sibling
- Setup walkthrough Step 2 + Step 3 use `your-org/apexyard-portfolio`
and explain the `<fork>-portfolio` pattern
- Config-block + symlink path examples updated to
`../apexyard-portfolio/...`
- Daily workflow + cross-machine clone commands updated
- The two existing `your-org/ops` references that remain are
fork-rename examples (lines 52, 64) — kept as-is, since renaming
the fork to `ops` is still valid (the portfolio would then
default to `ops-portfolio`)
.claude/skills/setup/SKILL.md
- Step 2b's "default suggestion" for the private repo name is now
`your-org/<fork>-portfolio`, computed dynamically from the
fork's repo name via `gh repo view --json name -q .name` so the
suggestion is correct even when the fork was renamed
- Clone command no longer needs a second arg — the repo name IS
the directory name
- Config-block paths updated to `../apexyard-portfolio/...`
.claude/skills/split-portfolio/SKILL.md
- Step 3's suggested-name template is now `<account>/<fork>-portfolio`
with the same dynamic-fork-name resolution
Mechanism unchanged. The `portfolio:` config block in
`.claude/project-config.json` still takes any path; this PR is purely
default-suggestion + example prose.
No tests required (skills are markdown instructions; no automated
coverage today).
Refs `apexyard.projects.yaml.example` uses "ops repo" as a generic
term meaning "the operational management fork" — not a name — kept
as-is.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#165): skills reference page on the landing site + changelog link (#167)
Closes me2resh/apexyard#165.
Adds a public, browseable index of every apexyard slash command
alongside a one-click changelog link from the homepage nav.
site/skills.html (new):
- Lists all 39 skills currently shipping in .claude/skills/
- Each entry: slash command, argument hint, description (taken
verbatim from the SKILL.md frontmatter so the page matches the
runtime exactly)
- 10 categories: Setup & onboarding, Daily ops, Tickets & ideas,
Specs & decisions, Code review & merge, Architecture & dev tools,
Production-readiness audits, Workflow primitives, Communications,
Deprecated
- Same brutalist-terminal design tokens as the homepage — JetBrains
Mono, paper-cream background, single warning-red accent, sharp
corners. Inlined CSS to keep the static-only no-build-step
convention; design vars duplicated rather than extracted to a
shared file (~18 vars; cheap to keep in sync).
- Mobile responsive — skill grid collapses to single-column under
720px; titlebar nav hides non-CTA items on narrow viewports.
- Reduced-motion friendly (no animation in the first place).
- Internal anchor TOC at the top so the page scans in seconds.
site/index.html (nav addition):
- Added two nav links to the titlebar between "what's in the box"
and the github CTA:
• skills → ./skills.html
• changelog → https://github.com/me2resh/apexyard/releases
- The changelog link points at the GitHub releases page (not the
raw CHANGELOG.md file) so it auto-resolves to the latest tagged
release on each visit. v1.2.0 lands and the link is already there.
No new dependencies, no build step, no JS for the skills page. The
existing site convention (one-html-file-per-route, inlined CSS,
optional progressive-enhancement JS) is preserved.
Refs me2resh/apexyard#160 (release v1.2.0 + landing-site refresh) —
this is the second site-side deliverable for that ticket; the release
tag itself follows once me2resh/apexyard#159 (testing) closes.
Follow-up worth a separate ticket: a small generator script that
walks .claude/skills/*/SKILL.md, parses YAML frontmatter, and emits
the skills.html sections automatically. Out of scope for v1.2.0 —
first version is hand-curated and will need maintenance until that
generator lands.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#168): accept release/vN.N.N branches + release(...) PR titles (#169)
Closes me2resh/apexyard#168.
The /release skill prescribed `release/vA.B.C` as the source-branch
name and `release: vA.B.C` as the PR title for the dev → main release
PR (per AgDR-0007). Both were rejected by the framework's own
validators:
validate-branch-name.sh required {type}/{TICKET-ID}-{description};
release/v1.2.0 has no ticket-id portion.
validate-pr-create.sh required type(SCOPE): form with `release` not
in pr.title_type_whitelist.
The contradiction surfaced cutting v1.2.0 — the first release under
the dev/main model.
Three small changes:
1. .claude/hooks/validate-branch-name.sh — added an early-out branch
that accepts ^release/vN.N.N(-rcN)?$ as a valid name. Narrow,
intentional exception for the framework's release-cut convention;
release branches don't carry a ticket-id because the release itself
IS the ticket.
2. .claude/project-config.defaults.json — added "release" to
pr.title_type_whitelist so a title like `release(#160): v1.2.0`
passes validate-pr-create.sh's existing regex unchanged.
3. .claude/skills/release/SKILL.md step 4 — corrected the prescribed
PR title to `release(#<release-ticket>): vA.B.C` so future /release
invocations produce a title that satisfies the validators by
construction.
Tested:
bash .claude/hooks/validate-branch-name.sh against:
release/v1.2.0 → 0 (allowed, release-special-case)
release/v1.2.0-rc1 → 0 (allowed, RC variant)
release/v9.9.9 → 0 (allowed)
release/foo → 2 (correctly blocked)
release/v1 → 2 (correctly blocked)
chore/GH-168-fix → 0 (allowed, standard pattern)
feature/GH-1-x → 0 (allowed, standard pattern)
Full hook test suite: 196/196 cases green across 12 test files.
Refs: surfaced 2026-05-04 cutting the first release under AgDR-0007.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#170): exempt release/vN.N.N from validate-pr-create's branch-id check (#171)
Closes me2resh/apexyard#170.
Completes the work started in #169 (closing #168). #169 added a
release-pattern early-out to validate-branch-name.sh so release/vN.N.N
branches pass the branch-name validator. But validate-pr-create.sh has
its own independent branch-id check at line 273 that #169 didn't
touch — and it still rejects release/v1.2.0 because that name doesn't
contain a ticket-id substring.
This is the same class of contradiction #168 fixed; the fix is the
same shape. Add the same release-pattern early-out to the branch-id
check in validate-pr-create.sh:
- if the branch matches ^release/vN.N.N(-rcN)?$ → exempt (release
branches don't carry ticket-ids; the release itself is the ticket)
- otherwise → require a ticket-id substring as before
#168's acceptance criterion 3 ("validate-pr-create.sh accepts a PR
title `release(#160): v1.2.0` against the `release/v1.2.0` branch")
was checked off based on the title regex alone but didn't catch the
secondary branch-id check living in the same file. Surfaced trying
to open the v1.2.0 release PR.
Tested:
bash .claude/hooks/validate-pr-create.sh against:
release/v1.2.0 → 0 (allowed, exempt)
release/v1.2.0-rc1 → 0 (allowed, RC variant)
chore/GH-1-fix → 0 (allowed, has ticket-id)
release/foo → 2 (correctly blocked)
chore/no-ticket → 2 (correctly blocked)
Refs me2resh/apexyard#168 (the parent bug) + #169 (the partial fix).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#173): sync CHANGELOG.md from main → dev (#174)
Closes me2resh/apexyard#173.
The release-cut model (AgDR-0007 / #116) squash-merges
release/vN.N.N → main, which means the v1.2.0 CHANGELOG section that
landed on main via PR #172 was never propagated back to dev. This
commit copies main's CHANGELOG.md verbatim onto dev so the v1.2.0
section is now present on both branches.
The diff is exactly the v1.2.0 entry being prepended; no other lines
change.
Without this sync, the next release PR cut from dev would build a
v1.3.0 section on top of v1.1.0, silently dropping v1.2.0 from dev's
running history. The corollary skill-level fix (option B in the
ticket) — updating /release to source the previous CHANGELOG from
upstream/main — is filed as a follow-up and out of scope for this PR.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#181): /agdr skill — searchable AgDR library (#186)
Adds /agdr — a portfolio-wide index for Agent Decision Records.
Walks apexyard.projects.yaml, reads each project's docs/agdr/*.md
(local clone if available, else gh api fallback), parses the optional
YAML frontmatter for category + projects, and answers four queries:
- /agdr browse list across the portfolio, grouped by category
- /agdr search <term> full-text grep across all bodies, returns
<project>/AgDR-NNNN paths + matching paragraph
- /agdr show <id> print a specific record, disambiguates duplicates
- /agdr stats counts per category (the marketing-slide tile,
now backed by real data)
Six-category taxonomy: architecture | tech-stack | security | patterns
| integrations | other. Legacy AgDRs without frontmatter remain
first-class — they bucket as `other` and are flagged in browse so
operators can migrate at their own pace.
Backwards-compatible template change: templates/agdr.md gains an
optional `category:` (and optional `projects:`) line in the existing
frontmatter block. Omitting the line keeps every existing AgDR valid;
the skill defaults to `other` when missing.
Doc note in workflows/sdlc.md § Phase 2 points at /agdr search for
"have we decided this before?" lookups before drafting a design.
Smoke test in .claude/hooks/tests/test_agdr_skill.sh covers the
parser the spec specifies — frontmatter extraction, category
bucketing (including the legacy default-to-other path), id reading,
stats aggregation, and search match counts. 18 assertions, all green;
12 pre-existing test suites also green (no regressions).
Closes #181
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(me2resh/apexyard#183): /launch-check trend tracking (#185)
- Persist each run as JSON under <projects_dir>/<name>/launch-check/runs/
(timestamp + branch + commit + per-dimension scores + verdict + top_risks)
- Append "Trend (last 5 runs)" section to the per-run summary when at least 2
prior runs exist — markdown table + ASCII score chart
- Add /launch-check trend mode for read-only trend rendering (no full audit)
- Auto-derive notes column from score-delta vs previous run
(e.g. "Security +12, Analytics +10")
- Opt-in commit via .launch-check-history-tracked marker; gitignored by default
- New helper script render-trend.sh + test_launch_check_trend.sh (21 cases)
- Resolve projects dir via portfolio_projects_dir helper (no hardcoded path)
- Schema is forward-compatible — extra fields preserved on framework upgrade
AgDR-0014 documents the chart format / schema / opt-in choices.
Closes me2resh/apexyard#183
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#182): /status --briefing + bin/apexyard status CLI shim (#187)
* feat(me2resh/apexyard#182): /status --briefing + bin/apexyard CLI shim
Make slide 6's `$ apexyard status` invocation real — the smallest of three
slide-reality tickets, demonstrating the marketing-to-implementation
pattern before #181 / #183 land.
- New helper at .claude/skills/status/briefing.sh — computes the 4-line
"where am I" briefing (active workspace, active ticket, branch,
role-set) so the logic is testable in isolation and runnable from a
plain shell. Workspace inference walks up from cwd to the ops-fork
root (same algorithm as _lib-portfolio-paths.sh and /start-ticket).
- Ticket reads from .claude/session/tickets/<workspace> first, falls
back to .claude/session/current-ticket. Role-set is inferred from
the active ticket's GitHub labels (v1: backend / frontend / qa /
security / platform / sre / data / ux / ui / product / tech-lead,
plus the long forms). No match emits the explicit "<none — inferred
per task>" placeholder so the four-line shape is constant.
- New CLI shim bin/apexyard delegates `apexyard status` to the same
helper after walking up to find the ops-fork root. Works from any
workspace/<name>/ clone or the fork itself; symlink onto PATH to
install (no shadowing of the `claude` binary).
- /status SKILL.md gains a "Briefing mode" section documenting the
--briefing / -b flag; default /status output is unchanged.
- docs/multi-project.md "Daily workflow" demonstrates the new
invocation alongside /inbox and /status.
- New smoke tests at .claude/hooks/tests/test_status_briefing.sh — 8
cases covering ops-root cwd, workspace cwd, unknown cwd, ops-fallback
marker, per-project marker priority, label-based role inference, the
no-matching-label path, and the constant-four-line shape.
Closes me2resh/apexyard#182
* fix(me2resh/apexyard#182): replace curios-dog with example-app in SKILL.md output sample (leak fix)
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(#178): LSP integration spike — measurement + recommendation (#184)
* docs(#178): LSP spike — token-savings measurement + integration findings
- Phase 1: estimated token cost A vs B for three representative queries on a real TS Lambda backend (~9,750 LOC); shallow semantic queries see ~3-23x input-token savings, multi-hop traces see ~1.4x
- Phase 2: verified Claude Code shipped first-party LSP support in v2.0.74 (Dec 2025), gated behind ENABLE_LSP_TOOL=1, wired in via the plugin system (.lsp.json); MCP-wrapped LSP shims (cclsp, lsp-mcp) remain as fallback
- Phase 3: recommends Option 3 — interactive clone-first prompt at end of /handover, preserving today's "never auto-clone" principle while making the deep-dive path discoverable
- Phase 4: AgDR Y-statement and option matrix sketched; full AgDR is a follow-up if the spike says go
- Recommendation: GO. Adopt the built-in tool, document the opt-in path, change /handover to offer clone-first interactively. No code changes beyond /handover SKILL.md; no novel integration to maintain
Closes me2resh/apexyard#178
* docs: fix markdownlint MD031/MD032 in spike report
Add blank lines around lists and a fenced code block to satisfy
markdownlint-cli2 in CI. Pure formatting, no content change.
Refs me2resh/apexyard#178
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(me2resh/apexyard#190): annotate LSP-aware skills with opt-in callouts (#191)
- Add identical-shape "LSP-aware (optional, recommended)" callout near
the top of each of the four code-aware SKILL.md files.
- Per-skill savings number is the only variation:
- /code-review: ~3-15× cheaper for semantic queries
- /threat-model: ~3-15× shallow, ~1.4-5× multi-hop traces
- /security-review: ~3-15× cheaper for semantic querie…
* chore(#109): project-configurable ticket / branch / commit / PR schema (#118)
* chore(#109): project-configurable ticket / branch / commit / PR schema
Lift the prefix / type whitelists hardcoded across skills, hooks, and
CI into a versioned JSON config read through a shared shell library.
Shipped defaults at .claude/project-config.defaults.json; per-fork
overrides at the optional .claude/project-config.json; one reader
(_lib-read-config.sh) that every consumer now uses.
Added:
- .claude/project-config.defaults.json (v1 schema)
- .claude/hooks/_lib-read-config.sh (shared reader)
- docs/project-config.md (schema reference + extension guide)
- docs/agdr/AgDR-0006-project-configurable-ticket-schema.md
Migrated (still pass with no config present via last-resort fallback):
- validate-branch-name.sh → .branch.type_whitelist
- validate-commit-format.sh → .commit.type_whitelist (legacy
`commit_types` top-level key honoured as backward-compat fallback)
- validate-pr-create.sh → .pr.title_type_whitelist
- /feature, /task, /bug skills reference the config in their Rules
sections; none hardcodes the list any more
Unlocks subsequent config-readers for #107 / #110 / #111 / #112 /
#113 / #114 / #115 — each extends the schema under its own subtree
without further changes to the loader.
https://github.com/me2resh/apexyard/issues/109
* fix(#109): satisfy markdownlint MD032 and MD060 on new docs
Auto-fix MD032 (blank lines around lists) in AgDR-0006 and format
table-separator rows with surrounding spaces (MD060) in both new
doc files. Content unchanged; CI green.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#110): add block-private-refs-in-public-repos.sh hook (#119)
Adds a new PreToolUse hook that blocks gh issue/PR/comment creation and
gh api .../issues|/pulls calls targeting a public framework repo
(default: me2resh/apexyard + whatever `upstream` resolves to) when the
title or body references any registered private project from
apexyard.projects.yaml (by name, repo slug, owner/repo#N ticket ref, or
workspace path).
The hook is a sibling to check-secrets.sh — both scan outgoing content
for identifiers that should never leave the local environment. Skip
marker `<!-- private-refs: allow -->` in the body lets a deliberate
reference through with a visible warning.
Files touched:
- .claude/hooks/block-private-refs-in-public-repos.sh (new)
- .claude/hooks/tests/test_block_private_refs.sh (new)
- .claude/rules/leak-protection.md (new)
- .claude/settings.json (wire PreToolUse matchers for the 5 gh shapes)
- docs/rule-audit.md (append section 10 + bump counts)
Refs: https://github.com/me2resh/apexyard/issues/110
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#115): add warn-stale-review-markers.sh PostToolUse hook (#120)
Fires after `git push` to surface review markers that have gone stale
because new commits were pushed past an existing Rex / CEO / design
approval. The merge gate already catches this at `gh pr merge` time,
but only then -- this hook closes the gap by flagging it immediately
at push-time so the author isn't surprised at merge.
- `.claude/hooks/warn-stale-review-markers.sh`
- PostToolUse, non-blocking (PostToolUse exit 2 would push noise
into the conversation; this hook is purely informational).
- Resolves the PR HEAD via `gh pr view --json headRefOid` -- same
source-of-truth as the merge-gate hooks post-apexyard#47 / #55.
Falls back to local HEAD with a visible WARN when gh is offline.
- Silent on: no PR for branch, no markers, fresh markers,
failed push (detected via `rejected` / `failed to push` /
`fatal:` / `error:` markers in tool_response.stderr).
- Modes: `warn` (default) prints one stderr line per stale marker;
`delete` opts in to auto-removal via
`.claude/project-config.json` -> `review_markers.on_stale`.
TODO(apexyard#109): switch to the shared project-config reader
once it lands.
- `.claude/settings.json`
- Wires the hook on PostToolUse / Bash / `git push *`.
- `docs/rule-audit.md`
- Adds a row under section 3 (Code review & PR quality) and
bumps the mechanized count 26 -> 27 / total 73 -> 74.
- `.claude/hooks/tests/test_warn_stale_review_markers.sh`
- 8 cases: no PR, no markers, fresh markers, stale rex / ceo /
design (warn), delete mode, failed push. All pass locally.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#111): upgrade pre-push-gate from advisory reminder to blocking check-runner (#121)
* chore(#111): upgrade pre-push-gate from reminder to blocking check-runner
Previously pre-push-gate.sh just printed a checklist of things to run
locally before pushing — it was advisory. The rule it enforces is a
HARD STOP per pr-workflow.md. That asymmetry meant agents routinely
pushed broken work and discovered it only when CI went red.
Replaces the reminder with a blocking runner that reads the list of
shell commands from project config (.pre_push.commands) and executes
them in sequence before a push is allowed through. First non-zero
exit blocks the push with exit 2 and prints the failing command plus
the last 20 lines of its output.
- Config key: .pre_push.commands[] — array of {name, run} objects.
Shipped default is an empty list (hook stays a no-op on repos that
haven't configured their checks yet, including the framework repo
itself until it wires its own CI).
- Emergency bypass: '<!-- pre-push: skip -->' in the HEAD commit
message. Grep-able on purpose so bypasses stay auditable.
- Fail-fast: once a command fails, the rest don't run. Parallel
execution is a follow-up polish.
- 7 test cases in .claude/hooks/tests/test_pre_push_gate.sh — all
pass on the shipped default + a minimal custom config.
Updates docs/rule-audit.md to flip "partial" → "yes" for the
"before git push" rule.
Integrates with the shared config reader landed in #109.
https://github.com/me2resh/apexyard/issues/111
* fix(#111): remove orphaned footnote reference from rule-audit
The previous advisory-mode footnote was superseded by pre-push-111
but its definition was accidentally kept, tripping markdownlint MD053
(unused reference definition). Drop it.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#107): add validate-issue-structure.sh PreToolUse hook (#122)
Mechanically enforces the ticket body schema when an agent files raw
`gh issue create` calls instead of going through the interactive
/feature, /task, /bug skills. Matches bracketed title prefix
([Feature] / [Chore] / [Bug] / [Docs] / etc.) against
`.ticket.required_sections` in project-config, and blocks (exit 2)
when any required section is missing or empty. Skip marker
`<!-- validate-issue-structure: skip -->` bypasses with a visible
stderr WARN for legitimate off-template tickets (epics, meta-threads).
Changes:
- .claude/hooks/validate-issue-structure.sh — the hook; reads schema
via the shared _lib-read-config.sh, with inlined defaults for bare
checkouts predating the config-schema rollout. Handles
--body / --body-file / -F path.
- .claude/project-config.defaults.json — extends .ticket with
required_sections (Feature/Chore/Refactor/Testing/CI/Docs/Bug) and
skip_marker; other .ticket fields untouched.
- .claude/settings.json — new PreToolUse matcher on Bash(gh issue
create *) alongside the existing suggest-ticket-template.sh and
block-private-refs-in-public-repos.sh hooks.
- .claude/hooks/tests/test_validate_issue_structure.sh — 15 cases
covering pass + fail paths per prefix, empty section detection,
skip marker, unknown prefix, non-gh invocation, --body-file path.
- docs/rule-audit.md — new section 11 row, mechanized count +1.
Upstream ticket: https://github.com/me2resh/apexyard/issues/107
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#112): add require-agdr-for-arch-pr.sh PreToolUse hook (#123)
Closes the asymmetry noted in .claude/rules/agdr-decisions.md: every
other HARD STOP in the ruleset (merge approval, ticket-first,
migration-first) is mechanically enforced, but the /decide HARD STOP
was prose-only. The commit-time hook require-agdr-for-arch-changes.sh
catches one architectural change at commit; this new PR-time hook
catches the cumulative diff so reviewers always have a pointer to the
decision record.
- New hook at .claude/hooks/require-agdr-for-arch-pr.sh
- Fires on Bash(gh pr create *)
- Parses --title/--body/--body-file/-F <path>
- Resolves base branch from --base, else upstream/dev, origin/dev,
upstream/main, origin/main, main, master (in that order)
- Computes `git diff <merge-base>..HEAD --name-only`
- Triggers on any changed file matching .agdr_trigger_paths[], OR any
dep-file addition (package.json via jq key-set diff; other
dep files via a commented +/- line-count heuristic — version
bumps match +/- counts and do not fire)
- Blocks (exit 2) with a helpful message naming the triggers and
pointing at /decide if the body has no `AgDR-\d+-[a-z0-9-]+`
reference
- Skip marker `<!-- agdr: not-applicable -->` bypasses with a
visible WARN on stderr
- Silent exit 0 on non-gh commands, empty diffs, unresolvable base
- Wired via .claude/settings.json PreToolUse Bash(gh pr create *)
- Adds two new top-level keys to .claude/project-config.defaults.json:
agdr_trigger_paths (shell globs — domain/, infrastructure/,
migrations/, *.tf, .github/workflows/, etc.)
agdr_trigger_dep_files (literal basenames — package.json,
pyproject.toml, Cargo.toml, go.mod, Gemfile)
Hook has inline fallback defaults kept in sync.
- Adds docs/rule-audit.md entry in the AgDR section; bumps mechanized
count 26 to 27 and total rows 73 to 74.
- Adds .claude/hooks/tests/test_require_agdr_for_arch_pr.sh (7 cases;
all green): path-triggered without AgDR (block), with AgDR (pass),
dep-file added (block), version-only bump (no fire), skip marker
(pass + warn), non-matching diff (pass), non-gh command (no-op).
Closes https://github.com/me2resh/apexyard/issues/112
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#113): require Testing section in PR body (config-driven) (#124)
Extends validate-pr-create.sh with a required-sections check that
replaces the hardcoded Glossary-only grep. The list of required H2
headings is project-configurable via `.pr.required_sections[]`.
Shipped default is ["Testing", "Glossary"], matching the canonical PR
description shape in workflows/code-review.md.
- Each entry must appear as `## <Name>` (case-insensitive).
- Empty sections are tolerated at this layer (the issue-structure hook
#107 does stricter empty-content checks for issue bodies; for PR
bodies, empty sections are left to the reviewer's judgement).
- Skip marker `<!-- pr-sections: skip -->` bypasses with a visible
stderr WARN — for trivial PRs (lint-only fixes, version bumps)
where the full template is overkill.
- Reads from project config via the shared _lib-read-config.sh (#109).
Inline fallback matches shipped defaults so bare checkouts predating
#109 keep working.
- 8 test cases cover: all-sections pass, each missing section,
missing-both (both errors printed), skip marker, case-insensitive
headings, H3 rejection.
https://github.com/me2resh/apexyard/issues/113
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#114): enforce single Closes-keyword per PR body (#125)
* chore(#114): enforce single Closes-keyword per PR body
Caps distinct auto-closing references (close/closes/closed, fix/fixes/
fixed, resolve/resolves/resolved + N or owner/repo+N) at one per PR
body. Closes the loophole where the title validator limited the title
to one ticket but multiple Closes lines in the body would still auto-
close all of them on merge.
- Scans stripped of fenced code blocks so closing keywords inside a
code sample do not count.
- Distinct counting: the same number referenced twice (e.g. via Fixes
and Closes) counts as one.
- Cross-repo refs (owner/repo+N) count normally.
- Opt-in escape hatch: pr.allow_multiple_closes=true in
project-config disables the check for teams that deliberately batch
rollbacks or dependency bumps.
- Per-PR bypass: a multi-close-approved HTML comment in the body
prints a visible stderr WARN and lets that PR through. Grep-able
trace so bypasses are auditable.
- 10 test cases cover: one close passes, no-keyword passes, two
distinct block, three mixed block, same-number-twice passes, code-
fence-ignored, skip marker, cross-ref without keyword, opt-in
config, cross-repo close.
Reads configuration via the shared _lib-read-config.sh (apexyard+109).
https://github.com/me2resh/apexyard/issues/114
* fix(#114): strip inline backticks and tilde fences from close-count scan
Rex caught a self-reflexive bug in the initial commit: documentation
mentioning closing keywords inside inline backticks (say a PR body
that explains the new hook with examples) counted as real closes, and
a skip marker inside inline backticks silently bypassed the check.
Future PRs that document the feature would trip the same trap.
Fix the code-region stripper to cover:
- Triple-backtick fences (already handled)
- Tilde fences (new)
- Inline-backtick spans (new)
Also run the skip-marker check against the stripped body, so a marker
used purely as documentation no longer activates a real bypass.
Three new test cases pin the behaviour:
- closing keywords in inline backticks are ignored
- skip marker inside inline backticks does NOT bypass
- tilde fences also get stripped
13/13 tests pass.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#108): add /tickets-batch skill for bulk-file flow (#127)
The fast happy path for filing 5–20 structured tickets in one intent
without dropping to raw `gh issue create` (non-conformant) or running
`/feature` 20 times serially (~100 turns of interview).
- .claude/skills/tickets-batch/SKILL.md — new skill spec. Asks
shared-context questions (priority, epic, area-labels, repo) ONCE
for the whole batch, then runs a ≤3-question micro-interview per
ticket (type, one-line purpose, optional clarification when the
inference is low-confidence). Confirms the full batch as a table,
then files each via specific `gh issue create` calls (never a
bulk JSON dump — the validator runs per-issue). Output conforms
to `.ticket.required_sections` by construction. Caps at 20
tickets per invocation.
- CLAUDE.md — added a row for /tickets-batch in the Available
Skills table; bumped the count references from 33 to 34.
Refs https://github.com/me2resh/apexyard/issues/108
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#117): add /fan-out skill + parallel-work rule doc (#128)
- Add `.claude/skills/fan-out/SKILL.md` — spawns N parallel Agent calls
in a single assistant message, with per-task agent type, worktree
isolation, and foreground/background mode. Caps at 5 concurrent
agents. Refuses fan-out when tasks share file write targets or have
sequential dependencies. Includes pre-spawn active-ticket safety
check and worktree merge-back flow that pauses on conflict.
- Add `.claude/rules/parallel-work.md` — trigger heuristic for when an
agent should proactively offer fan-out (>= 2 file-independent,
context-independent, individually substantial work items). Pairs
with the skill: rule says when, skill says how.
- Update `CLAUDE.md` — bump rules count to 9, skills count to 34, add
`/fan-out` row to the skills table.
Refs https://github.com/me2resh/apexyard/issues/117
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#116): adopt release-cut branch model (dev/main + tags) — framework only (#126)
* chore(#116): adopt release-cut branch model (dev/main + tags)
Formalises the dev/main split that already exists informally — dev is
the daily-work branch where every PR lands, main is release-only,
tagged with semver on each merge. Framework-only: managed projects
under apexyard governance stay trunk-based.
Added:
- AgDR-0007 — decision record (options table covers full git flow vs
trunk-only vs gitflow-lite; chose gitflow-lite)
- /release skill — diff dev against main, propose semver bump from
conventional commits, generate CHANGELOG, open release PR, tag
after merge
- docs/release-process.md — prose runbook for cutting a release
(manual fallback for the skill)
- .git.protected_branches in project-config.defaults.json
(main/master/dev/develop)
Modified:
- block-main-push.sh — now blocks direct pushes/commits to all
configured protected branches (was: hardcoded main/master). Reads
.git.protected_branches via the shared config reader (apexyard#109).
- CLAUDE.md — new section under Git Conventions explaining the
dev/main model + the framework-only scope. Skill table entry for
/release. Skills count bumped to 34.
- docs/multi-project.md — note that upstream/main is release-only
and the dev/main split is framework-only.
Non-consequences (per AgDR-0007):
- No release/* or hotfix/* branches. Hotfixes are normal patches
cut quickly. Revisit if multi-version maintenance becomes a need.
- No automatic on-merge issue closing for dev PRs. The release PR's
body aggregates all Closes references for the batch and triggers
auto-close en masse when it merges to main. Manual close in the
meantime.
- CI workflows trigger on pull_request regardless of base, so
dev-targeting PRs already get the full check matrix — no
workflow file edits needed.
https://github.com/me2resh/apexyard/issues/116
* fix(#116): satisfy markdownlint MD032 + MD060 in 116 docs
Auto-fix added blank lines around lists in AgDR-0007 (MD032) and
spaced the table separators in AgDR-0007 + multi-project.md (MD060).
Content unchanged.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* fix(#106): CHANGELOG fallback in drift hook for squash-merged forks (#129)
* fix(#106): CHANGELOG fallback in drift hook for squash-merged forks
The v1.1.0 tag-reachability check (`git tag --merged main`) misfires on
forks that sync via GitHub's default squash-merge: the squash collapses
the upstream-tag commit into a synthetic SHA, the tag stops being
reachable, and the banner keeps firing forever.
Discovered live on the first real-world `/update` flow: ops fork
squash-merged the v1.1.0 sync PR, banner kept saying "v1.1.0 available"
even though the fork was content-caught-up.
This commit adds a CHANGELOG-content fallback that fires only when the
primary tag check fails. If the fork's main has a heading
`## [X.Y.Z]` matching the upstream tag's version, treat the release as
absorbed and stay silent. Tolerant grep (matches the apexyard CHANGELOG
format from v1.1.0 onward, with leading-`v` stripping for tag→heading
conversion).
The merge-commit and rebase paths are unchanged — primary tag check
still works for them, and the fallback never fires when it shouldn't.
Test coverage (5 cases):
- squash-merge fork caught up to v1.1.0 → silent (the fix)
- merge-commit fork caught up to v1.1.0 → silent (regression check)
- fork stopped at v1.0.0 → banner fires
- fork has its own newer tag → silent
- squash-merge but no CHANGELOG on fork → banner fires (no false silence)
Records the strategy update in docs/agdr/AgDR-0008-…md (extends
AgDR-0005's tag-based-drift design).
https://github.com/me2resh/apexyard/issues/106
* fix(#106): satisfy markdownlint MD032/MD060 + shellcheck SC2164
Rex flagged two CI-blocking issues on the original 106 commit:
- AgDR-0008 had three bulleted sub-lists in the Consequences section
without surrounding blank lines (MD032). Added blanks and padded the
one tight-pipe table separator (MD060).
- The 106 test fixture had five subshell `cd "$fk"` calls without
`|| exit 1` (SC2164). Added the guard to all five.
5/5 tests still pass after the fix. No semantic change to the hook
or the test logic.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#130): add /validate-idea skill — lightweight pre-spec gate (#131)
A 10-minute, 5-question check that sits between /idea and /write-spec.
Designed for solo founders running ApexYard — not a heavyweight
methodology like event storming or Wardley mapping.
Five questions, asked one at a time:
1. Who is this specifically for?
2. What do they do today instead?
3. What's the smallest version that proves the value?
4. What would prove this is wrong? (kill criteria)
5. Build, buy, or rent?
Output: a one-page validation doc with a GREEN/YELLOW/RED verdict.
RED auto-updates the IDEA-NNN backlog row to WONTDO.
Integration:
/idea — adds an optional default-no "Validate now?" step after
capture (and after the optional GitHub Issue offer).
/handover — adds a conditional "this looks dormant, validate?"
step at the end of the integration plan, gated on the dormancy
heuristic (last commit > 90d AND zero open PRs AND no recent
issue activity). Healthy projects don't see the prompt.
CLAUDE.md skills count bumped to 35; new skills row added.
https://github.com/me2resh/apexyard/issues/130
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat: configurable voice prompts on assistant pause (AgDR-0009-voice-prompts-on-pause) (#135)
Stop hook that speaks the assistant's question aloud (Jarvis-style)
when it pauses for user input. Initial phase is macOS-only via `say`,
no voice input — user replies via keyboard.
Default OFF. Adopters opt in by overriding `voice_prompts.enabled` to
true in `.claude/project-config.json`.
Files:
- .claude/hooks/voice-prompt-on-pause.sh — Stop hook with config gate,
trigger heuristic (questions-only by default), markdown stripping,
sentence-boundary truncation, fire-and-forget say invocation
- .claude/hooks/tests/test_voice_prompt_on_pause.sh — 9 cases covering
disabled-default, enabled+question, enabled+statement, approved-pattern,
abc-menu, malformed-transcript, no-say-on-PATH, trigger-always,
markdown-stripping
- .claude/project-config.defaults.json — voice_prompts schema block
added (enabled, voice, max_chars, rate_wpm, trigger), default OFF
- .claude/settings.json — new Stop hook entry wired with the standard
ops-root resolver wrapper
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — design rationale,
options matrix (status quo / macOS say / cloud TTS / ML detection),
consequences, future phases
- docs/project-config.md — new "Voice prompts" section with override
examples and privacy notes
Test mode: hook respects VOICE_PROMPTS_SYNC=1 to run say synchronously
(test runners need this so assertions don't race against orphaned
background processes). Production invocations always run async.
Future phases (out of scope here, AgDR §"Future phases"):
- Phase 2: cross-platform TTS (Linux espeak, Windows SpeechSynthesizer)
- Phase 3: cloud TTS providers (OpenAI, ElevenLabs) — privacy AgDR-worthy
- Phase 4: voice input via Whisper-based STT
- Phase 5: per-message overrides
Refs: https://github.com/me2resh/apexyard/issues/134
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#141): add /debug skill — structured hypothesis-driven debugging (#142)
* feat(#141): add /debug skill — structured hypothesis-driven debugging
Adds a methodology skill that enforces five disciplines:
1. Capture the symptom precisely (exact URL, exact response, exact step)
2. Read the architecture before guessing (map every layer the request
touches, file by file)
3. Form a hypothesis ladder (3–5 candidates, each with an explicit
evidence test that confirms or refutes it)
4. Gather evidence first, fix second
5. Verify the fix against the original symptom evidence (re-run the
same `curl` / browser repro you used in step 4 — unit tests
verify code, not feature, correctness)
Stack appendices (Web, Desktop) carry stack-specific surface-evidence
requirements (step 1), architecture-surface maps (step 2), and
evidence-tests cookbooks (step 4). The methodology body stays portable
across stacks; appendices are where stack-specific knowledge accrues
over time.
Web appendix covers browser routing, framework configs (Next/Nuxt/Vite),
SPA-fallback layers, CDN, origin, the shared API client, backend
handlers, and auth providers. Desktop appendix covers Electron / Tauri
/ native-shell concerns: app entry points, IPC bridges, native modules,
auto-updater, sandbox / entitlements, code signing, crash reports.
Includes "When NOT to use" guidance so the methodology overhead doesn't
sandbag simple bugs (typos, off-by-ones, greenfield exploration).
Motivated by a real OAuth debug session in a managed project where
three sequential fixes chased adjacent symptoms because each was
hypothesis-then-fix without evidence in between. The skill is the
"never do that again" guardrail.
Closes #141
* fix(#141): scrub private project issue numbers from anti-pattern table
Rex review on PR #142 caught that line 155 of the skill's anti-pattern
table still named the originating PRs (#375, #377, #380) from the
private project where the methodology was first exercised. The PR body
and commit message were correctly abstracted earlier, but this in-file
reference slipped through — the leak-protection hook only scans gh
issue/pr writes, not staged file content, so mechanical enforcement
didn't catch it.
Replaced with "Three sequential PRs chasing the same symptom because
each was based on a different guess (no evidence test in between
cycles)" — same pedagogical value, zero attribution.
Refs #141
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(#143): document split-portfolio mode + add /setup privacy gate (#144)
Adopters on GitHub Free with any private project hit a silent privacy bug
following today's docs: forking apexyard makes a public fork that you cannot
later flip to private (GitHub policy), and committing `apexyard.projects.yaml`
+ `projects/<name>/` to the fork publishes private project names + handover
findings on a public GitHub repo.
This PR documents the supported workaround — split-portfolio mode — and
adds an upfront privacy gate to the /setup skill so new adopters never
hit the trip-wire silently.
docs/multi-project.md:
- New "Two setup modes — pick the one that matches your privacy needs"
section before TL;DR, with a side-by-side table and the explicit
trip-wire callout.
- Existing TL;DR retitled "TL;DR — single-fork mode (default)" with a
one-line pointer to the split-portfolio section.
- New "Split-portfolio mode — public framework + private portfolio"
section between the existing setup steps and the directory-layout
section. Includes:
- The two-repo layout (~/ops/apexyard public + ~/ops/portfolio private)
- 7-step setup walkthrough with copy-pasteable commands
- Daily workflow + upstream sync notes (both unchanged)
- Trade-offs (two repos to maintain, two clones per machine, one
upstream-sync conflict path on `projects/README.md`)
- "Migrating from single-fork to split-portfolio" recovery flow with
the explicit warning that GitHub Issue / PR edit history survives a
force-push and must be redacted separately
.claude/skills/setup/SKILL.md:
- New Step 2a: privacy gate — asks "are any projects private?" before
proposing the config. Branches on the answer:
- All public → single-fork mode
- GitHub Pro / Team / Enterprise → single-fork mode (private
forks of public repos
are supported on those
plans)
- Any private + GitHub Free → split-portfolio mode
- New Step 2b: walks through the split-portfolio setup interactively
(private repo create, sibling clone, gitignore + symlink) when the
privacy gate triggers.
- Detection: `test -L apexyard.projects.yaml` short-circuits Step 2b
for adopters already in split mode.
- Explicit "do NOT auto-migrate" rule for adopters already in single-fork
mode with private names already pushed — that path is destructive
(force-push history rewrite + redact issue/PR bodies + delete backup
branch) and warrants a deliberate, eyes-open run, not a /setup side
effect.
Out of scope for this PR (tracked separately on #143):
- `portfolio:` config block in `onboarding.yaml` schema
- Skill audit + refactor to honour configured `registry` / `projects_dir`
/ `ideas_backlog` paths instead of hardcoded fork-relative paths
- `/split-portfolio` migration helper skill that automates the recovery
flow currently documented manually
This is the docs-and-setup-question minimum-viable starter — the
framework code refactor is mechanical and lands as a follow-up.
Refs #143
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#145): portfolio config + self-healing + /split-portfolio helper (#147)
* feat(me2resh/apexyard#145): portfolio config block + self-healing + /split-portfolio helper
Closes the framework primitive deferred from me2resh/apexyard#144. Adds
first-class config-driven path resolution for the portfolio registry,
projects dir, and ideas backlog, with self-healing surfacing of broken
config at session start, plus a new /split-portfolio skill that automates
the destructive recovery flow.
Schema, helper, and hook:
- .claude/project-config.defaults.json: new portfolio: block
(registry, projects_dir, ideas_backlog) with defaults matching
today's single-fork layout
- .claude/hooks/_lib-portfolio-paths.sh: new sourceable helper exposing
portfolio_registry, portfolio_projects_dir, portfolio_ideas_backlog,
portfolio_validate, portfolio_clear_cache. Resolves relative paths
against the ops-fork root.
- .claude/hooks/check-portfolio-config.sh: new SessionStart hook —
silent on OK, one-line banner on broken config, never blocks session
- .claude/hooks/tests/test_portfolio_paths.sh: 13 cases covering
defaults, absolute/relative overrides, validate states, cache clear
Skill audit (18 SKILL.md files):
- Adds Path resolution callout pointing at the helper
- handover bash blocks now source helper and use $(portfolio_registry)
instead of literal apexyard.projects.yaml
- setup Step 2b now writes the portfolio: config block (recommended)
and validates via portfolio_validate before declaring success;
symlink approach kept as legacy fallback
New skill (.claude/skills/split-portfolio/SKILL.md):
- 10-step migration with explicit operator-confirmation gates at each
destructive step (force-push, body redaction, branch deletion)
- --verify mode: read-only state report (mode, paths, validate, drift)
- --dry-run mode: prints commands without executing
- Pre-flight refusals: already-private fork, paid GitHub plan, dirty
working tree, already-migrated state
- Step 9 writes the portfolio: config block (not symlinks) — symlink
fallback documented for adopters on older framework versions
- Step 9 surfaces the GitHub timeline-API survival caveat verbatim
- Idempotent re-runs: detects partial-migration state and resumes
Docs (docs/multi-project.md):
- Layout section describes both modes (config-block recommended,
symlink legacy) with self-healing notes
- Setup steps split into config-block mode and legacy symlink mode
- Migration section now points at /split-portfolio skill; manual
recipe preserved as fallback
AgDR (docs/agdr/AgDR-0010-portfolio-config-and-self-healing.md):
- Full Y-statement, options, decision, consequences, future phases
- Schema decision rationale: project-config.json over onboarding.yaml
because runtime path resolution belongs in project-config
Closes me2resh/apexyard#145
Refs me2resh/apexyard#146 (delivered same PR; closed manually post-merge
per the single-Closes-keyword rule in validate-pr-create.sh)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(me2resh/apexyard#145): markdownlint MD031 — blank lines around fences
CI's markdownlint-cli2 (v0.34.0) flagged the JSON + bash fenced code
blocks I added in setup/SKILL.md Step 2b without surrounding blank
lines. Added the required blanks. No content change.
Refs me2resh/apexyard#147
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>
* docs(me2resh/apexyard#148): correct privacy-gate wording — adopter action, not framework auto-publish (#149)
The privacy-gate wording introduced in PR #144 (and unchanged in PR #147)
attributed the publication to the framework rather than the adopter:
"the standard fork-and-commit setup will silently publish your private
project names on a public GitHub repo"
That's factually wrong. ApexYard never pushes anything without explicit
operator approval — the publication only happens when the adopter
themselves runs git push. The "silently publish" framing read as if the
framework auto-publishes, which is misleading and undermines trust in
the rest of the framework's safety claims.
Two prose-only edits, no code, no behavior change:
- .claude/skills/setup/SKILL.md Step 2a — replaced "will silently
publish ..." with adopter-action language ("you might accidentally
publish ... a stray git push after registering them — I won't push
without your approval, but the risk is on the adopter once the data
is committed locally")
- docs/multi-project.md trip-wire callout — replaced "silently publish
their portfolio names the moment they push" with "risk accidentally
publishing their portfolio names with a stray push (the framework
itself never pushes without operator approval, but once the registry
is committed locally the next push exposes it)"
Verified: `grep -r "silently publish" .claude/skills/ docs/` returns no
hits.
Closes me2resh/apexyard#148
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#150): bootstrap-skill exemption + Bash-write coverage (#152)
Closes the legitimate-bypass case (me2resh/apexyard#150) and the
illegitimate-bypass case (me2resh/apexyard#151) together so the
ticket-first gate is coherent. Shipping either alone would leave a
window where the framework is internally inconsistent — see AgDR-0011
for the full rationale.
Bootstrap exemption (me2resh/apexyard#150):
- .claude/session/active-bootstrap marker, written by /setup,
/handover, /update, /split-portfolio on entry; cleared on exit
- SessionStart sweep (clear-bootstrap-marker.sh) for stale markers
from interrupted sessions
- require-active-ticket.sh reads the marker and exempts skills on
the configured ticket.bootstrap_skills list
- bootstrap_skills list lives in .claude/project-config.defaults.json
(extendable per fork via .claude/project-config.json)
Bash-write coverage (me2resh/apexyard#151):
- new _lib-detect-bash-write.sh — heuristic detector for output
redirection, tee, sed -i, awk -i inplace, python/node/ruby
embedded interpreters
- require-active-ticket.sh + require-migration-ticket.sh now fire
on Bash in addition to Edit|Write|MultiEdit
- design choice: false-negatives preferred over false-positives
(the matcher errs toward "let through" rather than block legit
read-only commands)
Tests: 32 unit cases on the lib + 12 integration cases on the hook,
including the exact me2resh/apexyard#151 bypass repro from the issue
body.
Closes me2resh/apexyard#150
Will manually close me2resh/apexyard#151 post-merge per the
single-Closes-per-PR rule (precedent: AgDR-0010 / PR #147).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#153): extend Bash-write matcher beyond first-version coverage (#155)
Closes me2resh/apexyard#153.
Extends `_lib-detect-bash-write.sh` (introduced for me2resh/apexyard#151
in PR #152) with the matcher families flagged by Rex's review of #152.
AgDR-0011 already frames the matcher as a living list extended on
observation; this commit just walks the list.
New matcher families:
- File-moving builtins: `cp`, `mv`, `rm`, `dd`, `install` (anchored at
command-start; `--help`/`--version` and `git rm`/`git mv` excluded)
- Archive / network writes: `tar -x` / `tar --extract`, `curl -o` /
`--output`, `wget -O` / `--output-document`
- Additional embedded interpreters: `perl -e`, `php -r` (keyword-gated
like python/node/ruby); `go run`, `deno run`/`deno script.ts`,
`bun run`/`bun script.ts` (categorical script runners)
- Python helpers: `pathlib.Path().touch()`, `shutil.copy*`,
`shutil.move`, `os.rename` added to the `python -c` and python
heredoc keyword list
- Heredoc variants for `ruby` and `node` (previously only python
heredoc was covered)
Extractor extensions:
- `cp` / `mv`: last positional arg
- `curl -o` / `--output`: file argument
- `wget -O` / `--output-document`: file argument
- `tar -x`, `go run`, `deno`, `bun`, `perl -e`, `php -r`: return empty
(caller applies gate categorically per AgDR-0011)
Test count rose from 32 to 86. Negative-class counterexamples cover
the trickiest false-positive surfaces: `tar -t` listing, `cp --help`,
`rm --version`, `git rm`, `curl` bare URL fetch, `wget` bare URL fetch,
`deno fmt`, `deno test`, `go build`. Existing
`test_require_active_ticket_bash.sh` regression suite still passes 12/12.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(#154): mock gh in test sandboxes to remove live-tracker dependency (#156)
- add _lib-mock-gh.sh helper that installs a fake `gh` on the sandbox PATH;
intercepts `gh issue view <N> ... --json ...` and returns synthetic
`{"number":N,"state":"OPEN"}` (overridable per-num via mock_gh_set_state)
- wire the shim into test_single_closes_per_pr.sh and
test_validate_pr_required_sections.sh so the validator's CLOSED-issue
refusal no longer breaks the suite when upstream issues are closed
- both files previously failed every case (0/13 and 0/8) because their PR
titles reference #114 / #113, which are now CLOSED upstream
- post-fix: 13/13 and 8/8; full suite remains green
Closes me2resh/apexyard#154
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#132): structured CEO marker + same-turn merge in /approve-merge (#158)
* feat(#132): structured CEO marker + same-turn merge in /approve-merge
Closes me2resh/apexyard#132 (drop the "stop before merge" rule) and
me2resh/apexyard#48 (harden CEO marker against self-approval bypass)
together. The two threads compose — see AgDR-0012 for the full
rationale.
Streamline (#132):
- /approve-merge now runs `gh pr merge --squash --delete-branch`
in the same turn as the marker write, by default
- --no-merge opt-out preserves the deferred-merge case
- The discrete approval moment is the SKILL INVOCATION, not a
follow-up "now do the merge" message
Harden (#48):
- CEO marker is now a structured key/value file with required fields:
sha=<HEAD>
approved_by=user
skill_version=2
Validated by block-unreviewed-merge.sh; bare-SHA legacy markers
rejected with a clear "stale format" error pointing at /approve-merge
- The model's bare `echo SHA > <pr>-ceo.approved` bypass is now
mechanically rejected. Forging the structured fields requires a
deliberate, visible rule violation rather than a one-line accident
- Optional audit fields (approved_at, approval_summary) capture the
"what did the user say when they approved" trail
- Rex marker stays bare-SHA — different threat model (automated
reviewer, not human authorization moment)
pr-workflow.md reframed: "the load-bearing rule is explicit per-PR
approval, not two user messages." The merge is a deterministic
consequence of the approval invocation.
Tests: 12 cases on the hardened hook covering the new format end-to-end
(valid v2, missing rex/ceo, bare-SHA legacy rejected, missing
approved_by, wrong approved_by, skill_version=1, sha mismatch,
non-merge no-op, gh-api shape gated). Full suite: 205/205 across 13
test files.
Will manually close me2resh/apexyard#48 post-merge per the
single-Closes-per-PR rule (precedent: PR #152 / AgDR-0011).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#132): redact private project reference from AgDR-0012
Abstracted two references to a registered private project that named
the project's owner/repo. The leak-protection hook caught one in the
PR body; this fixup removes the matching references from the
AgDR-0012 file content (which would otherwise have shipped the names
to me2resh/apexyard public repo via the merge).
The pre-existing reference at .claude/rules/pr-workflow.md:130
(documenting #47) is untouched — it predates this PR and is already
on the public repo's history.
Refs me2resh/apexyard#132.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#132): markdownlint blanks-around-fences + typo fix
Two small fixups against red CI / Rex feedback:
- AgDR-0012 line 63: fenced code block now has a blank line before it
(MD031). markdownlint-cli2 0.13.0 was rejecting the indented fence
inside the bullet because the fence's preceding line was the bullet
text (no blank).
- approve-merge SKILL.md line 172: typo `deferes` → `defers` (Rex flag
on PR #158).
Refs me2resh/apexyard#132.
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>
* chore(#157): remove voice-prompts feature + correct hook/skill counts (#161)
* chore(#157): remove voice-prompts-on-pause feature + correct hook/skill counts
Closes me2resh/apexyard#157 (sunset the voice-prompts feature) and
me2resh/apexyard#77 (hook count off-by-one in CHANGELOG / CLAUDE.md)
in one bundled PR. AgDR-0013 supersedes AgDR-0009 with the full
rationale; both AgDRs are preserved (decision records are append-only
history).
Removed (#157):
- .claude/hooks/voice-prompt-on-pause.sh
- .claude/hooks/tests/test_voice_prompt_on_pause.sh
- Stop matcher block in .claude/settings.json (became empty after
voice removal)
- voice_prompts block in .claude/project-config.defaults.json
- "## Voice prompts" section in docs/project-config.md
- voice_prompts mention in AgDR-0010 line 32 (replaced with
leak_protection / ticket as still-current example config blocks)
Preserved:
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — historical record;
new "Superseded by: AgDR-0013" header at the top
- AgDR-0010 line 115 reference to AgDR-0009 — still accurate as a
historical pattern reference
Counts corrected (#77):
- CHANGELOG.md v0.3.0 stats: "17 hooks" → "18 hooks" (historical
fix — at v0.3.0 there were actually 18 hooks)
- CLAUDE.md table line: "18 shell scripts" → "24 shell scripts"
(current count after this removal)
- CLAUDE.md table line: "35 slash commands" → "39 slash commands"
- CLAUDE.md "Available skills (34)" → "Available skills (39)"
- CLAUDE.md quick-reference "Skills (35 slash commands)" →
"(39 slash commands)"
Why bundled: #77's correct count depends on whether voice is still in
the framework. Shipping #77 before #157 would write a number that's
wrong by one again the moment #157 lands. Same shape as previous
bundles (PR #152 / AgDR-0011, PR #158 / AgDR-0012).
Why no adopter-facing changelog mention of the voice removal: the
feature never reached a tagged release on main. v1.1.0 didn't have
it; v1.2.0 won't have it. From the adopter's perspective there's
nothing to retire. AgDR-0013 captures the framework's internal record
for future contributors. See AgDR-0013 § "No adopter-facing changelog
mention".
Tests: full hook test suite green (196 cases across 12 files —
test_voice_prompt_on_pause.sh removed). No regressions.
Will manually close me2resh/apexyard#77 post-merge per the
single-Closes-per-PR rule (precedent: PRs #152, #158).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#157): unwire voice from settings + configs + docs + AgDR-0013
Continuation of d09d7b5 (the file deletions). Squash-merge will
collapse both into one PR commit. This commit captures:
- .claude/settings.json — Stop matcher block removed (was the only
hook in it; entire matcher gone)
- .claude/project-config.defaults.json — voice_prompts block + its
_comment removed
- docs/project-config.md — "## Voice prompts" section removed
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — "Superseded by"
header added at the top, content otherwise preserved as history
- docs/agdr/AgDR-0010-portfolio-config-and-self-healing.md — line 32
example reference swapped from voice_prompts to
leak_protection / ticket (still-current config blocks)
- docs/agdr/AgDR-0013-sunset-voice-prompts.md — new supersession AgDR
- CLAUDE.md — hook count 18 → 24, skill count 35 → 39 (three
occurrences each, all aligned to current reality)
- CHANGELOG.md v0.3.0 stats — "17 hooks" → "18 hooks" historical fix
(#77 acceptance criterion 1)
Refs me2resh/apexyard#157 + me2resh/apexyard#77.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#157): markdownlint MD028 + resolve stale AgDR-numbering ref
Three small fixups against Rex CHANGES-REQUESTED on PR #161:
- AgDR-0009 line 3: promote "Superseded by:" header out of a
blockquote. The original "I decided ..." canonical blockquote at
line 5 was being merged with the new supersession blockquote
(markdownlint MD028 — "no blanks inside blockquote").
- AgDR-0013 line 3: same shape — "Supersedes:" header now a plain
bold paragraph, canonical "I decided ..." blockquote untouched.
- approve-merge SKILL.md line ~187: stale conditional reference
"AgDR-0012 (or 0013 — depends on whether voice-removal lands
first)" — order is now resolved (12 = approve-merge bundle, 13 =
voice removal). Drop the parenthetical.
Refs me2resh/apexyard#157 + me2resh/apexyard#77.
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(#160): multi-tab terminal demo on the landing site (#162)
Landing-site `site/index.html` terminal demo previously played one
canonical flow ("one ticket, start to finish") on autoplay. With the
v1.2.0 skill surface expansion (4 new skills, 8+ new hooks), one flow
no longer represents the framework's breadth.
Adds tabs to the terminal chrome — four flows visitors can either let
auto-cycle or click directly:
1. one ticket — existing flow, unchanged content
2. /handover — adopt an external repo into the portfolio
3. /setup — first-run framework bootstrap on a fresh fork
4. /fan-out — spawn 3 parallel agents on independent tickets
Auto-advance: on completion of the active tab's script, the demo
pauses ~1.8s then advances to the next tab. Loops at the end. User
can interrupt by clicking any tab or hitting Replay.
Implementation:
- HTML chrome — single title span replaced with a tablist of 4 button
tabs. ARIA `role="tablist"` / `role="tab"` / `aria-selected` so
keyboard + screen-reader users get the same semantics as sighted
ones.
- CSS — new `.shell-demo__tabs` + `.shell-demo__tab` with an accent
underline on the active tab. Tabs scroll horizontally on narrow
viewports (mobile responsive).
- JS — refactored the existing IIFE from one `script` array to an
array of four. Added `setActiveTab()` for ARIA state, `play(idx)`
takes a tab index, end-of-script auto-advances to `(idx + 1) % N`
unless the user clicked away during the pause. Adds a new `cmd`
type alongside `you` for slash-command invocations (renders with
the same `>` prompt prefix). prefers-reduced-motion still bails
early and leaves the static seed visible.
- Static seed (the non-JS fallback) still shows tab 0's content, so
reduced-motion / no-JS visitors see the one-ticket flow as before.
Hero metrics also corrected to current reality (#77 / PR #161 covers
CLAUDE.md and CHANGELOG.md; this PR catches the same numbers in the
landing site):
Skills 32 → 39
Hooks 18 → 24
The tabs ship in v1.2.0 alongside the framework changes that make
the new flows worth showcasing.
Refs me2resh/apexyard#160 (release v1.2.0 + landing-site refresh).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#163): default the split-portfolio sibling repo name to <fork>-portfolio (#164)
Closes me2resh/apexyard#163.
The split-portfolio mode docs and skills previously suggested
`your-org/ops` as the default name for the private sibling repo, and
`portfolio/` as the local clone directory. Both too generic — adopters
running multiple ops setups end up with `your-org/ops` collisions, and
a bare `portfolio/` dir gives no signal about which framework it
belongs to when it sits next to other unrelated `portfolio/` dirs.
`<fork>-portfolio` is now the default — keeps the relationship to the
public fork explicit on disk and on GitHub. If the fork is named
`your-org/apexyard`, the portfolio defaults to
`your-org/apexyard-portfolio`. If the fork was renamed (e.g. `cos`),
the portfolio defaults to `cos-portfolio`. Adopters with custom names
keep working — the `portfolio:` config block resolves whatever path
they configured.
Files updated:
docs/multi-project.md
- Layout diagrams use `apexyard-portfolio/` as the sibling
- Setup walkthrough Step 2 + Step 3 use `your-org/apexyard-portfolio`
and explain the `<fork>-portfolio` pattern
- Config-block + symlink path examples updated to
`../apexyard-portfolio/...`
- Daily workflow + cross-machine clone commands updated
- The two existing `your-org/ops` references that remain are
fork-rename examples (lines 52, 64) — kept as-is, since renaming
the fork to `ops` is still valid (the portfolio would then
default to `ops-portfolio`)
.claude/skills/setup/SKILL.md
- Step 2b's "default suggestion" for the private repo name is now
`your-org/<fork>-portfolio`, computed dynamically from the
fork's repo name via `gh repo view --json name -q .name` so the
suggestion is correct even when the fork was renamed
- Clone command no longer needs a second arg — the repo name IS
the directory name
- Config-block paths updated to `../apexyard-portfolio/...`
.claude/skills/split-portfolio/SKILL.md
- Step 3's suggested-name template is now `<account>/<fork>-portfolio`
with the same dynamic-fork-name resolution
Mechanism unchanged. The `portfolio:` config block in
`.claude/project-config.json` still takes any path; this PR is purely
default-suggestion + example prose.
No tests required (skills are markdown instructions; no automated
coverage today).
Refs `apexyard.projects.yaml.example` uses "ops repo" as a generic
term meaning "the operational management fork" — not a name — kept
as-is.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#165): skills reference page on the landing site + changelog link (#167)
Closes me2resh/apexyard#165.
Adds a public, browseable index of every apexyard slash command
alongside a one-click changelog link from the homepage nav.
site/skills.html (new):
- Lists all 39 skills currently shipping in .claude/skills/
- Each entry: slash command, argument hint, description (taken
verbatim from the SKILL.md frontmatter so the page matches the
runtime exactly)
- 10 categories: Setup & onboarding, Daily ops, Tickets & ideas,
Specs & decisions, Code review & merge, Architecture & dev tools,
Production-readiness audits, Workflow primitives, Communications,
Deprecated
- Same brutalist-terminal design tokens as the homepage — JetBrains
Mono, paper-cream background, single warning-red accent, sharp
corners. Inlined CSS to keep the static-only no-build-step
convention; design vars duplicated rather than extracted to a
shared file (~18 vars; cheap to keep in sync).
- Mobile responsive — skill grid collapses to single-column under
720px; titlebar nav hides non-CTA items on narrow viewports.
- Reduced-motion friendly (no animation in the first place).
- Internal anchor TOC at the top so the page scans in seconds.
site/index.html (nav addition):
- Added two nav links to the titlebar between "what's in the box"
and the github CTA:
• skills → ./skills.html
• changelog → https://github.com/me2resh/apexyard/releases
- The changelog link points at the GitHub releases page (not the
raw CHANGELOG.md file) so it auto-resolves to the latest tagged
release on each visit. v1.2.0 lands and the link is already there.
No new dependencies, no build step, no JS for the skills page. The
existing site convention (one-html-file-per-route, inlined CSS,
optional progressive-enhancement JS) is preserved.
Refs me2resh/apexyard#160 (release v1.2.0 + landing-site refresh) —
this is the second site-side deliverable for that ticket; the release
tag itself follows once me2resh/apexyard#159 (testing) closes.
Follow-up worth a separate ticket: a small generator script that
walks .claude/skills/*/SKILL.md, parses YAML frontmatter, and emits
the skills.html sections automatically. Out of scope for v1.2.0 —
first version is hand-curated and will need maintenance until that
generator lands.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#168): accept release/vN.N.N branches + release(...) PR titles (#169)
Closes me2resh/apexyard#168.
The /release skill prescribed `release/vA.B.C` as the source-branch
name and `release: vA.B.C` as the PR title for the dev → main release
PR (per AgDR-0007). Both were rejected by the framework's own
validators:
validate-branch-name.sh required {type}/{TICKET-ID}-{description};
release/v1.2.0 has no ticket-id portion.
validate-pr-create.sh required type(SCOPE): form with `release` not
in pr.title_type_whitelist.
The contradiction surfaced cutting v1.2.0 — the first release under
the dev/main model.
Three small changes:
1. .claude/hooks/validate-branch-name.sh — added an early-out branch
that accepts ^release/vN.N.N(-rcN)?$ as a valid name. Narrow,
intentional exception for the framework's release-cut convention;
release branches don't carry a ticket-id because the release itself
IS the ticket.
2. .claude/project-config.defaults.json — added "release" to
pr.title_type_whitelist so a title like `release(#160): v1.2.0`
passes validate-pr-create.sh's existing regex unchanged.
3. .claude/skills/release/SKILL.md step 4 — corrected the prescribed
PR title to `release(#<release-ticket>): vA.B.C` so future /release
invocations produce a title that satisfies the validators by
construction.
Tested:
bash .claude/hooks/validate-branch-name.sh against:
release/v1.2.0 → 0 (allowed, release-special-case)
release/v1.2.0-rc1 → 0 (allowed, RC variant)
release/v9.9.9 → 0 (allowed)
release/foo → 2 (correctly blocked)
release/v1 → 2 (correctly blocked)
chore/GH-168-fix → 0 (allowed, standard pattern)
feature/GH-1-x → 0 (allowed, standard pattern)
Full hook test suite: 196/196 cases green across 12 test files.
Refs: surfaced 2026-05-04 cutting the first release under AgDR-0007.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#170): exempt release/vN.N.N from validate-pr-create's branch-id check (#171)
Closes me2resh/apexyard#170.
Completes the work started in #169 (closing #168). #169 added a
release-pattern early-out to validate-branch-name.sh so release/vN.N.N
branches pass the branch-name validator. But validate-pr-create.sh has
its own independent branch-id check at line 273 that #169 didn't
touch — and it still rejects release/v1.2.0 because that name doesn't
contain a ticket-id substring.
This is the same class of contradiction #168 fixed; the fix is the
same shape. Add the same release-pattern early-out to the branch-id
check in validate-pr-create.sh:
- if the branch matches ^release/vN.N.N(-rcN)?$ → exempt (release
branches don't carry ticket-ids; the release itself is the ticket)
- otherwise → require a ticket-id substring as before
#168's acceptance criterion 3 ("validate-pr-create.sh accepts a PR
title `release(#160): v1.2.0` against the `release/v1.2.0` branch")
was checked off based on the title regex alone but didn't catch the
secondary branch-id check living in the same file. Surfaced trying
to open the v1.2.0 release PR.
Tested:
bash .claude/hooks/validate-pr-create.sh against:
release/v1.2.0 → 0 (allowed, exempt)
release/v1.2.0-rc1 → 0 (allowed, RC variant)
chore/GH-1-fix → 0 (allowed, has ticket-id)
release/foo → 2 (correctly blocked)
chore/no-ticket → 2 (correctly blocked)
Refs me2resh/apexyard#168 (the parent bug) + #169 (the partial fix).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#173): sync CHANGELOG.md from main → dev (#174)
Closes me2resh/apexyard#173.
The release-cut model (AgDR-0007 / #116) squash-merges
release/vN.N.N → main, which means the v1.2.0 CHANGELOG section that
landed on main via PR #172 was never propagated back to dev. This
commit copies main's CHANGELOG.md verbatim onto dev so the v1.2.0
section is now present on both branches.
The diff is exactly the v1.2.0 entry being prepended; no other lines
change.
Without this sync, the next release PR cut from dev would build a
v1.3.0 section on top of v1.1.0, silently dropping v1.2.0 from dev's
running history. The corollary skill-level fix (option B in the
ticket) — updating /release to source the previous CHANGELOG from
upstream/main — is filed as a follow-up and out of scope for this PR.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#181): /agdr skill — searchable AgDR library (#186)
Adds /agdr — a portfolio-wide index for Agent Decision Records.
Walks apexyard.projects.yaml, reads each project's docs/agdr/*.md
(local clone if available, else gh api fallback), parses the optional
YAML frontmatter for category + projects, and answers four queries:
- /agdr browse list across the portfolio, grouped by category
- /agdr search <term> full-text grep across all bodies, returns
<project>/AgDR-NNNN paths + matching paragraph
- /agdr show <id> print a specific record, disambiguates duplicates
- /agdr stats counts per category (the marketing-slide tile,
now backed by real data)
Six-category taxonomy: architecture | tech-stack | security | patterns
| integrations | other. Legacy AgDRs without frontmatter remain
first-class — they bucket as `other` and are flagged in browse so
operators can migrate at their own pace.
Backwards-compatible template change: templates/agdr.md gains an
optional `category:` (and optional `projects:`) line in the existing
frontmatter block. Omitting the line keeps every existing AgDR valid;
the skill defaults to `other` when missing.
Doc note in workflows/sdlc.md § Phase 2 points at /agdr search for
"have we decided this before?" lookups before drafting a design.
Smoke test in .claude/hooks/tests/test_agdr_skill.sh covers the
parser the spec specifies — frontmatter extraction, category
bucketing (including the legacy default-to-other path), id reading,
stats aggregation, and search match counts. 18 assertions, all green;
12 pre-existing test suites also green (no regressions).
Closes #181
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(me2resh/apexyard#183): /launch-check trend tracking (#185)
- Persist each run as JSON under <projects_dir>/<name>/launch-check/runs/
(timestamp + branch + commit + per-dimension scores + verdict + top_risks)
- Append "Trend (last 5 runs)" section to the per-run summary when at least 2
prior runs exist — markdown table + ASCII score chart
- Add /launch-check trend mode for read-only trend rendering (no full audit)
- Auto-derive notes column from score-delta vs previous run
(e.g. "Security +12, Analytics +10")
- Opt-in commit via .launch-check-history-tracked marker; gitignored by default
- New helper script render-trend.sh + test_launch_check_trend.sh (21 cases)
- Resolve projects dir via portfolio_projects_dir helper (no hardcoded path)
- Schema is forward-compatible — extra fields preserved on framework upgrade
AgDR-0014 documents the chart format / schema / opt-in choices.
Closes me2resh/apexyard#183
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#182): /status --briefing + bin/apexyard status CLI shim (#187)
* feat(me2resh/apexyard#182): /status --briefing + bin/apexyard CLI shim
Make slide 6's `$ apexyard status` invocation real — the smallest of three
slide-reality tickets, demonstrating the marketing-to-implementation
pattern before #181 / #183 land.
- New helper at .claude/skills/status/briefing.sh — computes the 4-line
"where am I" briefing (active workspace, active ticket, branch,
role-set) so the logic is testable in isolation and runnable from a
plain shell. Workspace inference walks up from cwd to the ops-fork
root (same algorithm as _lib-portfolio-paths.sh and /start-ticket).
- Ticket reads from .claude/session/tickets/<workspace> first, falls
back to .claude/session/current-ticket. Role-set is inferred from
the active ticket's GitHub labels (v1: backend / frontend / qa /
security / platform / sre / data / ux / ui / product / tech-lead,
plus the long forms). No match emits the explicit "<none — inferred
per task>" placeholder so the four-line shape is constant.
- New CLI shim bin/apexyard delegates `apexyard status` to the same
helper after walking up to find the ops-fork root. Works from any
workspace/<name>/ clone or the fork itself; symlink onto PATH to
install (no shadowing of the `claude` binary).
- /status SKILL.md gains a "Briefing mode" section documenting the
--briefing / -b flag; default /status output is unchanged.
- docs/multi-project.md "Daily workflow" demonstrates the new
invocation alongside /inbox and /status.
- New smoke tests at .claude/hooks/tests/test_status_briefing.sh — 8
cases covering ops-root cwd, workspace cwd, unknown cwd, ops-fallback
marker, per-project marker priority, label-based role inference, the
no-matching-label path, and the constant-four-line shape.
Closes me2resh/apexyard#182
* fix(me2resh/apexyard#182): replace curios-dog with example-app in SKILL.md output sample (leak fix)
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(#178): LSP integration spike — measurement + recommendation (#184)
* docs(#178): LSP spike — token-savings measurement + integration findings
- Phase 1: estimated token cost A vs B for three representative queries on a real TS Lambda backend (~9,750 LOC); shallow semantic queries see ~3-23x input-token savings, multi-hop traces see ~1.4x
- Phase 2: verified Claude Code shipped first-party LSP support in v2.0.74 (Dec 2025), gated behind ENABLE_LSP_TOOL=1, wired in via the plugin system (.lsp.json); MCP-wrapped LSP shims (cclsp, lsp-mcp) remain as fallback
- Phase 3: recommends Option 3 — interactive clone-first prompt at end of /handover, preserving today's "never auto-clone" principle while making the deep-dive path discoverable
- Phase 4: AgDR Y-statement and option matrix sketched; full AgDR is a follow-up if the spike says go
- Recommendation: GO. Adopt the built-in tool, document the opt-in path, change /handover to offer clone-first interactively. No code changes beyond /handover SKILL.md; no novel integration to maintain
Closes me2resh/apexyard#178
* docs: fix markdownlint MD031/MD032 in spike report
Add blank lines around lists and a fenced code block to satisfy
markdownlint-cli2 in CI. Pure formatting, no content change.
Refs me2resh/apexyard#178
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(me2resh/apexyard#190): annotate LSP-aware skills with opt-in callouts (#191)
- Add identical-shape "LSP-aware (optional, recommended)" callout near
the top of each of the four code-aware SKILL.md files.
- Per-skill savings number is the only variation:
- /code-review: ~3-15× cheaper for semantic queries
- /threat-model: ~3-15× shallow, ~1.4-5× multi-hop traces
- /security-review: ~3-15× cheaper for semantic querie…
* chore(#109): project-configurable ticket / branch / commit / PR schema (#118)
* chore(#109): project-configurable ticket / branch / commit / PR schema
Lift the prefix / type whitelists hardcoded across skills, hooks, and
CI into a versioned JSON config read through a shared shell library.
Shipped defaults at .claude/project-config.defaults.json; per-fork
overrides at the optional .claude/project-config.json; one reader
(_lib-read-config.sh) that every consumer now uses.
Added:
- .claude/project-config.defaults.json (v1 schema)
- .claude/hooks/_lib-read-config.sh (shared reader)
- docs/project-config.md (schema reference + extension guide)
- docs/agdr/AgDR-0006-project-configurable-ticket-schema.md
Migrated (still pass with no config present via last-resort fallback):
- validate-branch-name.sh → .branch.type_whitelist
- validate-commit-format.sh → .commit.type_whitelist (legacy
`commit_types` top-level key honoured as backward-compat fallback)
- validate-pr-create.sh → .pr.title_type_whitelist
- /feature, /task, /bug skills reference the config in their Rules
sections; none hardcodes the list any more
Unlocks subsequent config-readers for #107 / #110 / #111 / #112 /
#113 / #114 / #115 — each extends the schema under its own subtree
without further changes to the loader.
https://github.com/me2resh/apexyard/issues/109
* fix(#109): satisfy markdownlint MD032 and MD060 on new docs
Auto-fix MD032 (blank lines around lists) in AgDR-0006 and format
table-separator rows with surrounding spaces (MD060) in both new
doc files. Content unchanged; CI green.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#110): add block-private-refs-in-public-repos.sh hook (#119)
Adds a new PreToolUse hook that blocks gh issue/PR/comment creation and
gh api .../issues|/pulls calls targeting a public framework repo
(default: me2resh/apexyard + whatever `upstream` resolves to) when the
title or body references any registered private project from
apexyard.projects.yaml (by name, repo slug, owner/repo#N ticket ref, or
workspace path).
The hook is a sibling to check-secrets.sh — both scan outgoing content
for identifiers that should never leave the local environment. Skip
marker `<!-- private-refs: allow -->` in the body lets a deliberate
reference through with a visible warning.
Files touched:
- .claude/hooks/block-private-refs-in-public-repos.sh (new)
- .claude/hooks/tests/test_block_private_refs.sh (new)
- .claude/rules/leak-protection.md (new)
- .claude/settings.json (wire PreToolUse matchers for the 5 gh shapes)
- docs/rule-audit.md (append section 10 + bump counts)
Refs: https://github.com/me2resh/apexyard/issues/110
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#115): add warn-stale-review-markers.sh PostToolUse hook (#120)
Fires after `git push` to surface review markers that have gone stale
because new commits were pushed past an existing Rex / CEO / design
approval. The merge gate already catches this at `gh pr merge` time,
but only then -- this hook closes the gap by flagging it immediately
at push-time so the author isn't surprised at merge.
- `.claude/hooks/warn-stale-review-markers.sh`
- PostToolUse, non-blocking (PostToolUse exit 2 would push noise
into the conversation; this hook is purely informational).
- Resolves the PR HEAD via `gh pr view --json headRefOid` -- same
source-of-truth as the merge-gate hooks post-apexyard#47 / #55.
Falls back to local HEAD with a visible WARN when gh is offline.
- Silent on: no PR for branch, no markers, fresh markers,
failed push (detected via `rejected` / `failed to push` /
`fatal:` / `error:` markers in tool_response.stderr).
- Modes: `warn` (default) prints one stderr line per stale marker;
`delete` opts in to auto-removal via
`.claude/project-config.json` -> `review_markers.on_stale`.
TODO(apexyard#109): switch to the shared project-config reader
once it lands.
- `.claude/settings.json`
- Wires the hook on PostToolUse / Bash / `git push *`.
- `docs/rule-audit.md`
- Adds a row under section 3 (Code review & PR quality) and
bumps the mechanized count 26 -> 27 / total 73 -> 74.
- `.claude/hooks/tests/test_warn_stale_review_markers.sh`
- 8 cases: no PR, no markers, fresh markers, stale rex / ceo /
design (warn), delete mode, failed push. All pass locally.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#111): upgrade pre-push-gate from advisory reminder to blocking check-runner (#121)
* chore(#111): upgrade pre-push-gate from reminder to blocking check-runner
Previously pre-push-gate.sh just printed a checklist of things to run
locally before pushing — it was advisory. The rule it enforces is a
HARD STOP per pr-workflow.md. That asymmetry meant agents routinely
pushed broken work and discovered it only when CI went red.
Replaces the reminder with a blocking runner that reads the list of
shell commands from project config (.pre_push.commands) and executes
them in sequence before a push is allowed through. First non-zero
exit blocks the push with exit 2 and prints the failing command plus
the last 20 lines of its output.
- Config key: .pre_push.commands[] — array of {name, run} objects.
Shipped default is an empty list (hook stays a no-op on repos that
haven't configured their checks yet, including the framework repo
itself until it wires its own CI).
- Emergency bypass: '<!-- pre-push: skip -->' in the HEAD commit
message. Grep-able on purpose so bypasses stay auditable.
- Fail-fast: once a command fails, the rest don't run. Parallel
execution is a follow-up polish.
- 7 test cases in .claude/hooks/tests/test_pre_push_gate.sh — all
pass on the shipped default + a minimal custom config.
Updates docs/rule-audit.md to flip "partial" → "yes" for the
"before git push" rule.
Integrates with the shared config reader landed in #109.
https://github.com/me2resh/apexyard/issues/111
* fix(#111): remove orphaned footnote reference from rule-audit
The previous advisory-mode footnote was superseded by pre-push-111
but its definition was accidentally kept, tripping markdownlint MD053
(unused reference definition). Drop it.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#107): add validate-issue-structure.sh PreToolUse hook (#122)
Mechanically enforces the ticket body schema when an agent files raw
`gh issue create` calls instead of going through the interactive
/feature, /task, /bug skills. Matches bracketed title prefix
([Feature] / [Chore] / [Bug] / [Docs] / etc.) against
`.ticket.required_sections` in project-config, and blocks (exit 2)
when any required section is missing or empty. Skip marker
`<!-- validate-issue-structure: skip -->` bypasses with a visible
stderr WARN for legitimate off-template tickets (epics, meta-threads).
Changes:
- .claude/hooks/validate-issue-structure.sh — the hook; reads schema
via the shared _lib-read-config.sh, with inlined defaults for bare
checkouts predating the config-schema rollout. Handles
--body / --body-file / -F path.
- .claude/project-config.defaults.json — extends .ticket with
required_sections (Feature/Chore/Refactor/Testing/CI/Docs/Bug) and
skip_marker; other .ticket fields untouched.
- .claude/settings.json — new PreToolUse matcher on Bash(gh issue
create *) alongside the existing suggest-ticket-template.sh and
block-private-refs-in-public-repos.sh hooks.
- .claude/hooks/tests/test_validate_issue_structure.sh — 15 cases
covering pass + fail paths per prefix, empty section detection,
skip marker, unknown prefix, non-gh invocation, --body-file path.
- docs/rule-audit.md — new section 11 row, mechanized count +1.
Upstream ticket: https://github.com/me2resh/apexyard/issues/107
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#112): add require-agdr-for-arch-pr.sh PreToolUse hook (#123)
Closes the asymmetry noted in .claude/rules/agdr-decisions.md: every
other HARD STOP in the ruleset (merge approval, ticket-first,
migration-first) is mechanically enforced, but the /decide HARD STOP
was prose-only. The commit-time hook require-agdr-for-arch-changes.sh
catches one architectural change at commit; this new PR-time hook
catches the cumulative diff so reviewers always have a pointer to the
decision record.
- New hook at .claude/hooks/require-agdr-for-arch-pr.sh
- Fires on Bash(gh pr create *)
- Parses --title/--body/--body-file/-F <path>
- Resolves base branch from --base, else upstream/dev, origin/dev,
upstream/main, origin/main, main, master (in that order)
- Computes `git diff <merge-base>..HEAD --name-only`
- Triggers on any changed file matching .agdr_trigger_paths[], OR any
dep-file addition (package.json via jq key-set diff; other
dep files via a commented +/- line-count heuristic — version
bumps match +/- counts and do not fire)
- Blocks (exit 2) with a helpful message naming the triggers and
pointing at /decide if the body has no `AgDR-\d+-[a-z0-9-]+`
reference
- Skip marker `<!-- agdr: not-applicable -->` bypasses with a
visible WARN on stderr
- Silent exit 0 on non-gh commands, empty diffs, unresolvable base
- Wired via .claude/settings.json PreToolUse Bash(gh pr create *)
- Adds two new top-level keys to .claude/project-config.defaults.json:
agdr_trigger_paths (shell globs — domain/, infrastructure/,
migrations/, *.tf, .github/workflows/, etc.)
agdr_trigger_dep_files (literal basenames — package.json,
pyproject.toml, Cargo.toml, go.mod, Gemfile)
Hook has inline fallback defaults kept in sync.
- Adds docs/rule-audit.md entry in the AgDR section; bumps mechanized
count 26 to 27 and total rows 73 to 74.
- Adds .claude/hooks/tests/test_require_agdr_for_arch_pr.sh (7 cases;
all green): path-triggered without AgDR (block), with AgDR (pass),
dep-file added (block), version-only bump (no fire), skip marker
(pass + warn), non-matching diff (pass), non-gh command (no-op).
Closes https://github.com/me2resh/apexyard/issues/112
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#113): require Testing section in PR body (config-driven) (#124)
Extends validate-pr-create.sh with a required-sections check that
replaces the hardcoded Glossary-only grep. The list of required H2
headings is project-configurable via `.pr.required_sections[]`.
Shipped default is ["Testing", "Glossary"], matching the canonical PR
description shape in workflows/code-review.md.
- Each entry must appear as `## <Name>` (case-insensitive).
- Empty sections are tolerated at this layer (the issue-structure hook
#107 does stricter empty-content checks for issue bodies; for PR
bodies, empty sections are left to the reviewer's judgement).
- Skip marker `<!-- pr-sections: skip -->` bypasses with a visible
stderr WARN — for trivial PRs (lint-only fixes, version bumps)
where the full template is overkill.
- Reads from project config via the shared _lib-read-config.sh (#109).
Inline fallback matches shipped defaults so bare checkouts predating
#109 keep working.
- 8 test cases cover: all-sections pass, each missing section,
missing-both (both errors printed), skip marker, case-insensitive
headings, H3 rejection.
https://github.com/me2resh/apexyard/issues/113
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#114): enforce single Closes-keyword per PR body (#125)
* chore(#114): enforce single Closes-keyword per PR body
Caps distinct auto-closing references (close/closes/closed, fix/fixes/
fixed, resolve/resolves/resolved + N or owner/repo+N) at one per PR
body. Closes the loophole where the title validator limited the title
to one ticket but multiple Closes lines in the body would still auto-
close all of them on merge.
- Scans stripped of fenced code blocks so closing keywords inside a
code sample do not count.
- Distinct counting: the same number referenced twice (e.g. via Fixes
and Closes) counts as one.
- Cross-repo refs (owner/repo+N) count normally.
- Opt-in escape hatch: pr.allow_multiple_closes=true in
project-config disables the check for teams that deliberately batch
rollbacks or dependency bumps.
- Per-PR bypass: a multi-close-approved HTML comment in the body
prints a visible stderr WARN and lets that PR through. Grep-able
trace so bypasses are auditable.
- 10 test cases cover: one close passes, no-keyword passes, two
distinct block, three mixed block, same-number-twice passes, code-
fence-ignored, skip marker, cross-ref without keyword, opt-in
config, cross-repo close.
Reads configuration via the shared _lib-read-config.sh (apexyard+109).
https://github.com/me2resh/apexyard/issues/114
* fix(#114): strip inline backticks and tilde fences from close-count scan
Rex caught a self-reflexive bug in the initial commit: documentation
mentioning closing keywords inside inline backticks (say a PR body
that explains the new hook with examples) counted as real closes, and
a skip marker inside inline backticks silently bypassed the check.
Future PRs that document the feature would trip the same trap.
Fix the code-region stripper to cover:
- Triple-backtick fences (already handled)
- Tilde fences (new)
- Inline-backtick spans (new)
Also run the skip-marker check against the stripped body, so a marker
used purely as documentation no longer activates a real bypass.
Three new test cases pin the behaviour:
- closing keywords in inline backticks are ignored
- skip marker inside inline backticks does NOT bypass
- tilde fences also get stripped
13/13 tests pass.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#108): add /tickets-batch skill for bulk-file flow (#127)
The fast happy path for filing 5–20 structured tickets in one intent
without dropping to raw `gh issue create` (non-conformant) or running
`/feature` 20 times serially (~100 turns of interview).
- .claude/skills/tickets-batch/SKILL.md — new skill spec. Asks
shared-context questions (priority, epic, area-labels, repo) ONCE
for the whole batch, then runs a ≤3-question micro-interview per
ticket (type, one-line purpose, optional clarification when the
inference is low-confidence). Confirms the full batch as a table,
then files each via specific `gh issue create` calls (never a
bulk JSON dump — the validator runs per-issue). Output conforms
to `.ticket.required_sections` by construction. Caps at 20
tickets per invocation.
- CLAUDE.md — added a row for /tickets-batch in the Available
Skills table; bumped the count references from 33 to 34.
Refs https://github.com/me2resh/apexyard/issues/108
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#117): add /fan-out skill + parallel-work rule doc (#128)
- Add `.claude/skills/fan-out/SKILL.md` — spawns N parallel Agent calls
in a single assistant message, with per-task agent type, worktree
isolation, and foreground/background mode. Caps at 5 concurrent
agents. Refuses fan-out when tasks share file write targets or have
sequential dependencies. Includes pre-spawn active-ticket safety
check and worktree merge-back flow that pauses on conflict.
- Add `.claude/rules/parallel-work.md` — trigger heuristic for when an
agent should proactively offer fan-out (>= 2 file-independent,
context-independent, individually substantial work items). Pairs
with the skill: rule says when, skill says how.
- Update `CLAUDE.md` — bump rules count to 9, skills count to 34, add
`/fan-out` row to the skills table.
Refs https://github.com/me2resh/apexyard/issues/117
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* chore(#116): adopt release-cut branch model (dev/main + tags) — framework only (#126)
* chore(#116): adopt release-cut branch model (dev/main + tags)
Formalises the dev/main split that already exists informally — dev is
the daily-work branch where every PR lands, main is release-only,
tagged with semver on each merge. Framework-only: managed projects
under apexyard governance stay trunk-based.
Added:
- AgDR-0007 — decision record (options table covers full git flow vs
trunk-only vs gitflow-lite; chose gitflow-lite)
- /release skill — diff dev against main, propose semver bump from
conventional commits, generate CHANGELOG, open release PR, tag
after merge
- docs/release-process.md — prose runbook for cutting a release
(manual fallback for the skill)
- .git.protected_branches in project-config.defaults.json
(main/master/dev/develop)
Modified:
- block-main-push.sh — now blocks direct pushes/commits to all
configured protected branches (was: hardcoded main/master). Reads
.git.protected_branches via the shared config reader (apexyard#109).
- CLAUDE.md — new section under Git Conventions explaining the
dev/main model + the framework-only scope. Skill table entry for
/release. Skills count bumped to 34.
- docs/multi-project.md — note that upstream/main is release-only
and the dev/main split is framework-only.
Non-consequences (per AgDR-0007):
- No release/* or hotfix/* branches. Hotfixes are normal patches
cut quickly. Revisit if multi-version maintenance becomes a need.
- No automatic on-merge issue closing for dev PRs. The release PR's
body aggregates all Closes references for the batch and triggers
auto-close en masse when it merges to main. Manual close in the
meantime.
- CI workflows trigger on pull_request regardless of base, so
dev-targeting PRs already get the full check matrix — no
workflow file edits needed.
https://github.com/me2resh/apexyard/issues/116
* fix(#116): satisfy markdownlint MD032 + MD060 in 116 docs
Auto-fix added blank lines around lists in AgDR-0007 (MD032) and
spaced the table separators in AgDR-0007 + multi-project.md (MD060).
Content unchanged.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* fix(#106): CHANGELOG fallback in drift hook for squash-merged forks (#129)
* fix(#106): CHANGELOG fallback in drift hook for squash-merged forks
The v1.1.0 tag-reachability check (`git tag --merged main`) misfires on
forks that sync via GitHub's default squash-merge: the squash collapses
the upstream-tag commit into a synthetic SHA, the tag stops being
reachable, and the banner keeps firing forever.
Discovered live on the first real-world `/update` flow: ops fork
squash-merged the v1.1.0 sync PR, banner kept saying "v1.1.0 available"
even though the fork was content-caught-up.
This commit adds a CHANGELOG-content fallback that fires only when the
primary tag check fails. If the fork's main has a heading
`## [X.Y.Z]` matching the upstream tag's version, treat the release as
absorbed and stay silent. Tolerant grep (matches the apexyard CHANGELOG
format from v1.1.0 onward, with leading-`v` stripping for tag→heading
conversion).
The merge-commit and rebase paths are unchanged — primary tag check
still works for them, and the fallback never fires when it shouldn't.
Test coverage (5 cases):
- squash-merge fork caught up to v1.1.0 → silent (the fix)
- merge-commit fork caught up to v1.1.0 → silent (regression check)
- fork stopped at v1.0.0 → banner fires
- fork has its own newer tag → silent
- squash-merge but no CHANGELOG on fork → banner fires (no false silence)
Records the strategy update in docs/agdr/AgDR-0008-…md (extends
AgDR-0005's tag-based-drift design).
https://github.com/me2resh/apexyard/issues/106
* fix(#106): satisfy markdownlint MD032/MD060 + shellcheck SC2164
Rex flagged two CI-blocking issues on the original 106 commit:
- AgDR-0008 had three bulleted sub-lists in the Consequences section
without surrounding blank lines (MD032). Added blanks and padded the
one tight-pipe table separator (MD060).
- The 106 test fixture had five subshell `cd "$fk"` calls without
`|| exit 1` (SC2164). Added the guard to all five.
5/5 tests still pass after the fix. No semantic change to the hook
or the test logic.
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#130): add /validate-idea skill — lightweight pre-spec gate (#131)
A 10-minute, 5-question check that sits between /idea and /write-spec.
Designed for solo founders running ApexYard — not a heavyweight
methodology like event storming or Wardley mapping.
Five questions, asked one at a time:
1. Who is this specifically for?
2. What do they do today instead?
3. What's the smallest version that proves the value?
4. What would prove this is wrong? (kill criteria)
5. Build, buy, or rent?
Output: a one-page validation doc with a GREEN/YELLOW/RED verdict.
RED auto-updates the IDEA-NNN backlog row to WONTDO.
Integration:
/idea — adds an optional default-no "Validate now?" step after
capture (and after the optional GitHub Issue offer).
/handover — adds a conditional "this looks dormant, validate?"
step at the end of the integration plan, gated on the dormancy
heuristic (last commit > 90d AND zero open PRs AND no recent
issue activity). Healthy projects don't see the prompt.
CLAUDE.md skills count bumped to 35; new skills row added.
https://github.com/me2resh/apexyard/issues/130
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat: configurable voice prompts on assistant pause (AgDR-0009-voice-prompts-on-pause) (#135)
Stop hook that speaks the assistant's question aloud (Jarvis-style)
when it pauses for user input. Initial phase is macOS-only via `say`,
no voice input — user replies via keyboard.
Default OFF. Adopters opt in by overriding `voice_prompts.enabled` to
true in `.claude/project-config.json`.
Files:
- .claude/hooks/voice-prompt-on-pause.sh — Stop hook with config gate,
trigger heuristic (questions-only by default), markdown stripping,
sentence-boundary truncation, fire-and-forget say invocation
- .claude/hooks/tests/test_voice_prompt_on_pause.sh — 9 cases covering
disabled-default, enabled+question, enabled+statement, approved-pattern,
abc-menu, malformed-transcript, no-say-on-PATH, trigger-always,
markdown-stripping
- .claude/project-config.defaults.json — voice_prompts schema block
added (enabled, voice, max_chars, rate_wpm, trigger), default OFF
- .claude/settings.json — new Stop hook entry wired with the standard
ops-root resolver wrapper
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — design rationale,
options matrix (status quo / macOS say / cloud TTS / ML detection),
consequences, future phases
- docs/project-config.md — new "Voice prompts" section with override
examples and privacy notes
Test mode: hook respects VOICE_PROMPTS_SYNC=1 to run say synchronously
(test runners need this so assertions don't race against orphaned
background processes). Production invocations always run async.
Future phases (out of scope here, AgDR §"Future phases"):
- Phase 2: cross-platform TTS (Linux espeak, Windows SpeechSynthesizer)
- Phase 3: cloud TTS providers (OpenAI, ElevenLabs) — privacy AgDR-worthy
- Phase 4: voice input via Whisper-based STT
- Phase 5: per-message overrides
Refs: https://github.com/me2resh/apexyard/issues/134
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#141): add /debug skill — structured hypothesis-driven debugging (#142)
* feat(#141): add /debug skill — structured hypothesis-driven debugging
Adds a methodology skill that enforces five disciplines:
1. Capture the symptom precisely (exact URL, exact response, exact step)
2. Read the architecture before guessing (map every layer the request
touches, file by file)
3. Form a hypothesis ladder (3–5 candidates, each with an explicit
evidence test that confirms or refutes it)
4. Gather evidence first, fix second
5. Verify the fix against the original symptom evidence (re-run the
same `curl` / browser repro you used in step 4 — unit tests
verify code, not feature, correctness)
Stack appendices (Web, Desktop) carry stack-specific surface-evidence
requirements (step 1), architecture-surface maps (step 2), and
evidence-tests cookbooks (step 4). The methodology body stays portable
across stacks; appendices are where stack-specific knowledge accrues
over time.
Web appendix covers browser routing, framework configs (Next/Nuxt/Vite),
SPA-fallback layers, CDN, origin, the shared API client, backend
handlers, and auth providers. Desktop appendix covers Electron / Tauri
/ native-shell concerns: app entry points, IPC bridges, native modules,
auto-updater, sandbox / entitlements, code signing, crash reports.
Includes "When NOT to use" guidance so the methodology overhead doesn't
sandbag simple bugs (typos, off-by-ones, greenfield exploration).
Motivated by a real OAuth debug session in a managed project where
three sequential fixes chased adjacent symptoms because each was
hypothesis-then-fix without evidence in between. The skill is the
"never do that again" guardrail.
Closes #141
* fix(#141): scrub private project issue numbers from anti-pattern table
Rex review on PR #142 caught that line 155 of the skill's anti-pattern
table still named the originating PRs (#375, #377, #380) from the
private project where the methodology was first exercised. The PR body
and commit message were correctly abstracted earlier, but this in-file
reference slipped through — the leak-protection hook only scans gh
issue/pr writes, not staged file content, so mechanical enforcement
didn't catch it.
Replaced with "Three sequential PRs chasing the same symptom because
each was based on a different guess (no evidence test in between
cycles)" — same pedagogical value, zero attribution.
Refs #141
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(#143): document split-portfolio mode + add /setup privacy gate (#144)
Adopters on GitHub Free with any private project hit a silent privacy bug
following today's docs: forking apexyard makes a public fork that you cannot
later flip to private (GitHub policy), and committing `apexyard.projects.yaml`
+ `projects/<name>/` to the fork publishes private project names + handover
findings on a public GitHub repo.
This PR documents the supported workaround — split-portfolio mode — and
adds an upfront privacy gate to the /setup skill so new adopters never
hit the trip-wire silently.
docs/multi-project.md:
- New "Two setup modes — pick the one that matches your privacy needs"
section before TL;DR, with a side-by-side table and the explicit
trip-wire callout.
- Existing TL;DR retitled "TL;DR — single-fork mode (default)" with a
one-line pointer to the split-portfolio section.
- New "Split-portfolio mode — public framework + private portfolio"
section between the existing setup steps and the directory-layout
section. Includes:
- The two-repo layout (~/ops/apexyard public + ~/ops/portfolio private)
- 7-step setup walkthrough with copy-pasteable commands
- Daily workflow + upstream sync notes (both unchanged)
- Trade-offs (two repos to maintain, two clones per machine, one
upstream-sync conflict path on `projects/README.md`)
- "Migrating from single-fork to split-portfolio" recovery flow with
the explicit warning that GitHub Issue / PR edit history survives a
force-push and must be redacted separately
.claude/skills/setup/SKILL.md:
- New Step 2a: privacy gate — asks "are any projects private?" before
proposing the config. Branches on the answer:
- All public → single-fork mode
- GitHub Pro / Team / Enterprise → single-fork mode (private
forks of public repos
are supported on those
plans)
- Any private + GitHub Free → split-portfolio mode
- New Step 2b: walks through the split-portfolio setup interactively
(private repo create, sibling clone, gitignore + symlink) when the
privacy gate triggers.
- Detection: `test -L apexyard.projects.yaml` short-circuits Step 2b
for adopters already in split mode.
- Explicit "do NOT auto-migrate" rule for adopters already in single-fork
mode with private names already pushed — that path is destructive
(force-push history rewrite + redact issue/PR bodies + delete backup
branch) and warrants a deliberate, eyes-open run, not a /setup side
effect.
Out of scope for this PR (tracked separately on #143):
- `portfolio:` config block in `onboarding.yaml` schema
- Skill audit + refactor to honour configured `registry` / `projects_dir`
/ `ideas_backlog` paths instead of hardcoded fork-relative paths
- `/split-portfolio` migration helper skill that automates the recovery
flow currently documented manually
This is the docs-and-setup-question minimum-viable starter — the
framework code refactor is mechanical and lands as a follow-up.
Refs #143
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#145): portfolio config + self-healing + /split-portfolio helper (#147)
* feat(me2resh/apexyard#145): portfolio config block + self-healing + /split-portfolio helper
Closes the framework primitive deferred from me2resh/apexyard#144. Adds
first-class config-driven path resolution for the portfolio registry,
projects dir, and ideas backlog, with self-healing surfacing of broken
config at session start, plus a new /split-portfolio skill that automates
the destructive recovery flow.
Schema, helper, and hook:
- .claude/project-config.defaults.json: new portfolio: block
(registry, projects_dir, ideas_backlog) with defaults matching
today's single-fork layout
- .claude/hooks/_lib-portfolio-paths.sh: new sourceable helper exposing
portfolio_registry, portfolio_projects_dir, portfolio_ideas_backlog,
portfolio_validate, portfolio_clear_cache. Resolves relative paths
against the ops-fork root.
- .claude/hooks/check-portfolio-config.sh: new SessionStart hook —
silent on OK, one-line banner on broken config, never blocks session
- .claude/hooks/tests/test_portfolio_paths.sh: 13 cases covering
defaults, absolute/relative overrides, validate states, cache clear
Skill audit (18 SKILL.md files):
- Adds Path resolution callout pointing at the helper
- handover bash blocks now source helper and use $(portfolio_registry)
instead of literal apexyard.projects.yaml
- setup Step 2b now writes the portfolio: config block (recommended)
and validates via portfolio_validate before declaring success;
symlink approach kept as legacy fallback
New skill (.claude/skills/split-portfolio/SKILL.md):
- 10-step migration with explicit operator-confirmation gates at each
destructive step (force-push, body redaction, branch deletion)
- --verify mode: read-only state report (mode, paths, validate, drift)
- --dry-run mode: prints commands without executing
- Pre-flight refusals: already-private fork, paid GitHub plan, dirty
working tree, already-migrated state
- Step 9 writes the portfolio: config block (not symlinks) — symlink
fallback documented for adopters on older framework versions
- Step 9 surfaces the GitHub timeline-API survival caveat verbatim
- Idempotent re-runs: detects partial-migration state and resumes
Docs (docs/multi-project.md):
- Layout section describes both modes (config-block recommended,
symlink legacy) with self-healing notes
- Setup steps split into config-block mode and legacy symlink mode
- Migration section now points at /split-portfolio skill; manual
recipe preserved as fallback
AgDR (docs/agdr/AgDR-0010-portfolio-config-and-self-healing.md):
- Full Y-statement, options, decision, consequences, future phases
- Schema decision rationale: project-config.json over onboarding.yaml
because runtime path resolution belongs in project-config
Closes me2resh/apexyard#145
Refs me2resh/apexyard#146 (delivered same PR; closed manually post-merge
per the single-Closes-keyword rule in validate-pr-create.sh)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(me2resh/apexyard#145): markdownlint MD031 — blank lines around fences
CI's markdownlint-cli2 (v0.34.0) flagged the JSON + bash fenced code
blocks I added in setup/SKILL.md Step 2b without surrounding blank
lines. Added the required blanks. No content change.
Refs me2resh/apexyard#147
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>
* docs(me2resh/apexyard#148): correct privacy-gate wording — adopter action, not framework auto-publish (#149)
The privacy-gate wording introduced in PR #144 (and unchanged in PR #147)
attributed the publication to the framework rather than the adopter:
"the standard fork-and-commit setup will silently publish your private
project names on a public GitHub repo"
That's factually wrong. ApexYard never pushes anything without explicit
operator approval — the publication only happens when the adopter
themselves runs git push. The "silently publish" framing read as if the
framework auto-publishes, which is misleading and undermines trust in
the rest of the framework's safety claims.
Two prose-only edits, no code, no behavior change:
- .claude/skills/setup/SKILL.md Step 2a — replaced "will silently
publish ..." with adopter-action language ("you might accidentally
publish ... a stray git push after registering them — I won't push
without your approval, but the risk is on the adopter once the data
is committed locally")
- docs/multi-project.md trip-wire callout — replaced "silently publish
their portfolio names the moment they push" with "risk accidentally
publishing their portfolio names with a stray push (the framework
itself never pushes without operator approval, but once the registry
is committed locally the next push exposes it)"
Verified: `grep -r "silently publish" .claude/skills/ docs/` returns no
hits.
Closes me2resh/apexyard#148
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#150): bootstrap-skill exemption + Bash-write coverage (#152)
Closes the legitimate-bypass case (me2resh/apexyard#150) and the
illegitimate-bypass case (me2resh/apexyard#151) together so the
ticket-first gate is coherent. Shipping either alone would leave a
window where the framework is internally inconsistent — see AgDR-0011
for the full rationale.
Bootstrap exemption (me2resh/apexyard#150):
- .claude/session/active-bootstrap marker, written by /setup,
/handover, /update, /split-portfolio on entry; cleared on exit
- SessionStart sweep (clear-bootstrap-marker.sh) for stale markers
from interrupted sessions
- require-active-ticket.sh reads the marker and exempts skills on
the configured ticket.bootstrap_skills list
- bootstrap_skills list lives in .claude/project-config.defaults.json
(extendable per fork via .claude/project-config.json)
Bash-write coverage (me2resh/apexyard#151):
- new _lib-detect-bash-write.sh — heuristic detector for output
redirection, tee, sed -i, awk -i inplace, python/node/ruby
embedded interpreters
- require-active-ticket.sh + require-migration-ticket.sh now fire
on Bash in addition to Edit|Write|MultiEdit
- design choice: false-negatives preferred over false-positives
(the matcher errs toward "let through" rather than block legit
read-only commands)
Tests: 32 unit cases on the lib + 12 integration cases on the hook,
including the exact me2resh/apexyard#151 bypass repro from the issue
body.
Closes me2resh/apexyard#150
Will manually close me2resh/apexyard#151 post-merge per the
single-Closes-per-PR rule (precedent: AgDR-0010 / PR #147).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#153): extend Bash-write matcher beyond first-version coverage (#155)
Closes me2resh/apexyard#153.
Extends `_lib-detect-bash-write.sh` (introduced for me2resh/apexyard#151
in PR #152) with the matcher families flagged by Rex's review of #152.
AgDR-0011 already frames the matcher as a living list extended on
observation; this commit just walks the list.
New matcher families:
- File-moving builtins: `cp`, `mv`, `rm`, `dd`, `install` (anchored at
command-start; `--help`/`--version` and `git rm`/`git mv` excluded)
- Archive / network writes: `tar -x` / `tar --extract`, `curl -o` /
`--output`, `wget -O` / `--output-document`
- Additional embedded interpreters: `perl -e`, `php -r` (keyword-gated
like python/node/ruby); `go run`, `deno run`/`deno script.ts`,
`bun run`/`bun script.ts` (categorical script runners)
- Python helpers: `pathlib.Path().touch()`, `shutil.copy*`,
`shutil.move`, `os.rename` added to the `python -c` and python
heredoc keyword list
- Heredoc variants for `ruby` and `node` (previously only python
heredoc was covered)
Extractor extensions:
- `cp` / `mv`: last positional arg
- `curl -o` / `--output`: file argument
- `wget -O` / `--output-document`: file argument
- `tar -x`, `go run`, `deno`, `bun`, `perl -e`, `php -r`: return empty
(caller applies gate categorically per AgDR-0011)
Test count rose from 32 to 86. Negative-class counterexamples cover
the trickiest false-positive surfaces: `tar -t` listing, `cp --help`,
`rm --version`, `git rm`, `curl` bare URL fetch, `wget` bare URL fetch,
`deno fmt`, `deno test`, `go build`. Existing
`test_require_active_ticket_bash.sh` regression suite still passes 12/12.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* test(#154): mock gh in test sandboxes to remove live-tracker dependency (#156)
- add _lib-mock-gh.sh helper that installs a fake `gh` on the sandbox PATH;
intercepts `gh issue view <N> ... --json ...` and returns synthetic
`{"number":N,"state":"OPEN"}` (overridable per-num via mock_gh_set_state)
- wire the shim into test_single_closes_per_pr.sh and
test_validate_pr_required_sections.sh so the validator's CLOSED-issue
refusal no longer breaks the suite when upstream issues are closed
- both files previously failed every case (0/13 and 0/8) because their PR
titles reference #114 / #113, which are now CLOSED upstream
- post-fix: 13/13 and 8/8; full suite remains green
Closes me2resh/apexyard#154
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#132): structured CEO marker + same-turn merge in /approve-merge (#158)
* feat(#132): structured CEO marker + same-turn merge in /approve-merge
Closes me2resh/apexyard#132 (drop the "stop before merge" rule) and
me2resh/apexyard#48 (harden CEO marker against self-approval bypass)
together. The two threads compose — see AgDR-0012 for the full
rationale.
Streamline (#132):
- /approve-merge now runs `gh pr merge --squash --delete-branch`
in the same turn as the marker write, by default
- --no-merge opt-out preserves the deferred-merge case
- The discrete approval moment is the SKILL INVOCATION, not a
follow-up "now do the merge" message
Harden (#48):
- CEO marker is now a structured key/value file with required fields:
sha=<HEAD>
approved_by=user
skill_version=2
Validated by block-unreviewed-merge.sh; bare-SHA legacy markers
rejected with a clear "stale format" error pointing at /approve-merge
- The model's bare `echo SHA > <pr>-ceo.approved` bypass is now
mechanically rejected. Forging the structured fields requires a
deliberate, visible rule violation rather than a one-line accident
- Optional audit fields (approved_at, approval_summary) capture the
"what did the user say when they approved" trail
- Rex marker stays bare-SHA — different threat model (automated
reviewer, not human authorization moment)
pr-workflow.md reframed: "the load-bearing rule is explicit per-PR
approval, not two user messages." The merge is a deterministic
consequence of the approval invocation.
Tests: 12 cases on the hardened hook covering the new format end-to-end
(valid v2, missing rex/ceo, bare-SHA legacy rejected, missing
approved_by, wrong approved_by, skill_version=1, sha mismatch,
non-merge no-op, gh-api shape gated). Full suite: 205/205 across 13
test files.
Will manually close me2resh/apexyard#48 post-merge per the
single-Closes-per-PR rule (precedent: PR #152 / AgDR-0011).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#132): redact private project reference from AgDR-0012
Abstracted two references to a registered private project that named
the project's owner/repo. The leak-protection hook caught one in the
PR body; this fixup removes the matching references from the
AgDR-0012 file content (which would otherwise have shipped the names
to me2resh/apexyard public repo via the merge).
The pre-existing reference at .claude/rules/pr-workflow.md:130
(documenting #47) is untouched — it predates this PR and is already
on the public repo's history.
Refs me2resh/apexyard#132.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#132): markdownlint blanks-around-fences + typo fix
Two small fixups against red CI / Rex feedback:
- AgDR-0012 line 63: fenced code block now has a blank line before it
(MD031). markdownlint-cli2 0.13.0 was rejecting the indented fence
inside the bullet because the fence's preceding line was the bullet
text (no blank).
- approve-merge SKILL.md line 172: typo `deferes` → `defers` (Rex flag
on PR #158).
Refs me2resh/apexyard#132.
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>
* chore(#157): remove voice-prompts feature + correct hook/skill counts (#161)
* chore(#157): remove voice-prompts-on-pause feature + correct hook/skill counts
Closes me2resh/apexyard#157 (sunset the voice-prompts feature) and
me2resh/apexyard#77 (hook count off-by-one in CHANGELOG / CLAUDE.md)
in one bundled PR. AgDR-0013 supersedes AgDR-0009 with the full
rationale; both AgDRs are preserved (decision records are append-only
history).
Removed (#157):
- .claude/hooks/voice-prompt-on-pause.sh
- .claude/hooks/tests/test_voice_prompt_on_pause.sh
- Stop matcher block in .claude/settings.json (became empty after
voice removal)
- voice_prompts block in .claude/project-config.defaults.json
- "## Voice prompts" section in docs/project-config.md
- voice_prompts mention in AgDR-0010 line 32 (replaced with
leak_protection / ticket as still-current example config blocks)
Preserved:
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — historical record;
new "Superseded by: AgDR-0013" header at the top
- AgDR-0010 line 115 reference to AgDR-0009 — still accurate as a
historical pattern reference
Counts corrected (#77):
- CHANGELOG.md v0.3.0 stats: "17 hooks" → "18 hooks" (historical
fix — at v0.3.0 there were actually 18 hooks)
- CLAUDE.md table line: "18 shell scripts" → "24 shell scripts"
(current count after this removal)
- CLAUDE.md table line: "35 slash commands" → "39 slash commands"
- CLAUDE.md "Available skills (34)" → "Available skills (39)"
- CLAUDE.md quick-reference "Skills (35 slash commands)" →
"(39 slash commands)"
Why bundled: #77's correct count depends on whether voice is still in
the framework. Shipping #77 before #157 would write a number that's
wrong by one again the moment #157 lands. Same shape as previous
bundles (PR #152 / AgDR-0011, PR #158 / AgDR-0012).
Why no adopter-facing changelog mention of the voice removal: the
feature never reached a tagged release on main. v1.1.0 didn't have
it; v1.2.0 won't have it. From the adopter's perspective there's
nothing to retire. AgDR-0013 captures the framework's internal record
for future contributors. See AgDR-0013 § "No adopter-facing changelog
mention".
Tests: full hook test suite green (196 cases across 12 files —
test_voice_prompt_on_pause.sh removed). No regressions.
Will manually close me2resh/apexyard#77 post-merge per the
single-Closes-per-PR rule (precedent: PRs #152, #158).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#157): unwire voice from settings + configs + docs + AgDR-0013
Continuation of d09d7b5 (the file deletions). Squash-merge will
collapse both into one PR commit. This commit captures:
- .claude/settings.json — Stop matcher block removed (was the only
hook in it; entire matcher gone)
- .claude/project-config.defaults.json — voice_prompts block + its
_comment removed
- docs/project-config.md — "## Voice prompts" section removed
- docs/agdr/AgDR-0009-voice-prompts-on-pause.md — "Superseded by"
header added at the top, content otherwise preserved as history
- docs/agdr/AgDR-0010-portfolio-config-and-self-healing.md — line 32
example reference swapped from voice_prompts to
leak_protection / ticket (still-current config blocks)
- docs/agdr/AgDR-0013-sunset-voice-prompts.md — new supersession AgDR
- CLAUDE.md — hook count 18 → 24, skill count 35 → 39 (three
occurrences each, all aligned to current reality)
- CHANGELOG.md v0.3.0 stats — "17 hooks" → "18 hooks" historical fix
(#77 acceptance criterion 1)
Refs me2resh/apexyard#157 + me2resh/apexyard#77.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#157): markdownlint MD028 + resolve stale AgDR-numbering ref
Three small fixups against Rex CHANGES-REQUESTED on PR #161:
- AgDR-0009 line 3: promote "Superseded by:" header out of a
blockquote. The original "I decided ..." canonical blockquote at
line 5 was being merged with the new supersession blockquote
(markdownlint MD028 — "no blanks inside blockquote").
- AgDR-0013 line 3: same shape — "Supersedes:" header now a plain
bold paragraph, canonical "I decided ..." blockquote untouched.
- approve-merge SKILL.md line ~187: stale conditional reference
"AgDR-0012 (or 0013 — depends on whether voice-removal lands
first)" — order is now resolved (12 = approve-merge bundle, 13 =
voice removal). Drop the parenthetical.
Refs me2resh/apexyard#157 + me2resh/apexyard#77.
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(#160): multi-tab terminal demo on the landing site (#162)
Landing-site `site/index.html` terminal demo previously played one
canonical flow ("one ticket, start to finish") on autoplay. With the
v1.2.0 skill surface expansion (4 new skills, 8+ new hooks), one flow
no longer represents the framework's breadth.
Adds tabs to the terminal chrome — four flows visitors can either let
auto-cycle or click directly:
1. one ticket — existing flow, unchanged content
2. /handover — adopt an external repo into the portfolio
3. /setup — first-run framework bootstrap on a fresh fork
4. /fan-out — spawn 3 parallel agents on independent tickets
Auto-advance: on completion of the active tab's script, the demo
pauses ~1.8s then advances to the next tab. Loops at the end. User
can interrupt by clicking any tab or hitting Replay.
Implementation:
- HTML chrome — single title span replaced with a tablist of 4 button
tabs. ARIA `role="tablist"` / `role="tab"` / `aria-selected` so
keyboard + screen-reader users get the same semantics as sighted
ones.
- CSS — new `.shell-demo__tabs` + `.shell-demo__tab` with an accent
underline on the active tab. Tabs scroll horizontally on narrow
viewports (mobile responsive).
- JS — refactored the existing IIFE from one `script` array to an
array of four. Added `setActiveTab()` for ARIA state, `play(idx)`
takes a tab index, end-of-script auto-advances to `(idx + 1) % N`
unless the user clicked away during the pause. Adds a new `cmd`
type alongside `you` for slash-command invocations (renders with
the same `>` prompt prefix). prefers-reduced-motion still bails
early and leaves the static seed visible.
- Static seed (the non-JS fallback) still shows tab 0's content, so
reduced-motion / no-JS visitors see the one-ticket flow as before.
Hero metrics also corrected to current reality (#77 / PR #161 covers
CLAUDE.md and CHANGELOG.md; this PR catches the same numbers in the
landing site):
Skills 32 → 39
Hooks 18 → 24
The tabs ship in v1.2.0 alongside the framework changes that make
the new flows worth showcasing.
Refs me2resh/apexyard#160 (release v1.2.0 + landing-site refresh).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#163): default the split-portfolio sibling repo name to <fork>-portfolio (#164)
Closes me2resh/apexyard#163.
The split-portfolio mode docs and skills previously suggested
`your-org/ops` as the default name for the private sibling repo, and
`portfolio/` as the local clone directory. Both too generic — adopters
running multiple ops setups end up with `your-org/ops` collisions, and
a bare `portfolio/` dir gives no signal about which framework it
belongs to when it sits next to other unrelated `portfolio/` dirs.
`<fork>-portfolio` is now the default — keeps the relationship to the
public fork explicit on disk and on GitHub. If the fork is named
`your-org/apexyard`, the portfolio defaults to
`your-org/apexyard-portfolio`. If the fork was renamed (e.g. `cos`),
the portfolio defaults to `cos-portfolio`. Adopters with custom names
keep working — the `portfolio:` config block resolves whatever path
they configured.
Files updated:
docs/multi-project.md
- Layout diagrams use `apexyard-portfolio/` as the sibling
- Setup walkthrough Step 2 + Step 3 use `your-org/apexyard-portfolio`
and explain the `<fork>-portfolio` pattern
- Config-block + symlink path examples updated to
`../apexyard-portfolio/...`
- Daily workflow + cross-machine clone commands updated
- The two existing `your-org/ops` references that remain are
fork-rename examples (lines 52, 64) — kept as-is, since renaming
the fork to `ops` is still valid (the portfolio would then
default to `ops-portfolio`)
.claude/skills/setup/SKILL.md
- Step 2b's "default suggestion" for the private repo name is now
`your-org/<fork>-portfolio`, computed dynamically from the
fork's repo name via `gh repo view --json name -q .name` so the
suggestion is correct even when the fork was renamed
- Clone command no longer needs a second arg — the repo name IS
the directory name
- Config-block paths updated to `../apexyard-portfolio/...`
.claude/skills/split-portfolio/SKILL.md
- Step 3's suggested-name template is now `<account>/<fork>-portfolio`
with the same dynamic-fork-name resolution
Mechanism unchanged. The `portfolio:` config block in
`.claude/project-config.json` still takes any path; this PR is purely
default-suggestion + example prose.
No tests required (skills are markdown instructions; no automated
coverage today).
Refs `apexyard.projects.yaml.example` uses "ops repo" as a generic
term meaning "the operational management fork" — not a name — kept
as-is.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#165): skills reference page on the landing site + changelog link (#167)
Closes me2resh/apexyard#165.
Adds a public, browseable index of every apexyard slash command
alongside a one-click changelog link from the homepage nav.
site/skills.html (new):
- Lists all 39 skills currently shipping in .claude/skills/
- Each entry: slash command, argument hint, description (taken
verbatim from the SKILL.md frontmatter so the page matches the
runtime exactly)
- 10 categories: Setup & onboarding, Daily ops, Tickets & ideas,
Specs & decisions, Code review & merge, Architecture & dev tools,
Production-readiness audits, Workflow primitives, Communications,
Deprecated
- Same brutalist-terminal design tokens as the homepage — JetBrains
Mono, paper-cream background, single warning-red accent, sharp
corners. Inlined CSS to keep the static-only no-build-step
convention; design vars duplicated rather than extracted to a
shared file (~18 vars; cheap to keep in sync).
- Mobile responsive — skill grid collapses to single-column under
720px; titlebar nav hides non-CTA items on narrow viewports.
- Reduced-motion friendly (no animation in the first place).
- Internal anchor TOC at the top so the page scans in seconds.
site/index.html (nav addition):
- Added two nav links to the titlebar between "what's in the box"
and the github CTA:
• skills → ./skills.html
• changelog → https://github.com/me2resh/apexyard/releases
- The changelog link points at the GitHub releases page (not the
raw CHANGELOG.md file) so it auto-resolves to the latest tagged
release on each visit. v1.2.0 lands and the link is already there.
No new dependencies, no build step, no JS for the skills page. The
existing site convention (one-html-file-per-route, inlined CSS,
optional progressive-enhancement JS) is preserved.
Refs me2resh/apexyard#160 (release v1.2.0 + landing-site refresh) —
this is the second site-side deliverable for that ticket; the release
tag itself follows once me2resh/apexyard#159 (testing) closes.
Follow-up worth a separate ticket: a small generator script that
walks .claude/skills/*/SKILL.md, parses YAML frontmatter, and emits
the skills.html sections automatically. Out of scope for v1.2.0 —
first version is hand-curated and will need maintenance until that
generator lands.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#168): accept release/vN.N.N branches + release(...) PR titles (#169)
Closes me2resh/apexyard#168.
The /release skill prescribed `release/vA.B.C` as the source-branch
name and `release: vA.B.C` as the PR title for the dev → main release
PR (per AgDR-0007). Both were rejected by the framework's own
validators:
validate-branch-name.sh required {type}/{TICKET-ID}-{description};
release/v1.2.0 has no ticket-id portion.
validate-pr-create.sh required type(SCOPE): form with `release` not
in pr.title_type_whitelist.
The contradiction surfaced cutting v1.2.0 — the first release under
the dev/main model.
Three small changes:
1. .claude/hooks/validate-branch-name.sh — added an early-out branch
that accepts ^release/vN.N.N(-rcN)?$ as a valid name. Narrow,
intentional exception for the framework's release-cut convention;
release branches don't carry a ticket-id because the release itself
IS the ticket.
2. .claude/project-config.defaults.json — added "release" to
pr.title_type_whitelist so a title like `release(#160): v1.2.0`
passes validate-pr-create.sh's existing regex unchanged.
3. .claude/skills/release/SKILL.md step 4 — corrected the prescribed
PR title to `release(#<release-ticket>): vA.B.C` so future /release
invocations produce a title that satisfies the validators by
construction.
Tested:
bash .claude/hooks/validate-branch-name.sh against:
release/v1.2.0 → 0 (allowed, release-special-case)
release/v1.2.0-rc1 → 0 (allowed, RC variant)
release/v9.9.9 → 0 (allowed)
release/foo → 2 (correctly blocked)
release/v1 → 2 (correctly blocked)
chore/GH-168-fix → 0 (allowed, standard pattern)
feature/GH-1-x → 0 (allowed, standard pattern)
Full hook test suite: 196/196 cases green across 12 test files.
Refs: surfaced 2026-05-04 cutting the first release under AgDR-0007.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#170): exempt release/vN.N.N from validate-pr-create's branch-id check (#171)
Closes me2resh/apexyard#170.
Completes the work started in #169 (closing #168). #169 added a
release-pattern early-out to validate-branch-name.sh so release/vN.N.N
branches pass the branch-name validator. But validate-pr-create.sh has
its own independent branch-id check at line 273 that #169 didn't
touch — and it still rejects release/v1.2.0 because that name doesn't
contain a ticket-id substring.
This is the same class of contradiction #168 fixed; the fix is the
same shape. Add the same release-pattern early-out to the branch-id
check in validate-pr-create.sh:
- if the branch matches ^release/vN.N.N(-rcN)?$ → exempt (release
branches don't carry ticket-ids; the release itself is the ticket)
- otherwise → require a ticket-id substring as before
#168's acceptance criterion 3 ("validate-pr-create.sh accepts a PR
title `release(#160): v1.2.0` against the `release/v1.2.0` branch")
was checked off based on the title regex alone but didn't catch the
secondary branch-id check living in the same file. Surfaced trying
to open the v1.2.0 release PR.
Tested:
bash .claude/hooks/validate-pr-create.sh against:
release/v1.2.0 → 0 (allowed, exempt)
release/v1.2.0-rc1 → 0 (allowed, RC variant)
chore/GH-1-fix → 0 (allowed, has ticket-id)
release/foo → 2 (correctly blocked)
chore/no-ticket → 2 (correctly blocked)
Refs me2resh/apexyard#168 (the parent bug) + #169 (the partial fix).
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(#173): sync CHANGELOG.md from main → dev (#174)
Closes me2resh/apexyard#173.
The release-cut model (AgDR-0007 / #116) squash-merges
release/vN.N.N → main, which means the v1.2.0 CHANGELOG section that
landed on main via PR #172 was never propagated back to dev. This
commit copies main's CHANGELOG.md verbatim onto dev so the v1.2.0
section is now present on both branches.
The diff is exactly the v1.2.0 entry being prepended; no other lines
change.
Without this sync, the next release PR cut from dev would build a
v1.3.0 section on top of v1.1.0, silently dropping v1.2.0 from dev's
running history. The corollary skill-level fix (option B in the
ticket) — updating /release to source the previous CHANGELOG from
upstream/main — is filed as a follow-up and out of scope for this PR.
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(#181): /agdr skill — searchable AgDR library (#186)
Adds /agdr — a portfolio-wide index for Agent Decision Records.
Walks apexyard.projects.yaml, reads each project's docs/agdr/*.md
(local clone if available, else gh api fallback), parses the optional
YAML frontmatter for category + projects, and answers four queries:
- /agdr browse list across the portfolio, grouped by category
- /agdr search <term> full-text grep across all bodies, returns
<project>/AgDR-NNNN paths + matching paragraph
- /agdr show <id> print a specific record, disambiguates duplicates
- /agdr stats counts per category (the marketing-slide tile,
now backed by real data)
Six-category taxonomy: architecture | tech-stack | security | patterns
| integrations | other. Legacy AgDRs without frontmatter remain
first-class — they bucket as `other` and are flagged in browse so
operators can migrate at their own pace.
Backwards-compatible template change: templates/agdr.md gains an
optional `category:` (and optional `projects:`) line in the existing
frontmatter block. Omitting the line keeps every existing AgDR valid;
the skill defaults to `other` when missing.
Doc note in workflows/sdlc.md § Phase 2 points at /agdr search for
"have we decided this before?" lookups before drafting a design.
Smoke test in .claude/hooks/tests/test_agdr_skill.sh covers the
parser the spec specifies — frontmatter extraction, category
bucketing (including the legacy default-to-other path), id reading,
stats aggregation, and search match counts. 18 assertions, all green;
12 pre-existing test suites also green (no regressions).
Closes #181
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(me2resh/apexyard#183): /launch-check trend tracking (#185)
- Persist each run as JSON under <projects_dir>/<name>/launch-check/runs/
(timestamp + branch + commit + per-dimension scores + verdict + top_risks)
- Append "Trend (last 5 runs)" section to the per-run summary when at least 2
prior runs exist — markdown table + ASCII score chart
- Add /launch-check trend mode for read-only trend rendering (no full audit)
- Auto-derive notes column from score-delta vs previous run
(e.g. "Security +12, Analytics +10")
- Opt-in commit via .launch-check-history-tracked marker; gitignored by default
- New helper script render-trend.sh + test_launch_check_trend.sh (21 cases)
- Resolve projects dir via portfolio_projects_dir helper (no hardcoded path)
- Schema is forward-compatible — extra fields preserved on framework upgrade
AgDR-0014 documents the chart format / schema / opt-in choices.
Closes me2resh/apexyard#183
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* feat(#182): /status --briefing + bin/apexyard status CLI shim (#187)
* feat(me2resh/apexyard#182): /status --briefing + bin/apexyard CLI shim
Make slide 6's `$ apexyard status` invocation real — the smallest of three
slide-reality tickets, demonstrating the marketing-to-implementation
pattern before #181 / #183 land.
- New helper at .claude/skills/status/briefing.sh — computes the 4-line
"where am I" briefing (active workspace, active ticket, branch,
role-set) so the logic is testable in isolation and runnable from a
plain shell. Workspace inference walks up from cwd to the ops-fork
root (same algorithm as _lib-portfolio-paths.sh and /start-ticket).
- Ticket reads from .claude/session/tickets/<workspace> first, falls
back to .claude/session/current-ticket. Role-set is inferred from
the active ticket's GitHub labels (v1: backend / frontend / qa /
security / platform / sre / data / ux / ui / product / tech-lead,
plus the long forms). No match emits the explicit "<none — inferred
per task>" placeholder so the four-line shape is constant.
- New CLI shim bin/apexyard delegates `apexyard status` to the same
helper after walking up to find the ops-fork root. Works from any
workspace/<name>/ clone or the fork itself; symlink onto PATH to
install (no shadowing of the `claude` binary).
- /status SKILL.md gains a "Briefing mode" section documenting the
--briefing / -b flag; default /status output is unchanged.
- docs/multi-project.md "Daily workflow" demonstrates the new
invocation alongside /inbox and /status.
- New smoke tests at .claude/hooks/tests/test_status_briefing.sh — 8
cases covering ops-root cwd, workspace cwd, unknown cwd, ops-fallback
marker, per-project marker priority, label-based role inference, the
no-matching-label path, and the constant-four-line shape.
Closes me2resh/apexyard#182
* fix(me2resh/apexyard#182): replace curios-dog with example-app in SKILL.md output sample (leak fix)
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(#178): LSP integration spike — measurement + recommendation (#184)
* docs(#178): LSP spike — token-savings measurement + integration findings
- Phase 1: estimated token cost A vs B for three representative queries on a real TS Lambda backend (~9,750 LOC); shallow semantic queries see ~3-23x input-token savings, multi-hop traces see ~1.4x
- Phase 2: verified Claude Code shipped first-party LSP support in v2.0.74 (Dec 2025), gated behind ENABLE_LSP_TOOL=1, wired in via the plugin system (.lsp.json); MCP-wrapped LSP shims (cclsp, lsp-mcp) remain as fallback
- Phase 3: recommends Option 3 — interactive clone-first prompt at end of /handover, preserving today's "never auto-clone" principle while making the deep-dive path discoverable
- Phase 4: AgDR Y-statement and option matrix sketched; full AgDR is a follow-up if the spike says go
- Recommendation: GO. Adopt the built-in tool, document the opt-in path, change /handover to offer clone-first interactively. No code changes beyond /handover SKILL.md; no novel integration to maintain
Closes me2resh/apexyard#178
* docs: fix markdownlint MD031/MD032 in spike report
Add blank lines around lists and a fenced code block to satisfy
markdownlint-cli2 in CI. Pure formatting, no content change.
Refs me2resh/apexyard#178
---------
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* docs(me2resh/apexyard#190): annotate LSP-aware skills with opt-in callouts (#191)
- Add identical-shape "LSP-aware (optional, recommended)" callout near
the top of each of the four code-aware SKILL.md files.
- Per-skill savings number is the only variation:
- /code-review: ~3-15× cheaper for semantic queries
- /threat-model: ~3-15× shallow, ~1.4-5× multi-hop traces
- /security-review: ~3-15× cheaper for semantic querie…
Merged 72 upstream commits (v2.3.0 through v3.1.4) into the fork. Conflict resolution strategy: - Framework files (.claude/, docs/, workflows/, .github/): accepted upstream - site/ (GA4 + custom content): kept local - CLAUDE.md: accepted all upstream additions (new roles, skills, counts) - .gitignore: merged both sides (upstream onboarding.yaml comment + local split-portfolio v2 block) New in upstream: - Solution Architect role + Tariq agent (AgDR-0054) - /design-review and /approve-architecture skills - /report-apexyard-bug and /request-apexyard-feature skills - /release-sync skill - Loop mode rule (AgDR-0068) - 40 hooks (up from 24), 12 rules (up from 11), 59 skills (up from 54) Refs #3 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- .claude/framework-version set to v3.1.4 (first anchor, fork was on v2.2.0)
- .claude/project-config.json: add portfolio.{registry,projects_dir,ideas_backlog}
pointing to ../apexyard-portfolio (resolves startup hook error about missing registry)
Both v1.x migration scripts confirmed no-op: .apexyard-fork marker already
present (split-portfolio v2 was configured in a prior session).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
origin/main had 2 commits from PR #2 (split-portfolio v2 config) that were missing from the sync branch when it was cut from local main. Conflict resolution: - Framework files: kept HEAD (v3.1.4 upstream) - CLAUDE.md: kept HEAD counts (40 hooks, 12 rules) - project-config.json: took origin/main's updated markdownlint glob pattern Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Hook count: 39 → 40 across site/llms*.txt, site/*.md.gen, site/architecture.html, site/skill.md - Version pill: v2.3 → v3.1, softwareVersion: 2.3.0 → 3.1.4 - Releases shipped: 13 → 19 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
||
| - name: Run markdownlint-cli2 | ||
| uses: DavidAnson/markdownlint-cli2-action@v16 | ||
| uses: DavidAnson/markdownlint-cli2-action@v23 |
| persist-credentials: false | ||
|
|
||
| - name: Run analysis | ||
| uses: ossf/scorecard-action@v2.4.3 |
|
|
||
| - name: Run gitleaks | ||
| uses: gitleaks/gitleaks-action@v2 | ||
| uses: gitleaks/gitleaks-action@v3 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Syncs 72 upstream commits (v2.3.0 → v3.1.4). Closes #3