Skip to content

feat: multi-base-URL workspaces, nit list and nit clear - #13

Merged
kevmtt merged 29 commits into
mainfrom
feat/multi-base-workspace
Jul 27, 2026
Merged

feat: multi-base-URL workspaces, nit list and nit clear#13
kevmtt merged 29 commits into
mainfrom
feat/multi-base-workspace

Conversation

@kevmtt

@kevmtt kevmtt commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

What this is

Annotate the same project at several base URLs — local, staging, production — from one review folder, and manage each independently.

A review folder becomes a workspace: one subfolder per base URL, each byte-for-byte today's review folder. Nothing under overlay/, panel/, anchor/, capture/ changes, and annotations.json keeps its schema — ids on disk stay a1, a2.

nit-review/
  bases.json                  index: slug -> url (+ timestamps)
  staging/                    a base review — exactly today's layout
    annotations.json          review.url = https://staging.example.com
    review.md  fix-annotations.md  shots/
  localhost-4200/
    annotations.json          review.url = http://localhost:4200

A single-site review stays flat forever. The move to subfolders only fires when a genuinely new base URL arrives, so existing users never see their folder change shape.

Status: work in progress — 9 of 18 planned tasks. The workspace foundation, nit list and nit clear are complete and reviewed. The MCP surface, status/view/verify, export/import and the documentation pass are not yet implemented. See What's left.

What works today

nit review <url> routes into the base folder for its URL, creating and registering it as needed. --base staging names the folder; otherwise the slug is derived from host + port (localhost:4200localhost-4200).

nit list — read-only, safe against a review someone else is editing.

nit list                          every base URL with counts
nit list --base staging           the annotated urls inside one
nit list --all --actionable       everything still waiting, everywhere
nit list --json                   the same numbers for scripts

nit clear — removes annotations once they have served their purpose.

nit clear --base staging --verified   drop what is already ruled
nit clear --all --verified            same, every environment
nit clear --base staging --dry-run    see what would go
nit clear --all --yes                 no prompt, for scripts

A scope is always mandatory — bare nit clear lists the bases and exits 1 rather than guessing. --verified is sugar for --status <s>, so wontfix cleanup comes free.

-v now works as --version alongside -V.

Notable decisions

Ids stay plain on disk. annotations.json is unchanged, so every existing consumer, shared zip and agent-written file keeps working. The staging:a1 form is a presentation concern for output that spans bases, applied only at the MCP and nit list boundary. It splits on the first colon, so a merged base still resolves (staging:kevin:a1).

Migration is lazy and reversible-by-omission. A flat folder stays flat while reviewed at the URL it already holds. When a second base URL does arrive, the flat review moves into <slug>/ — and if any rename fails, every completed rename is undone before the error surfaces.

Slugs are validated before becoming path segments. bases.json is hand-editable and travels inside shared zips, so a crafted "slug": "../../.ssh" is rejected on read and on write. Base directories are only ever built from enumerated directory names, never from file content.

bases.json is an index, not the source of truth. A subfolder holding an annotations.json is a base whether or not it's listed; a stale entry is ignored; a corrupt index is rebuilt from the subfolders. Survives git clone, unzip and hand-editing.

Review found real bugs

Worth calling out, because they were in the plan's own code and passed their tests before being caught:

  • migrateFlat's rollback could delete data it had failed to restore — after a failed rename, the cleanup removed the target directory unconditionally, including files the rollback couldn't move back. Now it only removes the target on full rollback success, with failure-injection tests.
  • ensureBase never wrote annotations.json, so a newly created base was invisible to the next openWorkspace — two of the plan's own tests failed against the plan's own code.
  • ensureBase migrated on URLs it couldn't classify. A review folder with an empty review.url (reachable — freshReview(undefined) writes exactly that) got silently split in two; file:///x forked a new base per call. Non-http(s) URLs are now rejected before anything touches disk.
  • A security test that passed with its guard deleted. The isSafeSlug check in readIndex was genuinely unreachable from the path the test exercised. The guard is kept as defence-in-depth for the write path and is now covered directly.
  • resolveBase silently picked a base when two shared an origin, instead of reporting ambiguity like the prefix branch does.
  • nit clear --dry-run wasn't write-freecreateStore mkdirs shots/ on open, so a dry run created directories on a freshly cloned workspace.

Testing

362 tests passing, lint and typecheck clean.

New: unit-workspace (31 — slug derivation, resolution, layout detection, index self-healing, migration with rollback and failure injection), unit-clear, cli-list, cli-clear, cli-migrate. Four browser test files updated for the new write location, plus an assertion pinning that nit review writes to <out>/<slug>/ and not to <out>/annotations.json, so a silent regression to the flat path fails.

What's left

  • Task 9 follow-up: pin nit clear's safety guards with tests — the non-TTY refusal, screenshot deletion, and dry-run write-freeness all work and were verified by hand, but none is covered by a test yet
  • nit status / view / verify become base-aware (--base, aggregate reporting)
  • MCP: serve every base from one server, base-qualified ids, base filter
  • MCP: nit_clear_verified (verified-only; full clearing stays CLI-only behind the prompt)
  • MCP: base-scoped resources + nit://review/bases.md
  • nit export / nit import handle a whole workspace
  • nit doctor reports the layout
  • Documentation pass — README, wiki, src/README.md
  • src/cli/index.ts is 408 lines, past the project's own "well under 400" rule; deferred deliberately until the remaining commands land so the split happens once

Do not merge yetnit view, nit verify, nit status, nit export and the MCP server have not been updated for the workspace layout and will not behave correctly against a multi-base folder.

Spec and plan are committed under docs/superpowers/.

kevmtt added 29 commits July 25, 2026 19:15
@kevmtt
kevmtt marked this pull request as ready for review July 27, 2026 09:57
@kevmtt
kevmtt merged commit 9b61631 into main Jul 27, 2026
4 checks passed
@kevmtt
kevmtt deleted the feat/multi-base-workspace branch July 27, 2026 10:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant