Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
150 changes: 150 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Contributing

Thanks for looking. This is a tool that reads other people's repositories and
draws conclusions about them, and almost every rule below follows from that.

## Getting set up

```bash
git clone https://github.com/BryanZaneee/code-atlas.git
cd code-atlas
npm test # node --test test/*.test.mjs
node bin/atlas.mjs build --repo . --out atlas.html
```

Corpus tests skip when the validation repos are absent, so a fresh clone is
green.

## Posture checklist

The full version is [`docs/posture-checklist.md`](./docs/posture-checklist.md),
and it is the document to read before your first change. In short:

- **Do not leak the target repository.** No scanned source or file contents in
logs, errors, or stack traces; no secrets in source; `--json` is the
sanctioned channel; `--embed-source` stays opt-in and badged.
- **Do not overclaim.** Never emit a phantom endpoint; never let a modelled
path read as an observed one; never draw a number the tool does not measure;
nothing target-specific in `src/` or `bin/`; every classification carries its
provenance.
- **Secure defaults.** `serve` binds `127.0.0.1` as a literal; file access is
allowlist membership, not path sanitization; the proxy takes no host and no
URL; the tool is read-only on the target repo, `atlas init` excepted.
- **Dependencies are a discussion.** Propose one in the pull request, weighed
against the single-file promise and install friction. Vendoring is the third
option. Never add one to make a single target repo work.

## Trunk-based workflow

`develop` is the shared integration trunk. All changes must be made on a
short-lived branch and submitted in a pull request targeting `develop`; do not
commit directly to the trunk. Start each branch from the current `develop`, keep
it focused on one issue or small change, and delete it after the pull request is
merged.

`master` is the release branch and receives pull requests from `develop` alone.
Both are protected: a direct push is refused by GitHub, not by convention.

Use this branch format:

```text
<type>/<issue#>-<short-kebab-description>
```

Allowed types are `feature`, `fix`, `chore`, `docs`, and `test`. They are the
same vocabulary as the commit prefixes below, so a branch and its commits agree.
For
example: `feature/42-ts-migrate-adapters`.

## Commits

Conventional commits, 50/72:

- Subject ≤ 50 chars, imperative mood, no trailing period: `<prefix>: <what>`
- Blank line, then a body wrapped at 72 chars: **what and why, never how**
- Prefixes: `feat:` `fix:` `docs:` `refactor:` `test:` `chore:` `perf:` `style:`
`ci:` `build:` `revert:`
- One logical change per commit; the body is optional for trivial changes

**Working code and its test land in the same commit.** A `feat:` that a later
`test:` covers is two commits that cannot be bisected apart.

**Documentation moves with the code, in the same commit:**

| when | update |
| --- | --- |
| any box in a phase is finished | tick it in `ROADMAP.md` |
| a phase's gate passes | flip its status marker and the header count |
| the payload gains or changes a field | `docs/payload-schema.md`, then re-baseline the golden |
| an adapter's interface changes | `docs/adapters.md`, because it is an API change |
| a config key is added | `docs/config.md` |
| a design decision is made or reversed | `PLAN.md`, with the reason |

## Goldens

`test/golden/` holds committed payloads, not hashes, so a failure prints a diff
rather than reporting that *something* moved. Re-baseline them **deliberately**,
in the same commit as the change that moved them, and read the diff:

```bash
UPDATE_GOLDEN=1 npm test # then READ the diff before committing it
```

A golden that moves in a refactor is a bug, not a rebaseline.

## Pull request conventions

Pull requests must target `develop`, remain small enough for effective review,
and use the repository pull-request template. Complete its `Summary`, `Why`,
`Changes`, and `Testing` sections, including the applicable checks and tests
that were run. Complete the checklist before requesting review.

If a pull request changes an architectural decision, create or update the
relevant ADR under [`docs/adr/`](./docs/adr/) and link it in the pull request.
If no architectural decision changes, explicitly mark the ADR item as not
applicable. The threshold is **expensive to reverse**; see
[ADR-0001](./docs/adr/0001-record-architecture-decisions.md).

## Where things live

```
src/adapters/ ts · py · generic · index language knowledge, and the only place for it
src/scan/ source · walk acquire a ref, walk the tree
src/model/ graph · classify · endpoints · mounts · derive · tests · metrics
· chrome · findings
src/cli/ report · progress terminal output; reads a finished payload
src/serve/ server loopback viewer + read-only source
src/build/ build · assemble the pipeline, and the single-file viewer
src/viewer/ 00-… 90-… concatenated, in filename order
```

Two seams are load-bearing:

**`src/adapters/` ↔ `src/model/`.** Anything that turns a specifier string into
a file path is language-specific and belongs in an adapter; everything
downstream of "the edge list exists" is language-agnostic and belongs in the
model. Adapters are the documented contribution surface, so changes there are
API changes. See [`docs/adapters.md`](./docs/adapters.md).

**The viewer is concatenated in both modes.** `src/viewer/00-*.js … 90-*.js` are
joined into one script, once by `src/build/assemble.mjs` for `build`, and per
request by `src/serve/server.mjs`. Separate `<script src>` tags are not an
option: top-level `const` is script-scoped, so multiple tags would work in
`build` and break in `serve`. **Any new viewer file must be safe to
concatenate**, with no duplicate top-level names. `test/viewer.test.mjs`
enforces it.

## Adding a language

Copy `src/adapters/generic.mjs`, copy a fixture from `fixtures/hostile-ts/` or
`fixtures/hostile-py/`, and assert the exact resolution you expect,
**including the rows that must come back unresolved.** That is the honesty path, and a
counting test cannot protect it. `docs/adapters.md` walks the interface.

## Scope

PLAN.md's *Explicitly deferred* list is binding: no AST parsing, no call-graph
analysis, no real tracing or per-hop timings, no multi-repo diffing, and no
writing to the target repo beyond `atlas init`. The code viewer is read-only,
with no search and no editing. If a request amounts to "like Postman" or "like
VS Code", the answer is no.
136 changes: 135 additions & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@

Progress tracker for [PLAN.md](./PLAN.md). A phase is done when **every** box under it is checked — the gate is the definition of done, not a suggestion.

**Status:** Phases 0, 2, 2.5, 2.6, 3, 4, 5 and 6 complete · 8 of 13. Phase 1
**Status:** Phases 0, 2, 2.5, 2.6, 3, 4, 5 and 6 complete · 8 of 16. Phase 1
holds one gate a human has to measure. Phase 7's server and reader are in; what
is left there is `--embed-source` and `--gzip-source`, which share a gate.

Phases 10.5, 11 and 12 are broken into branch-sized tickets — see
**[The ticket ledger](#the-ticket-ledger)**. Work is tracked as GitHub issues;
the local ids (`W1`, `T4`, `E2`) are what dependencies and commit bodies
reference, and stay stable regardless of issue numbering.

| # | Milestone | Unblocks | Status |
| --- | --- | --- | --- |
| 0 | Repo skeleton, lift-and-shift, payload contract | everything | ● done |
Expand All @@ -21,6 +26,9 @@ is left there is `--embed-source` and `--gzip-source`, which share a gate.
| 8 | Request composer UI | 9 | ○ |
| 9 | Live proxy mode | — | ○ |
| 10 | Open-source packaging | — | ○ |
| 10.5 | Trunk workflow, PR conventions, CONTRIBUTING | 11, 12 | ◐ |
| 11 | Full TypeScript migration | 12 | ○ |
| 12 | In-place editing under `serve --allow-write` | — | ○ |

Legend: ○ not started · ◐ in progress · ● done · [~] deliberately deferred, with the reason

Expand Down Expand Up @@ -420,6 +428,132 @@ reference screenshots the user supplied, plus four things they named directly.*
- [ ] `docs/config.md`
- [ ] **Gate:** a reader who has never seen the repo goes from `git clone` to a rendered atlas of their own project using only the README, on a repo with no config

---

## The ticket ledger

Phases 10.5, 11 and 12 are branch-sized tickets rather than checkbox lists,
because they are big enough that "a phase is several commits" is not specific
enough guidance. Each row is one branch, one pull request, one issue.

- **Local id** (`W1`, `T4`, `E2`) is stable and is what the `depends` column and
commit bodies reference. Issue numbers are assigned at filing.
- **Branch**: `<type>/<issue#>-<slug>`, types `feature` · `fix` · `chore` ·
`docs` · `test` — see [CONTRIBUTING.md](./CONTRIBUTING.md).
- A ticket closes with its own acceptance criteria met and this table's status
updated in the same pull request.

Legend: ○ not started · ◐ in progress · ● done

### Epic W — Phase 10.5, trunk workflow

| id | # | type | slug | what | depends | status |
| --- | --- | --- | --- | --- | --- | --- |
| W1 | — | chore | `branch-topology` | `develop` cut from `master`, set as GitHub default; both protected — PR required, `test (24)` required, no direct push, no force push | — | ● |
| W2 | 1 | docs | `pr-template` | `.github/pull_request_template.md` | W1 | ◐ |
| W3 | 3 | docs | `contributing-posture` | `CONTRIBUTING.md`, `docs/posture-checklist.md`, this ledger | — | ◐ |
| W4 | 4 | docs | `adr-scaffold` | `docs/adr/0001` + `template.md`, linked from README | — | ◐ |
| W5 | 5 | chore | `ci-branches` | Workflow `branches: [master, develop]` — drops the never-existent `main` | W1 | ○ |

W1 needed no branch: it changes GitHub settings, not files. Its acceptance is
recorded here because a protection rule is not visible in a diff — verified by
`git push origin master` being refused by the protected-branch hook.

### Epic T — Phase 11, full TypeScript

The rule that decides every file: **Node code runs as TypeScript; browser code
compiles, because browsers cannot strip types.** `process.features.typescript`
is `"strip"` on Node ≥ 22.18, so `src/`, `bin/` and `test/` run as `.ts` with no
build step. `src/viewer/` is the exception — it is concatenated into a `<script>`
for a browser, so `tsc` emits `build/viewer/*.js` and `assemble()` reads there.

There is no public `module.stripTypeScript` to strip in-process, and using one
would be wrong anyway: it would make `typescript` a **runtime** dependency of a
tool people point at someone else's repository.

| id | # | type | slug | what | depends | status |
| --- | --- | --- | --- | --- | --- | --- |
| T0 | — | chore | `ts-toolchain` | `tsconfig.json` + `tsconfig.viewer.json`, `typescript` devDep, scripts, `engines>=24`, `.gitignore`, CI matrix → `["24"]`. **No renames.** ADR-0002 | W1–W5 | ○ |
| T1 | — | feature | `ts-config` | `src/config/` (4 files, 453 ln) → produces the `Config` type everything downstream imports | T0 | ○ |
| T2 | — | feature | `ts-adapters` | `src/adapters/` (4 files) + `docs/adapters.md`. **Highest value:** the adapter interface stops being a doc comment | T1 | ○ |
| T3 | — | feature | `ts-scan` | `src/scan/` (2 files) → `FileSet`, `Acquisition` | T1 | ○ |
| T4 | — | feature | `ts-model-core` | `graph`, `classify`, `tests`, `metrics`, `chrome` (710 ln) + the payload types mirroring `docs/payload-schema.md` | T2, T3 | ○ |
| T5 | — | feature | `ts-model-analysis` | `endpoints`, `mounts`, `derive`, `findings` (1,066 ln) | T4 | ○ |
| T6 | — | feature | `ts-pipeline` | `src/build/`, `src/cli/`, `src/serve/`, `bin/atlas.ts` | T5 | ○ |
| T7 | — | test | `ts-suite` | 22 test files → `.ts`. Confirm `generic.test` still globs `src/` — it filters by extension | T6 | ○ |
| T8 | — | chore | `viewer-outdir` | `assemble` gains `VIEWER_JS_DIR` → `build/viewer/`; throws on an empty bundle. **Viewer still `.js`** | T6 | ○ |
| T9 | — | feature | `ts-viewer-core` | `00-theme`, `10-state`, `15-helpers`, `20-select`, `30-layout`, `40-packets`, `60-pick`, `85-camera`, `90-boot` | T8 | ○ |
| T10 | — | feature | `ts-viewer-render` | `50-render` — 599 ln of canvas math, its own pull request | T9 | ○ |
| T11 | — | feature | `ts-viewer-panels` | `70-inspect`, `72-source`, `75-findings`, `80-sidebar`, `88-interact` (1,235 ln) | T9 | ○ |

`05-prism.js` stays `.js` — vendored, excluded from `checkJs`, carried through
the emit by `allowJs`.

**Epic gate.**

- [ ] `npm run typecheck` clean under `strict`, zero `any` in `src/` and `bin/`;
`@ts-expect-error` only with a comment naming what it defers
- [ ] `atlas build --repo fixtures/mini-monorepo --json` **byte-identical** to
pre-migration output except `meta.generatedAt`. **A golden that moves
during this epic is a bug, not a rebaseline**
- [ ] `npm test` green, and `generic.test.mjs` proven still to be reading `src/`
- [ ] Clean clone: `npm install && npm run build && node bin/atlas.ts build --repo .`
- [ ] `erasableSyntaxOnly` on, so nothing needing a compiler can enter `src/` unnoticed

### Epic E — Phase 12, in-place editing

Narrow by construction. Editing lives in `serve` alone: `build` emits a
shareable HTML artifact, and a shareable artifact that can write to a filesystem
is not a thing to ship. Reverses PLAN.md's read-only constraint and part of its
scope guard, with an ADR and a Decisions-reversed entry.

| id | # | type | slug | what | depends | status |
| --- | --- | --- | --- | --- | --- | --- |
| E1 | — | chore | `vendor-codemirror` | Vendor `src/viewer/06-codemirror.js` (~350 KB IIFE) with version, MIT licence and the exact build command in its header — the Prism precedent | T8 | ○ |
| E2 | — | feature | `write-endpoint` | `POST /api/source` + `--allow-write` + `baseHash` 409 + atomic rename + one stderr line per write + POST traversal suite. ADR-0003 | T6 | ○ |
| E3 | — | feature | `rescan-on-save` | Re-run `scan()` after a write, return the fresh payload, viewer swaps `ATLAS` holding camera, selection and view | E2 | ○ |
| E4 | — | feature | `source-edit-mode` | `72-source` edit mode: CodeMirror, `Cmd-S`, dirty mark, 409 resolution UI, capability-gated affordance | E1, E2, T11 | ○ |

**E2 is gated three ways, and the third is free.** `--allow-write` at the
process level; `resolveAllowed()` reused **verbatim** for membership — do not
write a second path check; and allowlist membership means the file was already
scanned, so **the write path cannot create a file.** That falls out for nothing
and it is the honest boundary: you can edit what the map shows, and the map
shows only what it read.

**Epic gate.**

- [ ] Without `--allow-write`, `POST /api/source` 403s naming the flag, and the
viewer offers no edit affordance
- [ ] With it, the full Phase 7 traversal list 404s on **POST** as well as GET —
`../../../etc/passwd`, `/etc/passwd`, `.env`, `node_modules/x`, in-repo
symlink pointing outside, `..%2f..%2f`, `Host: evil.example`, a null byte,
a post-scan symlink swap
- [ ] A path on disk but excluded by `keep`/`exclude` 404s; creating a file is impossible
- [ ] Stale `baseHash` → 409, and the file on disk is unchanged
- [ ] Edit, save, and the block's height changes without a page reload — LOC is
observed, so the map moves
- [ ] `build` output greps clean for the write endpoint
- [ ] A dirty buffer never redraws the map: the atlas draws what it read from disk

### Epic B — already-known work, ticketed

The *Open findings* below and two unmet gates, filed rather than left as prose.
None blocks the epics above. **B3 should wait for T2**, which makes it far
cheaper.

| id | # | type | slug | what | status |
| --- | --- | --- | --- | --- | --- |
| B1 | — | test | `fps-gate` | Measure 60 fps sustained drag with the window in front — the last Phase 1 gate. Human, not automatable: `requestAnimationFrame` is suspended in a backgrounded tab | ○ |
| B2 | — | fix | `curated-hop-badge` | Modelled hops inside *curated* flows carry no canvas badge, though `meta.derivedCount` counts them | ○ |
| B3 | — | chore | `adapter-seam` | Four language-specific breaches in `src/model/`. **Fix is to widen the adapter contract, not to add branches** | ○ |
| B4 | — | fix | `mount-require` | Mount resolution does not follow `require()`, so a CommonJS router reports at its declared path rather than the one it is served at | ○ |
| B5 | — | fix | `endpoint-provenance` | Endpoint and datastore nodes carry no provenance; `add()` discards which rule matched and where the prefix came from | ○ |
| B6 | — | chore | `gzip-ratio-gate` | Re-open with a measured number, or retire it. Currently 2.31× against a ≥3× bar taken from an estimate that omitted base64 | ○ |

`refactor` is not an allowed branch type, so B3 files as `chore`.


---

## Open findings
Expand Down
Loading
Loading