feat: multi-base-URL workspaces, nit list and nit clear - #13
Merged
Conversation
…split workspace.ts
kevmtt
marked this pull request as ready for review
July 27, 2026 09:57
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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, andannotations.jsonkeeps its schema — ids on disk staya1,a2.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.
What works today
nit review <url>routes into the base folder for its URL, creating and registering it as needed.--base stagingnames the folder; otherwise the slug is derived from host + port (localhost:4200→localhost-4200).nit list— read-only, safe against a review someone else is editing.nit clear— removes annotations once they have served their purpose.A scope is always mandatory — bare
nit clearlists the bases and exits 1 rather than guessing.--verifiedis sugar for--status <s>, sowontfixcleanup comes free.-vnow works as--versionalongside-V.Notable decisions
Ids stay plain on disk.
annotations.jsonis unchanged, so every existing consumer, shared zip and agent-written file keeps working. Thestaging:a1form is a presentation concern for output that spans bases, applied only at the MCP andnit listboundary. 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.jsonis 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.jsonis an index, not the source of truth. A subfolder holding anannotations.jsonis a base whether or not it's listed; a stale entry is ignored; a corrupt index is rebuilt from the subfolders. Survivesgit 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.ensureBasenever wroteannotations.json, so a newly created base was invisible to the nextopenWorkspace— two of the plan's own tests failed against the plan's own code.ensureBasemigrated on URLs it couldn't classify. A review folder with an emptyreview.url(reachable —freshReview(undefined)writes exactly that) got silently split in two;file:///xforked a new base per call. Non-http(s) URLs are now rejected before anything touches disk.isSafeSlugcheck inreadIndexwas 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.resolveBasesilently picked a base when two shared an origin, instead of reporting ambiguity like the prefix branch does.nit clear --dry-runwasn't write-free —createStoremkdirsshots/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 thatnit reviewwrites to<out>/<slug>/and not to<out>/annotations.json, so a silent regression to the flat path fails.What's left
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 yetnit status/view/verifybecome base-aware (--base, aggregate reporting)basefilternit_clear_verified(verified-only; full clearing stays CLI-only behind the prompt)nit://review/bases.mdnit export/nit importhandle a whole workspacenit doctorreports the layoutsrc/README.mdsrc/cli/index.tsis 408 lines, past the project's own "well under 400" rule; deferred deliberately until the remaining commands land so the split happens onceDo not merge yet —
nit view,nit verify,nit status,nit exportand 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/.