diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 92cec62..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,40 +0,0 @@ -version: 2 -updates: - - package-ecosystem: npm - directory: / - schedule: - interval: weekly - day: monday - time: '06:00' - timezone: Etc/UTC - open-pull-requests-limit: 5 - # Pin @types/node to the runtime's major (Node 22 LTS). Bumping the types - # past Node 22 would surface APIs the runtime can't actually call. - ignore: - - dependency-name: '@types/node' - update-types: ['version-update:semver-major'] - groups: - dev-deps: - dependency-type: development - update-types: [minor, patch] - runtime-deps: - dependency-type: production - update-types: [minor, patch] - commit-message: - prefix: chore - include: scope - - - package-ecosystem: github-actions - directory: / - schedule: - interval: weekly - day: monday - time: '06:00' - timezone: Etc/UTC - open-pull-requests-limit: 5 - groups: - gh-actions: - patterns: ['*'] - commit-message: - prefix: ci - include: scope diff --git a/.gitignore b/.gitignore index 68f719d..4286dd0 100644 --- a/.gitignore +++ b/.gitignore @@ -41,10 +41,11 @@ data/cron-*.log .env.* !.env.example -# OpenSpec — keep the `openspec/` proposals + specs in version control, -# but ignore the per-IDE integration files `openspec init` materializes -# under .claude/.codebuddy/.continue/.junie/.kiro. Those are local tooling -# for whichever editor each developer uses. +# Local planning scratch — design proposals live in Linear, not the repo. +openspec/ + +# Per-IDE agent integration files — local tooling for whichever editor each +# developer uses. .claude/* # Exception: project-specific Claude Code skills (`pupila-*`) ship with the # repo so contributors get the agent guidance automatically. Provider-generic diff --git a/AGENTS.md b/AGENTS.md index ec5d0d3..93a4687 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -113,8 +113,6 @@ tsconfig.test.json # extends above with rootDir=. so tests/ typecheck without l .github/ workflows/ check.yml # PR/push: biome + typecheck + tests + build + audit (only remaining workflow) - check.yml # PR/push: lint + typecheck + test + audit - dependabot.yml # weekly npm + github-actions PRs ``` > **CodeQL workflow removed.** Code Scanning isn't available on private repos without GitHub Advanced Security. If the repo ever goes public, restore `.github/workflows/codeql.yml` from commit `7397117`. @@ -299,9 +297,7 @@ One workflow only — the project moved to local-first scheduling. The previously included `jobs.yml` (daily aggregator cron) and `keepalive.yml` (cron-keepalive) workflows were removed. Daily aggregation now runs locally via `scripts/install-launchd.sh` (macOS) or `scripts/install-cron.sh` (Linux), which install two agents: one for `pnpm run dev`, one for `pnpm run ai-review`. See README "Schedule the daily run" for usage. -`.github/dependabot.yml` opens weekly grouped PRs for npm + github-actions. - -**Pinning.** All third-party actions are referenced by full 40-char commit SHA, not a floating `@v4` / `@v5` tag, with the version in a trailing comment. When updating an action, replace both the SHA and the comment. Dependabot keeps these current via PRs. +**Pinning.** All third-party actions are referenced by full 40-char commit SHA, not a floating `@v4` / `@v5` tag, with the version in a trailing comment. When updating an action, replace both the SHA and the comment manually. ## Tests diff --git a/CLAUDE.md b/CLAUDE.md index 5ab2627..cbaea08 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -72,7 +72,6 @@ pnpm run mcp # MCP server over stdio | `ui/` | Local-only React dashboard (see `ui/CLAUDE.md`) | | `scripts/` | Apply worker, installers (launchd/cron/mcp), clean | | `tests/` | 330 vitest cases, fixtures in `tests/fixtures/` | -| `openspec/changes/` | OpenSpec proposals (committed) | | `.claude/skills/` | Project skills (`pupila-*`) ship; provider-generic skills are local-only | ## Orchestrator flow @@ -165,7 +164,7 @@ Vitest, 330 cases across `tests/` (`*.test.ts` glob). Run via `pnpm test` or `pn One workflow only — `.github/workflows/check.yml` — every push to `main` and PR. Seven gates: Biome lint, typecheck (3 tsconfigs), Vitest, `tsc` build, Vite UI build, bundle-size budget, `pnpm audit`. Daily aggregation runs **locally** via launchd/cron (see `scripts/install-*.sh`). -Third-party actions pinned by 40-char SHA with version comment. Dependabot opens weekly grouped PRs. +Third-party actions pinned by 40-char SHA with version comment. Bump manually when needed. > **CodeQL workflow removed** — Code Scanning isn't available on private repos without GitHub Advanced Security. Restore from commit `7397117` if the repo goes public. diff --git a/README.md b/README.md index 682c0fd..21e8d95 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@

pupila logopupila

-# PUPILA: A-eye on every job board +# PUPILA: An A-Eye on every job board

Local job matching dashboard with source feeds, scoring, and scheduled file output @@ -236,7 +236,7 @@ Idempotent — running on an already-clean state prints `nothing to clean`. Afte | HTML scraping | Inline regex parsers (no cheerio/jsdom) | | Schedule | Local launchd (macOS) / cron (Linux), two agents: aggregator + AI review | | Output | Files in your local checkout (`data/jobs.json`, `data/feed.xml` RSS, `JOBS.md`, `data/archive/.json` on month-start) | -| Static analysis | Biome + tsc + build on every PR via `check.yml`; Dependabot for npm + GitHub Actions | +| Static analysis | Biome + tsc + build on every PR via `check.yml` | ## Architecture @@ -735,9 +735,8 @@ Keyword arrays are joined with `|` and compiled into word-bounded, case-insensit ``` pupila/ ├── .github/ -│ ├── workflows/ -│ │ └── check.yml # PR/push: biome + typecheck + tests + build + audit -│ └── dependabot.yml # weekly npm + github-actions updates +│ └── workflows/ +│ └── check.yml # PR/push: biome + typecheck + tests + build + audit ├── assets/ │ └── readme/ │ └── dashboard.jpg # README banner artwork @@ -853,7 +852,7 @@ The previously included `jobs.yml` (daily aggregator cron) and `keepalive.yml` ( > **Note on CodeQL.** A CodeQL workflow was previously included but removed because **Code Scanning isn't available on private repos for personal accounts** without GitHub Advanced Security. -The remaining workflow pins third-party actions to **commit SHAs** (not floating `@v4` / `@v5` tags) for supply-chain safety. [`Dependabot`](./.github/dependabot.yml) opens weekly PRs to bump those SHAs and the npm deps; the `check.yml` workflow validates each PR before merge. +The remaining workflow pins third-party actions to **commit SHAs** (not floating `@v4` / `@v5` tags) for supply-chain safety. Bump SHAs and npm deps manually (`pnpm update`, then refresh action SHAs from the upstream tag) when you want to update — `check.yml` gates the change like any other PR. ## Customization @@ -903,7 +902,6 @@ Defense-in-depth measures, ranked from runtime to build-time: - **Tests** (`pnpm test`) — Vitest, extensive backend + UI suite covering security-sensitive code (URL safety, regex filters, dedup tiebreaks, applied-status grouping, salary parsing, RSS escaping, custom-ATS HTML/GraphQL parsers, AI Apply core, apply-queue mutators, swipe-skip storage, profile bootstrap) plus every UI hook + key components. Runs on every PR. - **`pnpm audit --prod --audit-level high`** in [`check.yml`](./.github/workflows/check.yml). Reports known CVEs in production deps. - **Pinned actions.** The remaining workflow references third-party actions by commit SHA, not floating tags. Defends against tag-hijacking. -- **Dependabot** ([`dependabot.yml`](./.github/dependabot.yml)) — weekly PRs for npm + GitHub Actions. Each PR is gated by `check.yml`. - **Minimum permissions.** `check.yml` uses `contents: read` only — no workflow has write access to the repo. ## Known upstream issues (as of 2026-04) diff --git a/SECURITY.md b/SECURITY.md index fdfb9ee..d5430b7 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -45,7 +45,7 @@ Examples of in-scope issues: - Issues that require the attacker to already have local code execution on your machine — at that point they own everything anyway. - Exposing the UI publicly. The dev server binds to `127.0.0.1:5173` by design; running it on a public interface is a configuration mistake, not a vulnerability. - Committing `config/applied.json` or `config/candidate-brief.md` to a public fork. Both are gitignored; the user has to explicitly opt in to track them. -- Dependency CVEs already flagged by `pnpm audit` in CI — those are tracked in the open and Dependabot PRs. +- Dependency CVEs already flagged by `pnpm audit` in CI — those are tracked in the open. ## Hall of fame diff --git a/openspec/changes/mcp-server-ci-dependabot/.openspec.yaml b/openspec/changes/mcp-server-ci-dependabot/.openspec.yaml deleted file mode 100644 index 9f70866..0000000 --- a/openspec/changes/mcp-server-ci-dependabot/.openspec.yaml +++ /dev/null @@ -1,2 +0,0 @@ -schema: spec-driven -created: 2026-05-15 diff --git a/openspec/changes/mcp-server-ci-dependabot/README.md b/openspec/changes/mcp-server-ci-dependabot/README.md deleted file mode 100644 index cfd1899..0000000 --- a/openspec/changes/mcp-server-ci-dependabot/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# mcp-server-ci-dependabot - -DEV-84 — CI smoke test + dependabot grouping for MCP deps diff --git a/openspec/changes/mcp-server-ci-dependabot/proposal.md b/openspec/changes/mcp-server-ci-dependabot/proposal.md deleted file mode 100644 index 46f333c..0000000 --- a/openspec/changes/mcp-server-ci-dependabot/proposal.md +++ /dev/null @@ -1,31 +0,0 @@ -## Why - -The MCP feature added two runtime deps (`@modelcontextprotocol/sdk@1.29.0`, `zod@4.4.3`) on a fast version cadence. The existing `.github/workflows/check.yml` runs `pnpm typecheck` / `pnpm test` / `pnpm run lint` — but those gates exercise tool runners directly via Vitest, NOT the protocol round-trip. A regression in the SDK or in our stdio bootstrap (e.g., a stray `console.log` from a freshly-imported lib corrupting the JSON-RPC frame) would slip past CI today. - -Separately, Dependabot opens weekly grouped npm PRs but has no group for the MCP deps. Without grouping, an SDK bump and a zod bump arrive as two PRs that BOTH need review of the same surface — wasteful. - -## What Changes - -- Add an `mcp:smoke` script to `package.json` that pipes a single `tools/list` JSON-RPC request to `pnpm run mcp` via stdin and asserts the response contains all 17 expected tool names. Pure black-box check — no Vitest, no SDK Client; verifies the framing + registration end-to-end. -- Add an `MCP smoke` step to `.github/workflows/check.yml` after `pnpm run ui:build` and before `pnpm run lint:bundle-size`. Runs `pnpm run mcp:smoke` with a hard 30s timeout. -- Add an `mcp-deps` group to `.github/dependabot.yml` containing `@modelcontextprotocol/sdk` and `zod`. Weekly cadence (matches the existing config). Patch + minor bumps land in one PR. - -The smoke test MUST work on a fresh CI checkout — no `data/jobs.json`, no `config/profile.json`, no `config/candidate-brief.md`. Tools that depend on those files already return clean empty/error responses on missing files (verified by existing unit tests), so the smoke check only validates that the SERVER itself boots and registers tools. - -## Capabilities - -### New Capabilities - -- `mcp-ci-smoke`: A CI gate that boots `pnpm run mcp`, sends a single `tools/list` JSON-RPC request, and fails the job if the response is missing any of the 17 registered tools — independent of Vitest's in-process integration suite. - -### Modified Capabilities - -_None — `dependabot.yml` is configuration, not a capability spec._ - -## Impact - -- **Files touched:** `package.json` (one new script), `.github/workflows/check.yml` (one new step), `.github/dependabot.yml` (one new group entry). -- **CI runtime:** +5–15s for the smoke step (server boot + one request + teardown). -- **Affected APIs:** none — read-only check. -- **Risk:** the smoke script must NOT depend on user data files; broken if it does. Failure modes covered by the smoke check today are already covered by the in-process integration suite (`tests/mcp/integration.test.ts`) — the smoke adds value as a defense against environment-level regressions (Node version mismatch, missing executable bit on tsx, SDK API drift) that an in-process test can't catch. -- **Dependabot:** existing PRs already in flight will not be re-grouped; the change takes effect for the next refresh cycle. diff --git a/openspec/changes/mcp-server-ci-dependabot/specs/mcp-ci-smoke/spec.md b/openspec/changes/mcp-server-ci-dependabot/specs/mcp-ci-smoke/spec.md deleted file mode 100644 index 1f472ae..0000000 --- a/openspec/changes/mcp-server-ci-dependabot/specs/mcp-ci-smoke/spec.md +++ /dev/null @@ -1,62 +0,0 @@ -## ADDED Requirements - -### Requirement: CI runs an MCP smoke check on every push and PR - -The `.github/workflows/check.yml` workflow SHALL execute a `MCP smoke` step on every push to `main` and every pull request. The step invokes `pnpm run mcp:smoke`, which boots `pnpm run mcp`, sends a single `tools/list` JSON-RPC request, and validates the response. - -#### Scenario: A push to main runs the smoke check - -- **GIVEN** a commit is pushed to `main` -- **WHEN** the check workflow runs -- **THEN** the `MCP smoke` step executes after the UI build -- **AND** the workflow fails IF the step exits non-zero - -#### Scenario: The smoke check has a hard timeout - -- **GIVEN** the MCP server hangs during startup -- **WHEN** `pnpm run mcp:smoke` runs -- **THEN** it MUST exit non-zero within 30 wall-clock seconds, even if the server never responds - -### Requirement: Smoke check works without any user data - -The smoke script SHALL succeed on a fresh checkout with no `data/jobs.json`, no `config/profile.json`, no `config/candidate-brief.md`. The check validates server boot + tool registration ONLY — not tool execution against real data. - -#### Scenario: A fresh CI runner with no data files - -- **GIVEN** an `ubuntu-latest` runner with the repo just cloned (no gitignored files materialized) -- **WHEN** `pnpm run mcp:smoke` runs -- **THEN** it exits 0 -- **AND** the response is asserted against the canonical 17-tool list - -### Requirement: Every registered tool appears in the smoke check expected list - -The smoke script SHALL maintain an explicit list of expected tool names. Whenever a new tool is registered in `src/mcp/server.ts`, the smoke script's expected list MUST be updated in the same PR. - -#### Scenario: A new tool is added without updating the smoke - -- **GIVEN** a PR adds `register()` to `src/mcp/server.ts` but does NOT update `scripts/mcp-smoke.mjs` -- **WHEN** the smoke runs in CI -- **THEN** the check passes (the new tool is extra, not missing) — but CI does NOT enforce parity in this direction; reviewer responsibility - -#### Scenario: A tool is removed without updating the smoke - -- **GIVEN** a PR removes `registerListJobs()` from `src/mcp/server.ts` but leaves it in `scripts/mcp-smoke.mjs` expected list -- **WHEN** the smoke runs in CI -- **THEN** the check FAILS with `[mcp-smoke] missing tool: list_jobs` - -### Requirement: Dependabot groups MCP runtime deps - -The `.github/dependabot.yml` SHALL define an `mcp-deps` group containing `@modelcontextprotocol/sdk` and `zod`. Minor and patch updates for these packages SHALL land as a single grouped PR. - -#### Scenario: SDK and zod both have patch releases the same week - -- **GIVEN** `@modelcontextprotocol/sdk@1.29.1` and `zod@4.4.4` both publish before the weekly refresh -- **WHEN** Dependabot opens its next batch of PRs -- **THEN** exactly one PR exists with the title `Bump the mcp-deps group ...` -- **AND** it contains both updates - -#### Scenario: A major-version bump arrives - -- **GIVEN** `@modelcontextprotocol/sdk@2.0.0` publishes -- **WHEN** Dependabot processes it -- **THEN** the major bump opens as its OWN ungrouped PR — major versions are explicitly excluded from the group's `update-types` filter diff --git a/openspec/changes/mcp-server-ci-dependabot/tasks.md b/openspec/changes/mcp-server-ci-dependabot/tasks.md deleted file mode 100644 index 055bf1d..0000000 --- a/openspec/changes/mcp-server-ci-dependabot/tasks.md +++ /dev/null @@ -1,44 +0,0 @@ -## Tasks - -### 1. `mcp:smoke` script - -- [ ] Add `"mcp:smoke": "node scripts/mcp-smoke.mjs"` to `package.json` scripts. -- [ ] Create `scripts/mcp-smoke.mjs`. The script spawns `pnpm run mcp`, writes a single line `{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}\n` to its stdin, reads stdout until it sees a JSON-RPC response on a line starting with `{`, parses it, and asserts: - - [ ] `data.result.tools` is an array of length ≥ 17. - - [ ] Every name in the expected list appears in the response: `list_jobs`, `get_job_detail`, `get_brief`, `mark_applied`, `update_status`, `clear_applied`, `enqueue_apply`, `cancel_apply`, `skip_job`, `queue_status`, `worker_status`, `run_summary`, `get_ai_review`, `list_ai_reviews`, `trigger_fetch`, `get_fetch_status`, `regenerate_profile`. -- [ ] Wall-clock timeout: 30 seconds. On timeout, kill the child + exit 1 with a clear `[mcp-smoke] timed out` message. -- [ ] Exit 0 only when assertions pass; exit non-zero on parse failure, missing tool, or timeout. -- [ ] Verify locally on a working tree WITHOUT `data/jobs.json` / `config/profile.json` / `config/candidate-brief.md` — the smoke MUST pass on a fresh checkout. - -### 2. CI step in `check.yml` - -- [ ] After the `Vite — build UI bundle (ui/dist/)` step and before `lint:bundle-size`, add a new step: - ```yaml - - name: MCP — smoke test (tools/list over stdio) - run: pnpm run mcp:smoke - timeout-minutes: 1 - ``` -- [ ] No matrix changes — runs once on the existing `ubuntu-latest` runner. - -### 3. Dependabot grouping - -- [ ] Edit `.github/dependabot.yml`. Within the existing `npm` ecosystem block, add a `groups:` entry: - ```yaml - groups: - mcp-deps: - patterns: - - "@modelcontextprotocol/sdk" - - "zod" - update-types: - - "minor" - - "patch" - ``` -- [ ] Leave existing groups (if any) intact. -- [ ] Major version bumps stay ungrouped — those need individual review. - -### 4. Validation - -- [ ] `pnpm run mcp:smoke` exits 0 locally with the current 17 tools. -- [ ] Locally simulate a regression: temporarily remove one tool from `src/mcp/server.ts`, re-run smoke, confirm exit 1 with a clear "missing tool: " message; revert. -- [ ] Open the PR; CI's new `MCP smoke` step appears green. -- [ ] Confirm in Dependabot's next refresh that SDK + zod bumps would land as one grouped PR. (Cannot validate this without waiting for the cycle — document the expected behavior in the PR description.) diff --git a/openspec/changes/mcp-server-readme-docs/.openspec.yaml b/openspec/changes/mcp-server-readme-docs/.openspec.yaml deleted file mode 100644 index 9f70866..0000000 --- a/openspec/changes/mcp-server-readme-docs/.openspec.yaml +++ /dev/null @@ -1,2 +0,0 @@ -schema: spec-driven -created: 2026-05-15 diff --git a/openspec/changes/mcp-server-readme-docs/README.md b/openspec/changes/mcp-server-readme-docs/README.md deleted file mode 100644 index 5077119..0000000 --- a/openspec/changes/mcp-server-readme-docs/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# mcp-server-readme-docs - -DEV-83 — README + tool reference + troubleshooting docs for the MCP server diff --git a/openspec/changes/mcp-server-readme-docs/proposal.md b/openspec/changes/mcp-server-readme-docs/proposal.md deleted file mode 100644 index 3a1fe09..0000000 --- a/openspec/changes/mcp-server-readme-docs/proposal.md +++ /dev/null @@ -1,37 +0,0 @@ -## Why - -The MCP server now exposes 17 tools spanning every actionable UI surface (read / write / queue / aux / long-running). Today the only documentation is the inline tool `description` field — discoverable from inside an MCP client, but invisible to anyone deciding whether to install the server or troubleshooting a broken setup. - -CLAUDE.md is explicit that `README.md` is **hand-maintained** — the pipeline never overwrites it. All MCP-facing docs must be authored as a manual edit, modeled on the existing README walkthrough sections. - -This change sequences last in the v1 epic because the tool surface had to stabilize first; the 17 tools are now locked. - -## What Changes - -- New `## MCP server` section in `README.md` covering: - - **One-line install** (`bash scripts/install-mcp.sh`) — the canonical entry point from a cloned repo. - - **Verifying the install** — `claude mcp list` expected output + restart-required note for Claude Desktop and Cursor. - - **Tool reference** — table of all 17 tools grouped by category (Read / Applied / Queue / Aux / Long-running), one-sentence description per tool, "called by" mapping to the UI control it mirrors. - - **Troubleshooting** — six failure modes drawn from the build's risk notes: stale PID file, worker-not-running enqueue, no LLM CLI detected, `jobs.json` missing, JSON-RPC framing corruption from a stray `console.log`, install-script prereq failure. - - **Architecture note** — one-paragraph "MCP server is the fourth direct consumer of `src/lib/*`" pointer to `src/mcp/`. -- Update `CLAUDE.md` with a new `## MCP server` subsection paralleling the existing Settings / Jobs / Jinder docs, listing the tool surface and pointing at `src/mcp/` for future Claude Code sessions. - -This is **doc only** — no code changes, no test changes. README and CLAUDE.md are the only files touched. - -## Capabilities - -### New Capabilities - -- `mcp-server-docs`: User-facing documentation for the MCP server feature — install flow, tool reference, troubleshooting. Lives in the hand-maintained `README.md` and the Claude-Code-facing `CLAUDE.md`. - -### Modified Capabilities - -_None — this is net-new documentation; no existing spec changes._ - -## Impact - -- **Files touched:** `README.md`, `CLAUDE.md`. No code, no tests, no CI. -- **Affected APIs:** none. -- **Affected dependencies:** none. -- **Risk:** none on the runtime path. Sole risk is doc drift — every future MCP tool change must update the tool-reference table. Add a CLAUDE.md note to that effect to make the obligation explicit. -- **Audience:** any user installing the MCP server via `scripts/install-mcp.sh` + any future Claude Code session navigating `src/mcp/`. diff --git a/openspec/changes/mcp-server-readme-docs/specs/mcp-server-docs/spec.md b/openspec/changes/mcp-server-readme-docs/specs/mcp-server-docs/spec.md deleted file mode 100644 index 15329d3..0000000 --- a/openspec/changes/mcp-server-readme-docs/specs/mcp-server-docs/spec.md +++ /dev/null @@ -1,55 +0,0 @@ -## ADDED Requirements - -### Requirement: README documents the MCP server install path - -The `README.md` SHALL include a top-level `## MCP server` section that, at minimum, presents the one-line install command (`bash scripts/install-mcp.sh`) AND the verification command (`claude mcp list`). - -#### Scenario: A new user installs the MCP server from the README - -- **GIVEN** a user has cloned the repo -- **WHEN** they read `README.md` linearly -- **THEN** they find the `## MCP server` heading within the document -- **AND** the section contains the literal string `bash scripts/install-mcp.sh` -- **AND** the section contains the literal string `claude mcp list` - -### Requirement: README documents every registered MCP tool - -The `README.md` `## MCP server` section SHALL include a tool reference covering every tool registered by `createMcpServer()` in `src/mcp/server.ts`. Each tool documented MUST appear in the live `tools/list` JSON-RPC response. - -#### Scenario: A reader counts tools in the README - -- **GIVEN** the README's tool-reference table -- **WHEN** counted against the names returned by `tools/list` from `pnpm run mcp` -- **THEN** the two sets match exactly (no missing, no extra) - -#### Scenario: A new tool is added to the server - -- **GIVEN** a new file under `src/mcp/tools/` calling `server.registerTool(...)` -- **WHEN** the PR adding it is opened -- **THEN** the PR MUST also update the README tool-reference table — the obligation is documented in `CLAUDE.md` - -### Requirement: README documents at least six troubleshooting scenarios - -The `## MCP server` section SHALL include a "Troubleshooting" subsection covering at minimum: -1. MCP client does not list `pupila` after install -2. `enqueue_apply` returns a worker-not-running warning -3. `regenerate_profile` returns a precondition error -4. `list_jobs` returns zero rows on a fresh clone -5. Garbled output / JSON-RPC framing corruption -6. `scripts/install-mcp.sh` exits on missing prereq - -#### Scenario: A user hits a failure not in the troubleshooting list - -- **GIVEN** a documented failure mode missing from the section -- **WHEN** a user files an issue -- **THEN** the maintainer adds the failure mode to the README before closing — the section is a living rolodex, not a one-shot doc - -### Requirement: CLAUDE.md documents the MCP server architecture - -The hand-maintained `CLAUDE.md` SHALL include a `## MCP server` section paralleling the existing Settings / Jobs / Jinder documentation depth, covering the repo layout under `src/mcp/`, the stdio-transport invariant (no `console.log`), and the per-tool single-flight invariants. - -#### Scenario: A future Claude Code session reads CLAUDE.md - -- **GIVEN** a future agent navigating `src/mcp/` for the first time -- **WHEN** they read `CLAUDE.md` end-to-end -- **THEN** they find the architecture rationale, the stdio invariant, and the single-flight locks WITHOUT reading any source code diff --git a/openspec/changes/mcp-server-readme-docs/tasks.md b/openspec/changes/mcp-server-readme-docs/tasks.md deleted file mode 100644 index 55161fb..0000000 --- a/openspec/changes/mcp-server-readme-docs/tasks.md +++ /dev/null @@ -1,29 +0,0 @@ -## Tasks - -### 1. README — MCP server section - -- [ ] Open `README.md`. Find the existing "Local UI" / "AI per-job review" sections — model the new `## MCP server` heading on their depth (≤ 80 lines total, code-blocks-and-tables heavy). -- [ ] Add the one-line install command at the top of the section: `bash scripts/install-mcp.sh` (note: must be run from a cloned repo, not via curl|bash — the script is repo-coupled). -- [ ] Add the **Verifying the install** subsection with expected `claude mcp list` output showing `pupila` registered, plus a "restart Claude Desktop / Cursor to load" note. -- [ ] Add the **Tool reference** table grouped by category. Five columns: tool name, category, input fields, returns, UI equivalent. All 17 tools — copy the live `description` fields from `src/mcp/tools/*.ts`. -- [ ] Add the **Troubleshooting** subsection covering exactly six failure modes: - - [ ] `claude mcp list` does not show `pupila` after install (Claude Desktop / Cursor not restarted) - - [ ] `enqueue_apply` returns warning "Apply worker is not running" (start `pnpm run apply-worker` in another terminal) - - [ ] `regenerate_profile` returns precondition error (run `pnpm run setup-brief --file ~/cv.pdf` first) - - [ ] `list_jobs` returns `total: 0` on a fresh clone (run `pnpm run dev` once to seed `data/jobs.json`) - - [ ] User sees garbled output in their MCP client (a tool somewhere logged to stdout — file a bug citing `src/mcp/lib/stdout-guard.ts`) - - [ ] `scripts/install-mcp.sh` exits 1 with "missing prerequisite" (install node 22 / pnpm / git per the prereq-check failure message; **never** auto-install) -- [ ] Add the **Architecture** paragraph at the bottom of the section: "The MCP server is the fourth direct consumer of `src/lib/*` (alongside Vite middleware, apply-worker, and `pnpm run ai-review`). Tools live in `src/mcp/tools/`, shared logic in `src/lib/`." Link to `src/mcp/`. - -### 2. CLAUDE.md — MCP server subsection - -- [ ] Add a new `## MCP server` section after `## AI per-job review`, modeled on the existing `## Jinder (swipe-to-apply)` depth. -- [ ] Cover: repo layout (`src/mcp/{index,server,paths,errors}.ts`, `src/mcp/{schemas,tools,lib}/`), entry point (`pnpm run mcp` / `tsx src/mcp/index.ts`), stdio-transport invariant (no `console.log` ever — `src/mcp/lib/stdout-guard.ts` is the guardrail), the single-flight invariants for `trigger_fetch` and `regenerate_profile`, and the fact that `enqueue_apply` does NOT replace `pnpm run apply-worker`. -- [ ] Add the explicit obligation: "Every future MCP tool change MUST update the README tool-reference table — there is no codegen for it." -- [ ] Update `## Repo layout` to include the new `src/mcp/` tree and `scripts/install-mcp.sh` / `scripts/_merge-mcp-config.mjs`. - -### 3. Validation - -- [ ] `pnpm typecheck`, `pnpm test`, `pnpm run lint` all unchanged (this is doc-only — no regressions possible). -- [ ] Hand-review the README rendering on GitHub web (no `pnpm run` for README preview — eyeball it on the PR diff). -- [ ] Cross-check the tool-reference table against `pnpm run mcp | tools/list` to make sure every registered tool is documented. diff --git a/src/mcp/CLAUDE.md b/src/mcp/CLAUDE.md index b2f845f..cf7f2bb 100644 --- a/src/mcp/CLAUDE.md +++ b/src/mcp/CLAUDE.md @@ -126,9 +126,8 @@ src/mcp/ ## When something changes -- **README tool-reference table is hand-maintained.** Every tool change MUST update the `## MCP server` section in `README.md`. There is no codegen. See `openspec/changes/mcp-server-readme-docs/`. +- **README tool-reference table is hand-maintained.** Every tool change MUST update the `## MCP server` section in `README.md`. There is no codegen. - **Verify locally**: `pnpm run mcp` in one terminal, hit it from a connected client (Claude Code/Desktop/Cursor). Or use the in-process integration test. -- **CI smoke check** (when DEV-84 lands): `pnpm run mcp:smoke` will spawn the server, send `tools/list`, assert all expected tools present. ## Related