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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 92 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,40 @@

Local **solo EM workbench** for evidence-backed performance reviews and promotion cases.

ICs never log in. Self/peer input arrives as JSON import bundles. Your vault stays on disk — **Lock & encrypt** seals SQLite + files at rest.
ICs never log in. Self and peer input arrives as JSON import bundles. Your vault stays on disk: **Lock & encrypt** seals SQLite and dossier files at rest.

<p align="center">
<img src="./docs/screenshots/02-home.png" alt="Command center" width="900" />
</p>

## Why this exists

Most review tools are either a shared HR portal (everyone logs in) or a blank Google Doc. This app is for one engineering manager who wants:

1. **A year-round dossier** so writing season is not a scavenger hunt
2. **Role frameworks as the bar** so ratings map to expectations, not vibes
3. **Evidence-linked drafts** so extreme scores and promo claims stay grounded
4. **Local custody** so private notes and underperformance plans stay on your machine until you export

The product is intentionally single-user. Peers and ICs participate by filling exported forms that you import back. That keeps anonymity and trust simple for v1.

## Core loop

```
upload role R&R → assign people → capture dossier evidence
→ open a cycle → import self/peer bundles → writing desk (+ AI)
→ consistency + team performance check → share export → history / promo packets
```

| Stage | Intent |
|-------|--------|
| Roles + people | Define what "good" means before you score anyone |
| Dossier | Accumulate achievements, goals, feedback, docs, and concerns all year |
| Cycle + bundles | Time-box the review without building a multi-user portal |
| Writing desk | Draft, cite, lint, finalize, then share a subject packet |
| Consistency / team performance | Catch clustering, thin evidence, and odd rating shapes before you ship |
| Promotions | Reuse the same role bar for next-level / promo cases |

## Quick start

```bash
Expand All @@ -24,18 +52,20 @@ pnpm dev

First load: **Create & seed demo team** (8-person org), then unlock. Between sessions use **Lock & encrypt**.

Requires **pnpm 10.33.3** (see [`AGENTS.md`](./AGENTS.md) if Corepack hands you pnpm 11).

### Desktop (Tauri)

```bash
pnpm desktop:dev # native window (needs WebView + Rust)
pnpm desktop:build
```

See [`apps/desktop/README.md`](./apps/desktop/README.md).
Packaged builds on `main` CI upload installers as Actions artifacts (`desktop-windows-x86_64`, `desktop-macos-aarch64`, `desktop-linux-x86_64`). Details: [`apps/desktop/README.md`](./apps/desktop/README.md).

### Cursor MCP (bulk load)

`@prm/mcp` unlocks the vault and writes dossier evidence — pair with Jira/Google MCPs for cold-start backfill.
`@prm/mcp` unlocks the vault and writes dossier evidence. Pair it with Jira, Bitbucket, or other MCPs when you need a cold-start backfill instead of typing every note by hand.

```bash
pnpm mcp
Expand All @@ -51,91 +81,125 @@ Regenerate with `pnpm screenshots` (UI + API running; default password `workbenc

### Unlock

Encrypted vault entry. Seed password `workbench`.
**Intent:** Keep the workspace sealed when you walk away. The unlock gate is the trust boundary for local data.

**How it works:** On first run you create a workspace password and can seed a demo org. Later sessions decrypt the vault with that password. **Lock & encrypt** re-seals SQLite and files so a stolen laptop disk is not a readable review archive.

![Unlock workbench](./docs/screenshots/01-unlock.png)

### Home — command center
### Home (command center)

**Intent:** Answer "who is blocked, and what should I do next?" without hunting through every page.

Who to unblock next: roles, self bundles, then writing.
**How it works:** Pick a cycle to see pipeline status (self / peer / manager), deadline nudges for missing self reviews, and a compact cycle dashboard (writing time, evidence coverage, thin dossiers). Links out to writing desks, backfill, and team performance when the numbers look thin.

![Command center](./docs/screenshots/02-home.png)

### Team

Directs roster. Open a dossier to capture year-round evidence.
**Intent:** Maintain the roster of directs you actually manage reviews for.

**How it works:** List, add, or CSV-import people, then open a person to work their dossier. This is the directory, not the evaluation surface. Role assignment and cycle participation hang off these records.

![Team](./docs/screenshots/03-team.png)

### Person dossier

Achievements, goals, feedback, and documents — the citation pool for writing.
**Intent:** Build a citation pool all year so the writing desk is not empty in week one of reviews.

**How it works:** Capture achievements, goals, feedback (with optional 1:1 theme tags), documents, concern / underperformance notes, and external links (for example Jira or Bitbucket sync). Visibility controls keep EM-only material out of subject exports. AI digests and drafts pull from this pool (and from concerns only when you opt them in).

![Person dossier](./docs/screenshots/04-person.png)

### Roles

Assign current (+ next) RoleDefinitions — the evaluation bar.
**Intent:** Make the evaluation bar explicit. Ratings should map to role expectations, not a private mental model.

**How it works:** Author or import RoleDefinitions (ladder levels, competency keys, expectations). Assign each person a current role and optionally a next role for promo / gap work. Uploaded R&R docs can be structured manually or with AI extract. Writing desks and promotion packets read from these definitions.

![Roles](./docs/screenshots/05-roles.png)

### Cycles

Export/import self & peer JSON bundles outside the app.
**Intent:** Time-box a review season and collect self / peer input without live IC login.

**How it works:** Create a cycle with a window and soft phases. Export JSON bundles for self and peer forms, collect them outside the app, then import responses. Waive with a reason when someone is not returning a form. Participants snapshot who was on the team for that cycle.

![Cycles](./docs/screenshots/06-cycles.png)

### Writing desk

Competency scores + evidence links → bias lint → finalize (citation snapshots) → share HTML/PDF.
**Intent:** Produce an evidence-backed manager review in a bounded amount of time (target ≤45 minutes per person).

**How it works:** Score competencies and overall against the assigned role, link dossier evidence to non-middle scores, draft narrative sections (optionally with AI from an allowlisted citation set), run bias / faithfulness lint, log decision reasons when ratings move, then finalize. Finalize freezes citation snapshots. Share exports HTML (print to PDF) with private notes and EM-only sources stripped.

![Writing desk](./docs/screenshots/07-writing-desk.png)

### Promotions

Next-role packets for skip-level / promo cases.
**Intent:** Reuse the same role bar for next-level and promotion cases instead of a parallel doc pile.

**How it works:** Open a promotion packet against a target RoleDefinition. Mark gaps / demonstrating signals, draft the case (optionally with AI), and keep it alongside the cycle review history so skip-level conversations stay evidence-backed.

![Promotions](./docs/screenshots/08-promotions.png)

![Promotion packet](./docs/screenshots/09-promotion-detail.png)

### Consistency

Rating distribution and side-by-side drafts before you finalize.
**Intent:** Catch unfair shape in your own drafts before anyone else sees them (everyone a 4, extreme scores with no cites, missing roles).

**How it works:** For a selected cycle, show overall rating distribution and a card per person with competencies, evidence link counts, and flags (thin evidence, extreme overall, unassigned role). Jump into each writing desk to fix gaps. This is single-EM calibration, not a multi-manager room.

![Consistency](./docs/screenshots/10-consistency.png)

### Team performance

**Intent:** See the team's rating and coaching shape as graphs, not only as a queue of drafts.

**How it works:** Cycle-scoped charts for overall distribution, evidence vs overall, competency averages, promo readiness, writing-time buckets, level mix, and top 1:1 themes. The people table adds competency sparklines and prior-cycle overall deltas when history exists. Use it with Consistency: graphs for pattern, cards for per-person fixes.

![Team performance](./docs/screenshots/11-team-performance.png)

### Backfill

Fill thin dossiers (or pair with MCP bulk load) so cold-start writing isn’t empty.
**Intent:** Unblock writing when dossiers are still empty after you first adopt the tool.

![Backfill](./docs/screenshots/11-backfill.png)
**How it works:** Lists people below the thin-dossier threshold (few achievements / feedback / docs / goals). Walk a short wizard to add starter evidence, or use MCP / integrations for bulk load. The goal is a usable citation pool, not perfect history on day one.

![Backfill](./docs/screenshots/12-backfill.png)

### AI Chat

Grounded Q&A over workspace/dossier data — cites ids, doesn’t invent evidence.
**Intent:** Ask grounded questions over the workspace or a single dossier without inventing evidence.

**How it works:** Chat is scoped to workspace or person context, cites allowlisted ids from real dossier rows, and respects private-tier egress settings. It is for synthesis and retrieval, not a substitute for the writing desk finalize path.

![AI Chat](./docs/screenshots/12-chat.png)
![AI Chat](./docs/screenshots/13-chat.png)

### Settings

AI provider, egress gates (including document extracts), theme.
**Intent:** Configure providers, privacy gates, appearance, and vault maintenance in one place.

**How it works:** Set AI provider and models (Anthropic, OpenAI, Cursor gateway, Ollama), toggle egress for private notes / meetings / document extracts, manage Jira and Bitbucket credentials, run encrypted backup / restore, verify the activity log, and switch dark / light theme.

![Settings](./docs/screenshots/13-settings.png)
![Settings](./docs/screenshots/14-settings.png)

---

## What’s in this slice

- **Encrypted vault** (AES-256-GCM) for DB + dossier files on lock
- **People / roles / cycles** with self & peer JSON bundles
- **Writing desk** — competencies, evidence links, citation freeze, HTML/PDF subject packets
- **Consistency** view + **bias/tone lint** before share
- **Promotions** against next-role expectations
- **Backfill** wizard + MCP bulk load for thin dossiers
- **AI** — digests, section draft, peer synthesize, chat (Anthropic / OpenAI / Ollama) with citation allowlist
- **Dark / light** theme preference
- **Encrypted vault** (AES-256-GCM) for DB and dossier files on lock
- **People / roles / cycles** with self and peer JSON bundles
- **Writing desk**: competencies, evidence links, citation freeze, HTML subject packets
- **Consistency** and **team performance** views before you share
- **Bias / faithfulness lint** (advisory) on drafts
- **Promotions** against next-role expectations
- **Backfill** wizard plus MCP / Jira / Bitbucket paths for thin dossiers
- **AI**: digests, section draft, peer synthesize, chat, with citation allowlist
- **Desktop** packaging (Tauri) with CI installers on `main`
- **Dark / light** theme preference

## Docs

Product & architecture under [`/docs`](./docs). Core loop evaluation: [`docs/CORE_LOOP_EVAL.md`](./docs/CORE_LOOP_EVAL.md). Cursor cloud notes: [`AGENTS.md`](./AGENTS.md).
Product and architecture live under [`/docs`](./docs). Start with [`docs/PRODUCT_BRIEF.md`](./docs/PRODUCT_BRIEF.md). Core loop evaluation: [`docs/CORE_LOOP_EVAL.md`](./docs/CORE_LOOP_EVAL.md). Cursor cloud notes: [`AGENTS.md`](./AGENTS.md).
Binary file modified docs/screenshots/02-home.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/03-team.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/04-person.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/05-roles.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/06-cycles.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/07-writing-desk.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/08-promotions.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/09-promotion-detail.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshots/10-consistency.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed docs/screenshots/11-backfill.png
Binary file not shown.
Binary file added docs/screenshots/11-team-performance.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshots/12-backfill.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed docs/screenshots/13-settings.png
Binary file not shown.
Binary file added docs/screenshots/14-settings.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions docs/screenshots/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ pnpm screenshots
```

Requires system Chrome (`google-chrome-stable`) and root `puppeteer-core`.

Numbering matches the README Screens section (`01` unlock through `14` settings), including `11-team-performance`.
19 changes: 12 additions & 7 deletions scripts/capture-screenshots.mjs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
#!/usr/bin/env node
/**
* One-off gallery capture for README. Usage:
* node scripts/capture-screenshots.mjs
* Expects UI on localhost:5175 (or UI_URL) and API unlocked via password.
* Gallery capture for README. Usage:
* pnpm screenshots
* UI_URL=http://localhost:5173 PRM_PASSWORD=workbench pnpm screenshots
* Expects UI + API running (defaults: UI :5173, API :8787).
*/
import fs from "node:fs";
import path from "node:path";
import puppeteer from "puppeteer-core";

const UI = process.env.UI_URL || "http://localhost:5175";
const UI = process.env.UI_URL || "http://localhost:5173";
const API = process.env.API_URL || "http://127.0.0.1:8787";
const PASSWORD = process.env.PRM_PASSWORD || "workbench";
const OUT = path.resolve("docs/screenshots");
Expand Down Expand Up @@ -87,9 +88,10 @@ async function main() {
["07-writing-desk", `/cycles/${cycle.id}/write/${person.id}`],
["08-promotions", "/promotions"],
["10-consistency", "/consistency"],
["11-backfill", "/backfill"],
["12-chat", "/chat"],
["13-settings", "/settings"],
["11-team-performance", "/team-performance"],
["12-backfill", "/backfill"],
["13-chat", "/chat"],
["14-settings", "/settings"],
];
if (promo?.id) routes.splice(6, 0, ["09-promotion-detail", `/promotions/${promo.id}`]);

Expand All @@ -102,6 +104,9 @@ async function main() {
document.querySelector(".chat-composer")?.scrollIntoView({ block: "end" });
});
}
if (route === "/team-performance") {
await page.waitForSelector(".tp-metrics, .tp-grid, h2", { timeout: 15000 });
}
await new Promise((r) => setTimeout(r, 450));
await shot(page, name);
}
Expand Down
Loading