Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
9ea0f05
chore: kick off phase 1 (parser core + isolation)
ARCoder181105 Jul 21, 2026
5acc885
docs: add PRD and Phase 1 tasklist with locked build decisions
ARCoder181105 Jul 25, 2026
4ebe657
feat(parser): embed typescript.scm, add loadQueries + test; fix expor…
ARCoder181105 Jul 26, 2026
b745338
implement parser chunks C2, C3, C4: extract functions, arrow function…
ARCoder181105 Jul 26, 2026
07c1b72
implement parser chunks C4b, C5, C6, C7: overload indices, call extra…
ARCoder181105 Jul 26, 2026
17460de
implement parser chunks C8, C9: harden security.Walk and limit read s…
ARCoder181105 Jul 26, 2026
1839afa
implement parser chunks C10, C11: golden fixtures and extract_test.go
ARCoder181105 Jul 26, 2026
99b82e4
implement parser chunks C12, C13, C14: docker, CI, and docs sync
ARCoder181105 Jul 26, 2026
5ba15b3
fix: resolve go lint errors and separate Node and Go CI workflows
ARCoder181105 Jul 26, 2026
1f57513
fix: address copilot PR review comments for golden tests and artifacts
ARCoder181105 Jul 26, 2026
d1b2220
docs: mark Phase 1 tasks as completed in TASKLIST
ARCoder181105 Jul 26, 2026
c6bef5a
chore: address CodeRabbit PR review comments
ARCoder181105 Jul 26, 2026
bef2a00
chore: address remaining CodeRabbit findings
ARCoder181105 Jul 26, 2026
294f45c
fix(parser): handle f.Close() errcheck and add go-lint to Makefile
ARCoder181105 Jul 26, 2026
a1669df
fix(parser): tighten WalkDir validations and downgrade symlink securi…
ARCoder181105 Jul 26, 2026
ab069c6
fix(ci): rename migration file to .up.sql to fix golang-migrate in CI
ARCoder181105 Jul 26, 2026
7443c98
fix(docs): resolve final CodeRabbit findings for Phase 1
ARCoder181105 Jul 26, 2026
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
41 changes: 22 additions & 19 deletions .github/workflows/ci.yml → .github/workflows/go-ci.yml
Original file line number Diff line number Diff line change
@@ -1,29 +1,15 @@
name: CI
name: Go CI

on:
pull_request:
push:
branches: [main]

jobs:
node:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Version comes from package.json's "packageManager" field (single source of truth).
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile=false
- run: pnpm -r lint
- run: pnpm -r typecheck
- run: pnpm -r build
- run: pnpm -r test

go:
runs-on: ubuntu-latest
permissions:
contents: read
env:
DATABASE_URL: postgres://funcatlas:funcatlas@localhost:5432/funcatlas?sslmode=disable
services:
Expand All @@ -42,9 +28,11 @@ jobs:
--health-retries 5
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-go@v5
with:
go-version: "1.25"
go-version: "1.24"
cache-dependency-path: services/parser/go.sum
- name: Install CGO toolchain
run: sudo apt-get update && sudo apt-get install -y gcc
Expand All @@ -53,6 +41,21 @@ jobs:
with:
version: v2.12.2
working-directory: services/parser
- name: go mod tidy check
working-directory: services/parser
run: go mod tidy && git diff --exit-code go.mod go.sum
- name: go vet
working-directory: services/parser
run: go vet ./...
- name: go test
working-directory: services/parser
run: go test ./...
run: go test -race ./...
- name: go build
working-directory: services/parser
run: go build -o parser ./cmd/parser
- name: parser sample check
working-directory: services/parser
run: ./parser --repo ./testdata/sample --format summary
- name: migration check
run: |
docker run --rm --network host -v $(pwd)/services/parser/migrations:/migrations migrate/migrate -path /migrations -database "${DATABASE_URL}" up
27 changes: 27 additions & 0 deletions .github/workflows/node-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Node CI

on:
pull_request:
push:
branches: [main]

jobs:
node:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
# Version comes from package.json's "packageManager" field (single source of truth).
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm -r lint
- run: pnpm -r typecheck
- run: pnpm -r build
- run: pnpm -r test
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,6 @@ Thumbs.db
.turbo/

# --- Vercel (if used later) ---
.vercel/
.vercel/
out.json
extract_actual.json
7 changes: 5 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@ resolve calls → store in Postgres → explore via React Flow canvas (file →
code block). First language: **TypeScript**.

## Status
Planning only — no code yet. Phases: 0 bootstrap → 1 parser+isolation → 2 storage+resolution →
3 API+auth+canvas+search → 4 webhooks+queue+hardening.
- [x] Phase 0: Bootstrap
- [x] Phase 1: Parser & Isolation
- [ ] Phase 2: Storage & Resolution
- [ ] Phase 3: API, Auth, Canvas & Search
- [ ] Phase 4: Webhooks, Queue & Hardening
Comment on lines +11 to +15
Comment on lines +11 to +15

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Synchronize the Phase 1 status snapshot across all source-of-truth documents.

The documents currently claim three incompatible states: Phase 1 is complete, current/in progress, or not started. Align every listed location with the actual implementation and acceptance state before merging.

  • CLAUDE.md#L11-L15: update the load-bearing phase checklist.
  • PRD.md#L142-L145: update the release-plan status.
  • TASKLIST.md#L9-L25: refresh the Phase 0/Phase 1 baseline.
  • docs/NEXT_MODEL_HANDOFF.md#L17-L20: refresh the handoff status.
  • docs/NEXT_MODEL_HANDOFF.md#L103-L116: refresh the “verified facts.”
  • docs/PHASE1_TASKS.md#L9-L25: refresh the Phase 0 baseline and task starting point.
📍 Affects 5 files
  • CLAUDE.md#L11-L15 (this comment)
  • PRD.md#L142-L145
  • TASKLIST.md#L9-L25
  • docs/NEXT_MODEL_HANDOFF.md#L17-L20
  • docs/NEXT_MODEL_HANDOFF.md#L103-L116
  • docs/PHASE1_TASKS.md#L9-L25
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CLAUDE.md` around lines 11 - 15, Synchronize the Phase 1 status snapshot with
the actual implementation and acceptance state across CLAUDE.md lines 11-15,
PRD.md lines 142-145, TASKLIST.md lines 9-25, docs/NEXT_MODEL_HANDOFF.md lines
17-20 and 103-116, and docs/PHASE1_TASKS.md lines 9-25; update each phase
checklist, release-plan entry, baseline, task starting point, and verified-facts
section consistently, with no locations left describing Phase 1 as complete, in
progress, or not started inconsistently.


## Locked stack (do NOT re-decide without explicit reason)
- Monorepo: **pnpm + Turborepo**; shared types in **`packages/shared`** (Drizzle + Zod).
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Run `make <target>`. Most targets shell out to pnpm/turbo or docker.

.PHONY: install dev build lint typecheck test migrate up down health \
go-build go-test go-vet go-run go-tidy clean
go-build go-test go-vet go-run go-tidy go-lint clean

install: ## Install all workspace dependencies
pnpm install
Expand Down Expand Up @@ -48,6 +48,9 @@ go-vet: ## Vet the Go parser
go-tidy: ## Tidy Go modules
cd services/parser && go mod tidy

go-lint: ## Lint the Go parser
cd services/parser && golangci-lint run

go-run: ## Run the parser against a local repo (usage: make go-run REPO=./path)
cd services/parser && go run ./cmd/parser --repo "$(REPO)"

Expand Down
200 changes: 200 additions & 0 deletions PRD.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# PRD — funcatlas / CodeCanvas

> Product Requirements Document. Single source of truth for *what* we're building and *why*.
> Architecture, stack, and risks live in `docs/` (`ARCHITECTURE.md`, `TECH_STACK.md`,
> `DATA_MODEL.md`, `RISKS.md`, `SECURITY.md`, `UI_GUIDE.md`); this document is the **product
> contract**. Cross-reference `PLAN.md` for the phased execution plan and `TASKLIST.md` for the
> current chunk-by-chunk build.

---

## 1. Vision

An interactive visual map of any codebase: paste a repo URL → we clone it, parse with tree-sitter,
resolve call relationships, store the graph in Postgres, and let you explore it on a React Flow
canvas (file → card → function mind-map → code block). The goal is to make *the shape of a
codebase* legible for repos that no longer fit in one human head — without ever pretending to know
what a call points to when it doesn't.

**One-liner:** *"See the shape of any codebase — functions, calls, and confidence, visually."*

---

## 2. Why now / problem

- Codebases outgrow a single engineer's working memory; onboarding to a large repo is slow.
- Existing tools (IDE go-to-definition) are **local and per-file**; none give a *repo-level* map.
- Static-analysis dashboards are tables/numbers, not **explorable** graphs.
- A trustworthy, repo-wide visual call graph — with explicit uncertainty baked in — closes the gap.

---

## 3. Target users & personas

- **P1 — Mid/senior engineer onboarding to an unfamiliar repo.** *"Where do I even start?"* Wants
the high-level shape: entry points, hot functions, who calls what.
- **P2 — Tech lead / maintainer of a large TS project.** *"What's the blast radius if I change
this function?"* Wants N-hop traversal and caller sets.
- **P3 — OSS contributor evaluating a project.** Quick orientation before a first PR.

**Non-goal for MVP:** multi-tenant teams, RBAC, sharing canvases, real-time collaboration.

---

## 4. Goals & non-goals (MVP)

### Goals
- Parse a public TS repo end-to-end → trustworthy, confidence-tagged call graph persisted in Postgres.
- Explore it on a premium dark-mode React Flow canvas: sidebar → card → mind-map → code.
- GitHub OAuth login; register a repo by URL; automatic incremental refresh via webhook + queue.
- Function-name search across the repo (⌘K palette + search box).
- Production-grade isolation: parser runs non-root, read-only, no network egress, no symlink
follow, file/size/binary caps.

### Non-goals (post-MVP)
- LSP-based resolution (ships later as a v2 upgrade of `name_match`/`unresolved` → `exact`).
- Excalidraw freehand annotation layer.
- Multi-language support (TypeScript first; go deep before breadth).
- Neo4j / dedicated graph database.
- Real-time multi-user editing, saved canvas layouts.

---

## 5. User stories

- As a visitor, I land on `/` and see an animated live-graph hero so I understand the product in
5 seconds, then "Try a repo" → GitHub OAuth.
- As a logged-in user, I paste a repo URL → the repo is cloned/parsed → I see its file tree.
- As a user, I click a file → a card appears → click the card → a mind-map of that file's functions
branches out → click a function → Shiki-highlighted code block, with cross-file call links.
- As a user, edges show confidence: **solid** (`exact`) / **dashed** (`name_match`) / **dotted**
(`unresolved`) — a guess is never drawn as a fact.
- As a user, I ⌘K / search a function name → jump to it anywhere in the repo.
- As a user, when the repo's maintainer pushes, the graph updates automatically (webhook → queue).
- As a maintainer, I see the blast radius of changing a function (N-hop traversal).

---

## 6. Functional requirements

- **FR-1 Ingestion** — accept a public GitHub repo URL (post-OAuth, repo-read scope) **or** a local
path; clone/read into an isolated environment; never execute the repo's build/install scripts.
- **FR-2 Parsing** — tree-sitter extracts function/method definitions (name, qualified name, line
range, source), call expressions (callee name, enclosing caller, line), and import statements.
- **FR-3 Input hardening** — reject symlink/path-traversal escapes; skip files >1MB, binary files,
and dirs under `node_modules`/`.git`/`dist`/`build`; cap total file count and tree depth.
- **FR-4 Resolution** — link each call to a definition via same-file → imported symbol → package
fallback → unresolved; tag every edge `exact`/`name_match`/`unresolved`.
- **FR-5 Storage** — persist repos, files, functions, edges to Postgres (schema in `DATA_MODEL.md`),
with overload-safe uniqueness, `ON DELETE CASCADE`, and `parsed_commit`/`updated_at` for incremental diff.
- **FR-6 Graph API** (session-gated) — list repos; file tree; functions for file; edges for function;
raw source; search functions by name across a repo.
- **FR-7 Canvas** — sidebar file tree; card → mind-map → code; confidence-styled edges; minimap;
multi-open; ⌘K palette + name search; ≤2000 visible-node target with expand-on-click.
- **FR-8 Auth** — GitHub OAuth from day 1 (repo-read scope, refresh); Redis sessions; rate-limit.
- **FR-9 Incremental refresh** — GitHub webhook (HMAC-verified, replay-protected, per-repo
throttled) → BullMQ → diff changed files → re-parse only those → re-link only affected edges
(renames/deletes update every edge pointing at the old function — no orphans).
- **FR-10 Isolation runtime** — parser container runs non-root, read-only rootfs, no caps, and
**network NONE** during parse (clone happens in a separate network-enabled sidecar sharing tmpfs).

---

## 7. Non-functional requirements

- **NFR-1 Performance** — parse a 300-file TS repo in **< 90s**; `GET functions for file` p95
**< 150ms**; N-hop (depth 5, 10k edges) **< 500ms**; canvas 60fps at ≤2000 visible nodes.
- **NFR-2 Security** — no network egress during parse; untrusted repo can't read host files
(symlink/path containment); webhook replay/flood safe; session-gated API; secrets via env.
- **NFR-3 Correctness** — re-parse of a renamed/deleted function leaves **no orphan edges**;
resolution never silently claims certainty it lacks (confidence is first-class).
- **NFR-4 Operability** — single `docker compose up` brings up postgres, redis, api, web, parser;
`/healthz` endpoints; logs via `zap` (parser) + `pino` (api).
- **NFR-5 Maintainability** — shared TS types in `packages/shared`; single SQL migration source
(`services/parser/migrations/`); no full ORM on the Go side (`sqlx` explicit SQL); Drizzle on TS API side.
- **NFR-6 UX** — premium dark-mode-first; accent tokens; purposeful Framer Motion; skeleton/shimmer
loading; actionable errors; `prefers-reduced-motion` respected.

---

## 8. Success metrics (MVP)

- A real 300-file public TS repo parses, resolves, and renders end-to-end without OOM or error.
- N-hop traversal returns in <500ms at 10k edges; p95 `functions-for-file` <150ms.
- **0** successful symlink-escape / oversized-file reads (negative tests green).
- Pushing a commit to a registered repo updates the graph automatically; replayed/out-of-window
webhook rejected; webhook flood throttled.
- A pilot user can navigate an unfamiliar repo to *find the entry point* in <5 min.

---

## 9. Scope boundaries

**In:** TypeScript only; public repos via GitHub OAuth; single-user (per-repo isolation, not
multi-user); name/scope resolution; webhook incremental updates; function-name search.

**Out:** LSP resolution; Excalidraw; multi-language; Neo4j; layout persistence; multi-tenant.

---

## 10. Release plan (4 phases)

- **Phase 1 — Parser core + isolation** *(completed; branch `phase-1/parser-core-and-isolation`,
PR #21)*. Given a local repo path → correct IR JSON for TypeScript, with hardening and an
isolated Docker image. No DB writes, no UI. See `docs/PHASE1_TASKS.md` and `TASKLIST.md`.
- **Phase 2 — Storage + resolution**. Persist IR to Postgres; name/scope resolver with confidence;
re-parse leaves no orphans.
- **Phase 3 — API + auth + canvas + search**. GitHub OAuth; graph endpoints; React Flow canvas;
⌘K + name search; premium dark UI per `docs/UI_GUIDE.md`.
- **Phase 4 — Webhooks + queue + hardening**. BullMQ; HMAC webhook; incremental re-parse/relink;
full `SECURITY.md` compliance; `/healthz`.

---

## 11. Risks & open decisions

Tracked in `docs/RISKS.md` (R1–R18). Status snapshot at PRD authoring:

- **Pre-Phase-0 (resolved by code):** R10 (single migration source — `services/parser/migrations/`),
R9 (Go can't import TS shared types — `internal/ir/ir.go` carries Go-native IR; the RISKS.md OPEN
box is stale and should be flipped to DECIDED in `TASKLIST.md` chunk C14).
- **Pre-Phase-1 (decision due now):** R16 (pin `tree-sitter-typescript` grammar version).
- **Pre-Phase-2:** R6 (TS "package" definition), R7 (`qualified_name` format), R8 (overload +
edges → edges to overloaded `qualified_name`s tagged `unresolved`).
- **Pre-Phase-3/4:** R2 (session strategy), R4 (GitHub OAuth app), R5 (local webhook tooling),
R11–R15 (dev/prod modes, parse lock, webhook debounce, repo cleanup, canvas virtualization),
R17 (CI needs Docker), R18 (UI).
- **R1** (product vs repo name) and **R3** (benchmark repo) are *deferred, not blocking* Phase 1.

### Confirmed decisions (locked during PRD review)

- **Overload detection — future-proof:** assign `overload_index` in a **per-file post-pass** after
extraction + the qualified-name scope walk. Group functions in a file by `qualified_name`, order
by `start_line`, assign `0..n-1`. Same-name in *different* scopes (top-level `sync` vs `Repo.sync`)
already get distinct `qualified_name`s → each index `0` (not overloads). Genuine TS overloads
share `qualified_name` → get `0,1,2,…`. Phase 2 tags edges to overloaded `qualified_name`s
`unresolved` (R8). `overload_index` is part of the DB uniqueness key, so Phase 4's
delete-then-reinsert incremental relink never collides on `UNIQUE` and is stable across identical
re-parses (keyed by `start_line`).
- **Query loading:** `queries/typescript.scm` is embedded at build time (via `//go:embed`) and compiled at runtime.
- **`.gitignore` respect:** deferred post-MVP (skip-list already covers `node_modules`/etc.).
- **Clone vs parse containers:** separate `parser-clone` (network enabled) → shared tmpfs →
`parser-parse` (`network none`, read-only, non-root, no caps).
- **Naming convention picks:** top-level `qualified_name` = bare name; module-level call's
`CallerQualified` = `"<module>"`; `package_path` = `""` for files at repo root.
- **R3 benchmark:** defer to end of Phase 1 — use only `testdata/sample` now; pick a real ~300-file
TS OSS repo right before exit testing.

---

## 12. Glossary

- **IR** — intermediate representation; Go structs the parser emits (`File`, `Function`,
`CallSite`, `Import`, `Graph`) in `services/parser/internal/ir/ir.go`.
- **`qualified_name`** — scope-aware function key (e.g. `Repo.sync`, `getUser.inner`); the
uniqueness guarantee for `(file_id, qualified_name, overload_index)`.
- **`overload_index`** — disambiguator `0..n-1` assigned per `(file, qualified_name)` post-pass.
- **`resolution_confidence`** — `exact` / `name_match` / `unresolved`; drives UI edge style.
- **blast radius** — N-hop traversal over `edges` (recursive CTE, depth-bounded).
- **relink** — Phase 4 action: on rename/delete, update every edge pointing at the old function
so no orphan edges remain.
Loading
Loading