This document describes how to develop RepoPulse using the SpecKit / Specification-Driven Development (SDD) workflow.
arun-gupta/repo-pulse repo is cloned locallyCLAUDE.md exists — it points Claude Code to .specify/memory/constitution.mdOne feature at a time, fully through implementation before starting the next. Do not batch specs across features.
/speckit.specify[P1-F01]Repo Input
Review the generated spec. It is a contract — approve it before proceeding.
/speckit.plan[P1-F01]
Review the plan. Verify it does not introduce any dependency that would block Phase 2 or Phase 3.
/speckit.tasks[P1-F01]
Review the task list before implementation begins.
/speckit.implement[P1-F01]
Implement the feature per the generated tasks.
Before opening a PR, verify the Definition of Done (constitution Section XII):
console.log, or untyped values remaindocs/DEVELOPMENT.md reflects the feature’s completed status in the implementation order table (✅ Done)## Test plan section; all checkboxes are checked before mergeOnce done, open a PR and merge before starting the next feature.
Three project-scoped sub-agents in .claude/agents/ encode the RepoPulse workflow rules that live in CLAUDE.md, .specify/memory/constitution.md, and this document. Two are bounded, read-heavy checks; the third (pr-test-plan-runner) executes the automatable portion of a PR’s Test plan. Running them is how the rule becomes mechanical instead of a thing you have to remember.
| Agent | When to invoke | How to invoke |
|---|---|---|
spec-reviewer |
After /speckit.specify, before the mandatory approval gate in CLAUDE.md. |
Mention spec-reviewer in a prompt, or @spec-reviewer (agent) review specs/<N>-<slug>/spec.md. |
dod-verifier |
Before git push / PR open. Runs the Definition of Done checklist from constitution §XII against the current branch. |
@dod-verifier (agent) walk the DoD for this branch. |
pr-test-plan-runner |
After PR open, before asking the user to merge. Executes automatable items in ## Test plan (unit tests, lint, typecheck, build, focused vitest/eslint/tsc runs), ticks the matching checkboxes via gh pr edit, and posts an audit comment with the per-item results. |
@pr-test-plan-runner (agent) run PR #<N>. |
Each agent returns a structured report. spec-reviewer returns PASS / FAIL with citations into the constitution and docs/PRODUCT.md; dod-verifier returns a per-item SATISFIED / BLOCKED / REQUIRES HUMAN SIGN-OFF punch list with command output as evidence; pr-test-plan-runner returns a per-item AUTO-PASS / AUTO-FAIL / MANUAL / ALREADY-CHECKED classification plus an overall READY / BLOCKED verdict, and additionally posts a PR comment with an agent-generated disclaimer (> Automated report from pr-test-plan-runner …) so reviewers have a durable audit trail of what ran, when, and with what result.
PR merge discipline: pr-test-plan-runner’s tool allowlist is narrow — 13 patterns total: Bash(gh pr view:*), Bash(gh pr edit:*), Bash(gh pr comment:*) for PR interaction, plus ten automatable command prefixes (Bash(npm test:*), Bash(npm run lint:*), Bash(npm run typecheck:*), Bash(npm run build:*), Bash(npm run test:unit:*), Bash(npm run test:integration:*), Bash(npm run demo:*), Bash(npx vitest:*), Bash(npx eslint:*), Bash(npx tsc:*)). gh pr merge, gh pr close, gh pr ready, and gh pr review are explicitly absent from the allowlist, and the agent prompt additionally forbids extracted Test plan commands matching those prefixes, rm, git reset, git push --force, sudo, curl, wget, ssh, npm run test:e2e, and npx playwright. The CLAUDE.md rule that PR merging is a manual user action is intact — none of the three agents is permitted to merge.
Sub-agents inherit the parent session’s .claude/settings.json allowlist as a ceiling; they cannot widen it. No entry was added to settings.json for these three agents — the existing allowlist already covers every tool they need.
This section is the single source of truth for RepoPulse MCP guidance. CLAUDE.md intentionally points here instead of duplicating a server list.
Recommended shared aliases:
| Alias | Why it helps in this repo |
|---|---|
github |
Read issues, PRs, reviews, branches, and Actions runs without copying GitHub data into chat. Most useful for the issue-driven SpecKit flow and future Phase 3 integrations. |
playwright |
Exercise the Next.js UI, org-summary flows, auth states, and regressions through a browser tool instead of manual narration or screenshots. |
vercel |
Inspect preview deployments, environment mismatches, and runtime failures for the Vercel-hosted app. |
context7 |
Pull current framework and API docs (Next.js, React, Playwright, GitHub APIs) when implementation details are newer than the repo docs. |
Rules:
CLAUDE.md should stay as a pointer only..mcp.json) only when the whole repo benefits from the same integration.github, playwright, vercel, and context7 so .claude/settings.json can auto-approve them consistently..mcp.json; use environment-variable expansion for tokens, API keys, and machine-specific paths.Repo status:
.mcp.json. This keeps the shared workflow lightweight while still documenting the preferred aliases and usage..claude/settings.json pre-approves the aliases above if a project-scoped .mcp.json is added later. It does not blanket-approve arbitrary MCP servers.This is the planned implementation order for Phase 1. It may differ from the feature listing order in docs/PRODUCT.md, which remains the canonical product definition.
| # | Feature ID | Feature | Status |
|---|---|---|---|
| 1 | P1-F01 | Repo Input | ✅ Done |
| 2 | P1-F02 | Authentication | ✅ Done |
| 3 | P1-F04 | Data Fetching | ✅ Done |
| 4 | P1-F15 | Results Shell | ✅ Done |
| 5 | P1-F05 | Ecosystem Map | ✅ Done |
| 6 | P1-F03 | Deployment | ✅ Done |
| 7 | P1-F07 | Metric Cards | ✅ Done |
| 8 | P1-F09 | Contributors | ✅ Done |
| 9 | P1-F08 | Activity | ✅ Done |
| 10 | P1-F10 | Responsiveness | ✅ Done |
| 11 | P1-F11 | Health Ratios | ❌ Deprecated (tab removed, ratios live in domain tabs + Comparison) |
| 12 | P1-F16 | Org-Level Repo Inventory | ✅ Done |
| 13 | P1-F06 | Repo Comparison | ✅ Done |
| 14 | P1-F14 | GitHub OAuth Authentication | ✅ Done |
| 15 | P1-F13 | Export | ✅ Done |
| 16 | P1-F12 | Missing Data & Accuracy | ✅ Done |
Run these checks before opening a PR:
npm test
npm run test:e2e
npm run lint
npm run build
DEV_GITHUB_PAT)The GitHub OAuth App registers a single callback URL (http://localhost:3000/api/auth/callback). Running multiple worktrees concurrently means only the worktree on port 3000 can complete OAuth.
To work around this in next dev (only — never production), set a GitHub PAT in .env.local:
# .env.local
DEV_GITHUB_PAT=ghp_your_personal_access_token
Required scope: public_repo (read only).
When set, clicking “Sign in with GitHub” short-circuits the OAuth round-trip and grants a session using the PAT directly. Unset the variable (or leave it blank) to restore the normal OAuth flow.
Safety layers:
NODE_ENV === 'development' — ignored under next build / next start / deployed contexts.NODE_ENV=production and DEV_GITHUB_PAT is set, the app throws at startup. Vercel deploys with this var set will fail to boot..env.local is gitignored; no secret enters the repo.See issue #207 for the full rationale and constitution discussion.
agentctlInstall agentctl before use: see the install guide.
agentctl automates the parallel-worktree workflow: it provisions an isolated git worktree per issue, picks a free dev-server port, copies .env.local (so DEV_GITHUB_PAT flows through), starts next dev, and launches a coding agent with a kickoff prompt pointing at the issue.
Run agentctl --help for the canonical usage reference.
Spawn:
# interactive — slug auto-derived from the GitHub issue title
agentctl spawn 207
# headless — agent runs in background, log -> agent.log
agentctl spawn --headless 207
# batch
for i in 210 211 212; do agentctl spawn --headless "$i"; done
agentctl spawn creates ../repo-pulse-<issue>-<slug>/ on a new branch named <issue>-<slug>, picks the next free port in 3010–3100, runs npm install, starts next dev in the background (log: dev.log), writes a .agent key=value state file, and launches the agent with a prompt that runs the SpecKit lifecycle and opens a PR (never merges — see CLAUDE.md).
Agent selection:
The --agent <name> flag selects which coding agent adapter to use.
agentctl spawn 207 # uses claude adapter (default)
agentctl spawn --agent copilot 207 # uses copilot adapter
agentctl spawn --headless --agent copilot 207
.agent state file:
agentctl writes a single key=value .agent file in the worktree root:
agent=claude
session-id=abc-def-12345678
dev-pid=67890
agent-pid=12345
The core writes agent, session-id, and dev-pid after the dev server starts. The adapter appends agent-pid once the agent process is running.
The branch, worktree, and spec directory for a feature always share the same numeric prefix. Two paths, one convention:
| Flow | Worktree path | Branch | Spec directory |
|---|---|---|---|
| Worktree-driven (issue) | ../repo-pulse-249-align-numbering |
249-align-numbering |
specs/249-align-numbering/ |
| Manual sequential | (none) | 230-some-refactor |
specs/230-some-refactor/ |
| Timestamp (opt-in) | (none) | 20260416-143022-refactor |
specs/20260416-143022-refactor/ |
When agentctl spawn <N> pre-creates the branch <N>-<slug>, /speckit.specify inside the worktree detects the ^[0-9]+- prefix on the current HEAD and reuses it verbatim — it does not scan specs/ for the next free sequential number. Outside the worktree flow (a manual /speckit.specify on main), the sequential-scan fallback applies unchanged.
The rare collision — manual sequential claims slot N just before issue #N is filed and worktree-spawned — surfaces as a loud error naming the conflicting branch or spec directory. Resolution: rename/remove the colliding entity, or pick a different issue number. /speckit.specify never silently renumbers.
Mandatory pause after /speckit.specify. Both interactive and --headless spawns halt after /speckit.specify and wait for your explicit approval before continuing to /speckit.plan. The kickoff prompt tells the agent to report the generated spec path and wait for one of the phrases "proceed", "approved", or "go to plan". Spec revisions re-enter the paused state; only an approval phrase releases it. This exists because the spec is the highest-leverage artifact — revisions applied after plan/tasks are generated force the agent to re-derive everything downstream.
Permission model for headless spawns. The repo commits a project-scoped Claude Code permission policy at .claude/settings.json. Every session launched inside this repo or any git worktree of it — interactive or claude -p — inherits that allowlist. This is what unblocks headless spawns from freezing on the first tool-approval prompt (issue #238).
The allowlist is intentionally narrow:
Bash(.specify/scripts/bash/*) — SpecKit helpers (the original block point).Bash(git:*), Bash(gh:*), Bash(npm:*), Bash(node:*) — lifecycle commands.ls, cat, mkdir, grep, find, awk, sed, echo, printf, lsof, uuidgen) — scoped by command name, never as Bash(*).Read, Edit, Write, Grep, Glob.WebFetch(domain:github.com) and WebFetch(domain:api.github.com) — read-only GitHub lookups..claude/settings.json pre-approves only the documented shared aliases (github, playwright, vercel, context7) when they are defined in .mcp.json.Explicitly not allowed: Bash(rm:*), Bash(curl:*), Bash(wget:*), Bash(sudo:*), Bash(ssh:*), unapproved project-scoped MCP servers, any blanket wildcard, and bypassPermissions. A tool outside the allowlist falls through to the normal prompt path; in a headless session, that still blocks — which is the signal to extend the allowlist by PR, not to bypass it.
Extending the allowlist: edit .claude/settings.json in a PR. The PR description must name the SpecKit helper, git subcommand, npm script, gh subcommand, or project-scoped MCP alias that needs the new entry. No secrets, no blanket wildcards, no destructive shell commands.
Releasing a paused headless session. For --headless spawns, use the fire-and-forget release commands:
# Approve the generated spec and run Stage 2 (plan → tasks → implement → PR) in the background:
agentctl approve-spec <issue>
# Or send revision feedback to the paused session; it edits the spec in place and re-enters the pause:
agentctl revise-spec <issue> "Add an acceptance scenario for empty input."
Both commands resolve the worktree for <issue>, read the agent key from <worktree>/.agent to load the correct adapter, then call agent_resume as a detached nohup process appending to agent.log. They return control to your shell in under 5 seconds — you do not need to keep the invoking terminal open.
Preconditions (both commands exit non-zero with a clear error if unmet):
<issue> exists.<worktree>/.agent exists and contains both agent and session-id keys.<worktree>/specs/*/spec.md exists (the paused state has been reached).Additional rule for revise-spec: empty feedback is rejected. Repeated revise-spec rounds accumulate — each round edits the spec as it stands after the previous round.
Manual fallback (still supported): cd ../repo-pulse-<issue>-<slug> && claude --resume opens an interactive session if you want to review and revise the spec conversationally. This attaches your terminal; approve-spec / revise-spec do not.
The pause is per-worktree: in a batch spawn (for i in 210 211 212; do agentctl spawn --headless "$i"; done) you review and release each worktree independently, but the release itself is fire-and-forget, so for i in 210 211 212; do agentctl approve-spec "$i"; done walks away with three PRs on the way.
Cleanup:
# Post-merge, from the main repo: pull main, kill processes, remove the
# worktree, delete the local+remote branch (refuses if unmerged).
agentctl cleanup-merged 207
# Discard unmerged work (unrecoverable: kills processes, removes worktree,
# deletes local + remote branch — prompts for YES confirmation).
agentctl discard 207
# From inside the worktree (e.g. the same shell the agent session ran in),
# the issue number is inferred from the current branch's `^[0-9]+-` prefix:
cd ../repo-pulse-207-<slug>
agentctl cleanup-merged # no arg needed
When invoked with no argument from a linked worktree, the command chdirs to the main repo, auto-checks out main if the primary worktree is on another branch (refuses on dirty state — never force-discards), then runs the standard cleanup flow. On success, if the caller’s current working directory was the removed worktree, the command prints a final-line notice of the form note: your shell's previous CWD (...) no longer exists — run \cd
cleanup-merged verifies merge status by querying the associated PR’s state via gh pr view <branch> --json state — not by local ancestry. This matters because squash-merges and rebase-merges on GitHub produce a merge commit that is not an ancestor of the local feature branch, so the older ancestry check (git branch -d) would silently refuse even after a real merge. When the PR state is MERGED, the local branch is force-deleted, then git push origin --delete <branch> removes the remote ref. If the remote was already removed (e.g. GitHub’s “Automatically delete head branches” setting fired at merge time), the step prints Remote branch <branch> already removed and exits 0. Any other remote-delete failure (network, auth, protected branch) surfaces a warning and exits non-zero — the worktree and local branch are already gone, so only a manual git push origin --delete <branch> remains. When the PR is OPEN, CLOSED without merge, missing, or gh cannot reach GitHub, the command refuses and points to discard. Use discard as the escape hatch for unmerged branches; it also deletes the local and remote branch.
cleanup-merged is self-healing around two common failure modes. First, after killing the dev-server PID it waits up to 5 s for the process to exit before calling git worktree remove, because next dev can still hold file descriptors under .next/ when kill returns and macOS rejects the rmdir with Directory not empty. Second, if git worktree remove --force still fails but git has already unregistered the worktree admin dir, the command falls back to rm -rf <dir> and continues — the partial success is treated as a full cleanup rather than a hard failure. Re-invoking the command after an earlier mid-sequence failure also works: the command detects an orphaned dir / local branch / remote branch for <issue> by matching the ^<issue>-* branch prefix, and finishes whatever remains. The MERGED PR-state guard is still enforced on the recovery path.
If something gets stuck:
git worktree list # what's still registered
git worktree prune # drop stale entries for deleted paths
lsof -iTCP:3010-3100 -sTCP:LISTEN # any dev servers still bound?
Phase 2 adds new scoring buckets to the health score. Requirements specs live in the linked GitHub issues — not in PRODUCT.md. See Spec Ownership for the rationale.
| # | Feature ID | Feature | Issues | Status |
|---|---|---|---|---|
| 1 | P2-F01a | Documentation scoring (basic) | #66 | ✅ Done |
| 2 | P2-F02 | Licensing & Compliance | #115 | ✅ Done |
| 3 | P2-F03 | Inclusive naming | #107 | ✅ Done |
| 4 | P2-F07 | Security scoring | #68 | ✅ Done |
| 5 | P2-F04 | Governance & Transparency | #116 | ✅ Done |
| 6 | P2-F05 | Community scoring | #70 | ✅ Done |
| 7 | P2-F06a | CNCF foundation support | #398 (#399, #400, #157, #210, #211) | ✅ Done |
| 8 | P2-F06b | Foundation-aware recommendations (Apache, LF, others) | #119 | |
| 9 | P2-F08 | Accessibility & Onboarding | #117 | ✅ Done |
| 10 | P2-F09 | Release health scoring | #69 | ✅ Done |
| 11 | P2-F10 | Development cadence | #73 | ✅ Done |
| 12 | P2-F11 | Project maturity | #74 | ✅ Done |
| 13 | P2-F12 | Ecosystem Reach | #118 | |
| 14 | P2-F01b | Documentation scoring (advanced) | #110, #67 | |
| 15 | P2-F13 | Org governance audit — stale admin detection, 2FA enforcement | #287, #286 (children of #285) | ✅ Done |
| 16 | P2-F14 | Org governance audit — member permission distribution | #288 | ✅ Done |
| 17 | P2-F15 | Foundation input mode — top-level tab (repos + org) | #413 | ✅ Done |
| 18 | P2-F16 | Foundation input mode — Projects board sub-mode | #411 | |
| 19 | P2-F17 | Repo card progressive disclosure (UX density) | #483 | ✅ Done |
Phase 3 delivers the OSS Health Score through additional channels, wrapping the shared analyzer module without duplicating logic.
| # | Feature ID | Feature | Issues | Status |
|---|---|---|---|---|
| 1 | P3-F01 | Public REST API | #120 | |
| 2 | P3-F02 | GitHub Action | — | |
| 3 | P3-F03 | MCP Server | — | |
| 4 | P3-F04 | Embeddable badge | #72 | |
| 5 | P3-F05 | CLI tool | #82 | |
| 6 | P3-F06 | PR comment bot | #83 | |
| 7 | P3-F07 | VS Code extension | #84 | |
| 8 | P3-F08 | Webhook receiver | #85 |
Phase 4 ports the application to support GitLab in addition to GitHub. It introduces a provider abstraction layer (GitHub and GitLab implement the same interface) so the analyzer, delivery phases, and UI remain unchanged. Phase 4 begins only after Phase 3 is complete.
For Phase 2 and beyond, the requirements spec for each feature lives in its GitHub issue. The SpecKit workflow reads the issue as input:
GitHub Issue (requirements spec)
│ Acceptance criteria, signals, scoring approach, open questions
│
▼
/speckit.specify (reads issue as input)
│ Generates specs/NNN-feature-name/ with TypeScript contracts
│
▼
/speckit.plan → /speckit.tasks → /speckit.implement
│ Plan, break down, build
│
▼
PR with test plan → merge
│ Mark feature as ✅ Done in the table above
Key distinction: The GitHub issue defines what and why (requirements). SpecKit generates how (TypeScript interfaces, view props, data flow contracts). They are not duplicates — the issue is the input, the spec file is the output.
This replaces the Phase 1 pattern where PRODUCT.md contained inline acceptance criteria for every feature. Phase 1 specs are frozen in PRODUCT.md as a historical record.
Any PR that adds a new governance panel or signal (i.e. a new /api/org/ route that feeds OrgFixture.governance in app/demo/organization/page.tsx) must do all three of the following in the same PR:
governance object in scripts/generate-demo-fixtures.ts.npm run demo:fixtures and commit the updated fixtures/demo/org-ossf.json.npm run demo:check-parity exits 0.Why: hand-authoring values in fixtures/demo/ violates the accuracy policy (constitution §II). Issue #385 documented a real incident where memberPermission was committed with fabricated counts that were only caught during manual review. The CI workflow .github/workflows/demo-fixture-parity.yml enforces generator parity automatically on every PR that touches governance-related files.
There is no central signal registry. When adding a new signal to an existing lens (e.g. community, licensing, docs), update every touchpoint below or the signal will be invisible on one of the surfaces:
lib/analyzer/queries.ts + RepoOverviewResponse typeextract<Lens>Signals() in lib/analyzer/analyze.tslib/analyzer/analysis-result.ts (the *SignalSet interface)lib/<lens>/completeness.ts (*SignalKey, extractSignalPresence())lib/tags/<lens>.ts (the *_DOC_FILES / *_METRICS / *_ACTIVITY_ITEMS Sets)lib/tags/tab-counts.tsbuildLensReadouts() in lib/metric-cards/view-model.tslib/metric-cards/view-model.tscomponents/activity/*, components/documentation/*, etc.)lib/comparison/sections.tslib/export/json-export.ts and lib/export/markdown-export.tslib/recommendations/catalog.ts (if the signal’s absence should trigger advice).specify/ is managed by SpecKit. Do not manually edit files inside it outside of the workflows above.specs/NNN-feature-name/.## Test plan section — not in an in-repo checklist file..specify/memory/constitution.mddocs/PRODUCT.mddocs/DEPLOYMENT.md