Skip to content

feat(mods): add experimental fellowship-gates Claude Code mod - #113

Open
justinjdev wants to merge 1 commit into
mainfrom
feat/fellowship-gates-mod
Open

justinjdev wants to merge 1 commit into
mainfrom
feat/fellowship-gates-mod

Conversation

@justinjdev

@justinjdev justinjdev commented Oct 3, 2026 •

Copy link
Copy Markdown
Owner

Summary

Adds an experimental Claude Code mod at plugin/mods/fellowship-gates/ that shows fellowship quest status inside Claude Code:

  • Status line: ⚔ 2 quests · 1 gate pending, or the quest name and phase when there's only one quest.
  • Toast when a gate becomes newly pending.
  • Gate band above the prompt: one row per pending gate, each with three buttons:
    • Approve sends "Approve the gate for quest …" as your prompt. It goes through the prompt, not fellowship gate approve directly, so the lead still messages the teammate to resume it.
    • Reject… fills the prompt box with a rejection prompt for you to add feedback to.
    • Later hides that gate until its state changes.

The data comes from ~/.claude/fellowship/bin/fellowship status --json, polled every 10 seconds and after each turn. Merged quests are left out. With no binary or no quests, the mod shows nothing.

Scope

  • The mod is loaded explicitly with claude --plugin-dir plugin/mods/fellowship-gates. It is not wired into the fellowship plugin manifest yet.
  • I didn't update the site, README or changelog, since this isn't a shipped user-facing feature yet. Those should come with the follow-up that ships it.
  • .claude-plugin/types/ (types Claude Code generates on load) is gitignored.

Test plan

  • claude plugin validate plugin/mods/fellowship-gates passes
  • claude plugin test plugin/mods/fellowship-gates: 3 pass (status parsing, plus the band's Approve/Later flow on the terminal and desktop surfaces)
  • Type-check passes with strict TypeScript
  • Manual: run /fellowship with real quests and confirm the status line, the toast and the Approve button work (only tested against mocked status --json output so far)

🤖 Generated with Claude Code

https://claude.ai/code/session_01HeW5DKghWKp8v2UYCRv5xb


Generated by Claude Code

Summary by CodeRabbit

  • New Features
    • Added an experimental Fellowship gates view that displays quest status and highlights pending gates.
    • Review pending gates with Approve or Reject actions, or choose Later to hide a gate until its status changes.
    • Get notified when new gates become pending, and see quest and pending-gate counts in the status line.
  • Documentation
    • Added guidance for trying and validating the Fellowship gates feature.

Shows fellowship quest status in Claude Code's status line, toasts when a
gate becomes pending, and draws a band above the prompt with Approve /
Reject / Later buttons for each pending gate. Approve and Reject go through
the prompt so the lead still messages the teammate to resume.

Lives under plugin/mods/ and is loaded explicitly with --plugin-dir; it is
not wired into the fellowship plugin yet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HeW5DKghWKp8v2UYCRv5xb
@coderabbitai

coderabbitai Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

📝 Walkthrough

Walkthrough

Adds an experimental Claude Code plugin that polls fellowship quest status, displays a status line and notifications for pending gates, and provides Approve, Reject, and Later controls.

Changes

Fellowship Gates

Layer / File(s) Summary
Plugin registration and state contract
.claude-plugin/plugin.json, hooks/hooks.json, types/index.d.ts, tsconfig.json, .gitignore, README.md
Adds plugin metadata, registers the hook module, declares quest and plugin-state types, and documents setup and validation.
Status parsing and polling
hooks/status.ts, hooks/register.tsx, tests/gates.test.tsx
Parses quest status, formats the status line, and refreshes quest state after session start and turn completion and every 10 seconds. Tests cover parsing and status formatting.
Pending-gate actions
hooks/register.tsx, tests/gates.test.tsx
Renders Approve, Reject, and Later actions for eligible pending gates. Tests cover approval and dismissal on terminal and desktop surfaces.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ClaudeCode
  participant register
  participant FellowshipCLI as fellowship status --json
  participant AbovePrompt
  participant User
  participant PluginState
  ClaudeCode->>register: Start session or complete turn
  register->>FellowshipCLI: Request quest status
  FellowshipCLI-->>register: Return status output
  register->>PluginState: Update quests and status line
  AbovePrompt->>User: Display pending-gate actions
  User->>AbovePrompt: Select Approve, Reject, or Later
  AbovePrompt->>PluginState: Record dismissal when Later is selected
  AbovePrompt->>ClaudeCode: Submit approval prompt or fill rejection prompt
Loading

Merge Risk: 🔵 Low · up to fe1ab

Overlapping polls may briefly show outdated quest or gate status. This is a bounded risk for the experimental mod, but the refresh ordering should be fixed or accepted before merging.

Security Architecture Review

Security architecture risk: 🟡 Moderate · up to fe1ab

The feature is explicitly enabled and requires a user action before requesting approval. However, an approval request uses cached quest information without binding the user's decision to the current gate instance. Existing state checks limit the risk, but stale-request handling and the permissions of quest-data writers remain unresolved.

Retained concerns

  • Medium · security · inferred: Approval consent is not bound end-to-end to a gate instance. The new UI submits a cached quest and phase, while the unchanged CLI approves whichever gate is currently pending for the resolved quest. If an earlier gate settles and another becomes pending before processing, stale consent could advance the later gate unless the lead independently checks the requested phase and current gate identity.
Security review details

Security Blast Radius

  • inferred — Exposure begins in sessions explicitly loading the mod. Incorrect approval can unblock a quest, while any injected instruction would remain subject to the receiving session's available tools and permissions. Exact store scope and metadata-writer privileges were not resolved, so a cross-project or broader environment blast radius is not established.

Security Findings and Attack Paths

  • inferred — The concrete concern is stale consent reaching a different pending gate through the existing current-state approval command. A separate conditional instruction-injection path exists where influence over quest metadata changes generated user-authority text; effective attacker write access and downstream resistance were not established, so this is not a verified injection finding.

Trust Boundaries and Controls

  • observed — The existing workflow permits chat approval answers and instructs the lead to resolve the authoritative worktree. The CLI loads state and records approval within a transaction, rejecting nonpending or invalid-phase transitions. These controls counter a direct bypass claim, but do not compare an expected gate ID or phase supplied by this UI.

Resilience and Maintainability Implications

  • inferred — Duplicate approval before a new gate becomes pending is constrained by the authoritative pending-state check. Repeated same-phase gates can retain a Later dismissal if polling never observes the intervening nonpending state; this reduces notification visibility rather than changing gate enforcement.

Hardening Proposals

  • proposed — Bind approval and dismissal to an authoritative quest and gate-instance identity, revalidate that identity when acting, and reject mismatches at the transition boundary. Keep metadata distinct from user instructions, publish ordered snapshots, and verify session-scoped cleanup and state isolation.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 4 files. (5 skipped: 5 … Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding an experimental fellowship-gates Claude Code mod.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 3 functions across 4 files. (5 skipped: 5 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
plugin/mods/fellowship-gates/tests/gates.test.tsx (1)

13-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert that Reject fills the feedback prompt.

Press Reject and assert that prompt.fill receives the rejection text. The current test exercises Approve and Later, but not Reject.


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b5b94fd4-bf7c-4801-90d5-548a1313b5da
📥 Commits

Reviewing files that changed from the base of the PR and between e70e898 and fe1ab55.

📒 Files selected for processing (9)
  • plugin/mods/fellowship-gates/.claude-plugin/plugin.json
  • plugin/mods/fellowship-gates/.gitignore
  • plugin/mods/fellowship-gates/README.md
  • plugin/mods/fellowship-gates/hooks/hooks.json
  • plugin/mods/fellowship-gates/hooks/register.tsx
  • plugin/mods/fellowship-gates/hooks/status.ts
  • plugin/mods/fellowship-gates/tests/gates.test.tsx
  • plugin/mods/fellowship-gates/tsconfig.json
  • plugin/mods/fellowship-gates/types/index.d.ts

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Comment on lines +19 to +41
async function refresh($: EngineInterface) {
let list: Quest[] = []
try {
const ran = await $.process.run(['sh', '-c', STATUS_CMD], { cwd, timeoutMs: 15_000 })
if (ran.exitCode === 0) list = parseStatus(ran.stdout)
} catch {
// Binary missing or slow: show nothing rather than a stale count.
}

const pending = list.filter(q => q.gatePending).map(gateKey)
if (announced !== null) {
for (const key of pending) {
if (!announced.has(key)) $.ui.toast(`fellowship: ${key.replace(':', ' — ')} gate pending`)
}
}
announced = new Set(pending)

await update($, quests, () => list)
// Forget dismissals for gates that are no longer pending, so the same
// phase gating again after a rejection shows up again.
await update($, dismissed, keys => keys.filter(k => pending.includes(k)))
$.ui.status(statusLine(list))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Add a staleness check to refresh.

refresh runs from session.start, turn.complete, and the timer. These calls can overlap, and a slow older call (up to 15 s timeout) can finish after a newer one. The older result then overwrites quests, announced, and the status line with stale data. It can also cause duplicate or missed toasts. Capture a sequence number before the await and return early after the await if a newer call has started.

Proposed fix
+let seq = 0
 async function refresh($: EngineInterface) {
+  const mine = ++seq
   let list: Quest[] = []
   try {
     const ran = await $.process.run(['sh', '-c', STATUS_CMD], { cwd, timeoutMs: 15_000 })
     if (ran.exitCode === 0) list = parseStatus(ran.stdout)
   } catch {
     // Binary missing or slow: show nothing rather than a stale count.
   }
+  if (mine !== seq) return

Source: Learnings

This branch has not been deployed

No deployments
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