Skip to content

feat(#145): portfolio config + self-healing + /split-portfolio helper#147

Merged
atlas-apex merged 2 commits into
me2resh:devfrom
atlas-apex:feature/GH-145-portfolio-config-and-helper
May 3, 2026
Merged

feat(#145): portfolio config + self-healing + /split-portfolio helper#147
atlas-apex merged 2 commits into
me2resh:devfrom
atlas-apex:feature/GH-145-portfolio-config-and-helper

Conversation

@atlas-apex

Copy link
Copy Markdown
Collaborator

Summary

Closes the framework primitive deferred from #144.

  • portfolio: config block in .claude/project-config.{defaults,}.json for path resolution (registry, projects_dir, ideas_backlog). Defaults preserve today's single-fork behavior; adopters in split-portfolio mode override the keys.
  • _lib-portfolio-paths.sh helper library + portfolio_validate() for self-healing. New SessionStart hook surfaces a one-line banner if the resolved paths are broken — silent on success, never blocks.
  • 18 SKILL.md files updated with a "Path resolution" callout pointing at the helper. Two skills got deeper bash-block edits: handover (now sources the helper instead of hardcoded apexyard.projects.yaml); setup Step 2b (writes the config block as the first-class path; symlinks remain documented as legacy fallback).
  • New /split-portfolio skill — automates the destructive recovery flow with explicit operator-confirmation gates at each destructive step. --verify mode reads-only; --dry-run prints commands without executing. Refuses on already-private fork, paid GitHub plan, dirty working tree, or already-migrated state. Step 9 writes the new config block (not symlinks); Step 9 surfaces the GitHub timeline-API survival caveat verbatim.
  • AgDR-0010 captures the design rationale, including the schema-location decision (project-config.json over onboarding.yaml).

Targets dev per the apexyard release-cut model.

What changes

Area Files Lines
Schema .claude/project-config.defaults.json +7
Helper library .claude/hooks/_lib-portfolio-paths.sh +148 (new)
SessionStart hook .claude/hooks/check-portfolio-config.sh +60 (new)
SessionStart wiring .claude/settings.json +4
Helper tests .claude/hooks/tests/test_portfolio_paths.sh +236 (new)
New skill .claude/skills/split-portfolio/SKILL.md +267 (new)
Skill audit 18 × SKILL.md (callout insertion) ~12 each
Surgical edits .claude/skills/handover/SKILL.md, .claude/skills/setup/SKILL.md +28 + +45
Docs docs/multi-project.md +72 / -18
AgDR docs/agdr/AgDR-0010-portfolio-config-and-self-healing.md +145 (new)

26 files changed, ~1,416 insertions, 18 deletions.

Why "in this PR" (the self-healing scope)

The original ticket #145 only asked for the schema + helper + skill audit. During design, the question came up: "is this usable AND self-healing?" The honest answer was "usable yes, self-healing only partially." The cheapest way to close that gap was four extra hunks landing in this same PR: portfolio_validate() in the helper, the SessionStart banner that uses it, the /setup finalization gate, and --verify mode in /split-portfolio. ~150 extra lines, all separable. The acceptance criteria on #145 were updated before code to make Rex's review mechanical.

Closes vs Refs

This PR closes #145 (per the single-Closes-keyword rule in validate-pr-create.sh). #146 is delivered in the same PR but closed manually post-merge with a "delivered in PR #X" comment — that's the framework's documented pattern when a single PR delivers two coupled tickets.

Testing

Helper unit tests

bash .claude/hooks/tests/test_portfolio_paths.sh — 13 cases, all pass:

  • Defaults resolve to fork-rooted absolute paths (registry + projects_dir + ideas_backlog).
  • Defaults validate is OK.
  • Override with absolute path wins.
  • Override with sibling-dir path validates OK.
  • Override with relative path resolves against fork root.
  • Validate detects: missing registry, registry without projects: key, missing projects_dir, ideas_backlog with missing parent.
  • Validate accepts ideas_backlog missing-but-creatable (parent exists).
  • portfolio_clear_cache() resets resolver state.

Hook regression sweep

for t in .claude/hooks/tests/test_*.sh; do bash "$t"; done — 8/10 pass. Two pre-existing failures unrelated to this PR (test_single_closes_per_pr, test_validate_pr_required_sections) reference closed issue #114 in their fixtures and trip the network cross-ref check on a clean upstream/dev checkout. Verified pre-existing by running against pristine upstream/dev on a stash.

Live smoke

  • Helper runs in this fork's split-portfolio configuration: registry resolves through symlink, validate returns OK.
  • SessionStart hook silent on this fork's valid config.
  • SessionStart hook prints a clear banner when pointed at a tmpdir with a broken portfolio.registry. Exits 0 (informational, never blocks).

Manual smoke for /split-portfolio

Not run end-to-end against a real public fork — destructive operations, requires throwaway repos. Documented in the skill's AC list as "manual smoke test" rather than CI-tested. The skill's pre-flight refusals + idempotency mean a partial run is recoverable.

Glossary

Term Definition
portfolio: config block New top-level key in .claude/project-config.{defaults,}.json with three fields (registry, projects_dir, ideas_backlog) that resolve to absolute paths via the helper library. Defaults match the single-fork layout; adopters in split-portfolio mode override to point at a sibling repo.
_lib-portfolio-paths.sh Sourceable shell library exposing portfolio_registry, portfolio_projects_dir, portfolio_ideas_backlog, portfolio_validate, portfolio_clear_cache. Resolves relative paths against the ops-fork root (the directory containing onboarding.yaml + apexyard.projects.yaml). Cached per-process.
portfolio_validate Sanity-check that the resolved paths are actually usable: registry exists + parses as YAML + has projects:; projects_dir is a directory; ideas_backlog exists or is creatable. Returns 0 on OK, 1 with "broken: " on failure.
Self-healing banner New SessionStart hook (check-portfolio-config.sh) calls portfolio_validate once per session start. Silent on OK. One-line banner naming the broken field + suggested fix on failure. Never blocks the session — informational only.
Skill audit Mechanical pass through 18 SKILL.md files that touch portfolio paths, inserting a "Path resolution" callout pointing at the helper. Two skills got deeper edits to remove literal apexyard.projects.yaml references in bash blocks.
/split-portfolio skill New skill that automates the migration from single-fork to split-portfolio mode. 10-step process with explicit operator-confirmation gates at each destructive step. --verify mode for state reports without destructive ops. --dry-run prints commands without executing. Refuses on already-migrated, paid plan, dirty tree.
GitHub timeline-API survival When you edit an Issue or PR body via gh issue edit / gh pr edit, the original body content remains accessible via the GitHub timeline API forever. Body redaction hides the content from casual viewers + search engines, but full purge requires deleting the repo. The /split-portfolio skill surfaces this caveat verbatim at Step 8.
Single-Closes-keyword rule Enforced by validate-pr-create.sh (introduced #114). PR bodies must use exactly one Closes #N-style keyword to prevent surprise auto-closures. When a single PR delivers two coupled tickets, close one with Closes, reference the other with Refs, and close the second manually post-merge.

Closes #145
Refs #146 (closed manually post-merge)

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants