Repository navigation
Releases
v0.5.3 — repo transfer to artificemachine
v0.5.3
Changes
- Repository transferred to artificemachine/pencil-sync
- Updated
repositoryURL in package.json to reflect new home - Restored
./dist/prefix on bin entries (regression fix fromnpm pkg fixstripping them)
No functional changes.
v0.5.2 — 10-iteration audit remediation
v0.5.2 — Audit Remediation (10 TDD iterations)
Security fixes
- SSRF guard on
apiBaseUrl: warn on non-HTTPS and localhost/private-IP targets - Symlink traversal guard in
state-store.ts collectFiles()viaresolve()+startsWith() - ShipGuard SAST: corrected
disable_rulesfield; suppressed JS-002 (Array.prototype.join false positive) and JS-004 (array dedup false positive)
Bug fixes
pen-snapshot: diffPenSnapshots detects added/cleared props, whole-node deletions, and removed tokenspen-to-code: hexToRgbChannels parses rgb()/rgba() and rejects invalid-length hexpen-to-code: applyFillChanges single-pass keyed replacement prevents chained-diff corruptionpencil-mcp-client: batchGet/batchDesign throw on isError and non-JSON responsescode-to-pen: syncCodeToPenDirect returns failure on unreadable .pen or no-opprovider: honors settings.provider in runner creation and env-key resolutionai-runners: claude-haiku-4-5 alias in pricing table; systemInstruction for Google; warn on OpenAI finish_reason:lengthconfig: Zod v4 migration (message/error params); mcp-server null→undefined coercion
CI / tooling
.npmrclegacy-peer-deps=true resolves openai peerOptional zod v3 conflict on Node 22
v0.5.1 — penFile path fix
Fix
Allow penFile to live outside the project directory.
penFile is a read-only input — it may point to a shared Pencil workspace anywhere on disk. The path traversal guard was incorrectly applied to it, causing pencil-sync doctor to reject valid configs (e.g. penFile: /Users/.../Downloads/design.pen).
Only write targets (codeDir, stateFile) are confined by the traversal guard.
Changes
2026-06-19: fix(config): allow penFile outside configDir — read-only input must not be traversal-guarded
Full Changelog
https://github.com/celstnblacc/pencil-sync/blob/main/CHANGELOG.md
v0.5.0 — .pen parser real-schema hardening
Fixed
- Complex
fillvalues (gradients, images, arrays) no longer collapse to[object Object]— distinct values diff correctly, identical objects with different key order produce zero diffs - Per-side
cornerRadiusarrays ([t,r,b,l]) now diff correctly against each other and against scalar values - Gradient/image/array fills route to Claude instead of the hex fast-path (no more spurious "could not convert hex" errors)
Added
- Document-level design tokens (
variables/themes) are now tracked in the snapshot and surface as diffs when changed ref/reusablecomponent instances are resolved during tree flattening — instance visual props are tracked and diff correctlystableStringify()utility inutils.tsfor sorted-key canonical JSON serializationsrc/__tests__/fixtures/realistic.pen.ts— realistic Document fixture for all future real-schema tests
Notes
- 560 tests passing (+26 new)
- Migration-safe: complex values stored as canonical JSON strings (still
stringtype), so existing persisted snapshots remain valid
v0.4.1 — TUI wiring fix
Fixed
pencil-sync tuino longer exits immediately with "TUI starting..."- Root cause: tui action did not pass config to
startTui(); now loads config viainitConfig(), builds state map fromStateStore, and renders the ink<App>component
Notes
- All 534 tests passing
- Ink TUI dashboard (MappingPanel, EventFeed, BudgetMeter, keybinds s/d/q) fully functional
v0.4.0 — Live TUI dashboard + enhanced setup wizard
v0.4.0
New features
Enhanced setup wizard (pencil-sync setup)
- Step counter
[1/7]through[7/7]before each question - Colored auto-detected framework/styling values (yellow)
- Confirmation summary screen before writing config
- Back navigation — type
backat any step to return to the previous one; step 1 prints "Already at step 1."
Live TUI dashboard (pencil-sync tui)
- Full-screen ink/React UI — one
MappingPanelper mapping: last sync time, direction, tracked file count, "not yet synced" label - Scrollable
EventFeed— color-coded OK/error/warning/conflict events from bothSyncEngine.onEvent()and the logger TUI hook BudgetMeter— 20-char progress bar with $used / $max, OVER BUDGET label when exceeded- Keybinds:
s= manual sync all,d= run doctor,q= quit
SyncEngine.onEvent()
- Register a callback that fires after every
syncMapping()call (success and conflict paths) - Handler errors are silently swallowed — sync is never interrupted by a bad TUI handler
Internal
logger.ts—TuiEvent,setTuiEventHandler,clearTuiEventHandlerfor redirecting log output to the TUItypes.ts—TuiSyncEventshape- JSX/TSX support in tsconfig + Vitest esbuild transform
- Dependencies:
ink,react(runtime);@types/react,ink-testing-library(dev)
Tests
534 tests passing (32 test files, +3 new TUI test files, +31 new tests)
Full changelog
See CHANGELOG.md for all entries.
v0.3.0 — .pencil-sync/ directory, doctor, auto-detection, setup wizard
What's new in v0.3.0
.pencil-sync/ per-project directory
- Replaces flat
.pencil-sync-state.jsonwith.pencil-sync/state.json+.pencil-sync/last-run.json - Automatic migration on first load — existing projects just work
last-run.jsonwritten after every sync for inspection and tooling
pencil-sync doctor
Six preflight checks with a [✓]/[✗] checklist: Claude CLI on PATH, config valid, pen file accessible, code directory accessible, globs match files, budget > 0. Exits 0 when all pass, 1 when any fail.
Auto-detection helpers
detectStyling()now detectscss-modules(via*.module.cssscan) and plaincssfallback- New
findPenFiles()helper recursively finds.penfiles, skippingnode_modulesand hidden directories
pencil-sync setup interactive wizard
Replaces the dumb init command with a seven-question wizard:
- Auto-detects framework and styling, shows them as defaults
- Finds
.penfiles recursively, lets you pick by number or enter manually - Writes
pencil-sync.config.json, creates.pencil-sync/directory - Reports next steps after writing config
pencil-sync init is kept as an alias.
Tests
435 → 471 passing (27 test files)
Changelog
- feat(project-dir): replace flat state file with .pencil-sync/ directory
- feat(doctor): add pencil-sync doctor preflight command
- feat(detect): add detectStyling() css-modules/css detection and findPenFiles()
- feat(setup): add interactive pencil-sync setup wizard replacing init command
v0.2.0 — MCP server + host-agnostic Executor
What's new
pencil-sync-mcp — host-agnostic MCP server
pencil-sync now ships a second binary pencil-sync-mcp that exposes sync primitives as MCP tools. Any MCP-compatible host agent (Claude Code, Codex CLI, OpenCode, Gemini CLI) can use pencil-sync without spawning a nested claude subprocess.
7 MCP tools:
pencil_get_config— load configpencil_diff_design— diff .pen snapshot vs last synced statepencil_diff_code— diff code dir hashes vs last synced statepencil_detect_conflict— detect bidirectional conflictpencil_build_prompt— build the full LLM prompt for the host to applypencil_apply_fill_changes— deterministic CSS color substitution (no LLM)pencil_record_sync— persist sync state after edits
Executor interface (Option A)
All sync paths now go through an Executor interface (LocalClaudeExecutor is the only day-one implementation). Tests can inject a fake executor — no subprocess spawning in CI.
Stats
- 402 tests passing (up from 300)
- 0 TypeScript errors
@modelcontextprotocol/sdk+zodadded as runtime dependencies
v0.1.5 — Glob unification, fast-path fix, ESLint, coverage
What's Changed
Fixed
- P2-C: Color fast-path now returns
success: falsewhen fill diffs match zero CSS declarations (was silent success with 0 files). Partial matches surface assuccess: truewithwarnings[]. - P2-B: New `src/glob-matcher.ts` unifies glob matching across watcher and hasher. Supports brace `{a,b}` expansion and `[..]` character classes. Watcher now watches `codeDir` directly (chokidar v4 dropped glob support) and filters events through `matches()`.
- MODEL_PRICING: Added `claude-opus-4-8` pricing; `estimateCost` now warns when falling back to Sonnet pricing for an unknown model.
Added
- ESLint: `eslint.config.js` flat config with `typescript-eslint`, `no-floating-promises` (error), `no-unused-vars`/`no-explicit-any` (warn). `npm run lint` exits 0.
- Coverage: `@vitest/coverage-v8` provider with enforced thresholds: lines ≥81%, functions ≥83%, branches ≥77%, statements ≥80%.
Stats
- 347 tests passing across 22 test files
- Coverage baseline: Stmts 82.55%, Branch 79.5%, Funcs 85.87%, Lines 83.88%
v0.1.4
Refactor — P2-E + P2-D
Changes
- Unified ignore list (
src/ignored-dirs.ts): singleIGNORED_DIRSset andIGNORED_GLOBSarray shared by watcher and state-hasher. Closes the.nextdrift where Next.js build output was hashed but not watched. - Cross-platform path-traversal guard: replaced POSIX-only
validatePathWithinDirectoryinconfig.tswithvalidatePathWithinfromutils.ts. Removes the duplicate 20-line function and fixes the guard on Windows. - 322 tests passing; 6 new tests added.