release(#604): v3.1.1#605
Conversation
#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. #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>
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: #110 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
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>
… 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. #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>
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: #107 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
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 #112
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
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. #113 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
* 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). #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>
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 #108 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
- 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 #117 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
…work 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. #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>
…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). #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>
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.
#130
Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
…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: #134 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
…#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>
…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>
…#147) * feat(#145): portfolio config block + self-healing + /split-portfolio helper Closes the framework primitive deferred from #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 #145 Refs #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(#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 #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>
…ork 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 #148 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the legitimate-bypass case (#150) and the illegitimate-bypass case (#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 (#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 (#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 #151 bypass repro from the issue body. Closes #150 Will manually close #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>
…155) Closes #153. Extends `_lib-detect-bash-write.sh` (introduced for #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>
…cy (#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 #154 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
…#158) * feat(#132): structured CEO marker + same-turn merge in /approve-merge Closes #132 (drop the "stop before merge" rule) and #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 #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 #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 #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>
…#161) * chore(#157): remove voice-prompts-on-pause feature + correct hook/skill counts Closes #157 (sunset the voice-prompts feature) and #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 #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 #157 + #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 #157 + #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>
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 #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>
…portfolio (#164) Closes #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>
#167) Closes #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 #160 (release v1.2.0 + landing-site refresh) — this is the second site-side deliverable for that ticket; the release tag itself follows once #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>
…169) Closes #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>
…d check (#171) Closes #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 #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>
Closes #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>
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>
…ge (#564) The merge gate resolves the marker dir via _lib-ops-root.sh::resolve_ops_root (pin-first), but the code-reviewer agent and /approve-merge skill resolved it with a plain walk-up. In split-portfolio mode that walk-up lands on the private portfolio sibling (it has onboarding.yaml + apexyard.projects.yaml) where _lib-review-markers.sh doesn't exist — so markers were written to the wrong dir under a bare (unqualified) name and the gate couldn't see them, blocking every managed-project merge until hand-relocated. - code-reviewer.md + approve-merge/SKILL.md now read the session pin first (~/.claude/apexyard/ops-root-<session>), validate + self-heal a stale pin, and fall back to walk-up only when no valid pin exists — matching the gate. - Verified from a workspace clone: resolves MARKER_HOME to the real ops fork and produces the repo-qualified marker path the gate expects. Follow-up to #230 (which fixed the gate's read path; this fixes the writers). Refs #559 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
…#565) Step 6 (Tag + push) now explicitly: - Requires tagging upstream/main (the squash commit) not the release-branch HEAD - Includes a mandatory ancestry guard before pushing the tag: git merge-base --is-ancestor vA.B.C upstream/main exits non-zero → error + abort before the mis-placed tag is pushed - Adds a post-tag release checklist with three assertions (ancestry, git-describe discoverability, and exact-tip equality) - Notes that v2.3.0 re-pointing is operational / maintainer-handled (out of scope) Rule 6 updated to name the squash-merge mechanics and reference step 6. Refs #550 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
…566) - _lib-extract-push-ref.sh: strip shell redirections (2>&1, 2>, >, |) before token parsing so redirect operators are never mistaken for branch names - _lib-extract-push-ref.sh: for src:dst refspecs, validate the DST (right of colon) — not the src — because the dst is what lands on the remote; src may be HEAD, a SHA, or a local tracking ref that doesn't follow the branch-name convention - _lib-extract-push-ref.sh: add is_tag_push() helper that returns true for --tags, `tag <name>`, and refs/tags/ forms - validate-branch-name.sh: call is_tag_push() before extract_push_ref() and exit 0 when true — a branch-name validator has nothing to check on a tag push - test_validate_branch_name_pushref.sh: 13 new cases covering all three fix axes plus regression guard for #194 behaviour; 28 total, all pass Refs #547 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
- Replace the raw **/*.md glob in the markdownlint pre-push command with a git ls-files '*.md'-derived file list, so untracked markdown (installed-skill bundled docs, .claude/session/*.md scratch files, symlinked dirs) is never linted by the gate — eliminating false push failures for adopters with untracked markdown in the working tree - Add a graceful skip when no tracked .md files exist (new repo or branch with no markdown changes) - Add two regression-guard test cases to test_pre_push_gate.sh: case8 proves a lint-dirty UNTRACKED file does NOT block the push; case9 proves a lint-dirty TRACKED file still blocks it Refs #548 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…session cwd (#580) * fix(#549): block-main-push checks the operation's target branch, not session cwd The hook previously resolved the current branch via `git branch --show-current` in the hook's cwd (the harness primary checkout). When the primary checkout sat on a protected branch (e.g. `dev`) while the actual operation targeted a feature-branch worktree, the hook false-blocked legitimate commits and pushes. Push path: reuse `_lib-extract-push-ref.sh` (the same lib `validate-branch-name.sh` already uses for the same worktree-cwd problem — #194, #547) to read the DESTINATION branch directly from the push command. Tag pushes remain no-ops. Falls back to local HEAD only when no explicit ref is present in the command (bare `git push` relying on upstream tracking). Commit path: detect the `cd <path> && git commit` compound shell pattern and resolve the branch of the TARGET worktree via `git -C <path> branch --show-current` instead of the session cwd. Plain `git commit` (no `cd` prefix) retains the original behaviour. Tests: 26 cases covering (a) push to protected still blocks, (b) push to feature passes when cwd=dev, (c) tag push passes, (d) cd wt && commit on feature-branch wt passes while cwd=dev, (e) plain commit on protected still blocks. Refs #549 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#549): strip cd-path quotes + resolve last cd target (review fix) Addresses the #580 Rex review: - block-main-push.sh now strips surrounding quotes from the `cd <path>` target before `git -C`. Without this, `cd "path" && git commit` passed quotes to git -C → error → empty branch → the protected-branch check was skipped and a commit into a protected worktree slipped through (a false-negative in the dangerous direction). - Resolves the LAST `cd` in a chain (cd a && cd b && commit → b), fixing the secondary over-block Rex noted. - 5 new tests: quoted (double/single) cd into protected → blocks; quoted cd into feature → passes; chained cd resolves last target both directions. Suite now 31/31. Refs #549 * test(#549): drop dead WT_PROTECTED scaffolding (shellcheck SC2034) The original test left abandoned WT_PROTECTED + wt-main worktree setup from a first approach; the active assertion uses a secondary sandbox (SB2). shellcheck flagged WT_PROTECTED as unused (SC2034), failing CI. Removed the dead lines. Refs #549 --------- Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…rgs (#581) - Strip fd-to-fd (`2>&1`), combined (`&>`), append (`>>`), and overwrite (`>`) redirections from the `gh pr merge` span before scanning for the PR number — a digit that is part of a redirect operator can no longer be mistaken for a PR number. - Require the first post-`merge` token to be a bare integer; if it is an unexpanded shell variable (`$pr`, `${PR_NUMBER}`) return empty so the existing `gh pr view` fallback resolves the PR, rather than latching onto a stray digit (the `2` in `2>&1`). - Use `grep -oE` for word-boundary matching after `merge` instead of BSD-sed `\b` which is not portable to macOS. - Add `test_extract_pr.sh` — 14 focused unit tests for `extract_pr_number` covering the bug repro, all redirect forms, unexpanded variables, the `gh api` URL path shape, and edge cases (no number, non-merge command). Live repro (issue #568): `gh pr merge $pr --squash 2>&1 | tail` with $pr unexpanded at hook-eval time caused `block-unreviewed-merge.sh` to block with "PR #2 has no recorded code-reviewer approval" — the `2` came from the stderr redirect, not from a PR. Refs #568 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…e ticket gate (#582) * fix(#569): exempt .claude/, /tmp, rm, and \$VAR targets from bash-write ticket gate require-active-ticket.sh applied the ticket gate categorically to all Bash writes detected by _lib-detect-bash-write.sh, without applying the same target-aware path exemptions already used for Edit/Write/MultiEdit. This over-blocked four common workflow commands: - `cat > "\$CEO"` (variable target resolving to .claude/ path) - `cat > /tmp/commit-msg.txt` (absolute path outside the repo tree) - `cp .claude/session/... .claude/session/...` (already exempt dest) - `rm -f workspace/proj/.git/tmpfile` (deletion, adds no content) Fix: three targeted exemptions added to the Bash path of require-active-ticket.sh, reusing the existing exempt-path logic: 1. Deletion-only commands (`rm` with no content-writing sibling) exit 0 immediately via the new `bash_command_is_deletion_only` helper added to _lib-detect-bash-write.sh — deleting files never adds tracked content so a ticket requirement is pure friction. 2. Variable targets (`\$VAR`, `\${VAR}`) are unresolvable statically; treating them as non-blocking avoids false positives when the variable holds an exempt path (the canonical .claude/ marker-write case). 3. Absolute paths not under REPO_ROOT (e.g. /tmp/, /var/tmp/) are outside the tracked source tree and cannot affect apexyard-governed content — exempted after the REL_PATH strip fails. Writes into non-exempt tracked source paths (outside .claude/, docs/, *.md) are still blocked when no ticket is set — the gate is tightened, not loosened for the cases that matter. Tests: 10 new cases in test_require_active_ticket_bash.sh (the four #569 repros + 6 regressions); 11 new cases in test_detect_bash_write.sh for bash_command_is_deletion_only positive and negative classes. Both test suites: 97/97 and 26/26 passing. Refs #569 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix(#569): close the variable-target bypass (review fix) Addresses the #582 Rex review: the blanket `$*` exemption let a write into tracked source slip past the ticket gate whenever the target began with `$` (e.g. `echo x > $PWD/src/app.ts`, `> $D/app.ts`) — fail-open on a security gate. - Replace the broad `$*) exit 0` with: exempt only a temp-dir var (`$TMPDIR`/`$TMP`) and a BARE whole-target variable (`$CEO`, `${marker}` — no path tail). A variable WITH a path tail is neither bare nor absolute, so it falls through to the gate and blocks. - Do NOT expand $PWD/$HOME (adds nothing for blocking; tripped a /var↔/private/var symlink mismatch). - 3 new regression tests: `> $PWD/src/app.ts` and `> $D/app.ts` BLOCK; bare `${marker}` exempt. Suite 29/29. Refs #569 --------- Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
#586) * feat(#585): add interactive LLM game to the site with social score-sharing Ships the 'LLM Field Guide' interactive game (self-contained, no build) at /game, with an end-of-game 'Share your score' block. - site/game.html: the game + SEO/OG/Twitter meta (twitter:site=@me2resh, canonical, summary_large_image). renderOutro now offers Post on X (via=me2resh), LinkedIn, WhatsApp, and Copy link — each carrying the score, a link back to the game, and 'via @me2resh' (X uses its native via param). Plus a quiet back-to-ApexYard link. - Discovery: 'play the game' added to the primary nav + footer across index / how-it-works / architecture / skills, the 404 quick-links, and a homepage CTA (button + line in the final section). - /game clean URL via _redirects; /game added to sitemap.xml. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(#585): rebrand the game to 'You vs. the LLM' + ApexYard branding Make the game catchier and clearly ApexYard-branded: - Header now leads with the AY ApexYard mark + 'ApexYard · You vs. the LLM' (was an unbranded 'Field Guide · LLMs & Agents'); logo links home. - Intro hero: 'You vs. the LLM' + a challenge hook ('Think you understand how AI actually works? Prove it.'); start button 'Take the challenge'. - End screen + page title + OG/Twitter + share copy all renamed to 'You vs. the LLM — an ApexYard game'; share line is now a beat-my-score hook. Homepage CTA/teaser + 404 updated to match. - Folded in the two review a11y nits: share-label var(--faint) to var(--dim) for AA contrast; underline the back-to-ApexYard link. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(#585): polish homepage CTA label (drop awkward colon) Nour's editorial nit on PR #586: 'play: You vs. the LLM' reads awkward; use an en-dash. 'play — You vs. the LLM'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t dev (#589) Dependabot bump PRs (and release-sync merges) carry no ticket ID by design, so the 'Verify Ticket ID' CI failed on every one (#539-#543), blocking clean dependency patches. The local hooks already exempt sync/ (#545); give the CI check the same exemption. - pr-title-check.yml: early-return success when the PR head ref matches dependabot/ or sync/. Normal feature PRs still require a ticket. - dependabot.yml: target-branch dev, so future bumps follow the dev-first flow instead of targeting main. Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Bumps [gitleaks/gitleaks-action](https://github.com/gitleaks/gitleaks-action) from 2 to 3. - [Release notes](https://github.com/gitleaks/gitleaks-action/releases) - [Commits](gitleaks/gitleaks-action@v2...v3) --- updated-dependencies: - dependency-name: gitleaks/gitleaks-action dependency-version: '3' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [actions/github-script](https://github.com/actions/github-script) from 7 to 9. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](actions/github-script@v7...v9) --- updated-dependencies: - dependency-name: actions/github-script dependency-version: '9' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [DavidAnson/markdownlint-cli2-action](https://github.com/davidanson/markdownlint-cli2-action) from 16 to 23. - [Release notes](https://github.com/davidanson/markdownlint-cli2-action/releases) - [Commits](DavidAnson/markdownlint-cli2-action@v16...v23) --- updated-dependencies: - dependency-name: DavidAnson/markdownlint-cli2-action dependency-version: '23' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Manual application of dependabot #539 (upload-artifact) and #541 (codeql) against dev — their branches were cut from main and conflict with dev's diverged workflows, and dependabot can't target dev until the config (#588) reaches main via a release. Supersedes #539/#541. - upload-artifact@v4 -> v7: extract-subpacks-on-release, security-scan, scorecard - codeql-action @V3 -> v4: codeql (init+analyze), scorecard (upload-sarif) Closes #590 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(#594): governed looping — loop-mode trigger rule + AgDR Adopt looping as a first-class, governed apexyard pattern (not a new engine): a trigger-heuristic rule that proactively recommends a closed loop when work fits, bound to apexyard's existing gates as the eval. - .claude/rules/loop-mode.md: sibling of parallel-work.md / plan-mode.md. When to OFFER (repetitive over a set · machine-verifiable · bounded), when not to, which primitive (/loop · /fan-out · Workflow), and the guardrails — halt at the per-PR CEO merge gate (never self-approve), verify = build + tests + Rex (not just build), budget/iteration ceiling, no-progress detection, call skills not re-derived prompts. - AgDR-0068: records the decision (options, the ReAct→orchestration lineage, the in-session lesson that an unverified loop ships broken tests). Auto-loads via the @.claude/rules/*.md import. - CLAUDE.md + README.md: rule count 11→12, list + a code-standards bullet. Dogfooded: the discovery for this feature was itself run as a 4-agent discovery loop (Workflow). Closes #594 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(#594): clarify the verify-stage guardrail is advisory (Tariq review) Tariq's architecture-review note: distinguish the mechanically-enforced halt (CEO merge gate) from the verify=build+tests+Rex clause, which is recommended loop discipline caught at the merge boundary (red-CI + Rex), not blocked mid-iteration. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Squash-merge divergence from the v3.1.0 release PR creates phantom divergence between main and dev. This merge makes the v3.1.0 squash commit an ancestor of dev so future dev->main release PRs only see genuinely-new commits. Strategy: -X ours (dev wins on conflicts) — correct because dev already has the un-squashed equivalents of everything in the squash commit. Refs #403
The -X ours merge kept dev's site/index.html, which lacks the version-string bumps written on main during the v3.1.0 release flow (hero pill apexyard v3.1, releases-shipped range v0.1 → v3.1). dev ended up with softwareVersion=3.1.0 (non-conflicting, merged from main) but pill=v3.0 (dev won the conflict region), which is internally inconsistent and fails test_site_counts.sh. This commit restores main's site/index.html so dev tracks the full release version-state forward. Same shape as the CHANGELOG carry-forward (step 5b / apexyard#448) — site/index.html is a second "main-leads" file now that /release bumps it (#493/#562). main is a superset of dev's site here (the release branch was cut from dev + the version bumps), so nothing is lost. Refs #448
sync(#403): main→dev after v3.1.0 release
The floating @v2 major tag no longer resolves ("unable to find version v2"), failing the Scorecard workflow on every push to main (seen on the v3.1.0 release merge, run 27188194993). Pin to the latest published release v2.4.3. Closes #600 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…eering level (#603) - New capstone level "Engineer the loop" (levelLoop): toggle the guardrails that make a loop safe to run unattended (verify w/ tests+review, halt at human gate, budget+iteration ceiling, stop-on-no-progress, call named skills) and avoid the traps (self-approve merges, build-only verify, run unbounded). Reuses the cost-* toggle/meter UI; pass = all guardrails on, no trap enabled. 11 levels now. - Universal share message: "via @me2resh" -> "via #apexyard" across X / WhatsApp / Copy (dropped the X-only via= param; hashtag works as a brand tag everywhere). Card meta (twitter:site/creator) left as-is. - Mobile responsiveness: extended the @media(<=640px) block with the widget-level fixes the game lacked (RAG paper sheet, prompt-builder slots stack, knowledge- cutoff cards full-width, hallucination/injection docs, cost+loop toggles) plus a global overflow-x guard and a tighter <=380px pass. Touch was already handled (pointer events). Every round now fits a ~360-390px phone. - Bumped OG/Twitter "10 rounds" -> "11 rounds". Closes #602 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add the v3.1.1 CHANGELOG entry — patch release: scorecard-action pin (#601) and the game mobile/share/loop-level fix (#602). - Bump site version strings: JSON-LD softwareVersion 3.1.0 -> 3.1.1, version link text + href -> v3.1.1, releases-shipped 15 -> 16 (pill + range stay v3.1; major.minor unchanged). Closes #604 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
✅ Deploy Preview for apexyard ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
|
||
| - name: Run analysis | ||
| uses: ossf/scorecard-action@v2 | ||
| uses: ossf/scorecard-action@v2.4.3 |
atlas-apex
left a comment
There was a problem hiding this comment.
Code Review: PR #605 — v3.1.1 patch release
Commit: 356bb2d548d0d759bdf0e537167e41492d207041
Summary
Clean dev→main promotion cutting apexyard v3.1.1, a patch release carrying the two fixes that landed on dev since v3.1.0 so they publish to production (main is the deploy branch for yard.apexscript.com). The release commit (356bb2d) itself touches only CHANGELOG.md + site/index.html; the scorecard.yml (#601) and game.html (#603) changes in the full diff were already Rex-reviewed and merged on dev.
Verification performed
- CHANGELOG
## [3.1.1]entry — correctly lists both post-v3.1.0 fixes: (#601)ossf/scorecard-action@v2.4.3pin and (#602/#603) game mobile/share/loop-level. Diff confirmed a pure prepend — zero removed lines, the## [3.1.0]entry is byte-for-byte untouched. ✓ - site/index.html version strings —
softwareVersion 3.1.0 → 3.1.1, version link text+hrefv3.1.0 → v3.1.1, releases-shipped15 → 16. The hero pillapexyard v3.1and rangev0.1 → v3.1are correctly unchanged (major.minor stays 3.1 on a Z-bump). Exactly 3 lines removed, all expected swaps, nothing else touched. ✓ - Drift guard —
bash .claude/hooks/tests/test_site_counts.shPASS; explicit checks confirm site softwareVersion=3.1.1 matches CHANGELOG top entry, hero pill 3.1 matches, releases-shipped 16 matches CHANGELOG entry count. ✓ - Clean promotion — last three commits ahead of
mainare exactly#601(771e2ea),#603(b3e9f4a), and the release commit (356bb2d). No unexpected content. ✓ - PR hygiene — body carries
<!-- multi-close: approved -->, Summary (narrative), Testing, Glossary, andCloses #604(OPEN). ✓ - SHA match — review commit equals PR HEAD. ✓
Checklist Results
- ✅ Architecture & Design: N/A — release metadata only
- ✅ Code Quality: Pass — no code logic changed
- ✅ Testing: Pass — site-counts drift guard green; #601/#603 already QA'd on dev
- ✅ Security: Pass — no security-sensitive surface in the release commit
- ✅ Performance: N/A
- ✅ PR Description & Glossary: Pass
- ⚠ Summary Bullet Narrative: Pass — bullets are narrative with rationale
- ✅ Technical Decisions (AgDR):N/A — release bump, no new technical decisions
- ✅ Adopter Handbooks: N/A — no handbooks triggered by CHANGELOG/site metadata
Issues Found
None.
Suggestions
None — clean release bump.
Verdict
APPROVED
This is a release bump (no code logic, no AgDR owed, no coverage owed — #601/#603 were Rex-reviewed on dev). Tag v3.1.1 against the squash commit (ancestry-guarded) after merge, then run /release-sync v3.1.1.
🤖 Reviewed by Rex (Code Reviewer Agent)
📌 Reviewed commit: 356bb2d548d0d759bdf0e537167e41492d207041
Adds a persistent SKIP button in the footer (next to HINT), shown during every level's play phase and hidden on teaching slides / intro / outro / after submit. Tap-to-confirm guard (first tap arms + warns, second tap within 4s skips) so a stuck player can't zero a level by accident. On confirm: records 0 for the level and calls advance() to the next concept (or outro on the last level). Reuses the .hint-btn styling and the universal advance() flow — no per-level edits, no new behaviour in the 11 render functions. Closes #606 Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
devsince v3.1.0, so they deploy to production (the site + game at yard.apexscript.com publish frommain).ossf/scorecard-action@v2(a floating major tag) stopped resolving and failed the supply-chain workflow on every push tomain; pinned to@v2.4.3.site/game.html). Mobile-responsive pass so every round plays on a ~360–390px phone (the game already stacked some grids; this adds the missing widget breakpoints + anoverflow-xguard), a universalvia #apexyardshare message (works on X / LinkedIn / WhatsApp / Copy, not just X), and a new capstone level "Engineer the loop" teaching how to make an agent loop safe to run unattended.softwareVersion3.1.0 → 3.1.1, version link → v3.1.1, releases-shipped 15 → 16. Pill + range stayv3.1(major.minor unchanged).site-counts-checkgreen.This PR will tag v3.1.1 on
mainafter merge (against the squash commit, ancestry-guarded), followed by/release-sync v3.1.1.Testing
bash .claude/hooks/tests/test_site_counts.sh— passes (softwareVersion 3.1.1, 16 release entries).dev(ci(#600): pin ossf/scorecard-action to v2.4.3 #601, fix(#602): game mobile pass + #apexyard share + loop-engineering level #603); this PR is the dev→main promotion.Closes #604
Glossary
mainis the production branch the marketing site + game deploy from.