Skip to content

chore: gitignore Spec Kit/GSD local tooling (clean untracked clutter) - #172

Merged
debpalash merged 1 commit into
mainfrom
chore/gitignore-local-tooling
May 30, 2026
Merged

chore: gitignore Spec Kit/GSD local tooling (clean untracked clutter)#172
debpalash merged 1 commit into
mainfrom
chore/gitignore-local-tooling

Conversation

@debpalash

@debpalash debpalash commented May 30, 2026

Copy link
Copy Markdown
Owner

.specify/ and .claude/skills/speckit-*/ are local planning tooling (Spec Kit / GSD), not OmniVoice code — they've shown as untracked all along. Ignore them so git status is clean (the tracked .claude/skills/omnivoice/ MCP skill is unaffected — targeted glob). Also tracks the network-sharing implementation plan to pair with its committed design spec.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Documentation

    • Added detailed implementation plan for upcoming network sharing feature with Tailscale integration, including shared-access model, PIN-based access control, and QR code sharing capabilities.
  • Chores

    • Updated development environment configuration.

Review Change Stack

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR adds a .gitignore section for local planning tooling and introduces a comprehensive 7-phase implementation plan for network sharing and Tailscale integration. The plan specifies a PIN-gated second uvicorn server to enable LAN and Tailscale access without restarting the main backend, with loopback-only control endpoints and frontend QR/PIN UI.

Changes

Local Planning Tooling Configuration

Layer / File(s) Summary
Ignore planning tooling and speckit helpers
.gitignore
Added section excluding .specify/ (planning config) and .claude/skills/speckit-*/ (helper skills) while preserving tracked OmniVoice MCP skill.

Network Sharing & Tailscale Implementation Plan

Layer / File(s) Summary
Architecture overview and goals
docs/superpowers/plans/2026-05-30-network-sharing.md (lines 1–38)
Establishes the overall design: running a second uvicorn server with PIN middleware for non-loopback access and loopback-only control surface.
Backend implementation (Phases 1–2)
docs/superpowers/plans/2026-05-30-network-sharing.md (lines 39–438)
Phase 1 creates network_share module for LAN enumeration, PIN generation, and share listener lifecycle; extends /system/info and adds loopback-only /system/network/* endpoints. Phase 2 implements NetworkAccessMiddleware to gate non-loopback access and initializes app.state.network_share with tests.
Frontend PIN injection and network toggle (Phases 3–4)
docs/superpowers/plans/2026-05-30-network-sharing.md (lines 441–743)
Phase 3 injects X-OmniVoice-Pin in apiFetch, captures ?pin= from QR links, and adds RemoteAuthGate component. Phase 4 adds qrcode dependency, implements footer NetworkToggle with QR generation and copy/open utilities.
Tailscale integration, Settings panel, and verification (Phases 5–7)
docs/superpowers/plans/2026-05-30-network-sharing.md (lines 746–943)
Phase 5 wraps Tailscale CLI and adds loopback-only /system/tailscale/* endpoints. Phases 6–7 create SharingPanel in Settings, register "Sharing" tab, add user docs, and define full test/build suite. Final notes cover security constraints and string-localization requirements.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~8 minutes

Possibly related PRs

  • debpalash/OmniVoice-Studio#113: Both PRs modify .gitignore to control which bundled Claude skill files under .claude/skills/ are tracked—main PR adds ignores for speckit-*/.specify while explicitly keeping OmniVoice (omnivoice/) included, matching the retrieved PR's .claude/ unignore setup.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is incomplete; it lacks required template sections including Summary, Changes list, Type checkbox selection, Testing details, and Checklist items. Complete the PR description using the template: add a proper Summary section, itemize Changes, select the appropriate Type (likely 'Chore' or similar), describe Testing approach, and check off relevant Checklist items.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding gitignore entries for local Spec Kit/GSD tooling to clean up untracked files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/gitignore-local-tooling

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 and usage tips.

@greptile-apps

greptile-apps Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This chore PR suppresses untracked local tooling clutter from git status and commits a network-sharing implementation plan. No production code is changed.

  • .gitignore: adds .specify/ and .claude/skills/speckit-*/ to exclude Spec Kit / GSD local planning tooling. The existing negation chain (.claude/*!.claude/skills/!.claude/skills/**) correctly means speckit-*/ is the last-matching rule for those paths, so the targeted glob works as described. The tracked .claude/skills/omnivoice/ MCP skill directory is unaffected.
  • docs/superpowers/plans/2026-05-30-network-sharing.md: 943-line step-by-step implementation plan for PIN-gated LAN sharing and Tailscale remote access, pairing with the already-committed design spec. Two points in the plan code templates are worth tidying before the implementation phase begins: a hardcoded developer filesystem path in a test-run command, and a PIN cookie that omits httponly=True.

Confidence Score: 4/5

Safe to merge — no production code is changed, only gitignore rules and a planning document.

The gitignore additions are correct and well-ordered. The planning document has two small issues (a hardcoded local path and a cookie missing httponly) that are worth cleaning up before the implementation phase, but they pose no risk at this stage.

The two template code snippets in docs/superpowers/plans/2026-05-30-network-sharing.md (line 81 run command and line 417–418 cookie) should be corrected before the plan is executed.

Important Files Changed

Filename Overview
.gitignore Adds two targeted ignore patterns for local Spec Kit/GSD tooling; gitignore rule ordering is correct and the existing .claude/skills/omnivoice/ un-ignore is preserved.
docs/superpowers/plans/2026-05-30-network-sharing.md 943-line network sharing implementation plan; contains a hardcoded local developer path in a test run command, and the PIN cookie in the middleware template is missing httponly=True.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[".claude/skills/speckit-foo/"] --> B{".claude/*\nmatches?"}
    B -->|yes| C[ignored]
    C --> D{"!.claude/skills/\nmatches parent?"}
    D -->|yes| E[un-ignored]
    E --> F{"!.claude/skills/**\nmatches?"}
    F -->|yes| G[un-ignored]
    G --> H{".claude/skills/speckit-*/\nmatches?"}
    H -->|yes| I["ignored (speckit-* stays local)"]

    A2[".claude/skills/omnivoice/"] --> B2{".claude/*\nmatches?"}
    B2 -->|yes| C2[ignored]
    C2 --> D2{"!.claude/skills/\nmatches parent?"}
    D2 -->|yes| E2[un-ignored]
    E2 --> F2{"!.claude/skills/**\nmatches?"}
    F2 -->|yes| G2[un-ignored]
    G2 --> H2{".claude/skills/speckit-*/\nmatches?"}
    H2 -->|no| I2["tracked (MCP skill preserved)"]
Loading

Fix All in Claude Code

Reviews (1): Last reviewed commit: "chore: gitignore Spec Kit/GSD local tool..." | Re-trigger Greptile


- [ ] **Step 2: Run it — expect failure**

Run: `cd /Users/user4/Desktop/github/OmniVoice && python -m pytest tests/test_network_share.py -q`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Hardcoded local path leaks developer username

The run command embeds a full local filesystem path (/Users/user4/Desktop/github/OmniVoice), committing a developer's username and machine layout into the repo history. Any contributor following this plan will also get a confusing path that won't exist on their machine.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

Comment on lines +417 to +418
if request.cookies.get("ov_pin") != pin:
response.set_cookie("ov_pin", pin, samesite="lax")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 PIN cookie missing httponly flag in plan template

The template sets response.set_cookie("ov_pin", pin, samesite="lax") without httponly=True, which leaves the cookie readable by any JavaScript running on the page. If the page ever serves content from a path that allows script injection, the PIN can be exfiltrated. The session-storage path already exposes the PIN to JS intentionally, but the cookie should add httponly=True to avoid creating a second JS-accessible store unnecessarily — or the cookie approach should be dropped in favour of sessionStorage alone.

Fix in Claude Code

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/superpowers/plans/2026-05-30-network-sharing.md`:
- Line 81: Replace the hardcoded absolute path in the command `cd
/Users/user4/Desktop/github/OmniVoice && python -m pytest
tests/test_network_share.py -q` with a repository-relative invocation (or remove
the `cd` entirely); update the line to either `python -m pytest
tests/test_network_share.py -q` or `cd <repo-root-relative-path> && python -m
pytest tests/test_network_share.py -q` (preferably the first) so the command
runs from any environment without relying on `/Users/user4/...`.
- Around line 409-412: The middleware that reads PIN from request.headers.get,
request.query_params.get, or request.cookies.get("ov_pin") exposes two risks:
query params leak in logs/referrers and cookies persist indefinitely; update the
handler that reads/sets the PIN so (1) when setting the ov_pin cookie include an
explicit expiry/max_age and secure attributes (e.g., HttpOnly, Secure, SameSite)
and (2) add server-side validation of cookie age or issue timestamps with the
cookie and reject stale PINs in the same middleware/function that calls
request.cookies.get("ov_pin"); also ensure the sharing UI/docs clearly warn that
?pin= is exposed in logs/referrers.
- Around line 406-407: The middleware currently exempts requests using a broad
check path.startswith("/favicon") which will match unintended routes; update the
condition in the handler that returns await call_next(request) to remove the
broad startswith check and either rely on "/favicon.ico" being present in
_SHELL_PATHS or replace it with a more specific test such as matching exact
"/favicon.ico" or a stricter pattern (e.g., path.startswith("/favicon.") for
extensions). Locate the conditional that references _SHELL_PATHS and
path.startswith in the same block (the function that calls call_next(request))
and modify that expression accordingly so only intended favicon paths are
exempted.
- Around line 138-146: _find_free_port closes the test socket after finding a
port which creates a TOCTOU race where uvicorn may fail to bind; update the
enable() function (the code that starts uvicorn) to catch binding errors (e.g.,
OSError/EADDRINUSE) and retry: on failure call _find_free_port again and attempt
to re-bind/start uvicorn a bounded number of times (with a short backoff) before
giving up, logging each retry and the final error; alternatively, add a clear
doc comment in enable() noting the race and recommending retries if you prefer
not to implement automatic retries.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9b4dfb7e-7b1c-44d2-9aeb-cf430b1f4e88

📥 Commits

Reviewing files that changed from the base of the PR and between 1a0798f and 72a818f.

📒 Files selected for processing (2)
  • .gitignore
  • docs/superpowers/plans/2026-05-30-network-sharing.md


- [ ] **Step 2: Run it — expect failure**

Run: `cd /Users/user4/Desktop/github/OmniVoice && python -m pytest tests/test_network_share.py -q`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Remove hardcoded absolute path.

The test run command contains a hardcoded absolute path /Users/user4/Desktop/github/OmniVoice that is environment-specific and will fail on other machines. Use a relative path or remove the cd command entirely since pytest should be run from the repository root.

🔧 Proposed fix
-Run: `cd /Users/user4/Desktop/github/OmniVoice && python -m pytest tests/test_network_share.py -q`
+Run: `python -m pytest tests/test_network_share.py -q`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Run: `cd /Users/user4/Desktop/github/OmniVoice && python -m pytest tests/test_network_share.py -q`
Run: `python -m pytest tests/test_network_share.py -q`
🧰 Tools
🪛 LanguageTool

[uncategorized] ~81-~81: The official name of this software platform is spelled with a capital “H”.
Context: ...Step 2: Run it — expect failure** Run: `cd /Users/user4/Desktop/github/OmniVoice && python -m pytest tests/tes...

(GITHUB)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/plans/2026-05-30-network-sharing.md` at line 81, Replace the
hardcoded absolute path in the command `cd /Users/user4/Desktop/github/OmniVoice
&& python -m pytest tests/test_network_share.py -q` with a repository-relative
invocation (or remove the `cd` entirely); update the line to either `python -m
pytest tests/test_network_share.py -q` or `cd <repo-root-relative-path> &&
python -m pytest tests/test_network_share.py -q` (preferably the first) so the
command runs from any environment without relying on `/Users/user4/...`.

Comment on lines +138 to +146
def _find_free_port(base: int, tries: int = 20) -> int:
for p in range(base, base + tries):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
try:
s.bind(("0.0.0.0", p))
return p
except OSError:
continue
raise RuntimeError("no free share port available")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Race condition in port availability check.

The _find_free_port function has a time-of-check to time-of-use (TOCTOU) race condition. The socket is closed immediately after successfully binding, which means another process could bind to that port before uvicorn starts. While this is a common pattern and the race window is small, uvicorn's bind could still fail.

Consider handling bind failures in the enable() function with retry logic, or document this known limitation.

🛡️ Proposed fix: Add error handling in enable()
 async def enable(app) -> ShareState:
     global _server, _task, _state
     if _state.enabled:
         return _state
     port = _find_free_port(BACKEND_PORT + 1)
     pin = _gen_pin()
     config = uvicorn.Config(app, host="0.0.0.0", port=port, log_level="warning")
     server = uvicorn.Server(config)
     server.install_signal_handlers = lambda: None
     _task = asyncio.create_task(server.serve())
     for _ in range(100):
         if getattr(server, "started", False):
             break
         await asyncio.sleep(0.05)
+    if not getattr(server, "started", False):
+        # Clean up failed start
+        server.should_exit = True
+        raise RuntimeError(f"Share server failed to bind to port {port} within timeout")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/plans/2026-05-30-network-sharing.md` around lines 138 - 146,
_find_free_port closes the test socket after finding a port which creates a
TOCTOU race where uvicorn may fail to bind; update the enable() function (the
code that starts uvicorn) to catch binding errors (e.g., OSError/EADDRINUSE) and
retry: on failure call _find_free_port again and attempt to re-bind/start
uvicorn a bounded number of times (with a short backoff) before giving up,
logging each retry and the final error; alternatively, add a clear doc comment
in enable() noting the race and recommending retries if you prefer not to
implement automatic retries.

Comment on lines +406 to +407
if path in _SHELL_PATHS or path.startswith("/assets/") or path.startswith("/favicon"):
return await call_next(request)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Overly broad path exemption for favicon.

The middleware exempts both path.startswith("/favicon") and path in _SHELL_PATHS (which includes "/favicon.ico"). The startswith("/favicon") check will match any path beginning with /favicon, such as /favicon_admin or /favicondata.

Since /favicon.ico is already in _SHELL_PATHS, the startswith check should be removed or made more specific (e.g., path.startswith("/favicon.")).

♻️ Proposed fix
         path = request.url.path
-        if path in _SHELL_PATHS or path.startswith("/assets/") or path.startswith("/favicon"):
+        if path in _SHELL_PATHS or path.startswith("/assets/"):
             return await call_next(request)

Or if other favicon formats are needed:

-        if path in _SHELL_PATHS or path.startswith("/assets/") or path.startswith("/favicon"):
+        if path in _SHELL_PATHS or path.startswith("/assets/") or path.startswith("/favicon."):
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/plans/2026-05-30-network-sharing.md` around lines 406 - 407,
The middleware currently exempts requests using a broad check
path.startswith("/favicon") which will match unintended routes; update the
condition in the handler that returns await call_next(request) to remove the
broad startswith check and either rely on "/favicon.ico" being present in
_SHELL_PATHS or replace it with a more specific test such as matching exact
"/favicon.ico" or a stricter pattern (e.g., path.startswith("/favicon.") for
extensions). Locate the conditional that references _SHELL_PATHS and
path.startswith in the same block (the function that calls call_next(request))
and modify that expression accordingly so only intended favicon paths are
exempted.

Comment on lines +409 to +412
request.headers.get("x-omnivoice-pin")
or request.query_params.get("pin")
or request.cookies.get("ov_pin")
or ""

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Consider security implications of PIN in query parameter and cookie.

The middleware accepts PIN from three sources: header, query parameter, and cookie. Two considerations:

  1. Query parameter exposure: The ?pin= parameter will appear in server logs, browser history, and referrer headers. This is documented as intentional for QR code flows, but ensure this risk is communicated to users in the sharing UI and documentation.

  2. Cookie persistence: The ov_pin cookie persists the PIN across browser sessions (no expiry set on line 418). This means a PIN remains valid even after sharing is disabled, until the cookie expires or is cleared. Consider adding max_age or validating cookie age server-side.

🔒 Proposed fix: Add cookie expiry
         if not secrets.compare_digest(supplied, pin):
             return JSONResponse({"detail": "PIN required"}, status_code=401)
         response = await call_next(request)
         if request.cookies.get("ov_pin") != pin:
-            response.set_cookie("ov_pin", pin, samesite="lax")
+            response.set_cookie("ov_pin", pin, max_age=86400, samesite="lax", httponly=True, secure=False)
+            # max_age=86400 = 24 hours; httponly prevents JS access; secure=False because LAN is HTTP
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/plans/2026-05-30-network-sharing.md` around lines 409 - 412,
The middleware that reads PIN from request.headers.get,
request.query_params.get, or request.cookies.get("ov_pin") exposes two risks:
query params leak in logs/referrers and cookies persist indefinitely; update the
handler that reads/sets the PIN so (1) when setting the ov_pin cookie include an
explicit expiry/max_age and secure attributes (e.g., HttpOnly, Secure, SameSite)
and (2) add server-side validation of cookie age or issue timestamps with the
cookie and reject stale PINs in the same middleware/function that calls
request.cookies.get("ov_pin"); also ensure the sharing UI/docs clearly warn that
?pin= is exposed in logs/referrers.

@debpalash
debpalash merged commit 40cf9bf into main May 30, 2026
15 checks passed
@debpalash
debpalash deleted the chore/gitignore-local-tooling branch May 30, 2026 12:17
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.

1 participant