diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
new file mode 100644
index 0000000..9d66b49
--- /dev/null
+++ b/.claude-plugin/marketplace.json
@@ -0,0 +1,21 @@
+{
+ "$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
+ "name": "base",
+ "owner": {
+ "name": "ChristopherKahler",
+ "url": "https://github.com/ChristopherKahler"
+ },
+ "metadata": {
+ "description": "BASE — Builder's Automated State Engine: workspace lifecycle management, data surfaces, and audit tooling for Claude Code."
+ },
+ "plugins": [
+ {
+ "name": "base",
+ "description": "BASE — Builder's Automated State Engine: workspace lifecycle management, data surfaces, and audit tooling for Claude Code.",
+ "version": "3.1.5",
+ "source": ".",
+ "category": "framework",
+ "keywords": ["base", "workspace", "scaffold", "audit", "groom", "surfaces", "operator", "lifecycle"]
+ }
+ ]
+}
diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json
new file mode 100644
index 0000000..21a8485
--- /dev/null
+++ b/.claude-plugin/plugin.json
@@ -0,0 +1,10 @@
+{
+ "name": "base",
+ "version": "3.1.5",
+ "description": "BASE — Builder's Automated State Engine: workspace lifecycle management, data surfaces, and audit tooling for Claude Code.",
+ "author": {
+ "name": "Chris Kahler"
+ },
+ "license": "MIT",
+ "homepage": "https://github.com/ChristopherKahler/base#readme"
+}
diff --git a/.github/workflows/plugin-install.yml b/.github/workflows/plugin-install.yml
new file mode 100644
index 0000000..6ee0092
--- /dev/null
+++ b/.github/workflows/plugin-install.yml
@@ -0,0 +1,26 @@
+name: plugin-install
+
+on:
+ push:
+ pull_request:
+ workflow_dispatch:
+
+jobs:
+ validate-and-install:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: actions/setup-node@v4
+ with:
+ node-version: '22'
+ - name: Install Claude Code CLI
+ run: npm install -g @anthropic-ai/claude-code
+ - name: Validate plugin + marketplace manifest (strict)
+ run: claude plugin validate . --strict
+ - name: Install smoke test (claude can install the plugin)
+ run: |
+ set -euo pipefail
+ claude plugin marketplace add ./
+ claude plugin install base@base
+ claude plugin list
+ claude plugin list | grep -i base
diff --git a/.gitignore b/.gitignore
index de9dc4a..8acf352 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,8 @@ DIRECTORY-STRATEGY-SPEC.md
# Node
node_modules/
+# mcp/node_modules is created as a symlink by the SessionStart hook;
+# the trailing-slash pattern above matches directories but NOT symlinks,
+# so we add a slash-less entry to cover the symlink case as well.
+mcp/node_modules
+__pycache__/
diff --git a/.mcp.json b/.mcp.json
new file mode 100644
index 0000000..c56eafe
--- /dev/null
+++ b/.mcp.json
@@ -0,0 +1,12 @@
+{
+ "mcpServers": {
+ "base-mcp": {
+ "command": "node",
+ "args": ["${CLAUDE_PLUGIN_ROOT}/mcp/index.js"],
+ "env": {
+ "CLAUDE_PROJECT_DIR": "${CLAUDE_PROJECT_DIR}",
+ "NODE_PATH": "${CLAUDE_PLUGIN_DATA}/node_modules"
+ }
+ }
+ }
+}
diff --git a/src/framework/context/base-principles.md b/base-framework/context/base-principles.md
similarity index 98%
rename from src/framework/context/base-principles.md
rename to base-framework/context/base-principles.md
index ce6e71b..a8f3e8f 100644
--- a/src/framework/context/base-principles.md
+++ b/base-framework/context/base-principles.md
@@ -1,69 +1,69 @@
-# BASE Principles
-
-## Core Laws
-
-1. **If it's not current, it's harmful.** Stale context documents feed AI bad information. Maintenance isn't optional.
-2. **Every file earns its place.** If you can't explain why it's here in 5 seconds, it moves or dies.
-3. **Archive > delete.** When in doubt, archive. You can always delete later. You can't un-delete.
-4. **The workspace is the product.** Treat it like production code, not a scratch pad.
-5. **Clean as you go.** The best time to file something correctly is when you create it. The second best time is now.
-6. **Scaffold generates manifest. Manifest drives everything.** One configuration point. No manual bookkeeping.
-7. **Tools register themselves.** PAUL projects auto-register with BASE. No human memory required.
-
-## Drift Score
-
-Drift is the gap between documented state and actual state. Measured in days-overdue across all tracked areas.
-
-- **0** — Everything current. Workspace is clean.
-- **1-7** — Minor drift. Normal during execution sprints. Fix at next groom.
-- **8-14** — Moderate drift. Context documents are likely misleading AI. Groom soon.
-- **15+** — Critical drift. Sessions are operating on stale context. Groom NOW.
-
-## Maintenance Cadence
-
-| What | Default Cadence | Override |
-|------|----------------|---------|
-| projects.json | Every session or weekly | workspace.json |
-| Project directory | Monthly | workspace.json |
-| Tools/MCP | Monthly | workspace.json |
-| System layer | Monthly | workspace.json |
-| Full audit | Quarterly or after major shifts | On demand |
-
-## Backlog Rules
-
-Items have time-based properties enforced by grooming:
-
-- **Added** — auto-set when item enters backlog
-- **Review-by** — priority-based: High=7d, Medium=14d, Low=30d
-- **Staleness** — 2x review-by threshold. Auto-archive if reached without action.
-
-During groom: items past review-by surface as "decide or kill." Items past staleness auto-archive with a note.
-
-## Graduation Flow
-
-Backlog items don't sit forever. They graduate to active when the operator is ready to work on them.
-
-```
-BACKLOG (status=backlog in projects.json)
- → ACTIVE (status updated to in_progress/todo via base_update_project)
- → DONE (archived via base_archive_project with outcome)
-```
-
-**TASKS vs PROJECTS:** A task is bounded — it has a finish line. "Extract .mcp.json secrets" is a task. "Build the CARL MCP server" might start as a task but could become a project if it grows. The operator decides during groom.
-
-**Graduation is never automatic.** The groom flow asks explicitly: "Ready to work on any backlog items?" The operator decides what graduates and where it lands.
-
-**Items can also move backward:** An active project that loses priority can return to backlog status. A project that stalls can move to DEFERRED. Nothing is permanent.
-
-## Scaffold Modes
-
-BASE scaffold operates in two modes:
-
-- **Standard** (`/base:scaffold`) — Data layer only. Creates `.base/` with workspace.json, `.base/data/state.json`, ROADMAP.md. Scans and tracks what exists. Framework-agnostic.
-- **Full** (`/base:scaffold --full`) — Data layer + projects.json + entities.json. Offers CLAUDE.md audit. The "batteries included" version for AI builders who want the full system.
-
-Standard mode works for any workspace. Full mode provides Chris's proven operational structure.
-
-## File Location
-
-BASE operates strictly out of `.base/`. All data (projects.json, entities.json, state.json, psmm.json) lives in `.base/data/`. Configuration (workspace.json) and documentation (ROADMAP.md) live in `.base/`. Data is accessed via MCP tools (`base_list_projects`, `base_add_project`, `base_update_project`, `base_get_state`, etc.). `.base/data/` is the canonical location for all structured data.
+# BASE Principles
+
+## Core Laws
+
+1. **If it's not current, it's harmful.** Stale context documents feed AI bad information. Maintenance isn't optional.
+2. **Every file earns its place.** If you can't explain why it's here in 5 seconds, it moves or dies.
+3. **Archive > delete.** When in doubt, archive. You can always delete later. You can't un-delete.
+4. **The workspace is the product.** Treat it like production code, not a scratch pad.
+5. **Clean as you go.** The best time to file something correctly is when you create it. The second best time is now.
+6. **Scaffold generates manifest. Manifest drives everything.** One configuration point. No manual bookkeeping.
+7. **Tools register themselves.** PAUL projects auto-register with BASE. No human memory required.
+
+## Drift Score
+
+Drift is the gap between documented state and actual state. Measured in days-overdue across all tracked areas.
+
+- **0** — Everything current. Workspace is clean.
+- **1-7** — Minor drift. Normal during execution sprints. Fix at next groom.
+- **8-14** — Moderate drift. Context documents are likely misleading AI. Groom soon.
+- **15+** — Critical drift. Sessions are operating on stale context. Groom NOW.
+
+## Maintenance Cadence
+
+| What | Default Cadence | Override |
+|------|----------------|---------|
+| projects.json | Every session or weekly | workspace.json |
+| Project directory | Monthly | workspace.json |
+| Tools/MCP | Monthly | workspace.json |
+| System layer | Monthly | workspace.json |
+| Full audit | Quarterly or after major shifts | On demand |
+
+## Backlog Rules
+
+Items have time-based properties enforced by grooming:
+
+- **Added** — auto-set when item enters backlog
+- **Review-by** — priority-based: High=7d, Medium=14d, Low=30d
+- **Staleness** — 2x review-by threshold. Auto-archive if reached without action.
+
+During groom: items past review-by surface as "decide or kill." Items past staleness auto-archive with a note.
+
+## Graduation Flow
+
+Backlog items don't sit forever. They graduate to active when the operator is ready to work on them.
+
+```
+BACKLOG (status=backlog in projects.json)
+ → ACTIVE (status updated to in_progress/todo via base_update_project)
+ → DONE (archived via base_archive_project with outcome)
+```
+
+**TASKS vs PROJECTS:** A task is bounded — it has a finish line. "Extract .mcp.json secrets" is a task. "Build the CARL MCP server" might start as a task but could become a project if it grows. The operator decides during groom.
+
+**Graduation is never automatic.** The groom flow asks explicitly: "Ready to work on any backlog items?" The operator decides what graduates and where it lands.
+
+**Items can also move backward:** An active project that loses priority can return to backlog status. A project that stalls can move to DEFERRED. Nothing is permanent.
+
+## Scaffold Modes
+
+BASE scaffold operates in two modes:
+
+- **Standard** (`/base:scaffold`) — Data layer only. Creates `.base/` with workspace.json, `.base/data/state.json`, ROADMAP.md. Scans and tracks what exists. Framework-agnostic.
+- **Full** (`/base:scaffold --full`) — Data layer + projects.json + entities.json. Offers CLAUDE.md audit. The "batteries included" version for AI builders who want the full system.
+
+Standard mode works for any workspace. Full mode provides Chris's proven operational structure.
+
+## File Location
+
+BASE operates strictly out of `.base/`. All data (projects.json, entities.json, state.json, psmm.json) lives in `.base/data/`. Configuration (workspace.json) and documentation (ROADMAP.md) live in `.base/`. Data is accessed via MCP tools (`base_list_projects`, `base_add_project`, `base_update_project`, `base_get_state`, etc.). `.base/data/` is the canonical location for all structured data.
diff --git a/src/framework/frameworks/audit-strategies.md b/base-framework/frameworks/audit-strategies.md
similarity index 98%
rename from src/framework/frameworks/audit-strategies.md
rename to base-framework/frameworks/audit-strategies.md
index 4df45e1..fb3eaa9 100644
--- a/src/framework/frameworks/audit-strategies.md
+++ b/base-framework/frameworks/audit-strategies.md
@@ -1,53 +1,53 @@
-# Audit Strategies
-
-Reusable audit strategies that can be applied to any workspace area. The workspace manifest (`workspace.json`) maps areas to strategies. The audit command reads the manifest and applies the appropriate strategy to each area.
-
-## Strategies
-
-### staleness
-**Applies to:** Data files (projects.json, state.json, any tracked document)
-**What it does:** Check file modification timestamps against configured thresholds. Flag files past their groom cadence.
-**Config:**
-- `threshold_days` — days after which the file is considered stale
-**Output:** List of stale files with age, recommended action (update or review)
-
-### classify
-**Applies to:** Directories with lifecycle items (projects/, clients/)
-**What it does:** List all items in the directory. For each, present to operator for classification: active, archive, or delete. Check for planning docs, recent activity, git history.
-**Config:**
-- `states` — classification options (default: ["active", "archive", "delete"])
-- `archive_path` — where archived items go (default: `{path}/_archive/`)
-**Output:** Classification decisions, items moved to archive, items deleted
-
-### cross-reference
-**Applies to:** Tools/servers that have a config file mapping (e.g., MCP servers vs .mcp.json)
-**What it does:** Compare directory contents against a configuration file. Identify directories not referenced in config (orphaned) and config entries pointing to missing directories (broken).
-**Config:**
-- `config_file` — path to the configuration file to cross-reference
-**Output:** Orphaned items, broken references, recommendations
-
-### dead-code
-**Applies to:** System directories (hooks, commands, skills)
-**What it does:** Scan for files that appear unused — no references from other files, no recent invocations, no clear purpose. Presents findings for human decision.
-**Config:**
-- `reference_check` — whether to search for references in other files (default: true)
-**Output:** Potentially dead files with evidence, operator decides keep/delete
-
-### pipeline-status
-**Applies to:** Content pipelines, task queues, any workflow with stages
-**What it does:** Check items in each pipeline stage. Flag stuck items (in same stage too long), empty stages, bottlenecks.
-**Config:**
-- `stages` — ordered list of pipeline stages
-- `stuck_threshold_days` — days in one stage before flagging
-**Output:** Pipeline health report, stuck items, stage distribution
-
-## Extending Strategies
-
-Custom strategies can be added for workspace-specific needs. A strategy is defined by:
-1. A name (kebab-case)
-2. What it applies to (description)
-3. What it checks (logic)
-4. What config it needs (parameters)
-5. What it outputs (findings format)
-
-Add custom strategies to this file and reference them in `workspace.json`.
+# Audit Strategies
+
+Reusable audit strategies that can be applied to any workspace area. The workspace manifest (`workspace.json`) maps areas to strategies. The audit command reads the manifest and applies the appropriate strategy to each area.
+
+## Strategies
+
+### staleness
+**Applies to:** Data files (projects.json, state.json, any tracked document)
+**What it does:** Check file modification timestamps against configured thresholds. Flag files past their groom cadence.
+**Config:**
+- `threshold_days` — days after which the file is considered stale
+**Output:** List of stale files with age, recommended action (update or review)
+
+### classify
+**Applies to:** Directories with lifecycle items (projects/, clients/)
+**What it does:** List all items in the directory. For each, present to operator for classification: active, archive, or delete. Check for planning docs, recent activity, git history.
+**Config:**
+- `states` — classification options (default: ["active", "archive", "delete"])
+- `archive_path` — where archived items go (default: `{path}/_archive/`)
+**Output:** Classification decisions, items moved to archive, items deleted
+
+### cross-reference
+**Applies to:** Tools/servers that have a config file mapping (e.g., MCP servers vs .mcp.json)
+**What it does:** Compare directory contents against a configuration file. Identify directories not referenced in config (orphaned) and config entries pointing to missing directories (broken).
+**Config:**
+- `config_file` — path to the configuration file to cross-reference
+**Output:** Orphaned items, broken references, recommendations
+
+### dead-code
+**Applies to:** System directories (hooks, commands, skills)
+**What it does:** Scan for files that appear unused — no references from other files, no recent invocations, no clear purpose. Presents findings for human decision.
+**Config:**
+- `reference_check` — whether to search for references in other files (default: true)
+**Output:** Potentially dead files with evidence, operator decides keep/delete
+
+### pipeline-status
+**Applies to:** Content pipelines, task queues, any workflow with stages
+**What it does:** Check items in each pipeline stage. Flag stuck items (in same stage too long), empty stages, bottlenecks.
+**Config:**
+- `stages` — ordered list of pipeline stages
+- `stuck_threshold_days` — days in one stage before flagging
+**Output:** Pipeline health report, stuck items, stage distribution
+
+## Extending Strategies
+
+Custom strategies can be added for workspace-specific needs. A strategy is defined by:
+1. A name (kebab-case)
+2. What it applies to (description)
+3. What it checks (logic)
+4. What config it needs (parameters)
+5. What it outputs (findings format)
+
+Add custom strategies to this file and reference them in `workspace.json`.
diff --git a/src/framework/frameworks/claude-config-alignment.md b/base-framework/frameworks/claude-config-alignment.md
similarity index 100%
rename from src/framework/frameworks/claude-config-alignment.md
rename to base-framework/frameworks/claude-config-alignment.md
diff --git a/src/framework/frameworks/claudemd-strategy.md b/base-framework/frameworks/claudemd-strategy.md
similarity index 100%
rename from src/framework/frameworks/claudemd-strategy.md
rename to base-framework/frameworks/claudemd-strategy.md
diff --git a/src/framework/frameworks/satellite-registration.md b/base-framework/frameworks/satellite-registration.md
similarity index 97%
rename from src/framework/frameworks/satellite-registration.md
rename to base-framework/frameworks/satellite-registration.md
index 43fcc37..486fd6b 100644
--- a/src/framework/frameworks/satellite-registration.md
+++ b/base-framework/frameworks/satellite-registration.md
@@ -1,44 +1,44 @@
-# Satellite Registration Framework
-
-## What Are Satellites
-
-Satellites are projects that live in their own git repos inside the workspace (e.g., `apps/*`). They run their own Claude Code sessions independently. BASE needs visibility into them without owning them.
-
-## Registration Flow
-
-### Automatic (via PAUL init)
-When `/paul:init` runs in a subdirectory:
-1. Check if parent directory has `.base/workspace.json`
-2. If yes, write registration entry: project name, path, engine type, state file path, date
-3. Report: "Registered with BASE workspace: {workspace-name}"
-
-### Automatic (via BASE scaffold)
-When `/base:scaffold` runs:
-1. Scan configured satellite directories (default: `apps/`)
-2. Detect existing `.paul/` directories
-3. Auto-register discovered projects
-4. Report: "Found {N} satellite projects. Registered."
-
-### Automatic (via BASE groom)
-During groom:
-1. Read registered satellites from workspace.json
-2. Check each path exists (clean up broken registrations)
-3. Scan satellite directories for unregistered projects with `.paul/`
-4. Flag: "Found unregistered project: {name}. Register?"
-
-## Health Checks
-
-During `/base:pulse` and `/base:groom`, for each satellite:
-1. Read the state file (e.g., `.paul/STATE.md`)
-2. Check last modification date
-3. Extract current phase/milestone if parseable
-4. Report health: active, stale, or unknown
-
-BASE never modifies satellite state. It only reads and reports. PAUL (or whatever engine) manages the project. BASE manages the workspace those projects live in.
-
-## Deregistration
-
-Satellites are deregistered when:
-- The project directory no longer exists (auto-cleaned during groom)
-- The user explicitly removes it during audit
-- The project is archived (moved to `_archive/` or similar)
+# Satellite Registration Framework
+
+## What Are Satellites
+
+Satellites are projects that live in their own git repos inside the workspace (e.g., `apps/*`). They run their own Claude Code sessions independently. BASE needs visibility into them without owning them.
+
+## Registration Flow
+
+### Automatic (via PAUL init)
+When `/paul:init` runs in a subdirectory:
+1. Check if parent directory has `.base/workspace.json`
+2. If yes, write registration entry: project name, path, engine type, state file path, date
+3. Report: "Registered with BASE workspace: {workspace-name}"
+
+### Automatic (via BASE scaffold)
+When `/base:scaffold` runs:
+1. Scan configured satellite directories (default: `apps/`)
+2. Detect existing `.paul/` directories
+3. Auto-register discovered projects
+4. Report: "Found {N} satellite projects. Registered."
+
+### Automatic (via BASE groom)
+During groom:
+1. Read registered satellites from workspace.json
+2. Check each path exists (clean up broken registrations)
+3. Scan satellite directories for unregistered projects with `.paul/`
+4. Flag: "Found unregistered project: {name}. Register?"
+
+## Health Checks
+
+During `/base:pulse` and `/base:groom`, for each satellite:
+1. Read the state file (e.g., `.paul/STATE.md`)
+2. Check last modification date
+3. Extract current phase/milestone if parseable
+4. Report health: active, stale, or unknown
+
+BASE never modifies satellite state. It only reads and reports. PAUL (or whatever engine) manages the project. BASE manages the workspace those projects live in.
+
+## Deregistration
+
+Satellites are deregistered when:
+- The project directory no longer exists (auto-cleaned during groom)
+- The user explicitly removes it during audit
+- The project is archived (moved to `_archive/` or similar)
diff --git a/src/framework/tasks/audit-claude-md.md b/base-framework/tasks/audit-claude-md.md
similarity index 95%
rename from src/framework/tasks/audit-claude-md.md
rename to base-framework/tasks/audit-claude-md.md
index 9698a63..8cac3c0 100644
--- a/src/framework/tasks/audit-claude-md.md
+++ b/base-framework/tasks/audit-claude-md.md
@@ -13,8 +13,8 @@ As an AI builder, I want my CLAUDE.md audited against a proven strategy so I get
-@~/.claude/base-framework/frameworks/claudemd-strategy.md
-@~/.claude/base-framework/templates/claudemd-template.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/frameworks/claudemd-strategy.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/templates/claudemd-template.md
@@ -22,8 +22,8 @@ As an AI builder, I want my CLAUDE.md audited against a proven strategy so I get
Load the CLAUDE.md Strategy framework and template.
-1. Read `@~/.claude/base-framework/frameworks/claudemd-strategy.md` — this is the source of truth
-2. Read `@~/.claude/base-framework/templates/claudemd-template.md` — this is the structural reference
+1. Read `@${CLAUDE_PLUGIN_ROOT}/base-framework/frameworks/claudemd-strategy.md` — this is the source of truth
+2. Read `@${CLAUDE_PLUGIN_ROOT}/base-framework/templates/claudemd-template.md` — this is the structural reference
3. Internalize: five-section model (What/Why/Who/Where/How), NEVER pattern, line budget, audit criteria
You MUST understand the full strategy before reading the user's file. The strategy defines what "correct" looks like.
diff --git a/src/framework/tasks/audit-claude.md b/base-framework/tasks/audit-claude.md
similarity index 100%
rename from src/framework/tasks/audit-claude.md
rename to base-framework/tasks/audit-claude.md
diff --git a/src/framework/tasks/audit.md b/base-framework/tasks/audit.md
similarity index 97%
rename from src/framework/tasks/audit.md
rename to base-framework/tasks/audit.md
index 27ac7df..e015cb8 100644
--- a/src/framework/tasks/audit.md
+++ b/base-framework/tasks/audit.md
@@ -1,64 +1,64 @@
-
-Deep workspace optimization. Dynamically generate audit phases from the workspace manifest, run each area's configured audit strategy, and execute operator-approved changes.
-
-
-
-As an AI builder, I want a thorough workspace audit that adapts to my workspace structure, so that every area gets properly reviewed regardless of how complex my setup is.
-
-
-
-- Quarterly or after major workspace shifts
-- When user says "base audit", "deep clean", "optimize workspace"
-- Entry point routes here via /base:audit
-
-
-
-
-
-Read workspace manifest and generate audit phases dynamically.
-
-1. Read `.base/workspace.json`
-2. For each area, create an audit phase using its configured strategy
-3. Present phase list: "Audit will cover {N} phases: {list with strategies}"
-4. Create task tracking for each phase
-
-**Wait for operator confirmation. Allow them to skip or reorder phases.**
-
-
-
-Run each phase using its configured audit strategy.
-
-For each phase:
-1. Announce: "Phase {N}: {area-name} ({strategy})"
-2. Execute the strategy (reference frameworks/audit-strategies.md)
-3. Present findings
-4. Collect operator decisions (keep/archive/delete/move)
-5. Execute approved changes
-6. Mark phase complete
-
-Strategies are documented in `@frameworks/audit-strategies.md`.
-
-
-
-Record the audit results.
-
-1. Update `.base/data/state.json`
-2. Write audit record to `.base/audits/{YYYY-MM-DD}.md`
-3. Log to `.base/ROADMAP.md`
-4. Report final summary: phases completed, items changed, new drift score
-
-
-
-
-
-
-
-- [ ] Phases generated dynamically from manifest (not hardcoded)
-- [ ] Each area audited using its configured strategy
-- [ ] Operator approved all changes before execution
-- [ ] Audit record written to audits/ directory
-- [ ] state.json updated
-- [ ] ROADMAP.md updated with audit entry
-
+
+Deep workspace optimization. Dynamically generate audit phases from the workspace manifest, run each area's configured audit strategy, and execute operator-approved changes.
+
+
+
+As an AI builder, I want a thorough workspace audit that adapts to my workspace structure, so that every area gets properly reviewed regardless of how complex my setup is.
+
+
+
+- Quarterly or after major workspace shifts
+- When user says "base audit", "deep clean", "optimize workspace"
+- Entry point routes here via /base:audit
+
+
+
+
+
+Read workspace manifest and generate audit phases dynamically.
+
+1. Read `.base/workspace.json`
+2. For each area, create an audit phase using its configured strategy
+3. Present phase list: "Audit will cover {N} phases: {list with strategies}"
+4. Create task tracking for each phase
+
+**Wait for operator confirmation. Allow them to skip or reorder phases.**
+
+
+
+Run each phase using its configured audit strategy.
+
+For each phase:
+1. Announce: "Phase {N}: {area-name} ({strategy})"
+2. Execute the strategy (reference frameworks/audit-strategies.md)
+3. Present findings
+4. Collect operator decisions (keep/archive/delete/move)
+5. Execute approved changes
+6. Mark phase complete
+
+Strategies are documented in `@frameworks/audit-strategies.md`.
+
+
+
+Record the audit results.
+
+1. Update `.base/data/state.json`
+2. Write audit record to `.base/audits/{YYYY-MM-DD}.md`
+3. Log to `.base/ROADMAP.md`
+4. Report final summary: phases completed, items changed, new drift score
+
+
+
+
+
+
+
+- [ ] Phases generated dynamically from manifest (not hardcoded)
+- [ ] Each area audited using its configured strategy
+- [ ] Operator approved all changes before execution
+- [ ] Audit record written to audits/ directory
+- [ ] state.json updated
+- [ ] ROADMAP.md updated with audit entry
+
diff --git a/src/framework/tasks/carl-hygiene.md b/base-framework/tasks/carl-hygiene.md
similarity index 100%
rename from src/framework/tasks/carl-hygiene.md
rename to base-framework/tasks/carl-hygiene.md
diff --git a/src/framework/tasks/groom.md b/base-framework/tasks/groom.md
similarity index 100%
rename from src/framework/tasks/groom.md
rename to base-framework/tasks/groom.md
diff --git a/src/framework/tasks/history.md b/base-framework/tasks/history.md
similarity index 96%
rename from src/framework/tasks/history.md
rename to base-framework/tasks/history.md
index 67a20f9..e8edefe 100644
--- a/src/framework/tasks/history.md
+++ b/base-framework/tasks/history.md
@@ -1,34 +1,34 @@
-
-Show workspace evolution over time. Read ROADMAP.md and present the chronological record of major workspace changes.
-
-
-
-As an AI builder, I want to see how my workspace has evolved, so that I can understand the trajectory and make informed decisions about future changes.
-
-
-
-- When user wants to review workspace history
-- Entry point routes here via /base:history
-
-
-
-
-
-Read and present workspace evolution.
-
-1. Read `.base/ROADMAP.md`
-2. Present chronologically: dates, what changed, why
-3. Include audit summaries and major groom outcomes
-4. If ROADMAP.md is empty or missing: "No history yet. Run /base:audit or /base:groom to start building your workspace timeline."
-
-
-
-
-
-
-
-- [ ] History presented in clear chronological format
-- [ ] Includes both audits and significant groom outcomes
-
+
+Show workspace evolution over time. Read ROADMAP.md and present the chronological record of major workspace changes.
+
+
+
+As an AI builder, I want to see how my workspace has evolved, so that I can understand the trajectory and make informed decisions about future changes.
+
+
+
+- When user wants to review workspace history
+- Entry point routes here via /base:history
+
+
+
+
+
+Read and present workspace evolution.
+
+1. Read `.base/ROADMAP.md`
+2. Present chronologically: dates, what changed, why
+3. Include audit summaries and major groom outcomes
+4. If ROADMAP.md is empty or missing: "No history yet. Run /base:audit or /base:groom to start building your workspace timeline."
+
+
+
+
+
+
+
+- [ ] History presented in clear chronological format
+- [ ] Includes both audits and significant groom outcomes
+
diff --git a/src/framework/tasks/pulse.md b/base-framework/tasks/pulse.md
similarity index 96%
rename from src/framework/tasks/pulse.md
rename to base-framework/tasks/pulse.md
index 8bf09b6..2b6dae7 100644
--- a/src/framework/tasks/pulse.md
+++ b/base-framework/tasks/pulse.md
@@ -1,83 +1,83 @@
-
-Daily workspace activation. Read workspace state, calculate drift, present health dashboard, prime the operator for their session.
-
-
-
-As an AI builder, I want a quick workspace health briefing at session start, so that I know what needs attention before I start working.
-
-
-
-- Start of every work session
-- When user says "base pulse", "what's the state of things", "workspace status"
-- When the pulse hook detects overdue grooming and injects a prompt
-- Entry point routes here via /base:pulse
-
-
-
-
-
-Read workspace state from `.base/workspace.json` and `.base/data/state.json`.
-
-1. Read `.base/workspace.json` — the manifest
-2. Read `.base/data/state.json` — the last known state
-3. If either file is missing, suggest running `/base:scaffold` first
-4. Extract: last groom date, groom cadence, area list, satellite list
-
-
-
-Check each tracked area against filesystem reality.
-
-For each area in the manifest:
-1. Check filesystem timestamps on tracked paths (stat modification dates)
-2. Compare against last groom date and area-specific cadence
-3. Calculate days overdue (0 if within cadence)
-4. Classify: Current (within cadence), Stale (1-2x overdue), Critical (2x+ overdue)
-
-For each registered satellite:
-1. Check if state file exists and is readable
-2. Extract last modification date
-3. Report current phase if parseable
-
-Calculate total drift score: sum of days-overdue across all areas, with Critical areas weighted 2x.
-
-
-
-Present the health dashboard to the operator.
-
-Format:
-```
-BASE Pulse — {workspace-name}
-Last Groom: {date} ({N} days ago)
-Drift Score: {score}
-
-| Area | Status | Age | Due |
-|------|--------|-----|-----|
-...
-
-Satellites:
-| Project | Phase | Last Active |
-...
-
-{Recommendation based on drift score}
-```
-
-Recommendations:
-- Drift 0: "Workspace is clean. Proceed normally."
-- Drift 1-7: "Minor drift in {areas}. Consider grooming this week."
-- Drift 8-14: "Moderate drift. Run /base:groom soon."
-- Drift 15+: "Critical drift. Workspace context is stale. Run /base:groom now."
-
-
-
-
-
-
-
-- [ ] All manifest areas checked against filesystem reality
-- [ ] Drift score calculated correctly
-- [ ] Satellites checked for health
-- [ ] Clear recommendation provided based on drift level
-- [ ] Dashboard is concise and scannable (not a wall of text)
-
+
+Daily workspace activation. Read workspace state, calculate drift, present health dashboard, prime the operator for their session.
+
+
+
+As an AI builder, I want a quick workspace health briefing at session start, so that I know what needs attention before I start working.
+
+
+
+- Start of every work session
+- When user says "base pulse", "what's the state of things", "workspace status"
+- When the pulse hook detects overdue grooming and injects a prompt
+- Entry point routes here via /base:pulse
+
+
+
+
+
+Read workspace state from `.base/workspace.json` and `.base/data/state.json`.
+
+1. Read `.base/workspace.json` — the manifest
+2. Read `.base/data/state.json` — the last known state
+3. If either file is missing, suggest running `/base:scaffold` first
+4. Extract: last groom date, groom cadence, area list, satellite list
+
+
+
+Check each tracked area against filesystem reality.
+
+For each area in the manifest:
+1. Check filesystem timestamps on tracked paths (stat modification dates)
+2. Compare against last groom date and area-specific cadence
+3. Calculate days overdue (0 if within cadence)
+4. Classify: Current (within cadence), Stale (1-2x overdue), Critical (2x+ overdue)
+
+For each registered satellite:
+1. Check if state file exists and is readable
+2. Extract last modification date
+3. Report current phase if parseable
+
+Calculate total drift score: sum of days-overdue across all areas, with Critical areas weighted 2x.
+
+
+
+Present the health dashboard to the operator.
+
+Format:
+```
+BASE Pulse — {workspace-name}
+Last Groom: {date} ({N} days ago)
+Drift Score: {score}
+
+| Area | Status | Age | Due |
+|------|--------|-----|-----|
+...
+
+Satellites:
+| Project | Phase | Last Active |
+...
+
+{Recommendation based on drift score}
+```
+
+Recommendations:
+- Drift 0: "Workspace is clean. Proceed normally."
+- Drift 1-7: "Minor drift in {areas}. Consider grooming this week."
+- Drift 8-14: "Moderate drift. Run /base:groom soon."
+- Drift 15+: "Critical drift. Workspace context is stale. Run /base:groom now."
+
+
+
+
+
+
+
+- [ ] All manifest areas checked against filesystem reality
+- [ ] Drift score calculated correctly
+- [ ] Satellites checked for health
+- [ ] Clear recommendation provided based on drift level
+- [ ] Dashboard is concise and scannable (not a wall of text)
+
diff --git a/src/framework/tasks/scaffold.md b/base-framework/tasks/scaffold.md
similarity index 99%
rename from src/framework/tasks/scaffold.md
rename to base-framework/tasks/scaffold.md
index eab2aac..24d278c 100644
--- a/src/framework/tasks/scaffold.md
+++ b/base-framework/tasks/scaffold.md
@@ -311,7 +311,7 @@ The MCP server package lives globally at `~/.claude/base-framework/packages/base
2. If exists: "Want me to audit your CLAUDE.md against the CLAUDE.md Strategy?"
- If yes: route to `/base:audit-claude-md` (interactive, strategy-driven audit with CARL detection)
3. If doesn't exist: "Want me to generate a CLAUDE.md from the strategy template?"
- - If yes: use `@~/.claude/base-framework/templates/claudemd-template.md` as starting point, fill from detected workspace structure
+ - If yes: use `@${CLAUDE_PLUGIN_ROOT}/base-framework/templates/claudemd-template.md` as starting point, fill from detected workspace structure
**First groom:**
1. "Want to run an initial groom to establish baseline? This reviews each area once."
diff --git a/src/framework/tasks/status.md b/base-framework/tasks/status.md
similarity index 96%
rename from src/framework/tasks/status.md
rename to base-framework/tasks/status.md
index 55498de..4d950e7 100644
--- a/src/framework/tasks/status.md
+++ b/base-framework/tasks/status.md
@@ -1,35 +1,35 @@
-
-Quick one-liner workspace health check. No conversation, just the numbers.
-
-
-
-As an AI builder, I want a fast health check I can glance at, so that I know if anything needs attention without a full briefing.
-
-
-
-- When user wants a quick check without full pulse
-- Entry point routes here via /base:status
-
-
-
-
-
-Read state and output one-liner.
-
-1. Read `.base/data/state.json`
-2. Calculate current drift score from timestamps
-3. Count overdue areas and past-due backlog items
-4. Output single line: "BASE: Drift {score} | {N} areas overdue | {N} backlog items past review-by | Last groom: {date}"
-
-
-
-
-
-
-
-- [ ] Output is one line
-- [ ] Drift score is current (not cached)
-- [ ] Overdue counts are accurate
-
+
+Quick one-liner workspace health check. No conversation, just the numbers.
+
+
+
+As an AI builder, I want a fast health check I can glance at, so that I know if anything needs attention without a full briefing.
+
+
+
+- When user wants a quick check without full pulse
+- Entry point routes here via /base:status
+
+
+
+
+
+Read state and output one-liner.
+
+1. Read `.base/data/state.json`
+2. Calculate current drift score from timestamps
+3. Count overdue areas and past-due backlog items
+4. Output single line: "BASE: Drift {score} | {N} areas overdue | {N} backlog items past review-by | Last groom: {date}"
+
+
+
+
+
+
+
+- [ ] Output is one line
+- [ ] Drift score is current (not cached)
+- [ ] Overdue counts are accurate
+
diff --git a/src/framework/tasks/surface-convert.md b/base-framework/tasks/surface-convert.md
similarity index 100%
rename from src/framework/tasks/surface-convert.md
rename to base-framework/tasks/surface-convert.md
diff --git a/src/framework/tasks/surface-create.md b/base-framework/tasks/surface-create.md
similarity index 100%
rename from src/framework/tasks/surface-create.md
rename to base-framework/tasks/surface-create.md
diff --git a/src/framework/tasks/surface-list.md b/base-framework/tasks/surface-list.md
similarity index 100%
rename from src/framework/tasks/surface-list.md
rename to base-framework/tasks/surface-list.md
diff --git a/src/framework/tasks/weekly-domain-create.md b/base-framework/tasks/weekly-domain-create.md
similarity index 100%
rename from src/framework/tasks/weekly-domain-create.md
rename to base-framework/tasks/weekly-domain-create.md
diff --git a/src/framework/tasks/weekly.md b/base-framework/tasks/weekly.md
similarity index 100%
rename from src/framework/tasks/weekly.md
rename to base-framework/tasks/weekly.md
diff --git a/src/framework/templates/claudemd-template.md b/base-framework/templates/claudemd-template.md
similarity index 100%
rename from src/framework/templates/claudemd-template.md
rename to base-framework/templates/claudemd-template.md
diff --git a/src/framework/templates/workspace-json.md b/base-framework/templates/workspace-json.md
similarity index 100%
rename from src/framework/templates/workspace-json.md
rename to base-framework/templates/workspace-json.md
diff --git a/src/framework/utils/scan-claude-dirs.py b/base-framework/utils/scan-claude-dirs.py
similarity index 100%
rename from src/framework/utils/scan-claude-dirs.py
rename to base-framework/utils/scan-claude-dirs.py
diff --git a/bin/install.js b/bin/install.js
index 4551e06..3c209c4 100644
--- a/bin/install.js
+++ b/bin/install.js
@@ -125,19 +125,57 @@ function expandTilde(filePath) {
return filePath;
}
+// Text file suffixes that may contain ${CLAUDE_PLUGIN_ROOT} macro refs.
+const TEXT_SUFFIXES = new Set(['.md', '.json', '.js', '.mjs', '.py', '.txt', '.toml', '.yaml', '.yml', '.sh']);
+
+/**
+ * Expand ${CLAUDE_PLUGIN_ROOT} in a text file during npx copy.
+ * When install.js runs via npx, CLAUDE_PLUGIN_ROOT is not set; the macro
+ * is replaced with the actual install target so no literal placeholder
+ * remains in the installed output. Idempotent: files without the macro are
+ * copied byte-for-byte (the Buffer fast-path below detects that case).
+ */
+function copyFileExpandingMacro(srcPath, destPath, pluginRoot) {
+ const ext = path.extname(srcPath).toLowerCase();
+ if (!pluginRoot || (!TEXT_SUFFIXES.has(ext) && ext !== '')) {
+ // Non-text or no macro expansion needed — byte-for-byte copy.
+ fs.copyFileSync(srcPath, destPath);
+ return;
+ }
+ let content;
+ try {
+ content = fs.readFileSync(srcPath, 'utf8');
+ } catch {
+ // Binary read failed — fall back to byte copy.
+ fs.copyFileSync(srcPath, destPath);
+ return;
+ }
+ if (!content.includes('${CLAUDE_PLUGIN_ROOT}')) {
+ // Fast path: no macro present — write as-is.
+ fs.writeFileSync(destPath, content, 'utf8');
+ return;
+ }
+ // Replace ALL occurrences of ${CLAUDE_PLUGIN_ROOT} with the real path.
+ const expanded = content.split('${CLAUDE_PLUGIN_ROOT}').join(pluginRoot);
+ fs.writeFileSync(destPath, expanded, 'utf8');
+}
+
/**
- * Recursively copy directory
+ * Recursively copy directory, expanding ${CLAUDE_PLUGIN_ROOT} in text files.
+ * pluginRoot is the resolved install target (e.g. ~/.claude or ./.claude)
+ * so that any plugin-native refs in the source are grounded to real paths
+ * in the npx-installed output.
*/
-function copyDir(srcDir, destDir) {
+function copyDir(srcDir, destDir, pluginRoot) {
fs.mkdirSync(destDir, { recursive: true });
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
for (const entry of entries) {
const srcPath = path.join(srcDir, entry.name);
const destPath = path.join(destDir, entry.name);
if (entry.isDirectory()) {
- copyDir(srcPath, destPath);
+ copyDir(srcPath, destPath, pluginRoot);
} else {
- fs.copyFileSync(srcPath, destPath);
+ copyFileExpandingMacro(srcPath, destPath, pluginRoot);
}
}
}
@@ -347,43 +385,42 @@ function installCommands(isGlobal) {
console.log(` Installing commands to ${cyan}${locationLabel}${reset}\n`);
- // Copy commands
- const commandsSrc = path.join(src, 'src', 'commands');
+ // Copy commands (root tree, ${CLAUDE_PLUGIN_ROOT} → resolved claudeDir)
+ const commandsSrc = path.join(src, 'commands');
const commandsDest = path.join(claudeDir, 'commands', 'base');
- copyDir(commandsSrc, commandsDest);
+ copyDir(commandsSrc, commandsDest, claudeDir);
const commandCount = fs.readdirSync(commandsSrc).filter(f => f.endsWith('.md')).length;
console.log(` ${green}+${reset} commands/base/ (${commandCount} slash commands)`);
- // Copy skill entry point
- const skillSrc = path.join(src, 'src', 'skill');
+ // Copy skill entry point (root tree)
+ const skillSrc = path.join(src, 'skills', 'base');
const skillDest = path.join(claudeDir, 'skills', 'base');
- copyDir(skillSrc, skillDest);
- console.log(` ${green}+${reset} skills/base/ (entry point + MCP package sources)`);
-
- // Copy MCP package sources into skill (for scaffold reference)
- const packagesSrc = path.join(src, 'src', 'packages');
- const packagesDest = path.join(claudeDir, 'skills', 'base', 'packages');
- copyDir(packagesSrc, packagesDest);
+ copyDir(skillSrc, skillDest, claudeDir);
+ console.log(` ${green}+${reset} skills/base/ (entry point)`);
// Copy MCP package to base-framework/packages/ (global source for scaffold)
const frameworkPackagesDest = path.join(claudeDir, 'base-framework', 'packages', 'base-mcp');
fs.mkdirSync(frameworkPackagesDest, { recursive: true });
- copyDir(path.join(src, 'src', 'packages', 'base-mcp'), frameworkPackagesDest);
+ copyDir(path.join(src, 'mcp'), frameworkPackagesDest, claudeDir);
console.log(` ${green}+${reset} base-framework/packages/base-mcp/ (global MCP source for scaffold)`);
- // Copy BASE framework (tasks, templates, context, frameworks)
- const frameworkSrc = path.join(src, 'src', 'framework');
+ // Copy BASE framework (tasks, templates, context, frameworks) from root tree
+ const frameworkSrc = path.join(src, 'base-framework');
const frameworkDest = path.join(claudeDir, 'base-framework');
- copyDir(frameworkSrc, frameworkDest);
+ copyDir(frameworkSrc, frameworkDest, claudeDir);
console.log(` ${green}+${reset} base-framework/ (tasks, templates, context, frameworks, utils)`);
- // Copy all hooks to base-framework/hooks/ (source for scaffold)
+ // Copy all hooks to base-framework/hooks/ (source for scaffold), from root tree
const hooksFrameworkDest = path.join(claudeDir, 'base-framework', 'hooks');
fs.mkdirSync(hooksFrameworkDest, { recursive: true });
- const hooksSrcDir = path.join(src, 'src', 'hooks');
+ const hooksSrcDir = path.join(src, 'hooks');
const hookFiles = fs.readdirSync(hooksSrcDir).filter(f => f.endsWith('.py'));
for (const hookFile of hookFiles) {
- fs.copyFileSync(path.join(hooksSrcDir, hookFile), path.join(hooksFrameworkDest, hookFile));
+ copyFileExpandingMacro(
+ path.join(hooksSrcDir, hookFile),
+ path.join(hooksFrameworkDest, hookFile),
+ claudeDir
+ );
}
console.log(` ${green}+${reset} base-framework/hooks/ (${hookFiles.length} hooks for scaffold)`);
@@ -448,6 +485,7 @@ function installWorkspace() {
}
// Copy operator.json template (don't overwrite existing)
+ // src/templates/ is retained as the npx-mode template source (no ${CLAUDE_PLUGIN_ROOT} in JSON)
const operatorJsonDest = path.join(baseDir, 'operator.json');
if (!fs.existsSync(operatorJsonDest)) {
const operatorSrc = path.join(src, 'src', 'templates', 'operator.json');
@@ -470,17 +508,22 @@ function installWorkspace() {
console.log(` ${green}+${reset} .base/schemas/ (${schemaFiles.length} validation schemas)`);
}
- // Copy base-mcp
- const baseMcpSrc = path.join(src, 'src', 'packages', 'base-mcp');
+ // Copy mcp/ (root tree) to .base/base-mcp/ — dest name 'base-mcp' is required
+ // for the vendored-location resolver in mcp/index.js (checks basename==='base-mcp').
+ const baseMcpSrc = path.join(src, 'mcp');
const baseMcpDest = path.join(baseDir, 'base-mcp');
- copyDir(baseMcpSrc, baseMcpDest);
+ copyDir(baseMcpSrc, baseMcpDest, workspaceDir);
console.log(` ${green}+${reset} .base/base-mcp/`);
- // Copy all hooks to .base/hooks/
- const allHooksSrc = path.join(src, 'src', 'hooks');
+ // Copy all hooks to .base/hooks/ from root hooks/ tree, expanding ${CLAUDE_PLUGIN_ROOT}
+ const allHooksSrc = path.join(src, 'hooks');
const hookEntries = fs.readdirSync(allHooksSrc).filter(f => f.endsWith('.py'));
for (const file of hookEntries) {
- fs.copyFileSync(path.join(allHooksSrc, file), path.join(baseDir, 'hooks', file));
+ copyFileExpandingMacro(
+ path.join(allHooksSrc, file),
+ path.join(baseDir, 'hooks', file),
+ workspaceDir
+ );
}
console.log(` ${green}+${reset} .base/hooks/ (${hookEntries.length} hooks)`);
diff --git a/src/commands/audit-claude-md.md b/commands/audit-claude-md.md
similarity index 80%
rename from src/commands/audit-claude-md.md
rename to commands/audit-claude-md.md
index 5cc1c6f..7bbd775 100644
--- a/src/commands/audit-claude-md.md
+++ b/commands/audit-claude-md.md
@@ -1,5 +1,5 @@
---
-name: base:audit-claude-md
+name: audit-claude-md
description: Audit CLAUDE.md against the CLAUDE.md Strategy and generate a compliant version
allowed-tools: [Read, Write, Edit, Glob, Grep, Bash]
---
@@ -11,9 +11,9 @@ Audit the project's CLAUDE.md for strategy compliance, interactively rewrite it
-@~/.claude/base-framework/frameworks/claudemd-strategy.md
-@~/.claude/base-framework/templates/claudemd-template.md
-@~/.claude/base-framework/tasks/audit-claude-md.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/frameworks/claudemd-strategy.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/templates/claudemd-template.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/audit-claude-md.md
@@ -23,7 +23,7 @@ $ARGUMENTS
-Follow task: @~/.claude/base-framework/tasks/audit-claude-md.md
+Follow task: @${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/audit-claude-md.md
Key gates (do NOT skip):
1. Load strategy + template BEFORE reading user's CLAUDE.md
diff --git a/src/commands/audit-claude.md b/commands/audit-claude.md
similarity index 98%
rename from src/commands/audit-claude.md
rename to commands/audit-claude.md
index c982a54..96d05f4 100644
--- a/src/commands/audit-claude.md
+++ b/commands/audit-claude.md
@@ -1,5 +1,5 @@
---
-name: base:audit-claude
+name: audit-claude
description: Audit .claude/ directories across workspace for sprawl, duplication, and misalignment
allowed-tools: [Read, Write, Edit, Glob, Grep, Bash, AskUserQuestion]
---
diff --git a/src/commands/audit.md b/commands/audit.md
similarity index 70%
rename from src/commands/audit.md
rename to commands/audit.md
index 387d54b..2c24f9f 100644
--- a/src/commands/audit.md
+++ b/commands/audit.md
@@ -1,5 +1,5 @@
---
-name: base:audit
+name: audit
description: Deep workspace optimization
allowed-tools: [Read, Write, Edit, Glob, Grep, Bash, Agent, AskUserQuestion]
---
@@ -11,9 +11,9 @@ Deep workspace audit — comprehensive optimization across all areas with action
-@~/.claude/base-framework/tasks/audit.md
-@~/.claude/base-framework/context/base-principles.md
-@~/.claude/base-framework/frameworks/audit-strategies.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/audit.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/context/base-principles.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/frameworks/audit-strategies.md
@@ -23,7 +23,7 @@ $ARGUMENTS
-Follow task: @~/.claude/base-framework/tasks/audit.md
+Follow task: @${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/audit.md
diff --git a/src/commands/carl-hygiene.md b/commands/carl-hygiene.md
similarity index 84%
rename from src/commands/carl-hygiene.md
rename to commands/carl-hygiene.md
index 15ae108..16fec56 100644
--- a/src/commands/carl-hygiene.md
+++ b/commands/carl-hygiene.md
@@ -1,5 +1,5 @@
---
-name: base:carl-hygiene
+name: carl-hygiene
description: CARL domain maintenance and rule review
allowed-tools: [Read, Write, Edit, Glob, Grep, Bash, AskUserQuestion, carl_v2_list_domains, carl_v2_get_domain, carl_v2_get_staged, carl_v2_approve_proposal, carl_v2_remove_rule, carl_v2_replace_rules, carl_v2_archive_decision]
---
@@ -11,7 +11,7 @@ CARL rule lifecycle management — review staleness, staging pipeline, domain he
-@~/.claude/base-framework/tasks/carl-hygiene.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/carl-hygiene.md
@@ -22,7 +22,7 @@ $ARGUMENTS
-Follow task: @~/.claude/base-framework/tasks/carl-hygiene.md
+Follow task: @${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/carl-hygiene.md
diff --git a/src/commands/groom.md b/commands/groom.md
similarity index 71%
rename from src/commands/groom.md
rename to commands/groom.md
index 6e1aac5..ab2fb3e 100644
--- a/src/commands/groom.md
+++ b/commands/groom.md
@@ -1,5 +1,5 @@
---
-name: base:groom
+name: groom
description: Weekly workspace maintenance cycle
allowed-tools: [Read, Write, Edit, Glob, Grep, Bash, AskUserQuestion]
---
@@ -11,9 +11,9 @@ Structured weekly maintenance — review each workspace area, update statuses, a
-@~/.claude/base-framework/tasks/groom.md
-@~/.claude/base-framework/context/base-principles.md
-@~/.claude/base-framework/frameworks/audit-strategies.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/groom.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/context/base-principles.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/frameworks/audit-strategies.md
@@ -24,7 +24,7 @@ $ARGUMENTS
-Follow task: @~/.claude/base-framework/tasks/groom.md
+Follow task: @${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/groom.md
diff --git a/src/commands/history.md b/commands/history.md
similarity index 77%
rename from src/commands/history.md
rename to commands/history.md
index 80aedf9..b06c773 100644
--- a/src/commands/history.md
+++ b/commands/history.md
@@ -1,5 +1,5 @@
---
-name: base:history
+name: history
description: Workspace evolution timeline
allowed-tools: [Read, Glob, Bash]
---
@@ -11,7 +11,7 @@ Show workspace evolution — grooming history, audits, major changes over time.
-@~/.claude/base-framework/tasks/history.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/history.md
@@ -19,7 +19,7 @@ Show workspace evolution — grooming history, audits, major changes over time.
-Follow task: @~/.claude/base-framework/tasks/history.md
+Follow task: @${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/history.md
diff --git a/src/commands/orientation.md b/commands/orientation.md
similarity index 100%
rename from src/commands/orientation.md
rename to commands/orientation.md
diff --git a/src/commands/orientation/tasks/deep-why.md b/commands/orientation/tasks/deep-why.md
similarity index 100%
rename from src/commands/orientation/tasks/deep-why.md
rename to commands/orientation/tasks/deep-why.md
diff --git a/src/commands/orientation/tasks/elevator-pitch.md b/commands/orientation/tasks/elevator-pitch.md
similarity index 100%
rename from src/commands/orientation/tasks/elevator-pitch.md
rename to commands/orientation/tasks/elevator-pitch.md
diff --git a/src/commands/orientation/tasks/initiatives.md b/commands/orientation/tasks/initiatives.md
similarity index 100%
rename from src/commands/orientation/tasks/initiatives.md
rename to commands/orientation/tasks/initiatives.md
diff --git a/src/commands/orientation/tasks/key-values.md b/commands/orientation/tasks/key-values.md
similarity index 100%
rename from src/commands/orientation/tasks/key-values.md
rename to commands/orientation/tasks/key-values.md
diff --git a/src/commands/orientation/tasks/new-orientation.md b/commands/orientation/tasks/new-orientation.md
similarity index 100%
rename from src/commands/orientation/tasks/new-orientation.md
rename to commands/orientation/tasks/new-orientation.md
diff --git a/src/commands/orientation/tasks/north-star.md b/commands/orientation/tasks/north-star.md
similarity index 100%
rename from src/commands/orientation/tasks/north-star.md
rename to commands/orientation/tasks/north-star.md
diff --git a/src/commands/orientation/tasks/project-mapping.md b/commands/orientation/tasks/project-mapping.md
similarity index 100%
rename from src/commands/orientation/tasks/project-mapping.md
rename to commands/orientation/tasks/project-mapping.md
diff --git a/src/commands/orientation/tasks/reorientation.md b/commands/orientation/tasks/reorientation.md
similarity index 100%
rename from src/commands/orientation/tasks/reorientation.md
rename to commands/orientation/tasks/reorientation.md
diff --git a/src/commands/orientation/tasks/surface-vision.md b/commands/orientation/tasks/surface-vision.md
similarity index 100%
rename from src/commands/orientation/tasks/surface-vision.md
rename to commands/orientation/tasks/surface-vision.md
diff --git a/src/commands/orientation/tasks/task-seeding.md b/commands/orientation/tasks/task-seeding.md
similarity index 100%
rename from src/commands/orientation/tasks/task-seeding.md
rename to commands/orientation/tasks/task-seeding.md
diff --git a/src/commands/orientation/templates/operator-json.md b/commands/orientation/templates/operator-json.md
similarity index 100%
rename from src/commands/orientation/templates/operator-json.md
rename to commands/orientation/templates/operator-json.md
diff --git a/src/commands/pulse.md b/commands/pulse.md
similarity index 74%
rename from src/commands/pulse.md
rename to commands/pulse.md
index 97cedf3..0a0a8f6 100644
--- a/src/commands/pulse.md
+++ b/commands/pulse.md
@@ -1,5 +1,5 @@
---
-name: base:pulse
+name: pulse
description: Daily workspace health briefing
allowed-tools: [Read, Glob, Grep, Bash]
---
@@ -11,8 +11,8 @@ Workspace health briefing — drift score, stale areas, overdue grooming, quick
-@~/.claude/base-framework/tasks/pulse.md
-@~/.claude/base-framework/context/base-principles.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/pulse.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/context/base-principles.md
@@ -23,7 +23,7 @@ $ARGUMENTS
-Follow task: @~/.claude/base-framework/tasks/pulse.md
+Follow task: @${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/pulse.md
diff --git a/src/commands/scaffold.md b/commands/scaffold.md
similarity index 71%
rename from src/commands/scaffold.md
rename to commands/scaffold.md
index a8271b6..2e4f6ec 100644
--- a/src/commands/scaffold.md
+++ b/commands/scaffold.md
@@ -1,5 +1,5 @@
---
-name: base:scaffold
+name: scaffold
description: Set up BASE in a new workspace
argument-hint: "[--full]"
allowed-tools: [Read, Write, Edit, Glob, Bash, AskUserQuestion]
@@ -12,9 +12,9 @@ Guided workspace setup — scan, configure, install BASE infrastructure. Optiona
-@~/.claude/base-framework/tasks/scaffold.md
-@~/.claude/base-framework/templates/workspace-json.md
-@~/.claude/base-framework/templates/workspace-json.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/scaffold.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/templates/workspace-json.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/templates/workspace-json.md
@@ -22,7 +22,7 @@ $ARGUMENTS
-Follow task: @~/.claude/base-framework/tasks/scaffold.md
+Follow task: @${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/scaffold.md
diff --git a/src/commands/status.md b/commands/status.md
similarity index 77%
rename from src/commands/status.md
rename to commands/status.md
index 63cea08..5d8149e 100644
--- a/src/commands/status.md
+++ b/commands/status.md
@@ -1,5 +1,5 @@
---
-name: base:status
+name: status
description: Quick workspace health check
allowed-tools: [Read, Glob, Bash]
---
@@ -11,7 +11,7 @@ One-liner workspace health status — drift score and area summary.
-@~/.claude/base-framework/tasks/status.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/status.md
@@ -20,7 +20,7 @@ One-liner workspace health status — drift score and area summary.
-Follow task: @~/.claude/base-framework/tasks/status.md
+Follow task: @${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/status.md
diff --git a/src/commands/surface-convert.md b/commands/surface-convert.md
similarity index 83%
rename from src/commands/surface-convert.md
rename to commands/surface-convert.md
index 42d2de8..4adf607 100644
--- a/src/commands/surface-convert.md
+++ b/commands/surface-convert.md
@@ -1,5 +1,5 @@
---
-name: base:surface-convert
+name: surface-convert
description: Convert a markdown file into a data surface
argument-hint: ""
allowed-tools: [Read, Write, Edit, Glob, Bash, AskUserQuestion]
@@ -12,7 +12,7 @@ Convert an existing @-mentioned markdown file into a structured data surface. An
-@~/.claude/base-framework/tasks/surface-convert.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/surface-convert.md
@.base/hooks/_template.py
@@ -23,7 +23,7 @@ $ARGUMENTS
-Follow task: @~/.claude/base-framework/tasks/surface-convert.md
+Follow task: @${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/surface-convert.md
diff --git a/src/commands/surface-create.md b/commands/surface-create.md
similarity index 83%
rename from src/commands/surface-create.md
rename to commands/surface-create.md
index 43cee44..77d6167 100644
--- a/src/commands/surface-create.md
+++ b/commands/surface-create.md
@@ -1,5 +1,5 @@
---
-name: base:surface-create
+name: surface-create
description: Create a new data surface (guided)
argument-hint: "[surface-name]"
allowed-tools: [Read, Write, Edit, Glob, Bash, AskUserQuestion]
@@ -12,7 +12,7 @@ Create a new data surface through guided conversation. Generates JSON data file,
-@~/.claude/base-framework/tasks/surface-create.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/surface-create.md
@.base/hooks/_template.py
@@ -23,7 +23,7 @@ $ARGUMENTS
-Follow task: @~/.claude/base-framework/tasks/surface-create.md
+Follow task: @${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/surface-create.md
diff --git a/src/commands/surface-list.md b/commands/surface-list.md
similarity index 74%
rename from src/commands/surface-list.md
rename to commands/surface-list.md
index 39467f7..3b01d3e 100644
--- a/src/commands/surface-list.md
+++ b/commands/surface-list.md
@@ -1,5 +1,5 @@
---
-name: base:surface-list
+name: surface-list
description: Show all registered data surfaces
allowed-tools: [Read, Bash]
---
@@ -11,7 +11,7 @@ Display all registered data surfaces with item counts and hook status.
-@~/.claude/base-framework/tasks/surface-list.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/surface-list.md
@@ -19,7 +19,7 @@ Display all registered data surfaces with item counts and hook status.
-Follow task: @~/.claude/base-framework/tasks/surface-list.md
+Follow task: @${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/surface-list.md
diff --git a/src/commands/weekly-domain.md b/commands/weekly-domain.md
similarity index 83%
rename from src/commands/weekly-domain.md
rename to commands/weekly-domain.md
index 35d4a39..8a362f9 100644
--- a/src/commands/weekly-domain.md
+++ b/commands/weekly-domain.md
@@ -1,5 +1,5 @@
---
-name: base:weekly-domain
+name: weekly-domain
description: Create a custom domain phase for the weekly ritual
allowed-tools: [Read, Write, Edit, Glob, Grep, Bash, AskUserQuestion]
---
@@ -11,7 +11,7 @@ Guided creation of a custom domain phase for /base:weekly. Walks the user throug
-@~/.claude/base-framework/tasks/weekly-domain-create.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/weekly-domain-create.md
@@ -21,7 +21,7 @@ $ARGUMENTS
-Follow task: @~/.claude/base-framework/tasks/weekly-domain-create.md
+Follow task: @${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/weekly-domain-create.md
diff --git a/src/commands/weekly.md b/commands/weekly.md
similarity index 83%
rename from src/commands/weekly.md
rename to commands/weekly.md
index 69b0bb9..990f5a8 100644
--- a/src/commands/weekly.md
+++ b/commands/weekly.md
@@ -1,5 +1,5 @@
---
-name: base:weekly
+name: weekly
description: Weekly review and planning ritual
allowed-tools: [Read, Write, Edit, Glob, Grep, Bash, AskUserQuestion]
---
@@ -11,8 +11,8 @@ Guided weekly ritual — close the week, plan the next, run maintenance, lock in
-@~/.claude/base-framework/tasks/weekly.md
-@~/.claude/base-framework/context/base-principles.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/weekly.md
+@${CLAUDE_PLUGIN_ROOT}/base-framework/context/base-principles.md
@@ -24,7 +24,7 @@ $ARGUMENTS
-Follow task: @~/.claude/base-framework/tasks/weekly.md
+Follow task: @${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/weekly.md
diff --git a/src/hooks/_template.py b/hooks/_template.py
similarity index 100%
rename from src/hooks/_template.py
rename to hooks/_template.py
diff --git a/src/hooks/active-hook.py b/hooks/active-hook.py
similarity index 94%
rename from src/hooks/active-hook.py
rename to hooks/active-hook.py
index cca4716..ba3f981 100644
--- a/src/hooks/active-hook.py
+++ b/hooks/active-hook.py
@@ -17,7 +17,15 @@
SURFACE_NAME = "active"
HOOK_DIR = Path(__file__).resolve().parent
-WORKSPACE_ROOT = HOOK_DIR.parent.parent
+import os as _bh_os
+import sys as _bh_sys
+_bh_pd = _bh_os.environ.get("CLAUDE_PROJECT_DIR")
+if _bh_pd and _bh_pd.strip():
+ WORKSPACE_ROOT = Path(_bh_pd).resolve()
+else:
+ WORKSPACE_ROOT = HOOK_DIR.parent.parent
+ if not (WORKSPACE_ROOT / ".base").is_dir():
+ _bh_sys.stderr.write("[base-hook] CLAUDE_PROJECT_DIR unset and no .base/ at %s; hook is a no-op. Set CLAUDE_PROJECT_DIR.\n" % WORKSPACE_ROOT)
DATA_FILE = WORKSPACE_ROOT / ".base" / "data" / "projects.json"
BEHAVIOR_DIRECTIVE = f"""BEHAVIOR: This context is PASSIVE AWARENESS ONLY.
diff --git a/src/hooks/apex-insights.py b/hooks/apex-insights.py
similarity index 97%
rename from src/hooks/apex-insights.py
rename to hooks/apex-insights.py
index 9016734..ec778d3 100644
--- a/src/hooks/apex-insights.py
+++ b/hooks/apex-insights.py
@@ -11,7 +11,7 @@
from pathlib import Path
from collections import defaultdict
-WORKSPACE = Path(__file__).resolve().parent.parent.parent
+WORKSPACE = (lambda v: Path(v).resolve() if v and v.strip() else Path(__file__).resolve().parent.parent.parent)(__import__("os").environ.get("CLAUDE_PROJECT_DIR"))
PROJECTS_FILE = WORKSPACE / ".base" / "data" / "projects.json"
WORKSPACE_JSON = WORKSPACE / ".base" / "workspace.json"
diff --git a/src/hooks/backlog-hook.py b/hooks/backlog-hook.py
similarity index 88%
rename from src/hooks/backlog-hook.py
rename to hooks/backlog-hook.py
index 1f4599d..9d5a196 100644
--- a/src/hooks/backlog-hook.py
+++ b/hooks/backlog-hook.py
@@ -17,7 +17,15 @@
SURFACE_NAME = "backlog"
HOOK_DIR = Path(__file__).resolve().parent
-WORKSPACE_ROOT = HOOK_DIR.parent.parent
+import os as _bh_os
+import sys as _bh_sys
+_bh_pd = _bh_os.environ.get("CLAUDE_PROJECT_DIR")
+if _bh_pd and _bh_pd.strip():
+ WORKSPACE_ROOT = Path(_bh_pd).resolve()
+else:
+ WORKSPACE_ROOT = HOOK_DIR.parent.parent
+ if not (WORKSPACE_ROOT / ".base").is_dir():
+ _bh_sys.stderr.write("[base-hook] CLAUDE_PROJECT_DIR unset and no .base/ at %s; hook is a no-op. Set CLAUDE_PROJECT_DIR.\n" % WORKSPACE_ROOT)
DATA_FILE = WORKSPACE_ROOT / ".base" / "data" / "projects.json"
BEHAVIOR_DIRECTIVE = f"""BEHAVIOR: This context is PASSIVE AWARENESS ONLY.
diff --git a/src/hooks/base-pulse-check.py b/hooks/base-pulse-check.py
similarity index 94%
rename from src/hooks/base-pulse-check.py
rename to hooks/base-pulse-check.py
index ef4db98..96b2565 100644
--- a/src/hooks/base-pulse-check.py
+++ b/hooks/base-pulse-check.py
@@ -17,7 +17,15 @@
from pathlib import Path
HOOK_DIR = Path(__file__).resolve().parent
-WORKSPACE_ROOT = HOOK_DIR.parent.parent
+import os as _bh_os
+import sys as _bh_sys
+_bh_pd = _bh_os.environ.get("CLAUDE_PROJECT_DIR")
+if _bh_pd and _bh_pd.strip():
+ WORKSPACE_ROOT = Path(_bh_pd).resolve()
+else:
+ WORKSPACE_ROOT = HOOK_DIR.parent.parent
+ if not (WORKSPACE_ROOT / ".base").is_dir():
+ _bh_sys.stderr.write("[base-hook] CLAUDE_PROJECT_DIR unset and no .base/ at %s; hook is a no-op. Set CLAUDE_PROJECT_DIR.\n" % WORKSPACE_ROOT)
BASE_DIR = WORKSPACE_ROOT / ".base"
STATE_FILE = BASE_DIR / "data" / "state.json"
PROJECTS_FILE = BASE_DIR / "data" / "projects.json"
diff --git a/hooks/hooks.json b/hooks/hooks.json
new file mode 100644
index 0000000..bd846c0
--- /dev/null
+++ b/hooks/hooks.json
@@ -0,0 +1,64 @@
+{
+ "hooks": {
+ "UserPromptSubmit": [
+ {
+ "hooks": [
+ {
+ "type": "command",
+ "command": "python3.11 \"${CLAUDE_PLUGIN_ROOT}/hooks/psmm-injector.py\""
+ }
+ ]
+ },
+ {
+ "hooks": [
+ {
+ "type": "command",
+ "command": "python3.11 \"${CLAUDE_PLUGIN_ROOT}/hooks/active-hook.py\""
+ }
+ ]
+ },
+ {
+ "hooks": [
+ {
+ "type": "command",
+ "command": "python3.11 \"${CLAUDE_PLUGIN_ROOT}/hooks/backlog-hook.py\""
+ }
+ ]
+ },
+ {
+ "hooks": [
+ {
+ "type": "command",
+ "command": "python3.11 \"${CLAUDE_PLUGIN_ROOT}/hooks/base-pulse-check.py\""
+ }
+ ]
+ },
+ {
+ "hooks": [
+ {
+ "type": "command",
+ "command": "python3.11 \"${CLAUDE_PLUGIN_ROOT}/hooks/operator.py\""
+ }
+ ]
+ }
+ ],
+ "SessionStart": [
+ {
+ "hooks": [
+ {
+ "type": "command",
+ "command": "python3.11 \"${CLAUDE_PLUGIN_ROOT}/hooks/satellite-detection.py\""
+ }
+ ]
+ },
+ {
+ "hooks": [
+ {
+ "type": "command",
+ "command": "python3.11 \"${CLAUDE_PLUGIN_ROOT}/hooks/install-mcp-deps.py\""
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/hooks/install-mcp-deps.py b/hooks/install-mcp-deps.py
new file mode 100644
index 0000000..1c0cfed
--- /dev/null
+++ b/hooks/install-mcp-deps.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python3
+"""
+SessionStart hook: install base-mcp npm dependencies into CLAUDE_PLUGIN_DATA.
+
+Idempotent: exits 0 immediately if @modelcontextprotocol/sdk is already present.
+Fail-open: warns to stderr and exits 0 on any error so the session always starts.
+
+Strategy:
+ 1. npm install --omit=dev --prefix "$CLAUDE_PLUGIN_DATA"
+ -> places node_modules at $CLAUDE_PLUGIN_DATA/node_modules/
+ 2. symlink $CLAUDE_PLUGIN_ROOT/mcp/node_modules
+ -> $CLAUDE_PLUGIN_DATA/node_modules
+ Node ESM resolves bare specifiers by walking up from the importing file, so
+ node_modules must live adjacent to index.js. NODE_PATH is honored only by
+ the CommonJS loader (node:internal/modules/cjs), not the ESM loader; this
+ symlink bridges the two locations so ESM resolution succeeds.
+ The MCP server lives at ${CLAUDE_PLUGIN_ROOT}/mcp/index.js, so the symlink
+ target is ${CLAUDE_PLUGIN_ROOT}/mcp/node_modules.
+"""
+import os
+import sys
+import shutil
+import subprocess
+
+
+def warn(msg):
+ print(f"[install-mcp-deps] WARNING: {msg}", file=sys.stderr)
+
+
+def main():
+ plugin_root = os.environ.get("CLAUDE_PLUGIN_ROOT", "").strip()
+ plugin_data = os.environ.get("CLAUDE_PLUGIN_DATA", "").strip()
+
+ if not plugin_data:
+ warn("CLAUDE_PLUGIN_DATA is not set; skipping MCP deps install.")
+ sys.exit(0)
+
+ if not plugin_root:
+ warn("CLAUDE_PLUGIN_ROOT is not set; skipping MCP deps install.")
+ sys.exit(0)
+
+ sdk_marker = os.path.join(plugin_data, "node_modules", "@modelcontextprotocol", "sdk")
+ # MCP server lives at ${CLAUDE_PLUGIN_ROOT}/mcp/index.js — symlink goes next to it
+ mcp_dir = os.path.join(plugin_root, "mcp")
+ mcp_nm = os.path.join(mcp_dir, "node_modules")
+
+ # Idempotent: if sdk already installed, just (re)assert symlink and exit
+ if os.path.isdir(sdk_marker):
+ # Re-assert symlink so it survives if the plugin dir was refreshed
+ _assert_symlink(mcp_nm, plugin_data)
+ sys.exit(0)
+
+ # Source package.json is at ${CLAUDE_PLUGIN_ROOT}/mcp/package.json
+ src_pkg = os.path.join(mcp_dir, "package.json")
+ if not os.path.isfile(src_pkg):
+ warn(f"package.json not found at {src_pkg}; skipping.")
+ sys.exit(0)
+
+ try:
+ os.makedirs(plugin_data, exist_ok=True)
+ shutil.copy2(src_pkg, os.path.join(plugin_data, "package.json"))
+
+ lockfile = os.path.join(mcp_dir, "package-lock.json")
+ if os.path.isfile(lockfile):
+ shutil.copy2(lockfile, os.path.join(plugin_data, "package-lock.json"))
+
+ npm = shutil.which("npm")
+ if not npm:
+ warn("npm not found in PATH; skipping MCP deps install.")
+ sys.exit(0)
+
+ result = subprocess.run(
+ [npm, "install", "--omit=dev", "--prefix", plugin_data],
+ capture_output=True,
+ text=True,
+ timeout=120,
+ )
+ if result.returncode != 0:
+ warn(f"npm install failed (exit {result.returncode}): {result.stderr.strip()}")
+ sys.exit(0)
+
+ _assert_symlink(mcp_nm, plugin_data)
+ print(f"[install-mcp-deps] MCP deps installed to {plugin_data}", file=sys.stderr)
+
+ except Exception as exc:
+ warn(f"Unexpected error during MCP deps install: {exc}")
+ sys.exit(0)
+
+
+def _assert_symlink(link_path, plugin_data):
+ """Create or update the node_modules symlink inside the MCP dir."""
+ target = os.path.join(plugin_data, "node_modules")
+ try:
+ # Remove stale symlink so we can set the correct target
+ if os.path.islink(link_path):
+ if os.readlink(link_path) == target:
+ return # already correct
+ os.unlink(link_path)
+ elif os.path.isdir(link_path):
+ # A real node_modules exists (e.g. from a previous local install);
+ # leave it alone so we don't break a working setup.
+ return
+ os.symlink(target, link_path)
+ except Exception as exc:
+ print(
+ f"[install-mcp-deps] WARNING: could not assert symlink {link_path} -> {target}: {exc}",
+ file=sys.stderr,
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/hooks/operator.py b/hooks/operator.py
similarity index 77%
rename from src/hooks/operator.py
rename to hooks/operator.py
index 5b4aa0c..7debaf8 100644
--- a/src/hooks/operator.py
+++ b/hooks/operator.py
@@ -10,7 +10,15 @@
from pathlib import Path
HOOK_DIR = Path(__file__).resolve().parent
-WORKSPACE_ROOT = HOOK_DIR.parent.parent
+import os as _bh_os
+import sys as _bh_sys
+_bh_pd = _bh_os.environ.get("CLAUDE_PROJECT_DIR")
+if _bh_pd and _bh_pd.strip():
+ WORKSPACE_ROOT = Path(_bh_pd).resolve()
+else:
+ WORKSPACE_ROOT = HOOK_DIR.parent.parent
+ if not (WORKSPACE_ROOT / ".base").is_dir():
+ _bh_sys.stderr.write("[base-hook] CLAUDE_PROJECT_DIR unset and no .base/ at %s; hook is a no-op. Set CLAUDE_PROJECT_DIR.\n" % WORKSPACE_ROOT)
DATA_FILE = WORKSPACE_ROOT / ".base" / "operator.json"
diff --git a/src/hooks/psmm-injector.py b/hooks/psmm-injector.py
similarity index 85%
rename from src/hooks/psmm-injector.py
rename to hooks/psmm-injector.py
index 331faf4..2aa93c3 100644
--- a/src/hooks/psmm-injector.py
+++ b/hooks/psmm-injector.py
@@ -1,67 +1,75 @@
-#!/usr/bin/env python3
-"""
-Hook: psmm-injector.py
-Purpose: Per-Session Meta Memory — inject ephemeral session observations
- into every prompt so they stay hot in long sessions (1M window).
-
- Uses a single psmm.json file with session-keyed entries.
- Each session gets its own array keyed by Claude Code session UUID.
- Stale sessions are NOT auto-cleaned — that's the operator's job
- via CARL hygiene / BASE drift detection.
-
-Triggers: UserPromptSubmit
-Output: Current session's PSMM entries as system context, or silent if empty.
-"""
-
-import sys
-import json
-from pathlib import Path
-
-HOOK_DIR = Path(__file__).resolve().parent
-WORKSPACE_ROOT = HOOK_DIR.parent.parent
-PSMM_FILE = WORKSPACE_ROOT / ".base" / "data" / "psmm.json"
-
-
-def main():
- # Get session_id from hook input
- try:
- input_data = json.loads(sys.stdin.read())
- session_id = input_data.get("session_id", "")
- except (json.JSONDecodeError, OSError):
- session_id = ""
-
- if not session_id or not PSMM_FILE.exists():
- sys.exit(0)
-
- try:
- data = json.loads(PSMM_FILE.read_text())
- except (json.JSONDecodeError, OSError):
- sys.exit(0)
-
- sessions = data.get("sessions", {})
- session = sessions.get(session_id)
-
- if not session or not session.get("entries"):
- sys.exit(0)
-
- # Build output from this session's entries
- entries = session["entries"]
- lines = []
- for entry in entries:
- entry_type = entry.get("type", "NOTE")
- text = entry.get("text", "")
- timestamp = entry.get("timestamp", "")
- lines.append(f"- [{timestamp}] {entry_type}: {text}")
-
- if lines:
- created = session.get("created", "unknown")
- count = len(entries)
- print(f"""
-{chr(10).join(lines)}
-""")
-
- sys.exit(0)
-
-
-if __name__ == "__main__":
- main()
+#!/usr/bin/env python3
+"""
+Hook: psmm-injector.py
+Purpose: Per-Session Meta Memory — inject ephemeral session observations
+ into every prompt so they stay hot in long sessions (1M window).
+
+ Uses a single psmm.json file with session-keyed entries.
+ Each session gets its own array keyed by Claude Code session UUID.
+ Stale sessions are NOT auto-cleaned — that's the operator's job
+ via CARL hygiene / BASE drift detection.
+
+Triggers: UserPromptSubmit
+Output: Current session's PSMM entries as system context, or silent if empty.
+"""
+
+import os
+import sys
+import json
+from pathlib import Path
+
+HOOK_DIR = Path(__file__).resolve().parent
+WORKSPACE_ROOT = HOOK_DIR.parent.parent
+
+_project_dir = os.environ.get("CLAUDE_PROJECT_DIR")
+if _project_dir:
+ PSMM_FILE = Path(_project_dir) / ".base" / "data" / "psmm.json"
+else:
+ PSMM_FILE = Path(".").resolve() / ".base" / "data" / "psmm.json"
+ if not PSMM_FILE.exists():
+ PSMM_FILE = WORKSPACE_ROOT / ".base" / "data" / "psmm.json"
+
+
+def main():
+ # Get session_id from hook input
+ try:
+ input_data = json.loads(sys.stdin.read())
+ session_id = input_data.get("session_id", "")
+ except (json.JSONDecodeError, OSError):
+ session_id = ""
+
+ if not session_id or not PSMM_FILE.exists():
+ sys.exit(0)
+
+ try:
+ data = json.loads(PSMM_FILE.read_text())
+ except (json.JSONDecodeError, OSError):
+ sys.exit(0)
+
+ sessions = data.get("sessions", {})
+ session = sessions.get(session_id)
+
+ if not session or not session.get("entries"):
+ sys.exit(0)
+
+ # Build output from this session's entries
+ entries = session["entries"]
+ lines = []
+ for entry in entries:
+ entry_type = entry.get("type", "NOTE")
+ text = entry.get("text", "")
+ timestamp = entry.get("timestamp", "")
+ lines.append(f"- [{timestamp}] {entry_type}: {text}")
+
+ if lines:
+ created = session.get("created", "unknown")
+ count = len(entries)
+ print(f"""
+{chr(10).join(lines)}
+""")
+
+ sys.exit(0)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/src/hooks/satellite-detection.py b/hooks/satellite-detection.py
similarity index 95%
rename from src/hooks/satellite-detection.py
rename to hooks/satellite-detection.py
index ead4c49..8ea3f64 100644
--- a/src/hooks/satellite-detection.py
+++ b/hooks/satellite-detection.py
@@ -22,7 +22,15 @@
# Workspace root — find .base/ relative to this hook's location
HOOK_DIR = Path(__file__).resolve().parent
-WORKSPACE_ROOT = HOOK_DIR.parent.parent # hooks/ -> .base/ -> workspace
+import os as _bh_os
+import sys as _bh_sys
+_bh_pd = _bh_os.environ.get("CLAUDE_PROJECT_DIR")
+if _bh_pd and _bh_pd.strip():
+ WORKSPACE_ROOT = Path(_bh_pd).resolve()
+else:
+ WORKSPACE_ROOT = HOOK_DIR.parent.parent
+ if not (WORKSPACE_ROOT / ".base").is_dir():
+ _bh_sys.stderr.write("[base-hook] CLAUDE_PROJECT_DIR unset and no .base/ at %s; hook is a no-op. Set CLAUDE_PROJECT_DIR.\n" % WORKSPACE_ROOT) # hooks/ -> .base/ -> workspace
BASE_DIR = WORKSPACE_ROOT / ".base"
MANIFEST_FILE = BASE_DIR / "workspace.json"
PROJECTS_FILE = BASE_DIR / "data" / "projects.json"
diff --git a/src/packages/base-mcp/index.js b/mcp/index.js
similarity index 82%
rename from src/packages/base-mcp/index.js
rename to mcp/index.js
index e029ad5..3e3bbbf 100644
--- a/src/packages/base-mcp/index.js
+++ b/mcp/index.js
@@ -12,6 +12,7 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import path from 'path';
import { fileURLToPath } from 'url';
+import fs from 'node:fs';
// Tool group imports
import { TOOLS as projectTools, handleTool as handleProject } from './tools/projects.js';
@@ -27,7 +28,7 @@ import { TOOLS as satelliteTools, handleTool as handleSatellite } from './tools/
// Resolve workspace from this file's location: base-mcp/ → .base/ → workspace root
const __dirname = path.dirname(fileURLToPath(import.meta.url));
-const WORKSPACE_PATH = path.resolve(__dirname, '../..');
+const WORKSPACE_PATH = (() => { const c = process.env.CLAUDE_PROJECT_DIR; if (c && c.trim()) return path.resolve(c.trim()); const real = (p) => { try { return fs.realpathSync(path.resolve(p)); } catch { return path.resolve(p); } }; const h = process.env.HOME || process.env.USERPROFILE; const home = h ? real(h) : null; const f = real(path.resolve(__dirname, '../..')); const vendored = path.basename(__dirname) === 'base-mcp' && path.basename(path.dirname(__dirname)) === '.base'; if (vendored && f !== home) return f; const cwd = real(process.cwd()); if (cwd !== home) return cwd; throw new Error('[BASE] Refusing to start: CLAUDE_PROJECT_DIR is unset and no project workspace could be resolved (cwd and install location are your home directory; refusing to read a global ~/.base store). Set CLAUDE_PROJECT_DIR or launch base-mcp from the project root.'); })();
function debugLog(...args) {
console.error('[BASE]', new Date().toISOString(), ...args);
diff --git a/src/packages/base-mcp/package.json b/mcp/package.json
similarity index 100%
rename from src/packages/base-mcp/package.json
rename to mcp/package.json
diff --git a/src/packages/base-mcp/tools/entities.js b/mcp/tools/entities.js
similarity index 100%
rename from src/packages/base-mcp/tools/entities.js
rename to mcp/tools/entities.js
diff --git a/src/packages/base-mcp/tools/operator.js b/mcp/tools/operator.js
similarity index 100%
rename from src/packages/base-mcp/tools/operator.js
rename to mcp/tools/operator.js
diff --git a/src/packages/base-mcp/tools/projects.js b/mcp/tools/projects.js
similarity index 100%
rename from src/packages/base-mcp/tools/projects.js
rename to mcp/tools/projects.js
diff --git a/src/packages/base-mcp/tools/psmm.js b/mcp/tools/psmm.js
similarity index 100%
rename from src/packages/base-mcp/tools/psmm.js
rename to mcp/tools/psmm.js
diff --git a/src/packages/base-mcp/tools/satellite.js b/mcp/tools/satellite.js
similarity index 100%
rename from src/packages/base-mcp/tools/satellite.js
rename to mcp/tools/satellite.js
diff --git a/src/packages/base-mcp/tools/state.js b/mcp/tools/state.js
similarity index 100%
rename from src/packages/base-mcp/tools/state.js
rename to mcp/tools/state.js
diff --git a/src/packages/base-mcp/tools/validate.js b/mcp/tools/validate.js
similarity index 100%
rename from src/packages/base-mcp/tools/validate.js
rename to mcp/tools/validate.js
diff --git a/package.json b/package.json
index 413709a..ba9e9b8 100644
--- a/package.json
+++ b/package.json
@@ -7,11 +7,11 @@
},
"files": [
"bin",
- "src/commands",
- "src/skill",
- "src/framework",
- "src/packages",
- "src/hooks",
+ "commands",
+ "skills",
+ "base-framework",
+ "mcp",
+ "hooks",
"src/templates",
"schemas",
"README.md"
diff --git a/src/skill/base.md b/skills/base/base.md
similarity index 56%
rename from src/skill/base.md
rename to skills/base/base.md
index 5cd450d..fecb427 100644
--- a/src/skill/base.md
+++ b/skills/base/base.md
@@ -1,110 +1,110 @@
----
-name: base
-type: suite
-version: 0.1.0
-category: workspace-orchestration
-description: "Builder's Automated State Engine — workspace lifecycle management for Claude Code. Scaffold, audit, groom, and maintain AI builder workspaces. Manage data surfaces for structured context injection. Use when user mentions workspace setup, cleanup, organization, maintenance, grooming, auditing workspace health, surfaces, or BASE."
-allowed-tools: [Read, Write, Glob, Grep, Edit, Bash, Agent, AskUserQuestion]
----
-
-
-
-## What
-BASE (Builder's Automated State Engine) manages the lifecycle of a Claude Code workspace. It scaffolds new workspaces, audits existing ones, runs structured grooming cycles, and maintains workspace health through automated drift detection.
-
-## When to Use
-- User says "base", "workspace", "cleanup", "organize", "audit my workspace", "groom", "surface", "create a surface"
-- User wants to set up a new workspace from scratch
-- User wants to optimize or clean up an existing workspace
-- User asks about workspace health, staleness, or drift
-- Session start hook detects overdue grooming
-- User wants to review workspace evolution history
-
-## Not For
-- Project-level build orchestration (that's PAUL)
-- Session-level rule management (that's CARL)
-- Code quality auditing (that's AEGIS)
-- Skill/tool creation (that's Skillsmith)
-
-
-
-
-
-## Role
-Workspace operations engineer. Knows the territory, tracks what's drifting, enforces maintenance cadence. Tactical, not theoretical.
-
-## Style
-- Direct, structured, checklist-driven
-- Presents health dashboards and drift scores
-- Asks focused questions during grooming (voice-friendly)
-- Never skips areas — systematic coverage
-- Recommends, doesn't dictate
-
-## Expertise
-- Workspace architecture and file organization
-- Context document lifecycle (projects.json, state.json, entities.json)
-- Tool and configuration management
-- Drift detection and prevention patterns
-- Claude Code ecosystem (PAUL, CARL, AEGIS, Skillsmith integration)
-
-
-
-
-
-| Command | Description | Routes To |
-|---------|------------|-----------|
-| `/base:pulse` | Daily activation — workspace health briefing | `@~/.claude/base-framework/tasks/pulse.md` |
-| `/base:groom` | Weekly maintenance cycle | `@~/.claude/base-framework/tasks/groom.md` |
-| `/base:audit` | Deep workspace optimization | `@~/.claude/base-framework/tasks/audit.md` |
-| `/base:scaffold` | Set up BASE in a new workspace | `@~/.claude/base-framework/tasks/scaffold.md` |
-| `/base:status` | Quick health check (one-liner) | `@~/.claude/base-framework/tasks/status.md` |
-| `/base:history` | Workspace evolution timeline | `@~/.claude/base-framework/tasks/history.md` |
-| `/base:audit-claude-md` | Audit CLAUDE.md, generate recommended version | `@~/.claude/base-framework/tasks/audit-claude-md.md` |
-| `/base:carl-hygiene` | CARL domain maintenance and rule review | `@~/.claude/base-framework/tasks/carl-hygiene.md` |
-| `/base:surface create` | Create a new data surface (guided) | `@~/.claude/base-framework/tasks/surface-create.md` |
-| `/base:surface convert` | Convert markdown file to data surface | `@~/.claude/base-framework/tasks/surface-convert.md` |
-| `/base:surface list` | Show all registered surfaces | `@~/.claude/base-framework/tasks/surface-list.md` |
-
-
-
-
-
-## Always Load
-- `@~/.claude/base-framework/context/base-principles.md` — Core workspace management principles
-- `@~/.claude/base-framework/frameworks/audit-strategies.md` — Reusable audit strategy definitions
-
-## Load on Command
-- `@~/.claude/base-framework/tasks/pulse.md` — on `/base:pulse`
-- `@~/.claude/base-framework/tasks/groom.md` — on `/base:groom`
-- `@~/.claude/base-framework/tasks/audit.md` — on `/base:audit`
-- `@~/.claude/base-framework/tasks/scaffold.md` — on `/base:scaffold`
-- `@~/.claude/base-framework/tasks/status.md` — on `/base:status`
-- `@~/.claude/base-framework/tasks/history.md` — on `/base:history`
-- `@~/.claude/base-framework/tasks/carl-hygiene.md` — on `/base:carl-hygiene`
-- `@~/.claude/base-framework/tasks/surface-create.md` — on `/base:surface create`
-- `@~/.claude/base-framework/tasks/surface-convert.md` — on `/base:surface convert`
-- `@~/.claude/base-framework/tasks/surface-list.md` — on `/base:surface list`
-
-## Load on Demand
-- `@~/.claude/base-framework/templates/workspace-json.md` — When generating workspace.json
-- `@~/.claude/base-framework/frameworks/satellite-registration.md` — When handling PAUL project registration
-
-
-
-
-
-BASE loaded. Builder's Automated State Engine.
-
-Available commands:
-- `/base:pulse` — What's the state of my workspace?
-- `/base:groom` — Run weekly maintenance
-- `/base:audit` — Deep optimization session
-- `/base:scaffold` — Set up BASE in a new workspace
-- `/base:status` — Quick health check
-- `/base:history` — Workspace evolution timeline
-- `/base:surface create` — Create a new data surface
-- `/base:surface list` — Show registered surfaces
-
-What do you need?
-
-
+---
+name: base
+type: suite
+version: 0.1.0
+category: workspace-orchestration
+description: "Builder's Automated State Engine — workspace lifecycle management for Claude Code. Scaffold, audit, groom, and maintain AI builder workspaces. Manage data surfaces for structured context injection. Use when user mentions workspace setup, cleanup, organization, maintenance, grooming, auditing workspace health, surfaces, or BASE."
+allowed-tools: [Read, Write, Glob, Grep, Edit, Bash, Agent, AskUserQuestion]
+---
+
+
+
+## What
+BASE (Builder's Automated State Engine) manages the lifecycle of a Claude Code workspace. It scaffolds new workspaces, audits existing ones, runs structured grooming cycles, and maintains workspace health through automated drift detection.
+
+## When to Use
+- User says "base", "workspace", "cleanup", "organize", "audit my workspace", "groom", "surface", "create a surface"
+- User wants to set up a new workspace from scratch
+- User wants to optimize or clean up an existing workspace
+- User asks about workspace health, staleness, or drift
+- Session start hook detects overdue grooming
+- User wants to review workspace evolution history
+
+## Not For
+- Project-level build orchestration (that's PAUL)
+- Session-level rule management (that's CARL)
+- Code quality auditing (that's AEGIS)
+- Skill/tool creation (that's Skillsmith)
+
+
+
+
+
+## Role
+Workspace operations engineer. Knows the territory, tracks what's drifting, enforces maintenance cadence. Tactical, not theoretical.
+
+## Style
+- Direct, structured, checklist-driven
+- Presents health dashboards and drift scores
+- Asks focused questions during grooming (voice-friendly)
+- Never skips areas — systematic coverage
+- Recommends, doesn't dictate
+
+## Expertise
+- Workspace architecture and file organization
+- Context document lifecycle (projects.json, state.json, entities.json)
+- Tool and configuration management
+- Drift detection and prevention patterns
+- Claude Code ecosystem (PAUL, CARL, AEGIS, Skillsmith integration)
+
+
+
+
+
+| Command | Description | Routes To |
+|---------|------------|-----------|
+| `/base:pulse` | Daily activation — workspace health briefing | `@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/pulse.md` |
+| `/base:groom` | Weekly maintenance cycle | `@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/groom.md` |
+| `/base:audit` | Deep workspace optimization | `@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/audit.md` |
+| `/base:scaffold` | Set up BASE in a new workspace | `@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/scaffold.md` |
+| `/base:status` | Quick health check (one-liner) | `@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/status.md` |
+| `/base:history` | Workspace evolution timeline | `@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/history.md` |
+| `/base:audit-claude-md` | Audit CLAUDE.md, generate recommended version | `@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/audit-claude-md.md` |
+| `/base:carl-hygiene` | CARL domain maintenance and rule review | `@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/carl-hygiene.md` |
+| `/base:surface create` | Create a new data surface (guided) | `@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/surface-create.md` |
+| `/base:surface convert` | Convert markdown file to data surface | `@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/surface-convert.md` |
+| `/base:surface list` | Show all registered surfaces | `@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/surface-list.md` |
+
+
+
+
+
+## Always Load
+- `@${CLAUDE_PLUGIN_ROOT}/base-framework/context/base-principles.md` — Core workspace management principles
+- `@${CLAUDE_PLUGIN_ROOT}/base-framework/frameworks/audit-strategies.md` — Reusable audit strategy definitions
+
+## Load on Command
+- `@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/pulse.md` — on `/base:pulse`
+- `@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/groom.md` — on `/base:groom`
+- `@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/audit.md` — on `/base:audit`
+- `@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/scaffold.md` — on `/base:scaffold`
+- `@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/status.md` — on `/base:status`
+- `@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/history.md` — on `/base:history`
+- `@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/carl-hygiene.md` — on `/base:carl-hygiene`
+- `@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/surface-create.md` — on `/base:surface create`
+- `@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/surface-convert.md` — on `/base:surface convert`
+- `@${CLAUDE_PLUGIN_ROOT}/base-framework/tasks/surface-list.md` — on `/base:surface list`
+
+## Load on Demand
+- `@${CLAUDE_PLUGIN_ROOT}/base-framework/templates/workspace-json.md` — When generating workspace.json
+- `@${CLAUDE_PLUGIN_ROOT}/base-framework/frameworks/satellite-registration.md` — When handling PAUL project registration
+
+
+
+
+
+BASE loaded. Builder's Automated State Engine.
+
+Available commands:
+- `/base:pulse` — What's the state of my workspace?
+- `/base:groom` — Run weekly maintenance
+- `/base:audit` — Deep optimization session
+- `/base:scaffold` — Set up BASE in a new workspace
+- `/base:status` — Quick health check
+- `/base:history` — Workspace evolution timeline
+- `/base:surface create` — Create a new data surface
+- `/base:surface list` — Show registered surfaces
+
+What do you need?
+
+