feat(#41): per-project active ticket markers in ops repo#71
Conversation
Moves the active-ticket session state from a single `.claude/session/current-ticket` marker to a two-tier layout keyed by managed-project name: ops_root/.claude/session/tickets/<project> ← per-project, preferred ops_root/.claude/session/current-ticket ← ops-repo fallback Resolution in require-active-ticket.sh: 1. Discover ops root by walking up from git-toplevel until onboarding.yaml + apexstack.projects.yaml both exist. This handles the case where a managed-project clone under workspace/<name>/ has its own inner git root. 2. If FILE_PATH is under <ops_root>/workspace/<project>/, look up tickets/<project>. If present, exempt. 3. Fall back to current-ticket (ops-level marker). If present, exempt. 4. Otherwise block with a message that names both marker paths so the user can see which scope the tool looked at. Resolution in /start-ticket skill: 1. Walk up from CWD to find the ops root. 2. Parse the ticket's owner/repo from the argument. 3. Look up apexstack.projects.yaml for a project entry with a matching `repo:` field (yq when available; awk fallback). 4. If found → write to tickets/<project>. Otherwise → write to current-ticket. Benefits over the pre-#41 layout: - No .claude/session/ inside each managed-project clone — all session state is ops-fork-local, keyed by project name. One less per-machine concern per clone. - Switching projects no longer overwrites the previous ticket; each per-project marker is independent. - Ops-level edits still use current-ticket — existing workflows continue to work without migration. Smoke-tested against an isolated /tmp ops root with 10 cases covering both marker paths, missing markers, wrong-project markers, exempt paths, and ops-framework edits. All pass. Closes #41
atlas-apex
left a comment
There was a problem hiding this comment.
Code Review: PR #71
Commit: b97807e4d510c987df625f400f667a21a1807c0f
Summary
Two-tier per-project active-ticket markers in the ops fork. Hook walks up to find the ops root (onboarding.yaml + apexstack.projects.yaml), prefers tickets/<project> when the edit is under workspace/<project>/, falls back to current-ticket. Skill mirrors the lookup with a yq→awk fallback against the registry.
Checklist Results
- Architecture & Design: Pass
- Code Quality: Pass
- Testing: Pass (10-case smoke matrix)
- Security: Pass (no secrets, path handling sane)
- Performance: Pass
- PR Description/Glossary: Pass (4 terms)
- Technical Decisions: N/A (mechanical refactor of existing hook; no new deps / patterns)
Findings
1. Ops-root walk (focus #1) — correct. The walk starts at REPO_ROOT (inner git toplevel, e.g. workspace/curios-dog/) and climbs via dirname until both sentinels co-exist. A managed-project clone won't have apexstack.projects.yaml at its root, so the walk steps past it correctly to the ops fork. Good defence against the nested-git-root case.
2. Symlink / canonicalisation (focus #2) — latent, low risk. OPS_ROOT derives from git rev-parse --show-toplevel (canonical on macOS — resolves /tmp → /private/tmp), while FILE_PATH comes from the tool call verbatim. If a user's ops fork lived under a symlinked path the case "$FILE_PATH" in "$OPS_ROOT"/workspace/*) match could miss and degrade to the fallback marker (still safe — no false unblock). Not worth hardening now; a one-line note in the hook comment would suffice.
3. Backwards compat (focus #3) — documented. Ops-root current-ticket still works. The migration note in the skill's Notes explicitly calls out that pre-#41 per-workspace markers are no longer read. Clear.
4. awk fallback robustness (focus #4) — FRAGILE, worth tightening.
/^[[:space:]]*- name:/ { name=$3 }
/^[[:space:]]*repo:/ { if ($2 == r) { print name; exit } }Issues:
- Quoted values.
repo: "me2resh/curios-dog"→$2is"me2resh/curios-dog"(with quotes), won't match the bareOWNER_REPO. Same hazard onname:. - Pairing assumption. Only correct if
repo:is the very next matching line after its sibling- name:. If a project entry listsdocs:orstatus:betweenname:andrepo:, fine —namesticks. But if a nested key reusesname:(e.g. under aroles:subtree) you'd clobber it before hittingrepo:. - Indentation.
[[:space:]]*is permissive enough; that part's OK.
Suggest stripping quotes and anchoring on the top-level indent, e.g.:
/^-[[:space:]]+name:/ { gsub(/["'\'']/,"",$3); name=$3 }
/^[[:space:]]+repo:/ { gsub(/["'\'']/,"",$2); if ($2 == r) { print name; exit } }Not blocking — yq is preferred and present on most dev boxes — but the fallback should survive a quoted repo:. Worth a follow-up ticket if not fixed here.
5. Glossary — 4 terms, all load-bearing. Good.
Verdict
COMMENT / approve-pending-follow-up. Ship as-is if the awk fragility is tracked for a follow-up; otherwise fold in the quote-stripping now. No blockers.
Reviewed by Rex (Code Reviewer Agent)
Reviewed commit: b97807e4d510c987df625f400f667a21a1807c0f
Rex flagged on PR #71 review that the greppy fallback path breaks on registry entries using quoted repo values (repo: "me2resh/curios-dog"). Adds an unquote() awk helper that strips surrounding single or double quotes before comparing. Also adds a note about the key-order assumption (name: must precede repo: in each entry — matches apexstack.projects.yaml.example and /handover output) and points users at yq as the preferred path when in doubt.
atlas-apex
left a comment
There was a problem hiding this comment.
Re-review: PR #71
Commit: 1b4b6c7ee9de344fa3ee625abbc0b514c2c1bfa3
Incremental diff since b97807e is purely the unquote() awk helper + explanatory notes — nothing else changed. Addresses the prior COMMENT blocker on quoted repo: values cleanly, and the ordering caveat is now documented with a pointer to yq as the preferred path.
Checklist Results
- Architecture & Design: Pass
- Code Quality: Pass
- Testing: N/A (docs/skill)
- Security: Pass
- Performance: Pass
- PR Description & Glossary: Pass
- Technical Decisions (AgDR): N/A
Verdict
APPROVED (posted as comment — cannot self-approve own PR)
🤖 Reviewed by Rex (Code Reviewer Agent)
📌 Reviewed commit: 1b4b6c7ee9de344fa3ee625abbc0b514c2c1bfa3
…esh#71) * feat(me2resh#41): per-project active ticket markers in ops repo Moves the active-ticket session state from a single `.claude/session/current-ticket` marker to a two-tier layout keyed by managed-project name: ops_root/.claude/session/tickets/<project> ← per-project, preferred ops_root/.claude/session/current-ticket ← ops-repo fallback Resolution in require-active-ticket.sh: 1. Discover ops root by walking up from git-toplevel until onboarding.yaml + apexstack.projects.yaml both exist. This handles the case where a managed-project clone under workspace/<name>/ has its own inner git root. 2. If FILE_PATH is under <ops_root>/workspace/<project>/, look up tickets/<project>. If present, exempt. 3. Fall back to current-ticket (ops-level marker). If present, exempt. 4. Otherwise block with a message that names both marker paths so the user can see which scope the tool looked at. Resolution in /start-ticket skill: 1. Walk up from CWD to find the ops root. 2. Parse the ticket's owner/repo from the argument. 3. Look up apexstack.projects.yaml for a project entry with a matching `repo:` field (yq when available; awk fallback). 4. If found → write to tickets/<project>. Otherwise → write to current-ticket. Benefits over the pre-me2resh#41 layout: - No .claude/session/ inside each managed-project clone — all session state is ops-fork-local, keyed by project name. One less per-machine concern per clone. - Switching projects no longer overwrites the previous ticket; each per-project marker is independent. - Ops-level edits still use current-ticket — existing workflows continue to work without migration. Smoke-tested against an isolated /tmp ops root with 10 cases covering both marker paths, missing markers, wrong-project markers, exempt paths, and ops-framework edits. All pass. Closes me2resh#41 * fix: harden awk fallback in /start-ticket to unquote registry scalars Rex flagged on PR me2resh#71 review that the greppy fallback path breaks on registry entries using quoted repo values (repo: "me2resh/curios-dog"). Adds an unquote() awk helper that strips surrounding single or double quotes before comparing. Also adds a note about the key-order assumption (name: must precede repo: in each entry — matches apexstack.projects.yaml.example and /handover output) and points users at yq as the preferred path when in doubt. --------- Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
…esh#71) * feat(me2resh#41): per-project active ticket markers in ops repo Moves the active-ticket session state from a single `.claude/session/current-ticket` marker to a two-tier layout keyed by managed-project name: ops_root/.claude/session/tickets/<project> ← per-project, preferred ops_root/.claude/session/current-ticket ← ops-repo fallback Resolution in require-active-ticket.sh: 1. Discover ops root by walking up from git-toplevel until onboarding.yaml + apexstack.projects.yaml both exist. This handles the case where a managed-project clone under workspace/<name>/ has its own inner git root. 2. If FILE_PATH is under <ops_root>/workspace/<project>/, look up tickets/<project>. If present, exempt. 3. Fall back to current-ticket (ops-level marker). If present, exempt. 4. Otherwise block with a message that names both marker paths so the user can see which scope the tool looked at. Resolution in /start-ticket skill: 1. Walk up from CWD to find the ops root. 2. Parse the ticket's owner/repo from the argument. 3. Look up apexstack.projects.yaml for a project entry with a matching `repo:` field (yq when available; awk fallback). 4. If found → write to tickets/<project>. Otherwise → write to current-ticket. Benefits over the pre-me2resh#41 layout: - No .claude/session/ inside each managed-project clone — all session state is ops-fork-local, keyed by project name. One less per-machine concern per clone. - Switching projects no longer overwrites the previous ticket; each per-project marker is independent. - Ops-level edits still use current-ticket — existing workflows continue to work without migration. Smoke-tested against an isolated /tmp ops root with 10 cases covering both marker paths, missing markers, wrong-project markers, exempt paths, and ops-framework edits. All pass. Closes me2resh#41 * fix: harden awk fallback in /start-ticket to unquote registry scalars Rex flagged on PR me2resh#71 review that the greppy fallback path breaks on registry entries using quoted repo values (repo: "me2resh/curios-dog"). Adds an unquote() awk helper that strips surrounding single or double quotes before comparing. Also adds a note about the key-order assumption (name: must precede repo: in each entry — matches apexstack.projects.yaml.example and /handover output) and points users at yq as the preferred path when in doubt. --------- Co-authored-by: me2resh <ahmed.abdelaliem@gmail.com>
Summary
Moves the active-ticket session state from a single
.claude/session/current-ticketmarker to a two-tier layout keyed by managed-project name:<ops_root>/.claude/session/tickets/<project>— per-project, preferred<ops_root>/.claude/session/current-ticket— ops-level fallbackBoth live in the ops fork, gitignored. No more
.claude/session/inside each managed-project clone.What changed
.claude/hooks/require-active-ticket.sh— rewrites the marker-lookup logic. Walks up fromgit rev-parse --show-topleveluntil it finds bothonboarding.yamlandapexstack.projects.yaml(that's the ops root, even when a managed-project clone has its own inner git root). IfFILE_PATHis under<ops_root>/workspace/<project>/, checkstickets/<project>first; falls back tocurrent-ticket. Error message now names both lookup paths so the user sees which scope the hook checked..claude/skills/start-ticket/SKILL.md— step 4 rewritten to resolve the ops root, look up the ticket's tracker repo inapexstack.projects.yaml(yq when available, awk fallback), and pick the right marker path. Marker layout table moved to the top of the skill. Migration note added.Testing
Ran a 10-case smoke test against an isolated mock ops root in
/tmp:Covers: per-project happy path, cross-project isolation (sharppick marker does NOT unblock curios-dog edit), fallback to ops-level, exempt-path paths still exempt.
Backwards compatibility
.claude/session/current-ticketin the ops repo keep working (fallback path).workspace/<name>/.claude/session/current-ticket) are no longer read by the hook. Harmless, but users can delete them once the new layout is active. Migration note in the skill's "Notes" section.Glossary
onboarding.yamlandapexstack.projects.yaml. Discovered by walking up from the nearest git toplevel<ops_root>/.claude/session/tickets/<project>— the ticket currently active for a specific managed project<ops_root>/.claude/session/current-ticket— used for edits outside anyworkspace/<project>/subtree, or as a safety netapexstack.projects.yamlat the ops root — mapsrepoto projectname, used by/start-ticketto derive the marker pathRelated