Skip to content

feat(#41): per-project active ticket markers in ops repo#71

Merged
atlas-apex merged 2 commits into
mainfrom
feature/#41-per-project-tickets
Apr 17, 2026
Merged

feat(#41): per-project active ticket markers in ops repo#71
atlas-apex merged 2 commits into
mainfrom
feature/#41-per-project-tickets

Conversation

@atlas-apex

Copy link
Copy Markdown
Collaborator

Summary

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-level fallback

Both 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 from git rev-parse --show-toplevel until it finds both onboarding.yaml and apexstack.projects.yaml (that's the ops root, even when a managed-project clone has its own inner git root). If FILE_PATH is under <ops_root>/workspace/<project>/, checks tickets/<project> first; falls back to current-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 in apexstack.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:

PASS  code outside workspace, no fallback
PASS  workspace/curios-dog, no markers
PASS  workspace/curios-dog, per-project marker
PASS  workspace/curios-dog, only fallback
PASS  ops framework edit, fallback only
PASS  ops framework, only sharppick per-project marker
PASS  workspace/sharppick with sharppick marker
PASS  workspace/curios-dog with wrong project marker
PASS  .claude/ edit, no markers, always exempt
PASS  *.md edit, no markers, always exempt

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

Glossary

Term Definition
ops root The apexstack fork root — the directory containing both onboarding.yaml and apexstack.projects.yaml. Discovered by walking up from the nearest git toplevel
per-project marker <ops_root>/.claude/session/tickets/<project> — the ticket currently active for a specific managed project
ops-level fallback <ops_root>/.claude/session/current-ticket — used for edits outside any workspace/<project>/ subtree, or as a safety net
registry apexstack.projects.yaml at the ops root — maps repo to project name, used by /start-ticket to derive the marker path

Related

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 atlas-apex left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Code Review: PR #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"$2 is "me2resh/curios-dog" (with quotes), won't match the bare OWNER_REPO. Same hazard on name:.
  • Pairing assumption. Only correct if repo: is the very next matching line after its sibling - name:. If a project entry lists docs: or status: between name: and repo:, fine — name sticks. But if a nested key reuses name: (e.g. under a roles: subtree) you'd clobber it before hitting repo:.
  • 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 atlas-apex left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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

@atlas-apex atlas-apex merged commit 49f3e1a into main Apr 17, 2026
4 checks passed
@atlas-apex atlas-apex deleted the feature/#41-per-project-tickets branch April 17, 2026 11:30
osama-abu-baker pushed a commit to osama-abu-baker/apexyard that referenced this pull request Jun 3, 2026
…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>
mosta7il pushed a commit to mosta7il/apexyard that referenced this pull request Jun 8, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: per-project active ticket markers in ops repo

2 participants