Most of my commits land in private repos. one day will make all the repositaries public once they ready, and Many more ideas are still being built.
I build local-first systems — trading, personal finance, developer tooling —
that run on your own machine and don't hand your data to anyone else.
These are personal projects and the repos are private, so nothing below is linked. They're pre-1.0 — for each one I've written what the hard problem was, how it's built, and what I'd still call unfinished.
Slimdex — slimdex-mcp TypeScript · Model Context Protocol server · MIT
A coding agent asked a narrow question — "what calls this function?" — usually answers it the expensive way: open the file, read all 800 lines, use one. Do that four times and most of the context window is spent on code that was never relevant. Worse, context is re-read every turn, so one wasteful read keeps charging rent for the rest of the session.
Give the agent a way to ask precisely. A file skeleton instead of a file. A
line range instead of a module. One symbol's body instead of its
neighbourhood. A list of path:line:col instead of the files containing them.
Nineteen tools, one idea: return the answer, not the haystack. The headline
tool, get_context, assembles a symbol's definition, signature, callers and
imports into a single bounded brief — four round-trips collapsed into one
(add dependents or body when the question actually calls for them).
| Approach | Strength | Cost |
|---|---|---|
| Pack the whole repo into the prompt | Trivial, nothing to build | Spend scales with repo size, not question size |
| Embeddings / semantic search | Finds things by meaning | Heavy deps, an index that drifts, fuzzy answers to exact questions |
| Language servers / tree-sitter | Genuinely precise | A grammar or server per language, real install weight |
| Slimdex | Installs instantly, offline, zero config, no native deps | Regex heuristics — less precise than a real parser |
I took the last row deliberately: starting in a second and running offline is the value, and per-language grammars would have traded it away. That's a real trade, not a free win — if you need go-to-definition exactly right every time, a language server beats this.
Four decisions stacked.
A persistent index invalidated by mtime, so refreshing re-parses only what changed and stays cheap enough to run constantly — an incremental rebuild over 5,000 files costs milliseconds, not seconds.
A parser seam rather than a parser commitment — extraction sits behind a
Parser interface, so tree-sitter can drop in later without touching a tool
or the index format; the weakness is quarantined in one file. The same
quarantine pattern extends the graph past what regex extraction alone can
see: for languages with no import statement, a masked-token scan links a file
to the other files whose classes it names by reference, and a second pass
reads the repo's own configuration files (metadata records, bindings) for the
same names — so "what implements this interface" and "what wires this up"
get real answers even where there's no import to parse.
Budgeting on every response, because an aggregator that returns
everything is just a new way to waste context — sections are opt-in, callers
capped, size bounded, and every cap that trips says so out loud
(showing 3 of 68). A tool that silently truncates is worse than one that
over-returns, because the model can't tell.
The discipline ships with the server via MCP's instructions channel, so
knowing when to skeleton instead of read travels with it instead of sitting in
a README nobody feeds the model.
What got rejected is as telling: symbol-ID dictionaries (MCP has no
client-side expansion, so the model burns a call resolving an opaque token),
token-budget estimators (chars/4 lies across tokenizers), "see response #5"
caching (after compaction the referent is gone), and a session-budget refusal
tool (an agent hitting a refusal just falls back to reading whole files — the
failure mode it exists to prevent).
Realistic honest band across which is tested around my repositories whole workflow:
~45% on output-heavy days, ~55–60% on navigation-heavy days. Averaged over real use, call it ~50% — and 55–60% whenever the work leans toward reading and understanding rather than churning out new code. use it regularly for the best output across all sessions in an IDE.
Next up: the tree-sitter backend the seam exists for, coverage for tools that only have manual exercise, repeated benchmarks, an npm release.
Java · Spring Boot · React · PostgreSQL · multi-provider LLM
Not alerts. Not suggestions. On its own schedule it scans a liquidity-vetted universe, forms a thesis, argues with itself about it, sizes the position, places the entry, attaches the stop, manages the position while it's open, trails the stop, exits, and then reviews what it did that day and carries the lesson into tomorrow. No human in the loop at any step.
scan universe → indicators & regime → swarm deliberation (3 agents, veto)
→ evidence fusion vs memory → risk manager → execution guard (fails closed)
→ entry + stop/OCO → live position management → trailing stop → exit
→ end-of-day self-debrief → memory → tomorrow's decisions
algo.mode=paper is the default — simulated fills against live prices.
algo.mode=live sends real orders with real money. One property is the entire
boundary between them, and it defaults to the safe side.
Most "AI trading" is one prompt to one model. Here a trade must survive three specialists, each running on a model chosen for its cognitive job and its cost:
| Agent | Model tier | Reads | Decides |
|---|---|---|---|
| Scout | fastest | live news, sentiment, regional-language press | is the news environment hostile or supportive for this symbol |
| Quant | cheapest bulk | price, volume, mathematical structure only | is the edge real, or is this noise |
| Risk Officer | smartest | everything, adversarially | what's the hidden danger — and it holds a veto |
Two things make this more than theatre:
They vote blind. Each agent sees only its own brief, never the others' output. One model's hallucination cannot cascade into the others' reasoning — which is precisely what happens when you chain agents in a pipeline.
The veto is real. An unrebutted danger flagged by the Risk Officer kills the trade regardless of how confident the other two were. Paying for the most expensive model specifically to have it say no is the opposite of how these systems are usually built.
Disagreement is written to a decision ledger, not averaged into a single comfortable number.
At the end of each session the system debriefs itself — what it did, what worked, what must not be repeated — and those lessons are injected into the next session's decisions, keyed by market regime, so "what usually happens to this setup in a choppy tape" is answered from its own history rather than a hunch.
It has caught its own bugs this way. One debrief flagged an order-retry loop hammering the same symbol eight times in 2.5 hours at nearly identical prices, and separately caught a contradiction between the macro regime label and the per-stock one. Neither was in a bug report — the system found them in itself.
Between every decision and the broker sits ExecutionGuard:
- Re-quotes at order time — live tick (≤10s), else fresh REST, else a ≤30s tick. Nothing fresher than that and the trade is rejected, never placed on stale data.
- Spread gate — aborts above 0.15% of LTP; when depth is unavailable the gate is skipped loudly, logged, never silently.
- Drift rules — a volatility burst aborts; if price drifted toward target, risk:reward is recomputed against the original structural levels and the trade dies below 1.8.
- Risk-based sizing — quantity from actual rupee risk against the re-quoted stop, so drift automatically shrinks size at constant risk.
- ATR stop floor — a minimum stop distance of 1.5×ATR, the same rule the backtester uses.
That last one sounds like a detail. It isn't. When the live path accepts a stop the backtest would never have taken, every backtest number you own describes a strategy that never actually runs — and nothing tells you, because nothing compares the two. Enforcing one rule across both is the difference between having evidence and having a screenshot.
Every one of those rules is a decision to not trade. That's the expensive kind of discipline to build, and the reason most retail bots skip it.
The same engine drives a long-horizon investor side, and it does things I went looking for in retail tools before building them.
The AI is never the source of a number. Required return, CAGR over both 5 and 10 years, drift, drawdown, real-terms erosion — computed in Java before the model sees them. It reasons freely and may argue with the numbers out loud, but where its figures diverge from the computed ones the gap is shown, not silently resolved. One button recomputes the whole plan independently.
It tells you the goal is impossible. Ask most calculators for ₹10 crores in 15 years and they quietly assume 12% and hand you a monthly figure. This solves for the return actually required, compares it against what your holdings have genuinely delivered, and opens with "not with this portfolio" when that's true.
Five goals can't spend the same money. Every goal tracker measures each goal against your whole portfolio, so five goals and one account report five goals comfortably on track — funded by the same rupees. Here shares are attributed by unit count, over-allocation is rejected with what's actually free, and whatever is unassigned says so.
The tax bill sits next to the sell recommendation — not in a footnote.
Knowledge, not prompt-stuffing. Trading doctrine lives in versioned markdown modules under a token budget; each decision type loads only what it needs. The premise is that the model already knows the canon from training — these activate the right part. Every module ends in disqualifiers: when its own playbook must not be used. Knowing when a play is off is worth more than the play.
Anything, anywhere. Equities, ETFs, index and mutual fund schemes, commodities and global indices resolve through one history pipeline, so the same analysis runs on a domestic small-cap, on gold, or on a US index.
Spring Boot with a deliberately partitioned scheduler — a slow news or LLM call must never block the trading cycle, so they run on separate pools. LLM usage under a hard daily call budget, model tier chosen per task. PostgreSQL for trades, decisions, goals and conversation memory. WebAuthn passkey auth, WebSocket price streaming, React front end, Docker Compose for local infra, separate backend/frontend/e2e CI.
Worth knowing: in live mode this places real orders with real money, and no guard makes that safe — it makes it less unsafe. Nothing it produces is financial advice. LLM analysis can be confidently wrong, which is exactly why the arithmetic is computed in code and independently re-checkable on demand. Live trading has had limited real-money exposure so far; treat the engine as pre-1.0 and the backtests as evidence about a strategy, not a promise about an account. Single-user today, and broker-agnostic by design — the execution layer is adapter-shaped, so any brokerage with an API is one adapter away, and that's the roadmap.
Node.js · SQLite · browser extension · React
Aggregators want your bank credentials handed to a third party. Spreadsheets avoid that but can't tell a transfer from a purchase — so moving ₹50,000 between your own accounts shows up as both income and spending, and every total downstream is wrong. Across 10+ Canadian accounts and cards, that error compounds fast.
One local app that ingests from four collectors — a browser extension, an email
alert parser, a statement drop folder, and manual entry — normalizes everything
into a local SQLite file, and computes spend, EMIs, dues, cashback and savings
coaching on top. No hosted service, no vendor holding credentials, data in a file
on the laptop via Node's built-in node:sqlite.
Two mechanisms carry most of the value:
Idempotent ingest. Every transaction's primary key is
sha256(account|date|amount|description). Re-import the same CSV, or let the
extension read the same page twice, and the duplicate collapses. Ingest becomes
safe to repeat — which matters enormously when the collectors are scrapers.
The transfer matcher. After every ingest it looks for opposite-sign,
same-amount transactions across two of your own accounts within a ±4 day window,
cross-checked against description patterns (INTERAC, E-TFR, credit-card
payments from chequing). Matches are tagged with a shared transfer_pair_id and
excluded from both spend and income. Interac to other people stays real spend;
received stays income. Genuinely ambiguous ones go to a review queue rather
than being guessed — the system declines to be confidently wrong.
Income modelling handles hourly pay (rate × average hours, business-hour aware) alongside fixed salary, because "what will I actually be paid" is not a constant for everyone.
Express API over SQLite, with the ingest pipeline, insights engines and dues
scheduler behind it; React + react-three-fiber front end fed live over SSE. The
build is split into ownership lanes — backend, shared and extension on one side,
frontend on the other — with shared/api-contract.md as the single interface
between them and a rule that nobody edits outside their lane. It's a
merge-conflict-avoidance strategy for parallel agent work, and it held.
Next up: categorization and cashback rules are heuristics and do mis-bucket transactions; refining those, plus a test suite and CI, is the current focus.
Python · JavaScript · PDF.js
Editing a PDF usually means a paid membership, and uploading a document — often the sensitive kind, a contract, a statement, an ID — to a stranger's server. The offline alternative is a heavyweight suite with forty panels when you needed to erase a line and sign.
This does more than either. It runs locally — your documents are processed on your own machine, not parked on someone else's server — and it's free, no membership wall between you and the edit you need. And it isn't just PDFs: the same tool handles [Word docs / images / scans — fill in the actual formats] without switching apps for each file type.
A local workspace for documents, PDFs and simple sheets that opens like a desktop app rather than a browser tab. Nothing you open or write leaves the machine. The PDF editor is deliberately five tools — Edit text, Add text, Erase, Redact, Sign — with everything rarer behind a More menu, and only the active tool's controls on screen.
Redaction that actually redacts. Drawing a black rectangle over text is the common implementation and it's security theatre — the text sits underneath, still extractable by anyone who selects it. Folio's Redact flattens the affected page so the covered content is genuinely gone, and it's a separate tool from Erase (a sampling brush for cosmetic cleanup) precisely because conflating the two is how people leak documents.
Honest font reporting. When replacing text it tells you which case you're in — exact installed font, exact embedded glyph, closest metric match, or fallback — instead of silently substituting and letting you discover the mismatch after saving. It waits for PDF.js font metadata rather than falling back early.
A launcher that doesn't strand you. It picks a genuinely free port instead of guessing a fixed one, then health-checks that the server responds before opening a window — the difference between an app that opens and one that hangs on a port some other process took.
Python standard library only — http.server with a quiet handler, no framework,
no pip install — plus a vanilla-JS front end with a PWA manifest so it can be
installed to the taskbar. The dependency count is close to zero by design: this
is software meant to still run in five years without a package audit.
Next up: PDF editing is the least finished surface and carries its own written audit of the gaps. Setup is Windows-only today (PowerShell +
.vbslauncher); cross-platform packaging is the obvious follow-on.
Salesforce · Apex · LWC · Experience Cloud
Private-capital fund administration is a visibility problem before it's a data problem. A General Partner sees everything; an analyst sees fund performance but not investor identities; a Limited Partner must see their own commitments, capital calls and distributions and absolutely nobody else's. Get that wrong and you've shown one investor another's position.
A fund-administration app across nine custom objects — Fund, Investor, Commitment, Capital Call (and lines), Distribution (and lines), Investment, Valuation — with an LP portal on Experience Cloud, an LP dashboard and capital- raise components in LWC, and batch tooling to generate realistic test data.
Access is modelled as four graduated permission sets — GP, Investor Relations, Analyst, LP — rather than one admin profile and hope. The Analyst set is the telling one: full CRUD on Fund, Investment and Valuation, read-only on the investor-facing objects. On top of that sits an Apex sharing handler that grants LP record access programmatically, because declarative sharing alone doesn't express "this partner, these commitments, nothing adjacent."
Built on fflib Apex Enterprise Patterns — a full separation into Application factory, Domain, Selector, Service and Unit of Work layers, rather than business logic accumulating inside triggers. Each object gets a thin trigger handler delegating into domain classes; queries are confined to selectors; writes go through a unit of work. It's more ceremony than a small org needs, and it's the right call the moment sharing rules and financial records are involved.
Worth knowing: seven Apex test classes cover the service and sharing layers; coverage isn't uniform across the object model yet, and broadening it is the next task.
Languages TypeScript · Java · JavaScript · Python · Apex Backend Spring Boot · Node.js · Express · SQLite · Docker Frontend React · react-three-fiber · Lightning Web Components Practices fflib enterprise patterns · fail-closed execution guards · idempotent ingest · contract-first module boundaries Tooling Vitest · JUnit · GitHub Actions · Model Context Protocol
Most of my commits land in private repos, so the contribution graph is a poor proxy for what I'm working on.


