diff --git a/README.md b/README.md index b6d71df..aabd52e 100644 --- a/README.md +++ b/README.md @@ -2,17 +2,25 @@ **The AI operations stack for Rev A Manufacturing.** -One monorepo. One Railway deploy. One MCP endpoint. Three systems working together: - -- **REVA-TURBO plugin** — 46 skills that run inside Claude Code for every PM workflow at Rev A (RFQ → quote → China sourcing → compliance → quality → shipping). -- **Nakatomi CRM** (internal) — headless AI-native CRM, Postgres-backed, customized for Rev A's pipeline and custom fields. -- **AutoMem** (internal) — hybrid graph + vector memory (FalkorDB + Qdrant) for durable team-wide knowledge. - -A thin **MCP router** is the only publicly exposed service. It speaks the Model Context Protocol to agents and proxies internally to the CRM + memory backends. One URL, one bearer token, one connector config — that's what the plugin and Claude Desktop / Cursor need to know. +One monorepo. One Railway deploy. One MCP endpoint. Three systems working +together: + +- **REVA-TURBO plugin** — 48 skills that run inside Claude Code / Claude + Desktop for every PM workflow at Rev A (RFQ → quote → China sourcing → + compliance → quality → shipping). +- **Nakatomi CRM** (internal) — headless AI-native CRM, Postgres-backed, + customized for Rev A's pipeline and custom fields. +- **AutoMem** (internal) — hybrid graph + vector memory (FalkorDB + Qdrant) + for durable team-wide knowledge. + +A thin **MCP router** is the only publicly exposed service. It speaks the +Model Context Protocol to agents and proxies internally to the CRM and +memory backends. One URL, one bearer token, one connector config — that's +what the plugin and Claude Desktop / Cursor need to know. ```mermaid flowchart LR - Plugin["REVA-TURBO plugin
(~/.claude/skills/reva-turbo)"] + Plugin["REVA-TURBO plugin
(~/.claude/plugins/reva-turbo)"] Claude["Claude Desktop / Cursor"] subgraph Railway["Single Railway project"] Router["mcp-router
(public /mcp)"] @@ -21,8 +29,8 @@ flowchart LR PG[("Postgres")] Falkor[("FalkorDB")] Qdrant[("Qdrant")] - Router -- "crm.*" --> Nakatomi - Router -- "mem.*" --> AutoMem + Router -- "crm_*" --> Nakatomi + Router -- "mem_*" --> AutoMem Nakatomi --> PG AutoMem --> Falkor AutoMem --> Qdrant @@ -35,48 +43,68 @@ flowchart LR ``` RevOps-RevAMfg/ -├── plugin/ ← REVA-TURBO Claude Code plugin (46 skills, 20 commands) -│ ├── skills/ -│ ├── bin/ -│ ├── install.sh ← one-liner plugin installer -│ └── .claude-plugin/ +├── plugin/ ← REVA-TURBO Claude plugin (48 skills) +│ ├── skills/ ← one directory per skill (SKILL.md + assets) +│ ├── bin/ ← telemetry, session-track, config helpers +│ ├── setup ← post-install bootstrap +│ ├── install.sh ← curl-able CLI installer (legacy path) +│ ├── build-bundle.sh ← builds dist/reva-turbo-.zip +│ ├── .claude-plugin/ +│ │ └── plugin.json ← manifest + userConfig prompts + mcpServers +│ └── dist/ ← built zips (gitignored; shipped via Releases) ├── services/ -│ ├── mcp-router/ ← single unified MCP endpoint (FastAPI + FastMCP) -│ ├── nakatomi-backend/ ← Nakatomi CRM (vendored-by-reference, Rev A seed overlay) -│ └── automem-backend/ ← AutoMem memory (vendored-by-reference) +│ ├── mcp-router/ ← unified MCP endpoint (FastAPI + FastMCP) +│ ├── nakatomi-backend/ +│ │ └── seed/reva.py ← Rev A pipeline + custom-field overlay +│ └── automem-backend/ ← AutoMem is vendored-by-reference via Railway ├── railway/ -│ ├── template.yaml ← one-click Railway template (1 project, 3 services + 3 plugins) -│ ├── deploy.sh ← CLI deploy for MrDula Solutions admins -│ └── README.md +│ ├── deploy.sh ← phased deploy (init/services/seed/finalize) +│ ├── template.yaml ← reference stack spec (not executable) +│ └── README.md ← deploy reference └── docs/ - ├── ARCHITECTURE.md - ├── INSTALL.md - └── ROADMAP.md + ├── ARCHITECTURE.md, ARCHITECTURE_V2.md, AUTH.md, INSTALL.md, ROADMAP.md ``` ## For end users (Rev A PMs) -Your admin deploys the backend once and shares two things with you: the **router URL** (e.g. `https://reva-ops.up.railway.app/mcp`) and a one-time **signup token**. From there it's three steps — no terminal needed. +Your admin deploys the backend once and shares two things with you: the +**router URL** (e.g. `https://mcp-router-production-460a.up.railway.app/mcp`) +and a one-time **signup token**. From there it's three steps — no terminal +needed. -**1. Get your personal API key.** Visit `https://.up.railway.app/signup` in your browser. Enter your name, work email, a password (12+ chars — only used for future key resets), and the signup token. The page shows your `nk_...` key once. Copy it. +**1. Get your personal API key.** Visit `/signup` +in your browser. Enter your name, work email, a password (12+ chars — only +used for future key resets), and the signup token. The page shows your +`nk_...` key once. Copy it. -**2. Download the plugin bundle.** Grab the latest `reva-turbo-.zip` from the project's [GitHub Releases](https://github.com/mrdulasolutions/RevOps-RevAMfg/releases) page. +**2. Download the plugin bundle.** Grab the latest `reva-turbo-.zip` +from the project's [GitHub Releases](https://github.com/mrdulasolutions/RevOps-RevAMfg/releases) +page. (The zip contains a single top-level `reva-turbo/` directory — don't +unzip it before upload.) -**3. Upload it into Claude.** In Claude Desktop, open **Plugins → Personal → Local uploads → `+`** and drop in the zip. On enable, Claude prompts for two values: +**3. Upload it into Claude Desktop.** Open **Plugins → Personal → Local +uploads → `+`** and drop in the zip. On enable, Claude prompts for two +values: -- **`mcp_url`** — paste the router URL your admin shared (the full `/mcp` form) -- **`api_key`** — paste the `nk_...` key from step 1 (stored in your OS keychain — not a plaintext file) +- **`mcp_url`** — the router URL your admin shared (the full `/mcp` form) +- **`api_key`** — the `nk_...` key from step 1 (stored in your OS keychain, + not a plaintext file) -That's it. Run `/reva-turbo:revmyengine` and the engine is connected to the shared CRM and memory. Everything you log is available to the whole team, and every action is attributed to your user on the Nakatomi timeline. +That's it. Run `/reva-turbo:revmyengine` and the engine is connected to the +shared CRM and memory. Everything you log is available to the whole team, +and every action is attributed to your user on the Nakatomi timeline. -> Prefer the terminal? The legacy `install.sh` path still works: +> Prefer the terminal? The legacy CLI path still works for Claude Code +> users: > ```bash > curl -fsSL https://raw.githubusercontent.com/mrdulasolutions/RevOps-RevAMfg/main/plugin/install.sh \ > | REVA_MCP_URL=https://.up.railway.app/mcp bash > ``` -> It drops into the same signup wizard and writes `~/.claude/mcp.json` directly. Useful for Claude Code CLI users or CI. +> It drops into the same signup wizard and writes `~/.claude/mcp.json` +> directly. -See [`docs/AUTH.md`](./docs/AUTH.md) for the full auth flow and rotation story. +See [`docs/AUTH.md`](./docs/AUTH.md) for the full auth flow and rotation +story. ## For admins (MrDula Solutions) @@ -86,41 +114,63 @@ Deploy the backend for a new customer: git clone https://github.com/mrdulasolutions/RevOps-RevAMfg.git cd RevOps-RevAMfg ./railway/deploy.sh --project-name reva-ops --admin-email you@reva.com -# → prints public MCP URL + admin API key +# → prints public MCP URL + admin API key + signup token ``` -One Railway project. Three application services (`mcp-router`, `nakatomi-backend`, `automem-backend`). Three managed databases (Postgres, FalkorDB, Qdrant). Wired up automatically via Railway's private network — only `mcp-router` has a public domain. +One Railway project. Three application services (`mcp-router`, +`nakatomi-backend`, `automem-backend`). Three managed databases (Postgres, +FalkorDB, Qdrant). Wired up automatically via Railway's private network — +only `mcp-router` has a public domain. -See [`railway/README.md`](./railway/README.md) for the full deploy story. +The deploy is phased (`init` / `services` / `seed` / `finalize`), so any +individual phase can be re-run if something fails mid-flight. See +[`railway/README.md`](./railway/README.md) for the full story, including +the one-time Railway GitHub App install flow. ## Why one MCP endpoint -Two reasons we run a router instead of exposing Nakatomi's `/mcp` and AutoMem's `/mcp` separately: +Two reasons we run a router instead of exposing Nakatomi's `/mcp` and +AutoMem's `/mcp` separately: -1. **One connector config, not two.** Every MCP client (Claude Desktop, Cursor, the plugin) has to be pointed at every endpoint by hand. Doubling the connector count doubles the onboarding friction for a PM team. -2. **Cross-system tools.** "Remember this ITAR ruling and tag it to Acme's contact" is a memory write *and* a CRM link. A router owns that orchestration (`reva_remember_about_entity`); two isolated MCPs cannot. +1. **One connector config, not two.** Every MCP client (Claude Desktop, + Cursor, the plugin) has to be pointed at every endpoint by hand. + Doubling the connector count doubles the onboarding friction for a PM + team. +2. **Cross-system tools.** "Remember this ITAR ruling and tag it to Acme's + contact" is a memory write *and* a CRM link. A router owns that + orchestration (`reva_remember_about_entity`); two isolated MCPs cannot. Tool namespaces keep the surface tidy: -| Prefix | Backend | Examples | -|--------|----------|----------------------------------------------------------| -| `crm_` | Nakatomi | `crm_search_contacts`, `crm_create_deal`, `crm_move_deal_stage` | -| `mem_` | AutoMem | `mem_store`, `mem_recall`, `mem_associate` | -| `reva_`| router | `reva_remember_about_entity`, `reva_recall_for_entity` | +| Prefix | Backend | Examples | +|---------|----------|------------------------------------------------------------------| +| `crm_` | Nakatomi | `crm_search_contacts`, `crm_create_deal`, `crm_move_deal_stage` | +| `mem_` | AutoMem | `mem_store`, `mem_recall`, `mem_associate` | +| `reva_` | router | `reva_remember_about_entity`, `reva_recall_for_entity` | ## Rev A customizations -Delivered as overlays, not forks: +Delivered as overlays, not forks. Applied automatically by `railway/deploy.sh` +phase 3 (`seed`): -- **Pipeline** — `Manufacturing RFQ` with 12 stages (RFQ Received → Qualified → Quoted → Accepted → In Manufacturing → Inspection (G2) → Repackage → Shipped → Delivered → Invoiced → Paid → Closed Lost) -- **Custom fields** — `company.compliance`, `company.partner_scorecard`, `deal.quality_gates`, `deal.ncrs`, `deal.part_numbers`, `contact.role` -- **Memory taxonomy** — `reva/rfq`, `reva/quality`, `reva/compliance`, `reva/china-source`, `reva/partner-scorecard`, … +- **Pipeline — `Manufacturing RFQ`** (12 stages): RFQ Received → Qualified + → Quoted → Accepted → In Manufacturing → Inspection (G2) → Repackage → + Shipped → Delivered → Invoiced → Paid (won) → Closed Lost. +- **Custom fields** (8 total): `company.partner_scorecard`, + `company.compliance`, `company.region`, `contact.role`, + `deal.quality_gates`, `deal.ncrs`, `deal.part_numbers`, + `deal.china_source`. JSON-shaped payloads ride as `text` because + Nakatomi's custom-field schema is scalar-only. +- **Memory taxonomy** — `reva/rfq`, `reva/quality`, `reva/compliance`, + `reva/china-source`, `reva/partner-scorecard`, `reva/ncr`, + `reva/shipping`, `reva/itar`. -All defined in [`services/nakatomi-backend/seed/reva.py`](./services/nakatomi-backend/seed/reva.py) and applied automatically by `railway/deploy.sh`. +All defined in [`services/nakatomi-backend/seed/reva.py`](./services/nakatomi-backend/seed/reva.py). ## Documentation -- [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md) — component layout, data flow, hook system +- [`docs/ARCHITECTURE.md`](./docs/ARCHITECTURE.md) / [`docs/ARCHITECTURE_V2.md`](./docs/ARCHITECTURE_V2.md) — component layout, data flow, hook system +- [`docs/AUTH.md`](./docs/AUTH.md) — signup / rotation story - [`docs/INSTALL.md`](./docs/INSTALL.md) — plugin install reference (env overrides, offline, troubleshooting) - [`docs/ROADMAP.md`](./docs/ROADMAP.md) — what's shipped, what's next - [`plugin/CLIENT.md`](./plugin/CLIENT.md) — Rev A Manufacturing company profile @@ -131,4 +181,6 @@ All defined in [`services/nakatomi-backend/seed/reva.py`](./services/nakatomi-ba --- -Built by [MrDula Solutions](https://mrdulasolutions.com) for Rev A Manufacturing. Powered by Claude Code, [Nakatomi](https://github.com/mrdulasolutions/NakatomiCRM), and [AutoMem](https://github.com/mrdulasolutions/automem). +Built by [MrDula Solutions](https://mrdulasolutions.com) for Rev A +Manufacturing. Powered by Claude, [Nakatomi](https://github.com/mrdulasolutions/NakatomiCRM), +and [AutoMem](https://github.com/mrdulasolutions/automem). diff --git a/plugin/.claude-plugin/plugin.json b/plugin/.claude-plugin/plugin.json index e935579..00e868b 100644 --- a/plugin/.claude-plugin/plugin.json +++ b/plugin/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "reva-turbo", - "version": "2.0.0", - "description": "REVA-TURBO Skills Engine — Rev A Manufacturing PM workflow from RFQ intake through customer delivery. 46 skills covering quoting, China sourcing, compliance, quality, and fulfillment.", + "version": "2.1.1", + "description": "REVA-TURBO Skills Engine — Rev A Manufacturing PM workflow from RFQ intake through customer delivery. 48 skills + zero-friction onboarding: PM pastes their nk_… key in chat, plugin wires up the router automatically (no Settings UI hunting). Covers quoting, China sourcing, compliance, quality, and fulfillment.", "author": { "name": "Rev A Manufacturing / MrDula Solutions", "url": "https://github.com/mrdulasolutions" @@ -55,27 +55,14 @@ }, "requirements": { "shell": "bash >= 4", - "optional": { - "node": ">= 18 (for docx report generation)" - } - }, - "userConfig": { - "mcp_url": { - "description": "REVA MCP router URL (from your admin). Example: https://reva-ops.up.railway.app/mcp", - "sensitive": false - }, - "api_key": { - "description": "Your personal REVA API key (nk_...). Get one at /signup using the signup token your admin shared.", - "sensitive": true - } + "node": ">= 18 (required — mcp-remote bridges plugin stdio MCP to the HTTP router)" }, "mcpServers": { "reva": { - "type": "http", - "url": "${user_config.mcp_url}", - "headers": { - "Authorization": "Bearer ${user_config.api_key}" - } + "command": "bash", + "args": [ + "${CLAUDE_PLUGIN_ROOT}/bin/reva-mcp-launch.sh" + ] } } } diff --git a/plugin/VERSION b/plugin/VERSION index 227cea2..3e3c2f1 100644 --- a/plugin/VERSION +++ b/plugin/VERSION @@ -1 +1 @@ -2.0.0 +2.1.1 diff --git a/plugin/bin/reva-mcp-launch.sh b/plugin/bin/reva-mcp-launch.sh new file mode 100755 index 0000000..04a468c --- /dev/null +++ b/plugin/bin/reva-mcp-launch.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +# reva-mcp-launch.sh — stdio MCP launcher for the REVA-TURBO plugin. +# +# Claude Desktop spawns this on startup as the `reva` MCP server. It bridges +# Desktop's stdio transport to the Rev A MCP router over HTTP via the +# `mcp-remote` npm helper. +# +# Why a launcher (and not `${user_config.*}` substitution directly in +# plugin.json like a normal plugin): non-technical PMs don't want to +# navigate Plugins → Settings in Desktop to paste a URL and a key. With +# a launcher, the revmyengine skill can write the creds to a file on +# disk after the PM pastes their `nk_...` key into chat, and Desktop +# re-picks them up on the next app start. One paste, one restart — no +# Settings hunting. +# +# Credential precedence (first match wins): +# 1. $REVA_MCP_URL / $REVA_API_KEY in the environment (CI / advanced) +# 2. ~/.reva-turbo/state/mcp-credentials.env (written by the skill) +# 3. Baked-in production default URL (key must still come from 1 or 2) +# +# If no api_key is found, we exec mcp-remote with an empty bearer token. +# The router returns 401 on initialize, Desktop shows the server as +# errored, and the revmyengine preflight catches it and walks the PM +# through the paste flow. This is the right failure mode — loud enough +# that revmyengine notices, quiet enough not to scare the PM. +set -u + +CRED_FILE="${REVA_CRED_FILE:-$HOME/.reva-turbo/state/mcp-credentials.env}" + +# Load creds file if present. `set -a` exports everything it defines. +if [ -f "$CRED_FILE" ]; then + set -a + # shellcheck disable=SC1090 + . "$CRED_FILE" + set +a +fi + +# Fall back to the Rev A production router if the PM hasn't overridden it. +# The /mcp suffix matches the router's mount point in services/mcp-router. +: "${REVA_MCP_URL:=https://mcp-router-production-460a.up.railway.app/mcp}" +: "${REVA_API_KEY:=}" + +# Hand off to mcp-remote. `-y` bypasses the npx prompt, which would hang +# Desktop's stdio transport waiting for a y/n that never comes. +exec npx -y mcp-remote \ + "$REVA_MCP_URL" \ + --header "Authorization: Bearer ${REVA_API_KEY}" diff --git a/plugin/build-bundle.sh b/plugin/build-bundle.sh index 49ea97e..8c53ebd 100755 --- a/plugin/build-bundle.sh +++ b/plugin/build-bundle.sh @@ -1,10 +1,14 @@ #!/usr/bin/env bash # Build reva-turbo-.zip for Claude Desktop "Personal → Local uploads". # -# The ZIP unpacks with `.claude-plugin/plugin.json` at the archive root, which -# is what Desktop's plugin loader expects. We explicitly exclude dev cruft -# (`.git`, `node_modules`, `.DS_Store`) and the build artifacts dir itself -# so the bundle stays small and deterministic. +# The archive contains a single top-level directory `reva-turbo/` with the +# plugin's `.claude-plugin/plugin.json` at `reva-turbo/.claude-plugin/plugin.json`. +# This is the convention Desktop's uploader recognizes — a flat zip with the +# manifest at the archive root triggers "wrong format" on upload. +# +# We explicitly exclude dev cruft (`.git`, `node_modules`, `.DS_Store`, +# `__MACOSX`) and the build artifacts dir itself so the bundle stays small +# and deterministic. # # Usage: # ./build-bundle.sh # -> dist/reva-turbo-.zip @@ -15,6 +19,7 @@ PLUGIN_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" VERSION="$(cat "$PLUGIN_ROOT/VERSION" | tr -d '[:space:]')" DIST="$PLUGIN_ROOT/dist" OUT="$DIST/reva-turbo-${VERSION}.zip" +PLUGIN_SLUG="reva-turbo" if [ "${1:-}" = "clean" ]; then rm -rf "$DIST" @@ -33,18 +38,42 @@ fi mkdir -p "$DIST" rm -f "$OUT" -# Build from inside PLUGIN_ROOT so paths in the archive are relative to the plugin root. -# Explicit excludes keep the bundle small and reproducible. -cd "$PLUGIN_ROOT" -zip -rq "$OUT" . \ - -x "dist/*" \ - -x "*.git/*" -x "*.git" \ - -x "*/node_modules/*" -x "node_modules/*" \ - -x "*.DS_Store" \ - -x "build-bundle.sh" \ - -x "*.pyc" -x "*__pycache__*" +# Stage the plugin into a temp dir under the canonical slug, then zip that +# directory. This produces `reva-turbo/.claude-plugin/plugin.json` inside the +# archive — not a flat root — which is what Desktop's loader expects. +STAGE="$(mktemp -d -t reva-turbo-bundle)" +trap 'rm -rf "$STAGE"' EXIT + +# rsync copies everything except the exclusions. We deliberately bring in +# dotfiles (.claude-plugin/, .claude/) — they're part of the plugin. +rsync -a \ + --exclude 'dist/' \ + --exclude '.git/' \ + --exclude '.git' \ + --exclude 'node_modules/' \ + --exclude '.DS_Store' \ + --exclude '__MACOSX' \ + --exclude 'build-bundle.sh' \ + --exclude '*.pyc' \ + --exclude '__pycache__/' \ + "$PLUGIN_ROOT/" "$STAGE/$PLUGIN_SLUG/" + +# Belt-and-suspenders: strip stray AppleDouble and .DS_Store files the +# rsync filter might miss on nested mounts. +find "$STAGE" \( -name '.DS_Store' -o -name '._*' \) -delete 2>/dev/null || true + +( cd "$STAGE" && zip -rq "$OUT" "$PLUGIN_SLUG" ) SIZE="$(du -h "$OUT" | cut -f1)" +# unzip -l has 4 leading columns (length, date, time, name). Name can +# contain spaces, so we slice from column 4 onward. +TOP_ENTRIES="$(unzip -l "$OUT" \ + | awk 'NR>3 && NF>=4 {for (i=4;i<=NF;i++) printf "%s%s", $i, (i0 {print $1}' \ + | sort -u \ + | grep -v '^-\+$' \ + | grep -v '^[0-9]*\s*files' || true)" echo "Built $OUT ($SIZE)" +echo "Top-level entries: $(echo "$TOP_ENTRIES" | tr '\n' ' ')" echo echo "Upload via Claude Desktop → Plugins → Personal → Local uploads → +" diff --git a/plugin/skills/revmyengine/SKILL.md b/plugin/skills/revmyengine/SKILL.md index 3123b4f..eff5ef4 100644 --- a/plugin/skills/revmyengine/SKILL.md +++ b/plugin/skills/revmyengine/SKILL.md @@ -1,13 +1,15 @@ --- name: revmyengine preamble-tier: 1 -version: 1.2.0 +version: 2.1.1 description: | REVA-TURBO master orchestrator for Rev A Manufacturing PM workflow. Routes requests to the correct sub-skill based on context. Chains the RFQ-to-delivery lifecycle. Handles in-engine slash commands (/status, /help, /whoami, etc.). Injects trust level overlay and voice profile - into every skill invocation. Detects first-run and triggers setup wizard. + into every skill invocation. On first run, fetches the Rev A company + profile from the router (no local company setup needed) and asks a + single role question (PM / sales / compliance / C-level / eng). Use for any PM activity: "new RFQ", "quote", "send to China", "track order", "inspect", "dashboard", "escalate", or any /command. compatibility: Claude Code, Claude desktop, Claude CoWork @@ -19,6 +21,37 @@ allowed-tools: - Glob - Grep - AskUserQuestion + # Router MCP tools — the plugin's only remote data source. All company + # profile, pipeline schema, partners roster, memory, and CRM access + # flows through here. No local YAML. + - mcp__reva__reva_whoami + - mcp__reva__reva_get_company_profile + - mcp__reva__reva_get_workspace_config + - mcp__reva__reva_set_user_role + - mcp__reva__reva_remember_about_entity + - mcp__reva__reva_recall_for_entity + - mcp__reva__crm_search_contacts + - mcp__reva__crm_get_contact + - mcp__reva__crm_create_contact + - mcp__reva__crm_update_contact + - mcp__reva__crm_search_companies + - mcp__reva__crm_create_company + - mcp__reva__crm_list_pipelines + - mcp__reva__crm_create_deal + - mcp__reva__crm_move_deal_stage + - mcp__reva__crm_log_activity + - mcp__reva__crm_add_note + - mcp__reva__crm_create_task + - mcp__reva__crm_list_tasks + - mcp__reva__crm_relate + - mcp__reva__crm_timeline + - mcp__reva__crm_describe_schema + - mcp__reva__mem_store + - mcp__reva__mem_recall + - mcp__reva__mem_associate + - mcp__reva__mem_update + - mcp__reva__mem_delete + - mcp__reva__mem_health hooks: PreToolUse: - matcher: "Edit|Write" @@ -94,20 +127,196 @@ touch ~/.reva-turbo/.telemetry-prompted This only happens once. If `TEL_PROMPTED` is `yes`, skip entirely. -## First-Run Detection +## First-Run Detection (server-driven — no local company setup) -If `SETUP_DONE` is `"false"` or empty, prompt the PM: +REVA-TURBO is backed by a shared MCP router. The router already knows the +company profile (Rev A Manufacturing — legal name, leadership, escalation +matrix, partner list, capabilities) and the PM lifecycle schema. **The +plugin must never re-ask for any of that.** On first run we only need to +learn the user's *role*, then we pull everything else over MCP. -> 🔧 **Welcome to REVA-TURBO!** I'll help you configure the engine for your team. This takes about 10 minutes and covers your company profile, workflow, connectors, partners, shipping, and document preferences. +### Step 1 — call `reva_whoami` -Use AskUserQuestion: -- A) Run setup now — let's configure everything -- B) Skip for now — use defaults, I'll set up later +Call the MCP tool (this will be exposed as `mcp__reva__reva_whoami` once +the `reva` MCP server is connected — see the preflight check below): -If A: Read and execute `~/.claude/skills/reva-turbo/skills/reva-turbo-setup/SKILL.md` -If B: Run `~/.claude/skills/reva-turbo/bin/reva-turbo-config set setup_completed skipped` and continue. +``` +mcp__reva__reva_whoami {} +``` + +The response tells us four things: +1. The user's identity (user_id, email, display_name) — attribute workflow + events correctly. +2. The workspace they landed in (must be `slug: "reva"` — if not, something + is wrong with signup; stop and ask the user to re-run signup). +3. `pm_role` + `needs_role` — whether we must ask the role question. +4. `tool_prefixes` — confirms the router is the reva router + (`crm`, `mem`, `reva`). If these are missing or the tool call itself + fails, the router isn't connected — jump to **Preflight — MCP router + connected?** below and run the paste-your-key flow. Never send the + PM into Desktop Settings; the plugin now self-configures from a + pasted `nk_...` key. + +### Step 2 — ask the role question (only if `needs_role: true`) + +Use `AskUserQuestion`. Ask ONE question, five lettered options: + +> **What's your role at Rev A?** I'll activate the skills that match your +> day and keep the rest out of the way. You can change this any time with +> `/role`. + +Options: +- A) **Project Manager** — full RFQ → delivery workflow +- B) **Sales / BD** — intake, qualify, quote, customer comms +- C) **Compliance** — EAR / ITAR / HTS / ISF / audit +- D) **C-level** — dashboard, profit, pulse, intel +- E) **Engineering** — qualify, China package, inspect, NCR, change orders + +Map answer → role slug: A→`pm`, B→`sales`, C→`compliance`, D→`clevel`, +E→`eng`. Then call: + +``` +mcp__reva__reva_set_user_role {"role": ""} +``` + +### Step 3 — pull server config and cache locally + +Call both, once, and write the results to local state so other skills can +read them without re-hitting the router on every invocation: + +``` +mcp__reva__reva_get_company_profile {} +mcp__reva__reva_get_workspace_config {} +``` + +Write the results to: +- `~/.reva-turbo/state/company-profile.json` (from `reva_get_company_profile`) +- `~/.reva-turbo/state/workspace-config.json` (from `reva_get_workspace_config`) + +Mark setup done: + +```bash +~/.claude/skills/reva-turbo/bin/reva-turbo-config set setup_completed true +~/.claude/skills/reva-turbo/bin/reva-turbo-config set pm_role "" +~/.claude/skills/reva-turbo/bin/reva-turbo-config set bootstrap_version 2 2>/dev/null || true +``` + +### Step 4 — confirm and land + +Welcome the PM with the role-appropriate greeting and show the three +things they can do right now: + +> Welcome to REVA-TURBO, . You're connected to Rev A's +> shared CRM and memory as ****. Here are the three most common +> ways to start: +> +> - **New RFQ** — paste the email or specs, I'll intake + qualify. +> - **/status** — what's on your plate today. +> - **/role** — change your role or view company config. + +### Legacy setup wizard (advanced / admin only) + +The old 8-section `reva-turbo-setup` wizard is kept for admins who are +standing up a *new* deployment (company, partners roster, connectors, +document branding). A regular PM at Rev A should never hit it — their +environment is already configured server-side. + +If the PM explicitly types `/setup`, route to `reva-turbo-setup` with a +warning: *"This is the legacy deployment wizard. For Rev A, your config +comes from the router — you can safely ignore this. Continue only if +your admin asked you to."* + +### Preflight — MCP router connected? + +Before running Step 1, confirm the `reva` MCP server is actually +available. If the `mcp__reva__reva_whoami` tool doesn't exist in the +current tool surface, walk the PM through the one-minute paste flow. + +**Say exactly this** (keep it warm, concrete, two links, three steps): + +> **Welcome to Rev A — let's get you connected.** You need a personal +> API key to talk to the REVA router. It takes about 60 seconds. +> +> **Step 1.** Open this page and mint your key: +> **https://mcp-router-production-460a.up.railway.app/signup** +> +> (Your admin gave you a signup token — paste it on that page, pick a +> display name and email, click "Create account." You'll get a key that +> starts with `nk_`.) +> +> **Step 2.** Copy the whole key, paste it back here in this chat, and +> say something like *"here's my key: nk_…"*. I'll wire the plugin up +> for you — you don't need to open Settings. +> +> **Step 3.** Quit and reopen Claude Desktop (Cmd-Q, then relaunch). +> That's the only time we need you to restart. Come back here, say +> *"let's go"*, and we're off. +> +> ⚠️ One hygiene note: if you have a standalone Nakatomi or AutoMem +> connector installed under **Desktop → Settings → Connectors**, remove +> it. The REVA-TURBO plugin already bundles both — keeping a duplicate +> exposes the raw tool names (`search_contacts`, `memory_recall`) and +> breaks routing. + +**When the PM pastes a key** (any string starting with `nk_` in their +next message, or they invoke `/connect ` explicitly): run the +block in the **`/connect` — wire up credentials** section below, then +tell them to restart Desktop. + +**Do not proceed to Steps 1–4 until `mcp__reva__reva_whoami` succeeds.** +If a restart was just requested, acknowledge the paste, confirm the +file was written, and wait. + +### `/connect ` — wire up credentials (inline command) + +Extract the key from the PM's message (grep for `nk_[A-Za-z0-9_-]+`). +If the user also provided a router URL, capture that too (look for an +`https://…/mcp` token); otherwise default to the Rev A production URL. + +Write credentials to the file the MCP launcher reads on Desktop +startup (`bin/reva-mcp-launch.sh`): + +```bash +mkdir -p ~/.reva-turbo/state +_KEY="NK_KEY_HERE" # replace with the extracted nk_... value +_URL="MCP_URL_HERE" # replace; default: https://mcp-router-production-460a.up.railway.app/mcp +cat > ~/.reva-turbo/state/mcp-credentials.env < in workspace . Now + quit Claude Desktop (Cmd-Q) and reopen it. Then come back and say + 'let's go'."** +- If `curl` fails (401 / 404 / network error): do NOT tell them to + restart. Tell them exactly what came back: *"That key didn't + validate — the router replied . Double-check you pasted the + whole `nk_...` string, or re-mint at + https://mcp-router-production-460a.up.railway.app/signup."* Wipe + the file (`rm ~/.reva-turbo/state/mcp-credentials.env`) so the next + attempt starts clean. + +**Safety rails:** +- Never echo the key back in full — show only the first 8 chars and + last 4 (`nk_abcd1234…wxyz`). +- Never commit the key anywhere, never put it in `/refresh`'s + diagnostic output, never pass it to telemetry. +- The file mode is 600 — belt-and-suspenders against any other + process on the box. + +Do not proceed to Steps 1–4 until the tool call succeeds. ## Trust Level Injection @@ -156,7 +365,11 @@ Voice applies to greeting style, signoff, tone, email length, technical depth, f | `/audit` | delegated | → reva-turbo-audit-trail mode:summary | | `/alerts` | delegated | → reva-turbo-pulse mode:review | | `/rules` | delegated | → reva-turbo-rules mode:list | -| `/setup` | delegated | → reva-turbo-setup | +| `/setup` | delegated | → reva-turbo-setup (legacy; admin only) | +| `/role [slug]` | inline | Show or change PM role (pm/sales/compliance/clevel/eng); calls `reva_set_user_role` and refreshes local cache | +| `/refresh` | inline | Re-pull `reva_get_company_profile` + `reva_get_workspace_config` into local cache | +| `/connect ` | inline | Paste-key-in-chat onboarding: validate key against `/auth/me`, write `~/.reva-turbo/state/mcp-credentials.env`, prompt restart | +| `/connected` | inline | Diagnostic: confirm router + show tool counts (`crm_*`, `mem_*`, `reva_*`) and current `mcp_url` | | `/send-logs` | inline | Package dev log + email to matt@mrdula.solutions | | `/logs` | inline | Display recent telemetry entries in readable format | @@ -277,6 +490,92 @@ reva-turbo-rfq-intake After completing a skill, tell the PM: "Next step in the workflow: [skill name]. Want me to run it?" +## /role, /refresh, /connected — router-backed inline commands + +These three commands are the PM's bridge to the server-side config. Run +them inline (don't delegate to a sub-skill). + +### `/role` — show or change role + +No arg: read `~/.reva-turbo/state/company-profile.json` + +`~/.claude/skills/reva-turbo/bin/reva-turbo-config get pm_role`, display: + +``` +You are . +Role unlocks these skills: [first 6 from workspace-config.json +role_skill_map[], ellipsis if more] +Change: /role pm | sales | compliance | clevel | eng +``` + +With arg (e.g. `/role sales`): validate against the five slugs, call +`mcp__reva__reva_set_user_role {"role": ""}`, update local config +(`reva-turbo-config set pm_role `), re-pull workspace config into +`~/.reva-turbo/state/workspace-config.json`, confirm. + +### `/refresh` — re-sync from router + +Re-call: +- `mcp__reva__reva_get_company_profile` → write + `~/.reva-turbo/state/company-profile.json` +- `mcp__reva__reva_get_workspace_config` → write + `~/.reva-turbo/state/workspace-config.json` + +Print a one-line summary (company name, workspace slug, N pipelines, N +partners, N role skills for current role). + +### `/connected` — diagnostic + +Call `mcp__reva__reva_whoami`. Report: + +``` +✓ Router: () +✓ Identity: <> +✓ Role: +✓ Tool prefixes: crm_* / mem_* / reva_* +``` + +If the `mcp__reva__reva_whoami` call fails, check whether a creds file +exists so you can give the right next step: + +```bash +_CRED=~/.reva-turbo/state/mcp-credentials.env +[ -f "$_CRED" ] && echo "creds_file: present" || echo "creds_file: missing" +``` + +**Case A — `creds_file: missing`** (PM never ran `/connect`): + +``` +✗ Router not connected — no credentials on disk yet. + 1. Mint your key: https://mcp-router-production-460a.up.railway.app/signup + 2. Paste it back here: /connect nk_yourkeyhere + 3. Quit and reopen Claude Desktop. + + If you also have a standalone Nakatomi or AutoMem connector under + Desktop → Settings → Connectors, remove it — this plugin already + bundles both (crm_*/mem_*). Duplicates expose the raw tool names + (search_contacts, memory_recall) and break routing. +``` + +**Case B — `creds_file: present`** (creds exist but router still +silent — either the PM hasn't restarted Desktop yet, the key is bad, +or the router is down). Run a live probe: + +```bash +. ~/.reva-turbo/state/mcp-credentials.env +curl -fsS -o /dev/null -w "%{http_code}" \ + -H "Authorization: Bearer $REVA_API_KEY" \ + "${REVA_MCP_URL%/mcp}/auth/me" +``` + +- `200` → creds are good, MCP just hasn't reloaded: *"Quit Claude + Desktop (Cmd-Q) and reopen — the plugin re-reads credentials on app + start."* +- `401` → bad/expired key: *"Re-mint at + https://mcp-router-production-460a.up.railway.app/signup and run + /connect ."* +- Connection error → *"Can't reach the router. Check Wi-Fi; if it + persists, ping your admin — the router may be down."* + ## Workflow State Log every workflow transition to `~/.reva-turbo/state/workflow-state.jsonl`: diff --git a/services/mcp-router/router/signup.py b/services/mcp-router/router/signup.py index 7279776..37093b0 100644 --- a/services/mcp-router/router/signup.py +++ b/services/mcp-router/router/signup.py @@ -166,68 +166,173 @@ def _extract_detail(resp: httpx.Response, fallback: str) -> str: -REVA-OPS · Get your API key +REVA-OPS · Join the team
-

REVA-OPS

-

Get your personal API key. This key connects Claude Code (and any MCP client) to the Rev A CRM + memory.

+
+

REVA-OPS

+ Rev A Manufacturing +
+

You're one minute from having the full PM engine — CRM, + memory, and 48 skills — connected to Claude Desktop.

+ +
+
1 Mint API key
+
2 Install plugin
+
3 Run engine
+
- + - + - - + + - - + + - +
-

- Already have a key? Point your MCP client at /mcp - with header Authorization: Bearer <your key>. -

+
+

Step 2 — Install the plugin

+
+
    +
  1. Download the latest plugin zip from + GitHub Releases + — look for reva-turbo-<version>.zip + (v2.1.1 or later). Don't unzip it.
  2. +
  3. Claude Desktop → Plugins → Personal → Local uploads → + + and drop in the zip. Click Enable.
  4. +
  5. No settings to fill in. The 2.1.1 plugin + self-configures — you'll paste your key in chat in Step 3.
  6. +
+ +
+ Important — remove any legacy connectors. + If you previously added a standalone Nakatomi or + AutoMem MCP connector in Claude Desktop → Settings → + Connectors, remove it now. This plugin wraps + both behind the router with prefixed tool names + (crm_* / mem_* / reva_*). + Duplicates show up as raw search_contacts / + memory_recall tool names and break intent routing. +
+
+ +

Step 3 — Run the engine & paste your key

+
+

In any Claude Desktop chat, type:

+
/reva-turbo:revmyengine
+

The engine will greet you and notice it doesn't have a key yet. + It'll ask you to paste one. Reply with:

+
/connect <paste your nk_... key here>
+

The engine validates the key against the router, saves it to + your local config, and tells you to quit & reopen Claude + Desktop (Cmd-Q, relaunch). That one restart is the only manual + step — after it, say "let's go" and you're in.

+

+ Behind the scenes: the plugin is connected to the shared + Rev A workspace, so it asks exactly one question + (what's your role?) and pulls company profile, + partners, and pipelines from the router — no local setup. +

+
+ +

+ Need to do this from a terminal instead? + CLI install flow → +

+
diff --git a/services/mcp-router/router/tools/cross.py b/services/mcp-router/router/tools/cross.py index 8b9b554..331e76e 100644 --- a/services/mcp-router/router/tools/cross.py +++ b/services/mcp-router/router/tools/cross.py @@ -3,10 +3,17 @@ These are the reason we run a router (instead of two independent MCPs): orchestrate Nakatomi + AutoMem together so agents can think in terms of the Rev A workflow rather than the underlying systems. + +A few tools here (``reva_set_user_role``) need to mutate workspace-scoped +state that Nakatomi only lets ``owner``/``admin`` role tokens touch. For +those, we route through the router's admin token (``NAKATOMI_ADMIN_TOKEN``) +after identifying the caller with their own token. Regular PMs therefore +never need admin creds to record their role — the router is the authority. """ from __future__ import annotations +import os from typing import Any from mcp.server.fastmcp import Context, FastMCP @@ -16,6 +23,16 @@ from ..upstream import AutoMemClient, NakatomiClient +def _admin_token() -> str: + tok = os.environ.get("NAKATOMI_ADMIN_TOKEN", "") + if not tok: + raise RuntimeError( + "NAKATOMI_ADMIN_TOKEN is not set on the router — this workspace-" + "privileged call requires it. Ask your admin to set it." + ) + return tok + + def register(mcp: FastMCP) -> None: nakatomi = NakatomiClient() automem = AutoMemClient() @@ -57,16 +74,27 @@ async def remember_about_entity( mem_result = await automem.request("POST", "/memory", json=mem_body) memory_id = mem_result.get("memory_id") or mem_result.get("id") - # 3. Link on the CRM side + # 3. Link on the CRM side. Nakatomi's `/memory/link` expects + # `{connector, external_id, crm_entity_type, crm_entity_id, note, data}` + # — the note surfaces on the entity's timeline, `data` is freeform + # metadata Nakatomi stores on the link row. We deliberately do NOT + # require `automem` to be in Nakatomi's registered-connectors list + # (that registry only gates the CRM→memory sync pipeline, not link + # storage), so this works even before an upstream adapter lands. link_body = { - "entity_type": entity_type, - "entity_id": entity_id, - "memory_id": memory_id, "connector": "automem", - "summary": content[:240], + "external_id": str(memory_id or ""), + "crm_entity_type": entity_type, + "crm_entity_id": entity_id, + "note": content[:240], + "data": { + "source": "reva-mcp-router", + "memory_type": memory_type, + "tags": tag_list, + }, } link_result = await nakatomi.request( - "POST", "/memory-links", token=token, json=link_body + "POST", "/memory/link", token=token, json=link_body ) return { "memory_id": memory_id, @@ -98,3 +126,122 @@ async def recall_for_entity( if query: params["query"] = query return await automem.request("GET", "/recall", params=params) + + # --------------------------------------------------------------- + # Profile / role / who-am-I — server-side so PMs never have to + # configure company data locally. The plugin calls these on first + # run and caches to ~/.reva-turbo/state/. + # --------------------------------------------------------------- + + @mcp.tool(name="reva_whoami") + async def whoami(ctx: Context) -> dict: + """Return the caller's identity + Rev A workspace context. + + Used by the plugin's first-run bootstrap to decide what (if any) + questions to ask. The plugin should NOT prompt for anything that + this call can answer. + """ + token = token_from_ctx(ctx) + # /auth/me returns {id, email, display_name, ...} for the caller + me = await nakatomi.request("GET", "/auth/me", token=token) + ws = await nakatomi.request("GET", "/workspace", token=token) + data = ws.get("data") or {} + role_map = (data.get("user_roles") or {}) if isinstance(data, dict) else {} + user_id = (me or {}).get("id") + user_role = role_map.get(user_id) if user_id else None + return { + "user_id": user_id, + "email": (me or {}).get("email"), + "display_name": (me or {}).get("display_name"), + "workspace": { + "id": ws.get("id"), + "slug": ws.get("slug"), + "name": ws.get("name"), + }, + "pm_role": user_role, # None → plugin prompts once; then reva_set_user_role + "needs_role": user_role is None, + "tool_prefixes": { + "crm": settings.crm_tool_prefix, + "memory": settings.mem_tool_prefix, + "cross": "reva", + }, + } + + @mcp.tool(name="reva_get_company_profile") + async def get_company_profile(ctx: Context) -> dict: + """Return the Rev A Manufacturing company profile. + + Stored server-side in the workspace's ``data.company_profile`` + object so every PM pulls the same source-of-truth and no one has + to re-enter the company name, leadership, escalation matrix, or + capabilities at setup time. + """ + token = token_from_ctx(ctx) + ws = await nakatomi.request("GET", "/workspace", token=token) + data = ws.get("data") or {} + profile = data.get("company_profile") + if not profile: + return { + "configured": False, + "message": ( + "company_profile is not yet populated on this workspace. " + "Ask your admin to run `./railway/deploy.sh seed` to " + "publish the Rev A profile." + ), + } + return {"configured": True, **profile} + + @mcp.tool(name="reva_get_workspace_config") + async def get_workspace_config(ctx: Context) -> dict: + """Return the full Rev A workspace config: pipeline stages, custom + fields, partners roster, memory taxonomy, role→skill map. + + Lets the plugin render the dashboard and route intents without + maintaining any local YAML. + """ + token = token_from_ctx(ctx) + ws = await nakatomi.request("GET", "/workspace", token=token) + pipelines = await nakatomi.request("GET", "/pipelines", token=token) + fields = await nakatomi.request("GET", "/custom-fields", token=token) + data = ws.get("data") or {} + return { + "workspace": {"id": ws.get("id"), "slug": ws.get("slug"), "name": ws.get("name")}, + "pipelines": pipelines or [], + "custom_fields": fields or [], + "partners": data.get("partners") or [], + "memory_taxonomy": data.get("memory_taxonomy") or [], + "escalation_matrix": data.get("escalation_matrix") or [], + "role_skill_map": data.get("role_skill_map") or {}, + } + + @mcp.tool(name="reva_set_user_role") + async def set_user_role(ctx: Context, role: str) -> dict: + """Record the caller's PM role (pm | sales | compliance | clevel | eng). + + Writes into ``workspace.data.user_roles[user_id]``. Used by the + plugin to decide which skill subset to surface on the dashboard. + Non-destructive: preserves all other keys in workspace.data. + """ + role = (role or "").strip().lower() + allowed = {"pm", "sales", "compliance", "clevel", "eng"} + if role not in allowed: + raise RuntimeError( + f"role must be one of {sorted(allowed)}; got '{role}'" + ) + # Read identity with caller's token (each user sees only themselves), + # then write via admin token because PATCH /workspace is owner/admin-only. + token = token_from_ctx(ctx) + me = await nakatomi.request("GET", "/auth/me", token=token) + user_id = (me or {}).get("id") + if not user_id: + raise RuntimeError("could not resolve caller user_id") + admin = _admin_token() + ws = await nakatomi.request("GET", "/workspace", token=admin) + data = dict(ws.get("data") or {}) + roles = dict(data.get("user_roles") or {}) + roles[user_id] = role + data["user_roles"] = roles + await nakatomi.request( + "PATCH", "/workspace", token=admin, json={"data": data} + ) + return {"user_id": user_id, "role": role, "ok": True} diff --git a/services/nakatomi-backend/seed/reva.py b/services/nakatomi-backend/seed/reva.py index e692321..f798e40 100644 --- a/services/nakatomi-backend/seed/reva.py +++ b/services/nakatomi-backend/seed/reva.py @@ -81,6 +81,103 @@ "reva/partner-scorecard", "reva/ncr", "reva/shipping", "reva/itar", ] +# --------------------------------------------------------------------------- +# Company profile — lives in ``workspace.data.company_profile``. The router +# exposes this via ``reva_get_company_profile`` so every PM's plugin pulls +# the same source-of-truth on first run and never has to re-enter the +# company name, leadership, partners, or escalation matrix locally. +# +# `memory_taxonomy`, `role_skill_map`, `escalation_matrix`, and `partners` +# sit next to `company_profile` (under `workspace.data`) because the router's +# `reva_get_workspace_config` tool surfaces them together. +# --------------------------------------------------------------------------- + +REVA_COMPANY_PROFILE: dict[str, Any] = { + "legal_name": "Rev A Manufacturing", + "short_name": "Rev A Mfg", + "website": "https://www.revamfg.com", + "industry": "Contract manufacturing", + "business_model": ( + "Receive RFQ → Qualify → Quote → Send specs to China partners → " + "Receive goods → Inspect/Repackage → Ship to customer" + ), + "capabilities": [ + "Production machining (CNC milling, turning, multi-axis)", + "Injection tooling & molding", + "Prototyping / 3D printing / short-run", + "Sheet metal (laser, bending, welding)", + "Finishing (anodize, plate, powder coat, paint)", + "Assembly, sub-assembly, kitting, packaging", + ], + "leadership": [ + {"name": "Donovan Weber", "role": "President & Co-founder", + "escalation_default": True}, + ], + "pm_team": [ + {"name": "Ray Yeh", "role": "Senior Project Manager"}, + {"name": "Harley Scott", "role": "Senior Project Manager"}, + ], + "business_development": [ + {"name": "Matt Nebo", "role": "Director of Business Development", "region": "West Coast"}, + {"name": "Barry Coyle", "role": "Director of Business Development", "region": "Midwest"}, + {"name": "Bryce Martel", "role": "Director of Business Development", "region": "East Coast"}, + {"name": "Ryan Knight", "role": "Business Development"}, + ], + "report_prefix": "REVA-TURBO", + "report_naming": "REVA-TURBO-{Type}-{YYYY-MM-DD}-{ShortName}.docx", +} + +REVA_ESCALATION_MATRIX: list[dict[str, str]] = [ + {"issue": "Quality issue", "first": "Senior PM (Ray Yeh / Harley Scott)", "second": "Donovan Weber"}, + {"issue": "Delivery delay (>2wk)", "first": "Senior PM", "second": "Donovan Weber"}, + {"issue": "Customer complaint", "first": "Senior PM", "second": "Donovan Weber"}, + {"issue": "New capability request","first": "BD Director (regional)", "second": "Donovan Weber"}, + {"issue": "Payment / credit", "first": "Senior PM", "second": "Donovan Weber"}, + {"issue": "Legal / contractual", "first": "Donovan Weber (direct)", "second": ""}, +] + +# Role → which skill subset to surface on the PM's dashboard on first run. +# Roles not listed fall back to ``pm`` (the full workflow). +REVA_ROLE_SKILL_MAP: dict[str, list[str]] = { + "pm": [ + "reva-turbo-rfq-intake", "reva-turbo-rfq-qualify", "reva-turbo-rfq-quote", + "reva-turbo-china-package", "reva-turbo-china-track", + "reva-turbo-inspect", "reva-turbo-quality-gate", "reva-turbo-ncr", + "reva-turbo-logistics", "reva-turbo-customer-comms", + "reva-turbo-dashboard", "reva-turbo-order-track", "reva-turbo-escalate", + ], + "sales": [ + "reva-turbo-rfq-intake", "reva-turbo-rfq-qualify", "reva-turbo-rfq-quote", + "reva-turbo-customer-profile", "reva-turbo-customer-comms", + "reva-turbo-customer-gate", "reva-turbo-dashboard", "reva-turbo-intel", + ], + "compliance": [ + "reva-turbo-export-compliance", "reva-turbo-import-compliance", + "reva-turbo-isf-filing", "reva-turbo-audit-trail", + "reva-turbo-rules", "reva-turbo-dashboard", + ], + "clevel": [ + "reva-turbo-dashboard", "reva-turbo-pulse", "reva-turbo-intel", + "reva-turbo-profit", "reva-turbo-report", "reva-turbo-escalate", + ], + "eng": [ + "reva-turbo-rfq-qualify", "reva-turbo-china-package", + "reva-turbo-inspect", "reva-turbo-quality-gate", "reva-turbo-ncr", + "reva-turbo-change-order", "reva-turbo-partner-master", + ], +} + +REVA_MEMORY_TAXONOMY: list[dict[str, str]] = [ + {"tag": "reva/rfq", "purpose": "Incoming RFQs, scope, targets"}, + {"tag": "reva/quality", "purpose": "Gate outcomes, inspection notes"}, + {"tag": "reva/compliance", "purpose": "EAR/ITAR/HTS rulings and docs"}, + {"tag": "reva/china-source", "purpose": "Supplier decisions, buyer-agent notes"}, + {"tag": "reva/partner-scorecard", "purpose": "Partner quality / delivery / rate changes"}, + {"tag": "reva/ncr", "purpose": "Non-conformance records and RCAs"}, + {"tag": "reva/shipping", "purpose": "Freight, customs, ISF, delivery notes"}, + {"tag": "reva/itar", "purpose": "ITAR-specific findings (maximum scrutiny)"}, +] + def _slug(name: str) -> str: return re.sub(r"[^a-z0-9]+", "-", name.lower()).strip("-") @@ -150,11 +247,35 @@ def upsert_custom_fields(self) -> None: else: raise + def upsert_workspace_profile(self) -> None: + """Publish the Rev A company profile + config into ``workspace.data``. + + Non-destructive: preserves any other keys (e.g. ``user_roles`` that + PMs write via ``reva_set_user_role``). + """ + ws = self._req("GET", "/workspace") or {} + data = dict(ws.get("data") or {}) + data["company_profile"] = REVA_COMPANY_PROFILE + data["escalation_matrix"] = REVA_ESCALATION_MATRIX + data["role_skill_map"] = REVA_ROLE_SKILL_MAP + data["memory_taxonomy"] = REVA_MEMORY_TAXONOMY + # Leave partners alone if already populated — they're PM-editable. + data.setdefault("partners", []) + self._req("PATCH", "/workspace", json={"data": data}) + print( + f"+ workspace.data published: company_profile, escalation_matrix " + f"({len(REVA_ESCALATION_MATRIX)}), role_skill_map " + f"({len(REVA_ROLE_SKILL_MAP)} roles), memory_taxonomy " + f"({len(REVA_MEMORY_TAXONOMY)} tags)" + ) + def run(self) -> None: print(f"Seeding Rev A schema against {self.base_url}") self.upsert_pipeline() print("Custom fields:") self.upsert_custom_fields() + print("Workspace profile:") + self.upsert_workspace_profile() print("Tag vocabulary (advisory — Nakatomi allows any tag):") for t in REVA_TAG_VOCABULARY: print(f" • {t}")