vinta-analyze-codebase · diff
git:20260730.68ad759 to git:20260817.b83d50b
3 added, 2 removed. Audit B to B.
---
name: vinta-analyze-codebase
description: Walk a repository and produce a structured inventory of its languages, frameworks, build tools, test setups, deploy targets, monorepo shape, env model, multi-tenancy patterns, and CI providers. Used by [vinta-bootstrap-ai-tools](../vinta-bootstrap-ai-tools/SKILL.md) as the first step before drafting AGENTS.md / sub-agents / skills, but also runnable standalone when you want a quick "what is this codebase made of" report. Produces an in-memory inventory that downstream skills consume; does not write files.
---
# Analyze codebase
Build an inventory of what's in the repo so other skills don't have to re-discover it. The output is a structured summary, **not** a written file — the orchestrator passes it as context to the next sub-skill.
## What to scan, in order
Stop at the first finding for each section unless the section says otherwise. Don't recurse into `node_modules/`, `.git/`, `dist/`, `build/`, `.turbo/`, `.next/`, `__pycache__/`, `.venv/`, `target/`.
### 1. Top-level fingerprint
`ls -la` at repo root + `git log --oneline -10`. Capture:
- Repo name (from `package.json:name`, `pyproject.toml:[project].name`, `Cargo.toml:[package].name`, or git remote).
- Default branch (`git symbolic-ref refs/remotes/origin/HEAD` or fall back to `main`/`master`).
- Code host (parse `git remote get-url origin` — github.com / gitlab / bitbucket / self-hosted).
- Existing `AGENTS.md` / `CLAUDE.md` / `.cursorrules` / `.github/copilot-instructions.md` / `CONTRIBUTING.md` — read these in full. Existing conventions are gospel.
### 2. Languages + package managers
| Signal file | Language / package manager |
|---|---|
| `package.json` | JavaScript / TypeScript |
| `pnpm-lock.yaml`, `pnpm-workspace.yaml` | pnpm + (likely) workspaces |
| `yarn.lock` | yarn |
| `package-lock.json` | npm |
| `tsconfig.json` (one or many) | TypeScript |
| `pyproject.toml` | Python (modern) |
| `requirements*.txt`, `setup.py`, `setup.cfg` | Python (legacy) |
| `Cargo.toml` | Rust |
| `go.mod` | Go |
| `Gemfile` | Ruby |
| `pom.xml`, `build.gradle*` | Java / Kotlin |
| `composer.json` | PHP |
Capture every match — many repos are polyglot. Read each lockfile / `package.json` / `pyproject.toml` to extract direct dependencies (skip transitive).
### 3. Frameworks (from dependencies)
Match top-level deps against this table:
| Dep / signal | Framework |
|---|---|
| `react`, `react-dom` | React |
| `next` | Next.js (check `next.config.*` for App Router signals: `app/` directory) |
| `vite`, `@vitejs/*` | Vite |
| `@medplum/core`, `@medplum/fhirtypes`, `@medplum/react` | **Medplum** (FHIR backend) |
| `mantine`, `@mantine/*` | Mantine UI |
| `tailwindcss` | Tailwind |
| `@vercel/*` | Vercel platform / SDKs |
| `playwright`, `@playwright/test` | Playwright e2e |
| `vitest`, `jest` | Vitest / Jest |
| `biome`, `@biomejs/biome` | Biome lint+format |
| `eslint`, `prettier` | ESLint / Prettier |
| `Django` (in pyproject / requirements) | **Django** |
| `psqlextra`, `django-postgres-extra` | Django + PG partitioning |
| `strawberry-graphql` | Strawberry GraphQL |
| `fastapi` | FastAPI |
| `flask` | Flask |
| `pytest` | pytest |
| `mypy`, `ruff`, `black` | Python lint/type tools |
| `@tanstack/react-start`, `@tanstack/start` | TanStack Start (check `@tanstack/react-router`) |
| `react-router`, `react-router-dom`, `@react-router/*` | React Router |
| `tRPC`, `@trpc/*` | tRPC |
| `prisma`, `@prisma/client` | Prisma |
| `drizzle-orm` | Drizzle |
| `mongoose` | Mongoose / MongoDB |
Record stack matches — these drive which template directories `vinta-bootstrap-ai-tools` copies from.
### 4. Build / dev / lint / test commands
For Node/TS: read `package.json:scripts`. Capture: `dev`, `build`, `test`, `test:*`, `lint`, `lint:fix`, `format`, `typecheck`. Also: workspace tasks (`turbo run …`, `pnpm --filter …`).
For Python: read `pyproject.toml:[tool.poetry.scripts]` / `[project.scripts]` + `Makefile` + `tox.ini` + `noxfile.py`. Capture: test runner command, lint command, type-check command, dev server command.
For other languages: read `Makefile` + `justfile` + language-specific files (`Cargo.toml:[bin]`, etc).
Note: many repos have the test command in CI config but not surfaced as a top-level script. Read `.github/workflows/*.yml` / `.gitlab-ci.yml` / `.circleci/config.yml` / `Jenkinsfile` to confirm.
### 5. Monorepo shape
- `pnpm-workspace.yaml` → list workspaces.
- `turbo.json` → tasks + pipeline graph.
- `nx.json` / `lerna.json` → Nx / Lerna.
- `apps/` + `lib/` (or `apps/` + `packages/`) directory pattern → conventional monorepo layout.
- Single-package repos: report as such; downstream skills need to know.
For each app/package: name + path + framework (cross-ref the [Frameworks (from dependencies)](#3-frameworks-from-dependencies) scan above) + entry point.
### 6. Tests
- Unit / integration: where are they? `apps/*/src/**/*.test.ts`, `tests/`, `__tests__/`, `<pkg>/tests/unit`, `<pkg>/tests/integration`. Glob.
- E2E: `e2e/`, `playwright/`, `cypress/`. Read fixtures + setup files for: auth strategy, seed helpers, tenant scoping in seeds.
- Test data: factories, fixtures, mocks. Note conventions (`@medplum/mock`, `factory_boy`, MSW, etc).
- **Unit-test conventions (feeds `write-unit-test`).** Read a representative sample of existing unit tests (not e2e) and infer the **dominant** pattern for each axis below. These pre-fill `skills.write-unit-test.*` in the bootstrap interview (group C.9), so the shipped skill matches the suite instead of imposing a house style. Report `framework-default` for any axis where the suite is mixed or there's no signal (greenfield):
- **test_style** — `functions` (standalone `def test_x` / bare `it(...)`) vs `classes` (`TestCase` subclasses, `describe` grouping). Which dominates?
- **data_setup** — `factories` (factory_boy / FactoryBot / builder helpers — cross-ref `test_data_helpers`), `fixtures` (pytest fixtures / fixture files / `setUpTestData`), or `inline` (objects built in the test body).
- **assertion_style** — `plain-assert` (Python `assert` / JS `expect`) vs `framework-methods` (`self.assertEqual`-family).
- **db_isolation** — `transaction-rollback` (Django `TestCase`, pytest-django `db`, SQLAlchemy savepoint fixtures), `truncate` (`TransactionTestCase`), or `recreate`. Read the test base classes / DB fixtures to tell.
- **extras** — any other recurring convention worth carrying (test file co-location + naming pattern, one-assertion-per-test, a house HTTP-mock helper / recorded-cassette pattern, custom assertion helpers). List them; they seed `additional_conventions`.
### 7. Deploy / release model
- `.github/workflows/deploy*.yml` / `.gitlab-ci.yml` deploy stages — capture target (Vercel, AWS, Heroku, K8s, custom), trigger (push to main vs release tag vs manual), environments named (`staging`, `production`, `preview`).
- `vercel.json` / `vercel.ts` → Vercel project config.
- `Dockerfile` + `docker-compose*.yml` → containerized.
- `helm/`, `k8s/`, `kustomize/` → Kubernetes.
- `scripts/deploy*` / `scripts/release*` → manual deploy paths.
- For Medplum repos: look for `pnpm bots:deploy`, `deploy-bots.ts`, AccessPolicy seed scripts.
### 8. Environments + multi-tenancy
- `.env.example` files at root + per-app — list var names (not values), note prefix patterns (`VITE_`, `NEXT_PUBLIC_`, `REACT_APP_`).
- Vite `envPrefix` allowlist (`vite.config.ts`).
- Turbo `tasks.<name>.env` arrays (cache hash inputs).
- Multi-tenancy signals:
- `tenant_id` columns in models / SQL schemas → row-level multi-tenancy.
- `meta.account` references in FHIR resource creation → Medplum tenant compartmenting.
- `Organization` resource + `_compartment=Organization/<id>` queries → Medplum org-scoped.
- `<env>__<name>` prefix patterns in deploy scripts → per-env isolation in shared backend.
- `psqlextra` partitioning by tenant key → partition-based isolation.
- Single-tenant: explicitly note absence of these signals.
### 9. CI / CD
- CI provider: GitHub Actions, GitLab CI, CircleCI, Jenkins, Buildkite, custom.
- Workflow files, named jobs, on-which-events (push, pull_request, schedule, release).
- Required checks (branch protection if visible via `.github/branch-protection.yml` or surfaced in CONTRIBUTING).
- Pre-commit hooks: `.pre-commit-config.yaml`, `.husky/`, `lefthook.yml`.
### 10. Documentation already present
- `README.md` at root + per-app.
- `docs/` directory.
- Architecture decision records (`docs/adr/`, `docs/architecture/`).
### 11. Existing AI-tooling artifacts
This is a load-bearing input for the rest of the bootstrap flow — surface every artifact found, even partial/legacy ones. Read each in full; they tell you what conventions already exist so the downstream `vinta-write-agents-md` / `vinta-derive-subagents` / `vinta-derive-skills` steps can **preserve, merge, or migrate** rather than overwrite.
**Instruction documents** — search for any of these and capture their content:
- - `AGENTS.md` (root + per-app in monorepos).
+ - `AGENTS.md` (root + per-app in monorepos; also the legacy `ai-tools/AGENTS.md` path).
- `CLAUDE.md`, `.cursorrules`, `.cursor/rules/*.mdc`, `.github/copilot-instructions.md`.
- Codex / Cursor / Copilot instructions inside vendor dirs (`.codex/AGENTS.md`, etc.).
- Anything tagged "AI conventions" / "Coding agent guide" / "LLM rules" in CONTRIBUTING / docs.
**Skills** — for each vendor path, list every skill found with its `name` + `description` (from frontmatter):
| Path | What lives there |
|---|---|
| `.claude/skills/<name>/SKILL.md` | Claude Code project skills |
| `.cursor/skills/<name>/SKILL.md` | Cursor project skills |
| `.codex/skills/<name>/SKILL.md` | Codex project skills (legacy path) |
| `.github/skills/<name>/SKILL.md` | Copilot project skills |
| `.agents/skills/<name>/SKILL.md` | Universal skills (Codex/Cursor/Copilot) |
| `ai-tools/skills/<name>/SKILL.md` | Vinta canonical layout (already-bootstrapped) |
For each skill, also classify:
- `vinta-managed` — directory name starts with `vinta-` (installed by `vinta-ai-workflows`; can be left alone or refreshed via that CLI).
- `foundation-shape` — name matches the Vinta foundation set (`plan-feature`, `create-spec`, `create-qa-use-cases`, `implement-plan`, `implement-phase`, `review-phase`, `integrate-phase`, `amend-plan`, `add-e2e-test`, `add-env-var`, `write-unit-test`). `implement-phase` / `review-phase` / `integrate-phase` are the plan-execution sub-skills co-shipped with `implement-plan`.
- `project-custom` — anything else; written by the team.
**Sub-agents** — list every agent file with its name + description:
| Path | Format | Purpose |
|---|---|---|
| `.claude/agents/<name>.md` | markdown + frontmatter | Claude Code |
| `.cursor/agents/<name>.md` | markdown + frontmatter | Cursor |
| `.codex/agents/<name>.toml` | TOML | Codex |
| `.github/agents/<name>.agent.md` | markdown + frontmatter | Copilot |
| `ai-tools/agents/<name>.yaml` | YAML | Vinta canonical |
For each agent, also classify:
- `foundation-shape` — name matches `implementer` / `reviewer` / `fixer`.
- `stack-specialist` — recognizable stack agent (e.g. `migration-author`, `deploy-author`).
- `project-custom` — anything else.
**Setup wiring** — note presence of:
- `ai-tools/scripts/setup-ai-tools.mjs` (Vinta canonical multi-vendor setup).
- `package.json#scripts.setup:ai-tools` or similar alias.
- - Symlinks: `AGENTS.md → ai-tools/AGENTS.md`, `.claude/skills → ../ai-tools/skills`, etc.
+ - Symlinks: `CLAUDE.md → AGENTS.md`, `.github/copilot-instructions.md → ../AGENTS.md`, `.claude/skills → ../ai-tools/skills`, etc.
+ - **Legacy layout**: a root `AGENTS.md` that is a *symlink* into `ai-tools/AGENTS.md` predates the move of the doc to the repo root. Record it — [vinta-sync-ai-tools](../vinta-sync-ai-tools/SKILL.md) migrates it.
**Plan/spec docs** — flag presence (full inventory comes later from [vinta-migrate-plans-specs](../vinta-migrate-plans-specs/SKILL.md)):
- `ai-plans/` directory contents.
- Loose `IMPLEMENTATION_PLAN.md`, `SPEC.md`, `RFC.md` at root or under `docs/`.
**PR / MR templates** — list every existing template file (case-insensitive). Used by `implement-plan` / `amend-plan` to scaffold the prs-context `# Description` so generated PRs match the project's existing convention. Search:
- GitHub:
- `.github/pull_request_template.md` (and `PULL_REQUEST_TEMPLATE.md` casing).
- `.github/PULL_REQUEST_TEMPLATE/*.md` (multi-template directory).
- `pull_request_template.md` and `PULL_REQUEST_TEMPLATE.md` at repo root.
- `docs/pull_request_template.md` and `docs/PULL_REQUEST_TEMPLATE.md`.
- GitLab:
- `.gitlab/merge_request_templates/*.md`.
- Bitbucket / others: typically configured server-side, no file convention. Note in inventory if user-supplied.
For each template found, capture:
- Path (relative to repo root).
- A one-line summary of the section structure (`## Summary`, `## Test plan`, `## Screenshots`, `<!-- comments -->`, etc.) so downstream skills know how to fill it.
Output goes into `existing_ai_artifacts.pr_templates` (see schema below). The full content is read on demand by `implement-plan` / `amend-plan` when drafting prs-context files; analyze-codebase only enumerates.
If anything is found, **do NOT overwrite** — the downstream skills will read this section and decide preserve / merge / migrate per artifact, gated on user confirmation in [vinta-bootstrap-ai-tools](../vinta-bootstrap-ai-tools/SKILL.md#e-existing-ai-artifacts-per-artifact-disposition)'s **Existing AI artifacts (per-artifact disposition)** interview group.
## Output: structured inventory
Return a single structured object (in conversation, as a tool result, or just printed for the user / orchestrator to read). Shape:
```yaml
repo:
name: <string>
default_branch: <string>
code_host: github | gitlab | bitbucket | self-hosted | unknown
existing_ai_artifacts:
instructions:
- { path: AGENTS.md, content: <full text> }
- { path: .github/copilot-instructions.md, content: <full text> }
- ...
skills:
- { path: .claude/skills/checkout-flow/SKILL.md, name: checkout-flow,
description: <from frontmatter>, classification: project-custom }
- { path: ai-tools/skills/plan-feature/SKILL.md, name: plan-feature,
description: <...>, classification: foundation-shape }
- ...
agents:
- { path: .claude/agents/migration-author.md, name: migration-author,
description: <...>, classification: stack-specialist }
- ...
setup:
- ai-tools/scripts/setup-ai-tools.mjs
- package.json#scripts.setup:ai-tools
pr_templates:
- { path: .github/pull_request_template.md, sections: [Summary, Test plan, Screenshots] }
- { path: .github/PULL_REQUEST_TEMPLATE/feature.md, sections: [...] }
plans_dir_present: true | false # full migration list comes from vinta-migrate-plans-specs
# downstream skills MUST read existing_ai_artifacts before drafting anything new.
languages:
- typescript | python | rust | go | ...
package_managers:
- pnpm | npm | yarn | poetry | pip | cargo | ...
frameworks:
# each with version range + path of signal file
- { name: react, version: "^19", signal: package.json }
- { name: medplum, version: "^5", signal: package.json }
- { name: django, version: "^5.1", signal: pyproject.toml }
- ...
monorepo:
type: turbo | pnpm-workspaces | nx | lerna | none
apps:
- { name: <string>, path: <string>, framework: <string>, port: <int|null> }
packages:
- { name: <string>, path: <string>, role: shared-lib | sdk | ... }
commands:
install: <e.g. pnpm install>
dev: <e.g. pnpm dev>
build: <e.g. pnpm build>
test_unit: <e.g. pnpm test>
test_e2e: <e.g. pnpm test:e2e>
lint: <e.g. pnpm lint>
format: <e.g. pnpm format>
typecheck: <e.g. pnpm build (tsc) — or null if no separate command>
tests:
unit_framework: vitest | jest | pytest | ...
unit_paths: [<glob>]
e2e_framework: playwright | cypress | none
e2e_paths: [<path>]
test_data_helpers: [<file>] # factories, mock clients, seed scripts
conventions: # inferred dominant patterns → skills.write-unit-test.* (framework-default when mixed / no signal)
test_style: functions | classes | framework-default
data_setup: factories | fixtures | inline | framework-default
assertion_style: plain-assert | framework-methods | framework-default
db_isolation: transaction-rollback | truncate | recreate | framework-default
extras: [<free-form recurring convention>] # seeds additional_conventions; [] when none
deploy:
targets: [vercel, aws-ecs, heroku, k8s, medplum-bots, npm-publish, ...]
envs: [staging, production, preview, dev-<handle>, ...]
ci_provider: github-actions | gitlab-ci | circleci | jenkins | none
workflow_files: [<path>]
triggers: [push:main, release, manual, ...]
manual_deploy_paths: [<script paths that humans run by hand, e.g. npx medplum post Bundle ...>]
env_model:
example_files: [<.env.example paths>]
vars_count: <int>
vite_envprefix: [<list>] | null
turbo_env_lists: { <task>: [<vars>] } | null
multi_tenancy:
pattern: row-level | medplum-compartment | partitioned | per-env-prefix | none
evidence: [<file:line refs>]
ci:
provider: github-actions | gitlab-ci | ...
jobs: [<name>]
pre_commit: husky | pre-commit | lefthook | none
docs:
readme: present | absent
docs_dir: <path or null>
adrs: <path or null>
stacks_matched:
# cross-ref to bootstrap-ai-tools/resources/stacks/<name>
- medplum
- typescript-monorepo
- django
- ...
```
## Rules
- **Read, don't guess.** Every claim in the inventory cites a file. If you can't find the signal, the field is `null` or `unknown`, not a guess.
- **Don't run code.** No `pnpm install`, no `pytest`, no shell commands beyond `ls`, `cat`, `grep`, `git log`. Pure read.
- **Stop at the first match per section unless the section says otherwise.** A repo is a TypeScript repo if `package.json` + `tsconfig.json` exist; you don't need to grep every `.ts` file to confirm.
- **Existing AI artifacts override stack defaults.** If `AGENTS.md` exists, its content beats your detection. Surface conflicts (e.g. AGENTS.md says "use yarn" but lockfile is `pnpm-lock.yaml`) as findings, don't silently pick one.
- **Never use `§N` shorthand in any inventory output, summary, or downstream prompt.** Reference sections by their full heading name. `§N` references break when the source's section numbering changes.
## Pitfalls
- **Treating dev-deps as framework signals.** A repo with `playwright` in `devDependencies` doesn't necessarily have e2e tests — confirm by checking for `playwright.config.*` + `e2e/` or equivalent.
- **Inferring multi-tenancy from a `tenant_id` column on one model.** Check that the column is on the hot tables and that queries actually filter by it. A vestigial column is not a tenancy pattern.
- **Reporting `medplum-bots` as a deploy target without verifying.** `@medplum/core` in deps → SDK usage. A `bots/` directory + `deploy-bots.ts` script → actual bot deploy path. Different things.
- **Polyglot repos with mismatched conventions per language.** Report each language's conventions separately if they diverge (e.g. JS uses Biome, Python uses ruff). Don't average.