### Problem
Specs were stored as a single file per branch. This coupled specs to branches rather than product features, and made it harder to mentally organize the growing set of behaviours as a product evolves across branches.
### Solution
We split spec storage into multiple files — one per Feature block in the Gherkin output, named by slugifying the feature name (e.g., "CLI spec update" becomes cli-spec-update.feature). Specs now describe product features, not branch-scoped work.
The LLM pipeline is unchanged. It continues to operate on a single concatenated string — file boundaries are invisible to it. We introduce a virtualized I/O boundary: on read, all feature files are globbed, sorted, and concatenated. On write, the output is parsed by Feature block, existing files are removed, and each block is written to its own file. The removal before writing is safe because the content was already consumed and re-expressed by the LLM.
We also add a --scope flag to direct specs into a subdirectory (e.g., --scope derive writes to .machinen/specs/derive/), and remove the init command (no longer needed with multi-file storage).
The architecture blueprint is updated throughout — system flow, behaviour spec, API reference, invariants, and directory mapping.
The full-directory cp -c -R was copying _/ (15GB) and node_modules
since it doesn't respect .gitignore. Switch to git ls-files | xargs cp -c
for per-file APFS CoW clones that only copy tracked files.
- GET /disk-usage returns workspace count, sizes, ages, pnpm/playwright/logs sizes
- DELETE /disk-usage/workspaces removes specific or all stale workspaces
- Add API test for disk-usage endpoint
Fixes#17 (Fix 6)
- On macOS, use cp -c -R (APFS clone) for near-instant zero-disk copies
- Clone shares physical blocks until a file is actually modified
- Falls back to rsync/fs.cpSync on non-APFS volumes or Linux
Fixes#17 (Fix 3)
- Place workspace files directly in containerWorkDir/<repo>/<repo>/
- Remove /tmp/oa-workspace bind-mount and container-side cp -r
- Halves NVMe writes per run by eliminating Copy 2
Fixes#17 (Fix 2)
- Clean containerWorkDir on success, retain on failure for debugging
- Add cleanup.ts module with pruneStaleWorkspaces and getDiskUsage
- Add cleanup.test.ts with TDD tests for all cleanup behaviors
Fixes#17 (Fix 1)
## Problem
Engineers build features on branches using Claude Code. Those conversations implicitly contain the evolving behavioural intent of the feature — but that intent is trapped in JSONL logs, not captured in a structured, testable form.
## Solution
We add `derive`, a CLI tool that reads Claude Code conversation logs and maintains a living Gherkin behaviour spec per git branch. It extracts testable product behaviours from conversations and writes them to `.machinen/specs/<branch>.gherkin` in the project directory, where they travel with the branch via git.
The tool provides four modes — all with explicit token-spend control:
- **`derive`** — one-shot update. Discover conversations, read new messages, update the spec, exit.
- **`derive --reset`** — regenerate the spec from scratch. Reprocess all conversations sequentially, with a single review pass at the end. Supports `--keep-spec` to preserve hand-written content as starting context.
- **`derive watch`** — opt-in continuous mode. Run an initial update, then watch for conversation changes on the current branch (debounced, branch-scoped).
- **`derive init`** — create an empty spec file for manual seeding before any conversations are processed.
Under the hood, `derive` maintains a lightweight SQLite routing index (`~/.machinen/machinen.db`) that maps conversations to repos and branches and tracks read cursors. The spec pipeline is stateless: each update is a fresh `claude -p` call (Sonnet, no tools, low effort) that reads the current spec from disk, combines it with new conversation excerpts, and produces an updated spec. A two-pass architecture — extraction then review — ensures specs contain only externally observable behaviours (the "black-box test") and are deduplicated, consolidated, and simplified.
We have dogfooded `derive` on itself: `.machinen/specs/specs.gherkin` is the tool's own behaviour spec, generated from the conversations that built it. When we add test generation from specs, we will use this generated spec to dogfood further — closing the loop from conversations to specs to tests.
Example snippet of generated spec:
```gherkin
Feature: Conversation discovery
Scenario: New conversations are discovered on first run
Given the user is in a git repository on branch "feature-x"
And Claude Code conversations exist for this repository and branch
And no previous derive run has occurred
When the user runs derive
Then the conversations are discovered and indexed
And their messages are included in the spec update
Scenario: Conversations for other branches are ignored
Given the user is in a git repository on branch "feature-x"
And Claude Code conversations exist for both "feature-x" and "other-branch"
When the user runs derive
Then only conversations for "feature-x" contribute to the spec
Scenario: Multiple conversations are associated with a single branch
Given two or more Claude Code conversations exist for the same branch
When the user runs derive
Then all conversations for that branch contribute to the spec update
```