A local-only web dashboard that automatically finds feature gaps between two payment SDK repositories:
- hyperswitch-web — the web (JavaScript/ReScript) SDK
- hyperswitch-client-core — the mobile (ReScript) SDK
Both repos are part of the same product (Hyperswitch payment orchestration), but they've been developed independently. Features get added to one SDK and sometimes never make it to the other. This tool finds those gaps, verifies them with AI, and can even generate patches to fill them.
Click "Run Gap Analysis" and the system:
- Clones/pulls both repos locally
- Uses AI to extract features across 4 categories: configs, components, backend APIs, and payment methods
- Normalizes naming differences across repos (e.g.
card_holder_namein web =cardHolderin mobile) - Applies deterministic noise filters to remove false positives
- Shows a clean table of genuine feature gaps
Each gap row has a "Verify" button that:
- Sends an AI agent into the missing repo with file-reading tools
- The agent actually searches the codebase to confirm the feature is truly absent
- If found under a different name → auto-removes the row (false positive)
- If confirmed missing → marks it with a green "verified" badge
- If it's platform-specific (can't exist on that platform) → marks it accordingly
Each gap row has a "Generate Patch" button that:
- Creates a git branch in the target repo (
feat/gap-<id>-<name>) - Sends an AI agent with full code editing capabilities into the repo
- The agent reads the reference implementation from the other repo
- Implements the missing feature following the target repo's conventions
- Shows a color-coded diff viewer with the changes
- Provides a copyable git command to checkout the branch locally
Everything stays 100% local — no credentials, no GitHub pushes, no API keys needed.
┌─────────────────────────────────────────────────────────────┐
│ Browser (React) │
│ ┌──────────┐ ┌──────────┐ ┌────────────┐ ┌───────────┐ │
│ │ Run Gap │ │ Filter │ │ Verify │ │ Generate │ │
│ │ Analysis │ │ Pills │ │ Button │ │ Patch │ │
│ └────┬─────┘ └──────────┘ └─────┬──────┘ └─────┬─────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ Vite Dev Server (port 5173) │ │
│ │ Proxies /api → localhost:5174 │ │
│ └──────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Node/Express Server (port 5174) │
│ │
│ Routes: │
│ POST /analyze → Run full gap analysis │
│ POST /analyze/cancel → Kill running analysis │
│ GET /reports/latest → Latest report status │
│ GET /gaps → List gaps for a report │
│ POST /gaps/:id/validate → AI-verify one gap │
│ POST /gaps/:id/patch → AI-generate patch for one gap │
│ GET /patches/:id → Get patch details + diff │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ SQLite DB │ │ SHA Cache │ │ Patch Files │ │
│ │ data/app.db │ │ data/cache/ │ │ data/patches/ │ │
│ └──────────────┘ └──────────────┘ └──────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ LLM Layer (llm.ts) │ │
│ │ Shells out to `claude -p` (Claude CLI) │ │
│ │ Uses Max plan login — NO API key needed │ │
│ └────────┬─────────────────┬────────────────┬──────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Sonnet │ │ Sonnet │ │ Opus │ │
│ │ Extract │ │Normalize │ │ Validate │ │
│ │ (cheap) │ │ (cheap) │ │ + Patch │ │
│ └──────────┘ └──────────┘ │(powerful)│ │
│ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Local Workspace │
│ workspace/hyperswitch-web/ (cloned via git) │
│ workspace/hyperswitch-client-core/ (cloned via git) │
└─────────────────────────────────────────────────────────────┘
We call Claude through the Claude CLI (claude -p), not through an API. This means:
- No API key or
ANTHROPIC_API_KEYneeded - Uses the logged-in Claude Max plan subscription
- Each call spawns a subprocess:
claude -p --model <model> --output-format text - We track all active subprocesses so the Cancel button can kill them
| Tier | Model | Tools | Purpose | Cost |
|---|---|---|---|---|
| Extract | Sonnet (fast, cheap) | None | Read file contents, identify features | ~8 calls per run |
| Normalize | Sonnet | None | Cross-repo name matching, dedup | ~4 calls per run |
| Verify | Opus (powerful) | Glob, Grep, Read | Search codebase to confirm gap | 1 call per gap (on demand) |
| Patch | Opus | Edit, Write, Read, Glob, Grep | Implement missing feature | 1 call per patch (on demand) |
Input: File contents from the repo (we read them ourselves, pass to the prompt)
Prompt: "Here are the ReScript files from the payments directory. List every
payment method you find with its name, file path, and a code snippet."
Output: JSON array of {name, file, snippet}
- Runs 8 times in parallel (4 categories × 2 repos)
- Cached by git SHA — same commit = zero calls on re-run
Input: Two feature lists (web + mobile) for one category
Prompt: "Map naming variants across repos. 'card_holder_name' = 'cardHolder'.
Collapse sub-fields into parents. Output one canonical list."
Output: JSON array of {canonical_name, web: Feature|null, mobile: Feature|null}
- Where
web = nullormobile = null→ that's a gap - Cached by both SHAs — same commits = zero calls
Input: One claimed gap (e.g., "click_to_pay missing in mobile")
Tools: Glob (find files), Grep (search content), Read (read files)
CWD: Set to the missing repo's directory
Prompt: "Feature 'click_to_pay' is claimed missing from this repo.
USE your tools to actually look. Search under that name and
any plausible alias. Report: confirmed / false_positive / platform_specific."
Output: JSON verdict with rationale
- Opus can actually browse the filesystem to check
- Only runs when you click "Verify" — not on every analysis run
- Cached by repo SHA — verify once, free forever at that commit
Input: Gap details + reference implementation from the other repo
Tools: Edit, Write, Read, Glob, Grep
CWD: Set to the missing repo's directory
Prompt: "Implement 'click_to_pay' in this repo. Here's how it looks in the
web SDK: [code]. Follow this repo's conventions. Only touch
necessary files."
Output: Modified files on disk → we capture as git diff
- Creates a real git branch with real code changes
- You can checkout the branch and review/modify the code
Between extraction and normalization, we run zero-cost deterministic filters (server/src/analyzer/filter.ts):
-
Canonical group collapse:
appearance_theme,appearance_variables,color_primary,color_background,font_family→ all become ONE feature calledappearance_api. Both repos have this, so it's not flagged as a gap. -
UI primitive denylist:
tab_bar,date_element,button_element,generic_button_element→ dropped. These are infrastructure, not integrator features. -
Payment-method-specific form fields:
blik_code_input,pix_payment_input,vpa_id_input→ dropped. Mobile shows generic backend-driven forms, so these web-specific input components will never exist in mobile. -
Structural false positive rule: Payment methods "missing in mobile" are always false positives because the mobile SDK loads payment methods dynamically from the backend — you won't find payment method names in its source code.
These filters reduced raw gaps from ~240 → ~75 without any AI call.
| Layer | Technology | Why |
|---|---|---|
| Frontend | React + Vite + Tailwind CSS | Fast dev, good DX |
| Backend | Node.js + Express + TypeScript | Simple, typed |
| Database | SQLite (better-sqlite3) | Zero config, local file |
| Git ops | simple-git | Clone, pull, branch, diff |
| AI | Claude CLI (claude -p) |
No API key, uses Max plan |
| Caching | SHA-keyed JSON files on disk | Same commits = zero AI calls |
feature-gap-dashboard/
├── CLAUDE.md # AI assistant context (auto-loaded)
├── LEARNINGS.md # Iteration log — what worked, what didn't
├── DEMO_GUIDE.md # This file
├── data/
│ ├── app.db # SQLite database
│ ├── cache/
│ │ ├── extract/ # Cached extractor outputs (by repo SHA)
│ │ ├── normalize/ # Cached normalize outputs (by both SHAs)
│ │ └── validate/ # Cached validation verdicts (by repo SHA)
│ └── patches/ # Generated .patch files
├── workspace/
│ ├── hyperswitch-web/ # Cloned web repo
│ └── hyperswitch-client-core/ # Cloned mobile repo
├── server/
│ └── src/
│ ├── index.ts # Express server entry
│ ├── config.ts # Paths, ports, model selection
│ ├── db.ts # SQLite schema + types
│ ├── llm.ts # Claude CLI wrapper (ask, askJson)
│ ├── cache.ts # SHA-keyed disk cache
│ ├── analyzer/
│ │ ├── index.ts # Pipeline orchestrator
│ │ ├── filter.ts # Deterministic noise filters
│ │ ├── normalize.ts # Cross-repo name matching (Sonnet)
│ │ ├── validate.ts # Per-gap verification (Opus + tools)
│ │ ├── types.ts # Shared types
│ │ └── extractors/ # Per-category feature extractors
│ │ ├── paymentMethods.ts
│ │ ├── configProps.ts
│ │ ├── uiComponents.ts
│ │ └── backendApis.ts
│ └── routes/
│ ├── analyze.ts # POST /analyze, GET /reports, GET /gaps
│ ├── gaps.ts # POST /gaps/:id/validate
│ └── patches.ts # POST /gaps/:id/patch, GET /patches/:id
└── web/
└── src/
├── App.tsx # Main dashboard
├── api.ts # Typed API client
└── components/
├── GapTable.tsx # Gap table with Verify + Patch buttons
├── DiffViewer.tsx # Modal showing color-coded diff
└── RunButton.tsx # Run analysis button with spinner
- Both servers running:
npm run devin bothserver/andweb/ - Browser open to
http://localhost:5173 - Dashboard shows "idle" state (or a previous completed report)
1. "Here's the problem" (~30 seconds)
"We have two SDKs — web and mobile — for the same payment product. Features get added to one and sometimes never make it to the other. Finding these gaps manually means reading thousands of lines of ReScript across both repos. This dashboard automates that."
2. Run Gap Analysis (~1 minute wait)
- Click the Run Gap Analysis button
- While it runs, explain: "The system clones both repos, then uses Claude Sonnet to extract features across four categories — payment methods, configs, UI components, and backend APIs. It runs 8 extraction calls in parallel, then normalizes naming differences across repos."
- Point out: "This is cached by git commit SHA, so if the repos haven't changed, re-running is instant — zero AI calls."
3. Review the results (~1 minute)
- Show the gap count and the filter pills (All / Missing in mobile / Missing in web)
- Show the payment methods toggle: "Payment methods are hidden by default because the mobile SDK loads them dynamically from the backend — static analysis can't detect them, so they'd all be false positives."
- Scroll through some gaps: "Each row shows what's missing and where. But these are unverified — the AI found them through name matching, not by actually searching the code."
4. Verify a gap (~30 seconds)
- Pick gap #179 (
hide_card_nickname_field) or any config gap - Click Verify
- Explain: "This sends Claude Opus — the most powerful model — into the mobile repo with actual file-reading tools. It searches for this feature under any name or alias. If it finds it, the row is auto-removed as a false positive. If confirmed missing, it gets a green badge."
- Wait for the result (~15 seconds)
5. Generate a patch (~2 minutes)
- On the same verified gap, click Generate Patch
- Explain: "Now Opus gets write access to the mobile repo. It reads the reference implementation from the web SDK, understands the mobile SDK's code conventions, and implements the feature. It creates a real git branch with real code changes."
- Wait for the diff viewer to open (~1-2 minutes)
- Walk through the diff: "Here's what it changed — it added the config option to the types file and wired it into the dynamic fields component. This follows the exact same pattern the mobile SDK uses for other config flags."
- Show the branch checkout command at the bottom
6. Key takeaways (~30 seconds)
"Three things make this practical:
- It's local-only — no API keys, no GitHub access, runs on a shared machine using the Claude Max subscription.
- It's token-efficient — extraction and normalization use the cheaper Sonnet model. The expensive Opus model only runs when you explicitly click Verify or Generate Patch. And everything is cached by git SHA.
- Quality over quantity — deterministic filters remove ~60% of noise before any AI call. Then per-gap verification catches what the filters miss. You never see false positives in your final results."
- Raw extraction: ~256 features across both repos
- After deterministic filter: ~143 features (no AI cost)
- After normalization: ~75 candidate gaps
- After structural false-positive removal: ~40-50 actionable gaps
- Cold run time: ~1 minute (Sonnet extraction + normalization)
- Cached run time: instant (same git SHAs = zero AI calls)
- Per-gap verification: ~15 seconds (Opus)
- Per-gap patch generation: ~1-2 minutes (Opus with write tools)
-
Why Claude CLI instead of API? No API key needed. The team's Max plan subscription covers everything.
claude -pshells out to the locally installed CLI. -
Why two models? Sonnet is fast and cheap — good for bulk extraction. Opus is powerful and can use tools — needed for verification (searching code) and patching (writing code). Using Opus for everything would cost 10x more tokens.
-
Why lazy verification? Our first design validated all ~90 gaps up front with Opus. It took 11 minutes and burned through tokens. Lazy verification means you only pay for the gaps you care about.
-
Why deterministic filters? We learned through iteration that ~60% of "gaps" were noise — appearance sub-keys listed as separate features, generic UI primitives, platform-specific form inputs. A 30-line filter function eliminated them for free. See
LEARNINGS.mdfor the full iteration history. -
Why SHA-based caching? If neither repo has new commits, every AI call is skippable. This makes re-runs and demos essentially free.