Initialize local Foundation Plan drafts - #2
Conversation
Agents need a deterministic, private starting point before network behavior arrives. Add plan init to create the exact empty sketch/0.19 document and UUIDv7 Project state beneath a nested ignored .firstdraft directory. Validate keys and names at the schema and I-JSON boundary before invoking randomness or the filesystem. Refuse every existing .firstdraft path and use exclusive writes so initialization never overwrites existing data. Extend the package allowlist and tests for the new runtime files, and exercise the Node 22 compatibility job across Linux, Windows, and macOS.
Review: technicalReviewed the single commit The headline probeI ran
The CLI's output and the server's bootstrap contract agree end to end, verified before either has shipped. The PR body claims loader acceptance; the import-service acceptance goes one step further and also holds. Also verified
Small
SummaryThe first real command arrives with the same character as the scaffold: validated before it acts, exclusive where it writes, honest where it fails, and — the part I could prove mechanically — in exact agreement with the server it has never met. Nothing to fix. |
Lesson: what this PR actually didThe CLI gets its first verb. The theme is a contract between two programs in two languages in two repositories — and how you keep such a contract honest. The one-sentence version
Two programs, one contractStand back and look at what the last few PRs across both repos add up to. The server (#147, #151) said: I accept exactly the empty starter Plan, with these keys, this target, no entities, and a client-supplied UUIDv7. The CLI (this PR) says: I produce exactly that document and exactly that ID. Neither program has ever run against the other — the API push command doesn't even exist yet. Contracts like this rot in the gap between repositories. So the most important sentence in the PR body is the verification line: the generated Plan was fed to the actual Ruby loader in the main repo and accepted. In review I extended that probe one step further — the generated Plan plus the generated Project ID, through the actual import service, created a real Project row at graph version 1. When your client and server live in different repos, this kind of cross-repo test is the only thing standing between you and the day the two halves meet in production for the first time. The same discipline shows up in miniature inside the validation code. The server rejects application names containing NUL, lone surrogates, or Unicode noncharacters (#145, #151); the CLI now rejects them locally, before doing anything at all — same rules, and literally the same bitmask trick for noncharacters, translated from Ruby to JavaScript. Fail at the keyboard rather than at the network, but with identical judgment, so passing locally predicts passing remotely. Order of operations as a design principleLook at what And when it does touch the filesystem, every operation is defensive:
One small trick worth stealing: the created directory contains its own Rolling your own UUIDv7, legitimatelyThe scaffold PR (#1) committed to zero runtime dependencies, and this is the first place that costs something: the Project ID must be a UUIDv7 (time-ordered, per the identity design in the main repo's ADR 0017), Node's built-in So there are fifty lines of honest bit-twiddling: 48 bits of millisecond timestamp packed big-endian into the first six bytes, four version bits forced to The quiet parser fix
|
|
The next stacked CLI slice is #3. It adds conditional whole-document The client slice is exercised against the server contract in |
Summary
firstdraft plan initwith explicit application key and display name inputs.firstdraft/.gitignoreStack
This PR contains one commit and is intentionally based on #1. Review only:
The command is entirely local: it reads no project files, uses no environment configuration, and makes no network request. API push behavior remains deferred to the next slice.
Verification
npm run checkon Node 24.18.0npm test, typecheck, lint, package-content check, and packed-tarball smoke on Node 22.0.0