Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"hooks": [
{
"type": "command",
"command": "[ -d .bicameral ] && [ -z \"$BICAMERAL_SESSION_END_RUNNING\" ] && BICAMERAL_SESSION_END_RUNNING=1 claude -p '/bicameral:capture-corrections --auto-ingest' || true"
"command": "[ -d .bicameral ] && [ -z \"$BICAMERAL_SESSION_END_RUNNING\" ] && BICAMERAL_SESSION_END_RUNNING=1 claude -p '/bicameral-capture-corrections --auto-ingest' || true"
}
]
}
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ Running `bicameral-mcp setup` writes these files to your repo:
| `.gitignore` entry | Ignores `.bicameral/` in solo mode | Recommended |
| `.claude/settings.json` | PostToolUse hook: auto-calls `bicameral.link_commit` after git commits | Optional — improves sync |
| `.claude/settings.json` | SessionEnd hook: runs `bicameral-capture-corrections` to catch uningested mid-session decisions | Optional — closes correction capture gap |
| `.claude/skills/bicameral-*/SKILL.md` | Slash commands (`/bicameral:ingest`, `/bicameral:preflight`, `/bicameral:capture-corrections`, etc.) | Recommended |
| `.claude/skills/bicameral-*/SKILL.md` | Slash commands (`/bicameral-ingest`, `/bicameral-preflight`, `/bicameral-capture-corrections`, etc.) | Recommended |

### Removing Bicameral

Expand Down Expand Up @@ -227,11 +227,11 @@ After setup, Claude Code gets these slash commands:

| Command | When to use |
|---|---|
| `/bicameral:ingest` | Paste a transcript, PRD, or Slack thread to track its decisions |
| `/bicameral:preflight` | Surface relevant decisions and drift before implementing |
| `/bicameral:history` | See all tracked decisions grouped by feature area |
| `/bicameral:dashboard` | Open the live decision dashboard in your browser |
| `/bicameral:reset` | Wipe and replay the ledger (emergency use) |
| `/bicameral-ingest` | Paste a transcript, PRD, or Slack thread to track its decisions |
| `/bicameral-preflight` | Surface relevant decisions and drift before implementing |
| `/bicameral-history` | See all tracked decisions grouped by feature area |
| `/bicameral-dashboard` | Open the live decision dashboard in your browser |
| `/bicameral-reset` | Wipe and replay the ledger (emergency use) |

The agent also fires these automatically — `preflight` before any code change, `ingest` when you paste a document.

Expand Down
2 changes: 1 addition & 1 deletion assets/git-for-specs-deck.html
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ <h2 class="t-h1 rv">What This Looks Like in Practice</h2>
Symbol: <span style="color:var(--accent)">PaymentProcessor.charge</span><br>
Evidence: <span style="color:var(--red)">PayPal import added</span>
</div>
<div style="font-size:clamp(9px,1vw,11px);color:var(--text-dim)">Commit blocked. Resolve with <span class="chip">/bicameral:supersede</span> or <span class="chip">--no-verify</span></div>
<div style="font-size:clamp(9px,1vw,11px);color:var(--text-dim)">Commit blocked. Resolve with <span class="chip">/bicameral-supersede</span> or <span class="chip">--no-verify</span></div>
<div style="font-size:clamp(9px,1vw,11px);color:var(--text-dim)">Decision context shared via <strong style="color:var(--text)">version control</strong>, allowing for organic <strong style="color:var(--text)">cross-functional context-sharing</strong> and alignment</div>
</div>

Expand Down
2 changes: 1 addition & 1 deletion docs/DEV_CYCLE.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ Observable evidence that a real user / agent / contributor stubbed their
toe on something that should "just work." Symptoms, not fixes.

Examples:
- Slack thread from a design partner showing `claude -p '/bicameral:sync'`
- Slack thread from a design partner showing `claude -p '/bicameral-sync'`
exiting silently (#124).
- Dashboard footage of a mid-session constraint orphaning as a parallel
decision instead of linking to its parent.
Expand Down
8 changes: 8 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ test = [
"tiktoken>=0.7.0,<1.0.0",
"ruff>=0.5.0",
"mypy>=1.10.0",
"build>=1.0.0",
]

[project.scripts]
Expand All @@ -70,6 +71,13 @@ exclude = [
]
artifacts = ["skills/**/*.md", "skills/**/*.yaml"]

# `force-include` ships skill source unconditionally, regardless of Hatchling's
# package-discovery rules. Required because `skills/` has no `__init__.py` and
# `artifacts` alone does not bundle these files into the wheel (verified empty
# pre-fix). Locked by tests/test_installer_packaging.py.
[tool.hatch.build.targets.wheel.force-include]
"skills" = "skills"

[tool.ruff]
line-length = 100
target-version = "py311"
Expand Down
8 changes: 4 additions & 4 deletions scripts/hooks/post_commit_sync_reminder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

When the agent runs ``git commit`` / ``git merge`` / ``git pull`` /
``git rebase --continue``, inject a system-reminder telling the agent to
call ``/bicameral:sync`` so the decision ledger picks up the new HEAD,
call ``/bicameral-sync`` so the decision ledger picks up the new HEAD,
runs compliance checks, and produces authoritative reflected/drifted
verdicts before the next user turn.

Expand All @@ -12,7 +12,7 @@
from PostToolUse hooks is silently dropped to the debug log — only
UserPromptSubmit / UserPromptExpansion / SessionStart treat raw stdout
as agent-visible context. Symptom: the agent committed but never
followed through to call ``link_commit`` / ``/bicameral:sync`` because
followed through to call ``link_commit`` / ``/bicameral-sync`` because
the reminder never reached the model. Fix: emit the structured
envelope ``{"hookSpecificOutput": {"hookEventName": "PostToolUse",
"additionalContext": "..."}}``.
Expand All @@ -33,7 +33,7 @@
BASH_TOOL_NAME = "Bash"

# Substrings that mark a git write-op against HEAD that the agent should
# follow up with /bicameral:sync. Exact phrasing matches the legacy
# follow up with /bicameral-sync. Exact phrasing matches the legacy
# inline command's tuple so behavior is byte-identical except for the
# stdout envelope.
WRITE_OP_MARKERS: tuple[str, ...] = (
Expand All @@ -44,7 +44,7 @@
)

REMINDER_TEXT = (
"bicameral: new commit detected — run /bicameral:sync to resolve "
"bicameral: new commit detected — run /bicameral-sync to resolve "
"compliance and get authoritative reflected/drifted status"
)

Expand Down
22 changes: 11 additions & 11 deletions server.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ async def list_tools() -> list[Tool]:
"Sync a commit into the decision ledger. Updates implemented_by/touches edges, "
"recomputes content hashes, re-evaluates drift for affected decisions. "
"Idempotent — calling twice for the same commit is a no-op. "
"Slash alias: /bicameral:link-commit"
"Slash alias: /bicameral-link-commit"
),
inputSchema={
"type": "object",
Expand Down Expand Up @@ -169,7 +169,7 @@ async def list_tools() -> list[Tool]:
"At least one text field per decision must be non-empty or the decision is silently dropped. "
"The `query` field drives the post-ingest auto-brief and gap-judge chain — always pass it. "
"Auto-grounds decisions to code via semantic search over the symbol graph. Ensures the code index is fresh before grounding. "
"Slash alias: /bicameral:ingest"
"Slash alias: /bicameral-ingest"
),
inputSchema={
"type": "object",
Expand Down Expand Up @@ -202,7 +202,7 @@ async def list_tools() -> list[Tool]:
"Pass start_line/end_line when you have exact lines (e.g. from a Read call) — "
"omit them to let the server resolve the exact line range automatically. Binding the same "
"(decision, region) pair twice is idempotent. "
"Slash alias: /bicameral:bind"
"Slash alias: /bicameral-bind"
),
inputSchema={
"type": "object",
Expand Down Expand Up @@ -288,7 +288,7 @@ async def list_tools() -> list[Tool]:
"before confirming full mode. "
"DRY RUN BY DEFAULT — confirm=false returns the wipe plan without touching anything. "
"Pass confirm=true to actually wipe. "
"Slash alias: /bicameral:reset"
"Slash alias: /bicameral-reset"
),
inputSchema={
"type": "object",
Expand Down Expand Up @@ -332,7 +332,7 @@ async def list_tools() -> list[Tool]:
"When fired=false, the agent MUST produce no output and proceed silently — "
"that's the trust contract. When fired=true, render the surfaced context with "
"a '(bicameral surfaced)' attribution before continuing with the implementation. "
"Slash alias: /bicameral:preflight"
"Slash alias: /bicameral-preflight"
),
inputSchema={
"type": "object",
Expand Down Expand Up @@ -389,7 +389,7 @@ async def list_tools() -> list[Tool]:
"standalone. Rubric categories: missing_acceptance_criteria, "
"underdefined_edge_cases, infrastructure_gap, underspecified_integration, "
"missing_data_requirements. "
"Slash alias: /bicameral:judge_gaps"
"Slash alias: /bicameral-judge_gaps"
),
inputSchema={
"type": "object",
Expand Down Expand Up @@ -427,7 +427,7 @@ async def list_tools() -> list[Tool]:
"decision/region IDs are returned as structured rejections (not "
"exceptions) so the caller can retry the accepted subset. The server "
"never calls an LLM — every semantic judgment lives in the caller's "
"session. Slash alias: /bicameral:resolve_compliance"
"session. Slash alias: /bicameral-resolve_compliance"
),
inputSchema={
"type": "object",
Expand Down Expand Up @@ -506,7 +506,7 @@ async def list_tools() -> list[Tool]:
"as a negative signal; agents consult it to avoid implementing what the team rejected. "
"Both actions are idempotent (was_new=false if already in that state). "
"The signer field identifies the human or agent; the optional note captures the rationale. "
"Slash alias: /bicameral:ratify"
"Slash alias: /bicameral-ratify"
),
inputSchema={
"type": "object",
Expand Down Expand Up @@ -554,7 +554,7 @@ async def list_tools() -> list[Tool]:
"Writes an input_span→context_for→decision edge (confirmed or rejected). "
"Context-pending decisions with ≥1 confirmed context_for edge become eligible "
"for bicameral.ratify. "
"Slash alias: /bicameral:resolve-collision"
"Slash alias: /bicameral-resolve-collision"
),
inputSchema={
"type": "object",
Expand Down Expand Up @@ -597,7 +597,7 @@ async def list_tools() -> list[Tool]:
"Capped at 50 features; use feature_filter to drill in when truncated=True. "
"Does NOT fire on implementation, ingest, or drift-specific queries — use "
"bicameral.preflight or bicameral.ingest for those. "
"Slash alias: /bicameral:history"
"Slash alias: /bicameral-history"
),
inputSchema={
"type": "object",
Expand Down Expand Up @@ -629,7 +629,7 @@ async def list_tools() -> list[Tool]:
"Subsequent calls return the existing URL immediately — the server "
"is a singleton and stays running for the session. "
"Fires on: 'open dashboard', 'show live history', 'launch dashboard'. "
"Slash alias: /bicameral:dashboard"
"Slash alias: /bicameral-dashboard"
),
inputSchema={
"type": "object",
Expand Down
56 changes: 33 additions & 23 deletions setup_wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,22 +165,30 @@ def _select_agents() -> list[str]:
return selected


class RunnerNotFoundError(RuntimeError):
"""Raised when no viable runner for bicameral-mcp is on PATH.

There is no `bicameral_mcp` package — the entry point is `server:cli_main` —
so `python -m bicameral_mcp` is not a valid fallback.
"""


def _detect_runner() -> tuple[str, list[str]]:
"""Detect the best available runner for bicameral-mcp.

Preference order:
1. bicameral-mcp binary on PATH — uses the actual installed environment,
so local subpackages (dashboard/, etc.) and editable installs work.
2. python3 -m bicameral_mcp — fallback for source checkouts / venvs.
Only one runner is supported: the `bicameral-mcp` script installed by
`pip install bicameral-mcp` (or an editable install in a venv). pipx run
is intentionally NOT used: it downloads a fresh ephemeral copy from PyPI
on every server start, missing local-only modules and risking version skew.

pipx run is intentionally NOT used: it downloads a fresh ephemeral copy
from PyPI on every server start, which misses local-only modules and can
run a different version than what the user installed.
The previous `python -m bicameral_mcp` fallback was removed because there
is no `bicameral_mcp` package; the resulting MCP config was non-functional.
"""
if shutil.which("bicameral-mcp"):
return ("bicameral-mcp", [])
python = "python3" if shutil.which("python3") else "python"
return (python, ["-m", "bicameral_mcp"])
raise RunnerNotFoundError(
"No runner found for bicameral-mcp. Install with: pip install bicameral-mcp"
)


def _build_config(
Expand Down Expand Up @@ -384,7 +392,7 @@ def _build_session_end_command(mcp_config_path: str | None = None) -> str:
return (
'[ -d .bicameral ] && [ -z "$BICAMERAL_SESSION_END_RUNNING" ] && '
"BICAMERAL_SESSION_END_RUNNING=1 "
f"claude -p '/bicameral:capture-corrections --auto-ingest'{extra_flags} || true"
f"claude -p '/bicameral-capture-corrections --auto-ingest'{extra_flags} || true"
)


Expand All @@ -396,7 +404,7 @@ def _build_session_end_command(mcp_config_path: str | None = None) -> str:
# Fires after every Bash tool use. When the command is a git write-op
# (commit / merge / pull / rebase --continue), emits a hookSpecificOutput
# envelope whose additionalContext nudges the agent to invoke
# /bicameral:sync — running the full link_commit → compliance check
# /bicameral-sync — running the full link_commit → compliance check
# flow so status is authoritative immediately.
#
# Was a plain-stdout python -c one-liner. Per Claude Code 2.x hook docs
Expand Down Expand Up @@ -630,6 +638,9 @@ def _install_skills(repo_path: Path) -> int:
"""Copy skill definitions into .claude/skills/ in the target repo."""
skills_src = Path(__file__).parent / "skills"
if not skills_src.exists():
print(f" WARNING: skill source not found at {skills_src}")
print(" Skills were not installed. The wheel may have been built without skills/.")
print(" Re-install bicameral-mcp from a recent release, or report this bug.")
return 0

skills_dst = repo_path / ".claude" / "skills"
Expand Down Expand Up @@ -867,11 +878,11 @@ def run_setup(
agents = _select_agents()

# Step 3: Runner check
command, _ = _detect_runner()
if command not in ("bicameral-mcp",):
print("\n Note: bicameral-mcp binary not found on PATH.")
print(f" Using '{command} -m bicameral_mcp' as runner.")
print(" Install for a cleaner setup: pip install bicameral-mcp")
try:
_detect_runner()
except RunnerNotFoundError as e:
print(f"\n ERROR: {e}")
return 1

# Step 4: Collaboration mode + guided intensity + telemetry + gitignore
collab_mode = _select_collaboration_mode()
Expand All @@ -894,8 +905,7 @@ def run_setup(
# Step 6: Install skills + hooks (Claude Code only)
if "claude" in agents:
num_skills = _install_skills(repo_path)
if num_skills:
print(f" Claude Code: installed {num_skills} slash commands")
print(f" Claude Code: installed {num_skills} skill(s) at {repo_path}/.claude/skills/")
if _install_claude_hooks(repo_path):
print(
" Claude Code: installed hooks → link_commit on commit · capture-corrections on session end"
Expand Down Expand Up @@ -927,11 +937,11 @@ def run_setup(

if "claude" in agents:
print(" Claude Code slash commands:")
print(" /bicameral:ingest — ingest a transcript, Slack thread, or PRD")
print(" /bicameral:preflight — pre-flight: surface decisions before coding")
print(" /bicameral:history — list all tracked decisions by feature area")
print(" /bicameral:dashboard — open live decision dashboard in browser")
print(" /bicameral:reset — nuke and replay the ledger (emergency)")
print(" /bicameral-ingest — ingest a transcript, Slack thread, or PRD")
print(" /bicameral-preflight — pre-flight: surface decisions before coding")
print(" /bicameral-history — list all tracked decisions by feature area")
print(" /bicameral-dashboard — open live decision dashboard in browser")
print(" /bicameral-reset — nuke and replay the ledger (emergency)")
print()

print(" Or just ask naturally:")
Expand Down
4 changes: 2 additions & 2 deletions skills/bicameral-capture-corrections/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ capture-corrections output.
## SessionEnd batch mode

Fires via the `SessionEnd` hook in `.claude/settings.json`. Also invocable
manually as `/bicameral:capture-corrections`.
manually as `/bicameral-capture-corrections`.

### Steps

Expand Down Expand Up @@ -235,7 +235,7 @@ user's project `.claude/settings.json`. No manual configuration needed.

Command written by the setup wizard:
```
[ -d .bicameral ] && [ -z "$BICAMERAL_SESSION_END_RUNNING" ] && BICAMERAL_SESSION_END_RUNNING=1 claude -p '/bicameral:capture-corrections --auto-ingest' || true
[ -d .bicameral ] && [ -z "$BICAMERAL_SESSION_END_RUNNING" ] && BICAMERAL_SESSION_END_RUNNING=1 claude -p '/bicameral-capture-corrections --auto-ingest' || true
```

Two guards:
Expand Down
4 changes: 2 additions & 2 deletions skills/bicameral-config/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# /bicameral:config — Interactive Configuration
# /bicameral-config — Interactive Configuration

**Trigger**: user types `/bicameral:config`
**Trigger**: user types `/bicameral-config`

Walk through each bicameral configuration setting interactively, write the
updated `config.yaml`, and reinstall all hooks so changes take effect
Expand Down
2 changes: 1 addition & 1 deletion skills/bicameral-preflight/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ so you can see what your branch changes relative to main.
### 3.5 Scan recent user turns for uningested corrections

Before classifying server-returned findings, invoke
`/bicameral:capture-corrections` in **in-session mode**:
`/bicameral-capture-corrections` in **in-session mode**:

```
Skill("bicameral:capture-corrections", args="--mode in-session")
Expand Down
2 changes: 1 addition & 1 deletion skills/bicameral-reset/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ The fail-safe valve. When the ledger gets polluted — bad ingest, stale groundi
## When NOT to fire

- **Never fire automatically.** Reset is always user-initiated.
- Drift reports that look wrong → run `/bicameral:sync` first, escalate to reset only if that doesn't help.
- Drift reports that look wrong → run `/bicameral-sync` first, escalate to reset only if that doesn't help.
- If only one ingest looks bad, suggest re-running that ingest rather than wiping everything.

## The two-call pattern (always)
Expand Down
Loading
Loading