Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ jobs:
lfs: true

- name: Cache SPM dependencies
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: .build
key: ${{ runner.os }}-xcode-26.1.1-spm-${{ hashFiles('Package.swift', 'Package.resolved') }}
Expand Down Expand Up @@ -102,7 +102,7 @@ jobs:
lfs: true

- name: Cache SPM dependencies
uses: actions/cache@v4
uses: actions/cache@v5
with:
path: .build
key: ${{ runner.os }}-swift-6.2.3-spm-${{ hashFiles('Package.swift', 'Package.resolved') }}
Expand Down
67 changes: 36 additions & 31 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ Fourteen modules in `Sources/`:

**MCP data flow:** `exfig mcp` → StdioTransport (JSON-RPC on stdin/stdout) → tool handlers → PKLEvaluator / TokensFileSource / FigmaAPI
**MCP stdout safety:** `OutputMode.mcp` + `TerminalOutputManager.setStderrMode(true)` — all CLI output goes to stderr
**Claude Code plugins:** [exfig-plugins](https://github.com/DesignPipe/exfig-plugins) marketplace — MCP integration, setup wizard, export commands, config review, troubleshooting

**Batch mode:** Single `@TaskLocal` via `BatchSharedState` actor — see `ExFigCLI/CLAUDE.md`.

Expand Down Expand Up @@ -366,37 +367,41 @@ NooraUI.formatLink("url", useColors: true) // underlined primary

## Troubleshooting

| Problem | Solution |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| codegen:pkl gen.pkl error | gen.pkl `read?` bug: needs `--generator-settings` + `--project-dir` flags (see mise.toml) |
| xcsift "signal code 5" | False positive when piping `swift test` through xcsift; run `swift test` directly to verify |
| PKL tests need Pkl 0.31+ | Schemas use `isNotEmpty`; run tests via `./bin/mise exec -- swift test` to get correct Pkl in PATH |
| PKL FrameSource change | Update ALL entry init calls in tests (EnumBridgingTests, IconsLoaderConfigTests) |
| Build fails | `swift package clean && swift build` |
| Tests fail | Check `FIGMA_PERSONAL_TOKEN` is set |
| Formatting fails | Run `./bin/mise run setup` to install tools |
| test:filter no matches | SPM converts hyphens→underscores: use `ExFig_FlutterTests` not `ExFig-FlutterTests` |
| Template errors | Check Jinja2 syntax and context variables |
| Linux test hangs | Build first: `swift build --build-tests`, then `swift test --skip-build --parallel` |
| Android pathData long | Simplify in Figma or use `--strict-path-validation` |
| PKL parse error 1 | Check `PklError.message` — actual error is in `.message`, not `.localizedDescription` |
| Test target won't compile | Broken test files block entire target; use `swift test --filter Target.Class` after `build` |
| Test helper JSON decode | `ContainingFrame` uses default Codable (camelCase: `nodeId`, `pageName`), NOT snake_case |
| Web entry test fails | Web entry types use `outputDirectory` field, while Android/Flutter use `output` |
| Logger concatenation err | `Logger.Message` (swift-log) requires interpolation `"\(a) \(b)"`, not concatenation `a + b` |
| Deleted variables in output | Filter `VariableValue.deletedButReferenced != true` in variable loaders AND `CodeSyntaxSyncer` |
| mise "sources up-to-date" | mise caches tasks with `sources`/`outputs` — run script directly via `bash` when debugging |
| Jinja trailing `\n` | `{% if false %}...{% endif %}\n` renders `"\n"`, not `""` — strip whitespace-only partial template results |
| `Bundle.module` in tests | SPM test targets without declared resources don't have `Bundle.module` — use `Bundle.main` or temp bundle |
| SwiftLint trailing closure | When function takes 2+ closures, use explicit label for last closure (`export: { ... }`) not trailing syntax |
| CLI flag default vs absent | swift-argument-parser can't distinguish explicit `--flag default_value` from omitted. Use `Optional` + computed `effectiveX` property for flags that wizard may override |
| MCP `Client` ambiguous | `FigmaAPI.Client` vs `MCP.Client` — always use `FigmaAPI.Client` in MCP/ files |
| MCP `FigmaConfig` fields | No `colorsFileId` — use `config.getFileIds()` or `figma.lightFileId`/`darkFileId` |
| `distantFuture` on clock | `ContinuousClock.Instant` has no `distantFuture`; use `withCheckedContinuation { _ in }` for infinite suspend |
| MCP stderr duplication | `TerminalOutputManager.setStderrMode(true)` handles all output routing — don't duplicate in `ExFigLogHandler` |
| PKL template word search | Template comments on `lightFileId` contain cross-refs (`variablesColors`, `typography`); test section removal by matching full markers (`colors = new Common.Colors {`) not bare words |
| CI llms-full.txt stale | `llms-full.txt` is generated from README + DocC articles; after editing `Usage.md`, `ExFig.md`, or `README.md`, run `./bin/mise run generate:llms` and commit the result |
| Release build .pcm warnings | Stale `ModuleCache` — clean with: `rm -r .build/*/release/ModuleCache` then rebuild |
| Problem | Solution |
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| codegen:pkl gen.pkl error | gen.pkl `read?` bug: needs `--generator-settings` + `--project-dir` flags (see mise.toml) |
| xcsift "signal code 5" | False positive when piping `swift test` through xcsift; run `swift test` directly to verify |
| PKL tests need Pkl 0.31+ | Schemas use `isNotEmpty`; run tests via `./bin/mise exec -- swift test` to get correct Pkl in PATH |
| PKL FrameSource change | Update ALL entry init calls in tests (EnumBridgingTests, IconsLoaderConfigTests) |
| Build fails | `swift package clean && swift build` |
| Tests fail | Check `FIGMA_PERSONAL_TOKEN` is set |
| Formatting fails | Run `./bin/mise run setup` to install tools |
| test:filter no matches | SPM converts hyphens→underscores: use `ExFig_FlutterTests` not `ExFig-FlutterTests` |
| Template errors | Check Jinja2 syntax and context variables |
| Linux test hangs | Build first: `swift build --build-tests`, then `swift test --skip-build --parallel` |
| Android pathData long | Simplify in Figma or use `--strict-path-validation` |
| PKL parse error 1 | Check `PklError.message` — actual error is in `.message`, not `.localizedDescription` |
| Test target won't compile | Broken test files block entire target; use `swift test --filter Target.Class` after `build` |
| Test helper JSON decode | `ContainingFrame` uses default Codable (camelCase: `nodeId`, `pageName`), NOT snake_case |
| Web entry test fails | Web entry types use `outputDirectory` field, while Android/Flutter use `output` |
| Logger concatenation err | `Logger.Message` (swift-log) requires interpolation `"\(a) \(b)"`, not concatenation `a + b` |
| Deleted variables in output | Filter `VariableValue.deletedButReferenced != true` in variable loaders AND `CodeSyntaxSyncer` |
| mise "sources up-to-date" | mise caches tasks with `sources`/`outputs` — run script directly via `bash` when debugging |
| Jinja trailing `\n` | `{% if false %}...{% endif %}\n` renders `"\n"`, not `""` — strip whitespace-only partial template results |
| `Bundle.module` in tests | SPM test targets without declared resources don't have `Bundle.module` — use `Bundle.main` or temp bundle |
| SwiftLint trailing closure | When function takes 2+ closures, use explicit label for last closure (`export: { ... }`) not trailing syntax |
| CLI flag default vs absent | swift-argument-parser can't distinguish explicit `--flag default_value` from omitted. Use `Optional` + computed `effectiveX` property for flags that wizard may override |
| MCP `Client` ambiguous | `FigmaAPI.Client` vs `MCP.Client` — always use `FigmaAPI.Client` in MCP/ files |
| MCP `FigmaConfig` fields | No `colorsFileId` — use `config.getFileIds()` or `figma.lightFileId`/`darkFileId` |
| `distantFuture` on clock | `ContinuousClock.Instant` has no `distantFuture`; use `withCheckedContinuation { _ in }` for infinite suspend |
| MCP stderr duplication | `TerminalOutputManager.setStderrMode(true)` handles all output routing — don't duplicate in `ExFigLogHandler` |
| MCP `Process` race condition | Set `terminationHandler` BEFORE `process.run()` — process may exit before handler is installed, hanging the continuation forever |
| MCP pipe deadlock | Read stderr via concurrent `Task` BEFORE waiting for termination — pipe buffer (~64KB) can fill and block the subprocess |
| MCP `encodeJSON` errors | Use `throws` not `try?` — silently returning `"\(value)"` (Swift debug dump) breaks JSON consumers; top-level `do/catch` in `handle()` catches automatically |
| `VariablesColors` vs `Colors` | `ColorsVariablesLoader` takes `Common.VariablesColors?`, not `Common.Colors?` — different PKL types |
| PKL template word search | Template comments on `lightFileId` contain cross-refs (`variablesColors`, `typography`); test section removal by matching full markers (`colors = new Common.Colors {`) not bare words |
| CI llms-full.txt stale | `llms-full.txt` is generated from README + DocC articles; after editing `Usage.md`, `ExFig.md`, or `README.md`, run `./bin/mise run generate:llms` and commit the result |
| Release build .pcm warnings | Stale `ModuleCache` — clean with: `rm -r .build/*/release/ModuleCache` then rebuild |

## Additional Rules

Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,16 @@ See the [Getting Started guide](https://DesignPipe.github.io/exfig/documentation
cache: true
```

## Claude Code Plugins

Use ExFig directly from Claude Code with the [exfig-plugins](https://github.com/DesignPipe/exfig-plugins) marketplace:

```bash
claude /plugin marketplace add https://github.com/DesignPipe/exfig-plugins
```

Includes MCP integration, setup wizard, config review, troubleshooting, and `/export-*` slash commands.

## Documentation

Full documentation — platform guides, configuration reference, batch processing, design tokens, custom templates, and MCP server — is available at **[DesignPipe.github.io/exfig](https://DesignPipe.github.io/exfig/documentation/exfig)**.
Expand Down
13 changes: 13 additions & 0 deletions Sources/ExFigCLI/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,19 @@ reserved for MCP JSON-RPC protocol.

**Tool handler order:** Validate input parameters BEFORE expensive operations (PKL eval, API client creation).

### Adding an MCP Tool Handler

1. Add tool definition in `MCP/MCPToolDefinitions.swift` (JSON Schema via `.object([...])`)
2. Add case in `MCPToolHandlers.handle()` dispatch switch
3. Implement handler in an `extension MCPToolHandlers` (keeps `type_body_length` under 300)
4. `ExFigWarning` → string via `ExFigWarningFormatter().format(warning)` (no `.formattedMessage` property)
5. `Color.hex` is in AndroidExport, not accessible from ExFigCLI — use RGBA components
6. `ColorsVariablesLoader` takes `PKLConfig.Common.VariablesColors?`, not `.Colors?`
7. MCP `CallTool.Parameters.arguments` type is `[String: Value]?` (not `JSONValue`); accessors: `.stringValue`, `.intValue`, `.boolValue`
8. Export tools: `exfig_export` runs subprocess (self-invoke), reads JSON report from temp file; `exfig_download` returns tokens inline via loaders
9. `runSubprocess` pattern: set `terminationHandler` BEFORE `process.run()` (race condition); read stderr pipe concurrently via `Task` (deadlock at 64KB buffer); use `withThrowingTaskGroup` race for timeout
10. Validate cheap params (format, resource_type) BEFORE expensive operations (PKL eval, `state.getClient()`) — keeps tests fast and error messages clear

## Modification Patterns

### Adding a New Subcommand
Expand Down
5 changes: 3 additions & 2 deletions Sources/ExFigCLI/ExFig.docc/ExFig.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,9 @@ file version tracking, and experimental per-node granular cache.

**Developer Experience**
CI/CD ready (quiet mode, exit codes, JSON reports), GitHub Action for automated exports,
MCP server for AI assistant integration, customizable Jinja2 code templates,
and rich progress indicators with ETA.
MCP server for AI assistant integration,
[Claude Code plugins](https://github.com/DesignPipe/exfig-plugins) for setup wizards and slash commands,
customizable Jinja2 code templates, and rich progress indicators with ETA.

**Code Generation**
Type-safe Swift/Kotlin/Dart/TypeScript extensions, pre-configured UILabel subclasses,
Expand Down
Loading
Loading