Plan Z1 / spec §20.3.
apps/daemon/src/plugins/atoms/patch-edit.ts gains an
`atomicWriteFile(target, body)` helper:
1. Write body to `<target>.<random>.tmp` (sibling of target).
2. fs.rename(2) the tmp into place — atomic on POSIX when
source + target share a filesystem (which they always do
here because the tmp is sibling).
3. On failure, best-effort unlink the orphan tmp.
Every fs.writeFile call inside patch-edit now routes through it:
- file creation (--- /dev/null target)
- plain in-place edits
- plan/steps.json status updates
- plan/receipts/step-<id>.json receipt writes
Effect: a daemon crash mid-write (SIGKILL, ENOSPC, etc.) can no
longer leave the source file truncated — the rename either
happens fully or not at all. The tmp file is the only collateral,
and the orphan-cleanup path keeps even that contained.
The test harness asserts both halves of the contract:
- happy path leaves NO .tmp siblings on disk
- context-mismatch rejection leaves the original byte-equal
(atomicity covers the failure path too)
- plan/ + plan/receipts/ never accumulate .tmp leftovers
Daemon tests: 1699 \u2192 1702 (+3 cases on
plugins-patch-edit-atomic: clean rename leaves no .tmp, rejected
patch preserves byte-equal source, plan/receipt writes never
leak .tmp siblings).
Co-authored-by: Tom Huang <1043269994@qq.com>
Plan P1 / spec §10 / §21.3.1.
apps/daemon/src/plugins/atoms/token-map.ts ships the daemon-side
crosswalk behind the SKILL.md fragment landed in §3.M3. Given a
project cwd that already has a token bag (`code/tokens.json` from
design-extract OR `figma/tokens.json` from figma-extract) plus the
active design system's token vocabulary, the runner produces:
<cwd>/token-map/colors.json
<cwd>/token-map/typography.json
<cwd>/token-map/spacing.json
<cwd>/token-map/radius.json
<cwd>/token-map/shadow.json
<cwd>/token-map/unmatched.json [{ source, sourceName?, kind, reason, hint? }]
<cwd>/token-map/meta.json { sourceKind, generatedAt,
atomDigest, designSystemId?,
targetTokenCount,
sourceTokenCount,
matchedTokenCount }
Match strategies (deterministic, first-win):
1. Exact value match (#5b8def === #5b8def).
2. Normalised hex (#abc → #aabbcc, ignores case).
3. Fuzzy name (--color-primary → --ds-color-primary; strips
leading -- / ds- / odds- / theme- prefixes
and lower-cases).
Anything else lands unmatched[] with one of:
- 'no-target-equivalent' no target with same value/name
- 'target-collision' multiple sources mapped to one target
(first wins; subsequent listed unmatched
with hint pointing at the first claimant)
- 'invalid-source' malformed source value
Strict mode aborts on any unmatched; soft mode (default) populates
unmatched.json + continues so the human can audit.
Helper:
parseDesignSystemTokens(body) → DesignSystemToken[]
Heuristic DESIGN.md token extraction. Lifts CSS custom properties
(`--ds-color-fg: #111`) and markdown table rows that look like
token declarations. Daemon callers may pass a hand-curated bag
instead of relying on this parser.
Existing patch-edit typecheck noise fixed in passing:
- PatchStepRecord now extends Omit<RewriteStep, 'rationale'> so
optional override is sound under exactOptionalPropertyTypes.
- rationale assignment guarded against undefined.
Daemon tests: 1588 → 1600 (+12 cases on plugins-token-map: exact
match, normalised hex, fuzzy name, no-target-equivalent, target-
collision, strict mode abort, multi-kind crosswalk, code+figma
disk inputs round-trip + bucket persistence + missing-input error,
DESIGN.md CSS custom property + markdown table parsers).
Co-authored-by: Tom Huang <1043269994@qq.com>