skit: SyntaxKit codegen CLI for v0.0.5#156
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Code Review: skitrun POC (PR #156)
OverviewSolid research output. The Tuist analysis is thorough, the design doc is well-reasoned, and the three POC steps de-risk the approach incrementally. The wrap+spawn architecture is the right call — it keeps the host dead-simple and avoids an AST round-trip. The The main concerns below are one real bug that can deadlock and one Swift concurrency misuse that can exhaust the thread pool under load. Both are fixable before step 4. Bug: Pipe deadlock in
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## v0.0.5 #156 +/- ##
=========================================
Coverage ? 78.04%
=========================================
Files ? 127
Lines ? 4491
Branches ? 0
=========================================
Hits ? 3505
Misses ? 986
Partials ? 0
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Code Review —
|
| Core logic | ✅ Correct and well-structured |
| Blocking async | |
| Atomic writes | |
| Linux portability | |
| Import hoisting edge case | 📝 Worth a warning |
| Timeout | 📝 Add TODO comment |
| Help to stderr | 📝 Minor |
Good research PR — the design is sound and the POC ladder gives a clear path forward. The blocking waitUntilExit and non-atomic writes are the two items worth fixing before step 4 (bundled release).
Code Review — PR #156: Research + POC:
|
| Area | Status |
|---|---|
| Core design (wrap + spawn) | ✅ sound |
| Helpers caching | ✅ solid; dylib mtime → SHA-256 recommended |
| Folder-mode concurrency | ✅ works; cap at min(cpu, 8) recommended |
| Platform (Linux) | |
| Security | ✅ low risk; add call-convention comment |
| Test coverage | |
| Naming / conventions | _-prefix skip rule — reconsider before locking in |
| Timeout on spawned process |
The three POC steps that are done are genuinely solid. The above are the things I'd want resolved before step 4 (release bundle) ships, since they become harder to change once the CLI has users.
🤖 Generated with Claude Code
Code Review — PR #156 (Research + POC: skitrun codegen CLI)This is a well-documented research PR with solid POC implementation. The design decisions are clearly reasoned and the trade-offs are explicitly called out. Below is a detailed review across the areas you flagged and the things I noticed independently. OverviewThe PR delivers a working Design CorrectnessWrap-and-spawn is the right shape for v1. The key insight — that the host process only needs to assemble flags, not parse ASTs — keeps The departure from Tuist's token-delimited stdout is justified. SyntaxKit's output is Swift source, not a JSON descriptor that the host needs to reinterpret, so there's no need for the Naming. The Code Quality
The walk-up-to-root logic is correct, but the loop invariant is subtle. An explicit comment explaining that // dir.deletingLastPathComponent() returns itself at the filesystem root
if parent.path == dir.path { return nil }
The inline Python that string-matches the exact library product block is fragile. Three separate scripts duplicate the same Shell scripts — repeated staging boilerplate
The step 6 results note this: size + mtime is a coarse proxy that a deterministic rebuild can defeat. The correct long-term fix (embed a version constant in the bundle) is the right call. Worth leaving a Potential Bugs / Issues1. CryptoKit on Linux (blocking for step 7)
This needs to be resolved before step 7. The cleanest option is adding 2. Already filed separately. Worth noting for reviewers: this is a blocker for users wanting conditional codegen, which is a common pattern. The underlying issue (variadic 3. Walk-up false positives for Step 5 results note this: an unrelated 4. Import-line diagnostic offset Step 5 results: 5. No timeout on the spawned The design doc (§7) calls for a 60s default with 6. Bundle includes SwiftSyntax twice (45 MB total) Step 4 results identify this: SwiftSyntax is linked statically into PerformanceThe measured numbers are good:
The parallelism in folder mode is working (3 files in 1.41s vs. 3×0.72s = 2.16s serial). The observation that per-file compile work isn't deduplicated across a batch is correct — the right long-term fix (a single long-lived SecurityNo sandboxing for v1. The design doc acknowledges this and the Tuist comparison shows
Test CoverageNo automated tests for
The bash POC scripts serve as manual integration tests today and are clearly written, which is helpful. Folder Layout ConventionsThe SummaryThe POC is solid and the design is well-reasoned. The key things to address before merging (or continuing to v1):
The design doc, step results, and POC shell scripts are all worth keeping as permanent research artifacts — they'll be the canonical reference when the feature is revisited. 🤖 Generated with Claude Code |
Code Review — PR #156:
|
Code Review: PR #156 —
|
| Priority | Issue |
|---|---|
| Must fix | Process start failure → semaphore deadlock (#1 above) |
| Should fix | Output cache key missing output path (#7) |
| Should fix | Helpers walk-up depth limit (#5) |
| Nice to have | Import-line diagnostic offset (#3) |
| Document | libStamp weak-proxy caveat (#4), dylib loader trust boundary |
Great research foundation — the Tuist pipeline doc in particular is a useful reference regardless of where the final CLI design lands.
🤖 Generated with Claude Code
Code Review:
|
Code Review — PR #156: skitrun POCThis is a well-researched POC. The overall design (pure DSL input → wrap in Correctness Issues1. In folder mode with N inputs, 2. If the user writes 3. let escapedPath = originalPath
.replacingOccurrences(of: "\\", with: "\\\\")
.replacingOccurrences(of: "\"", with: "\\\"")A path containing a newline (unusual but legal on most filesystems) would break the guard !originalPath.contains("\n") else {
throw CLIError(message: "input path contains a newline: \(originalPath)")
}4.
For a POC this is fine. For productization, converting the inner Minor Issues5. let __skitrun_root = Group {A user DSL file that defines 6. The loop terminates when 7. The comment in 8. On Apple platforms, Design Questions (per the PR's ask)On naming ( My lean is On the skip rule ( The On This is the biggest user-visible hole. Any conditional codegen requires a workaround (computed vars in a helper, or Test CoverageThere are no tests for the new code. For a POC that's fine, but API Changes in ExamplesThe examples show a cluster of breaking API changes that aren't explicitly called out in the PR description:
These are the right moves for Summary: The POC ladder is complete and the research deliverables are solid. The concurrency issue (#4) and the |
Code Review — PR #156:
|
Code Review:
|
| Area | Status |
|---|---|
| Architecture & caching | ✅ Solid |
| Toolchain stamp + mismatch detection | ✅ Well done |
| Timeout watchdog | ✅ Correct |
Documentation (Docs/skit.md) |
✅ Excellent |
| Test coverage | ❌ None for new code |
| CI integration | ❌ Not added |
| DSL breaking changes documented | |
VS Code config (skitrun reference) |
|
| Cache key completeness |
The deferred items (#157 auto-rebuild, #158 if-in-Group crash) are reasonable and well-tracked. The two blocking items before merge are test coverage and documenting the DSL API changes.
🤖 Generated with Claude Code
Code Review — PR #156:
|
Code Review: PR #156 — skit: SyntaxKit Codegen CLI for v0.0.5SummaryThis PR unifies the previous
Net new Swift: ~1,000 lines across Code Quality and Best Practices ✅Strengths:
Issues:
Potential Bugs 🐛Medium severity:
Low severity:
Performance Considerations ⚡
Security Considerations 🔒
Test Coverage
|
Code Review: PR #156 —
|
Code Review — PR #156: skit CLI for v0.0.5OverviewThis is a substantial new feature: a unified Strengths
Concerns1. { "name": "Debug skitrun", "target": "skitrun" },
{ "name": "Release skitrun", "target": "skitrun" }The PR description says 2. No automated tests for The test plan is manual smoke tests.
These don't require spawning 3. Minor:
FNV-64's birthday-attack collision probability at N entries is approximately N²/2⁶⁵. At N = 10⁶: (10⁶)²/2⁶⁵ ≈ 2.7×10⁻⁸, not 10⁻⁹. Still negligibly small for this use case, but the comment is off by an order of magnitude. 4. private func isLibDir(_ path: String) -> Bool {
...
return fm.fileExists(atPath: "\(path)/\(dylibFilename(forLibrary: "SyntaxKit"))")
}A directory that contains only the dylib (no 5.
let env = ProcessInfo.processInfo.environment
.filter { $0.key.hasPrefix("SKIT_") || $0.key.hasPrefix("SYNTAXKIT_") }
Minor notes
SummaryThe core pipeline design is solid, the Linux compatibility work is careful, and the user-facing experience (toolchain stamp, |
Phase 1 documents how Tuist evaluates user manifests via xcrun swift +
token-delimited JSON over stdout, with citations into tuist/tuist and
swiftlang/swift-package-manager. Phase 2 sketches the SyntaxKit
equivalent: pure-DSL input files wrapped into a Group { … } closure
before spawning swift, with a 7-step POC ladder that retires cold-start
cost first.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Ran the hand-driven wrap+spawn flow against a temporarily-dynamic
SyntaxKit dylib. Pure-DSL input spliced into a Group {…} wrapper
compiles cleanly under xcrun swift with -I/-L/-l + an -Xcc include for
SwiftSyntax's C shims. Cold start 0.72s, warm 0.11s. Dylib weight 25MB
debug. Hoisted imports work; `if`-in-Group hits a type-checker crash
that's a separate SyntaxKit bug to file.
Design doc updated with the -Xcc requirement, the rpath flag, the
bundled-binary layout's new C-shims include directory, and the POC
findings in §7.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
One-command reproduction: flips Package.swift to a dynamic SyntaxKit library, builds, stages dylib + swiftmodules + _SwiftSyntaxCShims headers into /tmp/syntaxkit-poc/, writes a pure-DSL input + hand-rolled wrapper, and runs one cold + three warm timings. Restores Package.swift on exit via trap. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Measured SyntaxKit dylib under -c release with strip -x: 25 MB debug → 18 MB release → 9.3 MB stripped. Warm execution timings identical to debug. Distribution sizing is comfortable. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New executable target `skitrun` (POC name) wraps the hand-driven step-1
flow in real code: parse input with SwiftSyntax, hoist top-level imports,
emit a Group { … } wrapper fenced in #sourceLocation directives, spawn
`swift` via Foundation.Process, pipe stdout through, forward stderr with
literal-path fix-up.
#sourceLocation does the heavy lifting for diagnostic fidelity — a
NonexistentType in InputError.swift:4 now reports as
InputError.swift:4:37 from the spawned swift, with no manual stderr
arithmetic. Verified default-stdout, -o file output, hoisted Foundation
import, and error path rewriting all work end-to-end.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
skitrun InputDir/ -o OutDir/ walks **/*.swift (skipping `_`-prefixed files), runs the per-file wrap+spawn over withTaskGroup capped at activeProcessorCount, and writes successes into mirrored paths under OutDir/. Failures don't abort the batch: successes are still written and the CLI exits non-zero with a `skitrun: N/M succeeded` summary. Verified happy path (3 files, 1.41s wall vs 0.72s cold baseline), skip rule (deliberately-invalid `_HelperShouldBeSkipped.swift` is not visited), partial failure (3/4 succeeded with exit 1), and single-file regression. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
skitrun now resolves its lib/ directory automatically: --lib flag, then $SKITRUN_LIB_DIR, then <binary-dir>/lib/, then <binary-dir>/../lib/skitrun/ (Homebrew layout). No more /tmp/syntaxkit-poc/lib fallback. Clear diagnostic when no lib is found, naming all four search paths. poc-step4-release.sh builds a portable bundle under .build/skitrun-release/: release-config + stripped dylib (9.3 MB), modules, C-shims headers, and the binary itself (17 MB — SwiftSyntax statically linked, follow-up to deduplicate). Tested copying the bundle to unrelated directories, both same-dir and Homebrew layouts work zero-config. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
skitrun now walks up from the input looking for a Helpers/ directory. On hit, helpers compile via swiftc into libSyntaxKitHelpers.dylib under ~/Library/Caches/com.brightdigit.SyntaxKit/helpers/<sha256>/, keyed on helper-source hashes + swift --version + libSyntaxKit.dylib stamp + cache schema version. Compile lands in a tmp.<pid>.<uuid>/ staging dir then atomic-renames into the cache path so concurrent invocations are safe. Inputs then `import SyntaxKitHelpers` and call into the compiled module; runSwift splices -I/-L/-lSyntaxKitHelpers -Xlinker -rpath onto the spawn. Two new flags: --helpers <dir> overrides discovery, --no-helpers skips it entirely. Folder mode's enumerator now also yields directories so it can skipDescendants() on a Helpers/ directly under the input root — without it the helpers would be reprocessed as inputs. Verified end-to-end via Docs/research/poc-step5.sh: cold compile 2.96s, warm cache hit 0.54s (matches step-1 warm baseline), folder mode 2/2 with Helpers/ excluded, --no-helpers errors with `no such module 'SyntaxKitHelpers'` as expected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
skitrun now hashes (input bytes + helpers cache key + swift --version + libSyntaxKit stamp + sorted SKITRUN_*/SYNTAXKIT_* env vars) into a SHA-256 key under ~/Library/Caches/com.brightdigit.SyntaxKit/outputs/. On hit, processFile short-circuits — no temp wrapper, no swift spawn, just return the cached bytes. On miss the normal compile path runs and stores the rendered output. Only exit-0 runs are cached so failures always re-spawn with fresh diagnostics. Atomic write through a tmp.<pid>.<uuid>/ staging dir + rename mirrors the helpers cache; concurrent writers race safely, the loser drops their copy when the destination already exists. --no-cache disables the lookup wholesale (debugging, after manual cache deletion). Threads through runSingleFile + runDirectory so folder mode can opt out batch-wide. Helpers.swift's syntaxKitCacheRoot/captureSwiftVersion/libStamp are now internal so OutputCache.swift can reuse them without duplication. Verified via Docs/research/poc-step6.sh: cold 0.55s → warm 0.14s (4× faster, no swift spawn), --no-cache 0.27s, post-mutation miss 0.41s then 0.14s on the new key, two cache entries persist. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a §7 bullet recording that a long-lived HTTP server variant is on the table after the 7-step CLI ladder finishes — warm `swift` reuse across requests, shared helpers/output caches across tenants, but new isolation/request-shape questions. Captured here so it doesn't get lost between now and step 7. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Verifies the 7-step skitrun POC ladder works on Linux. Tests via Docker
(swift:6.0-jammy/aarch64): cold 0.73s, warm cache hit 0.26s, --no-cache
0.30s. Rendered output matches macOS exactly.
The real find of this step is a Foundation.Process bug: on Linux,
`waitUntilExit()` blocks indefinitely on already-exited children even
when EOF on the pipe proves the child has terminated. Reproduced
deterministically in `captureSwiftVersion`, where the parent reads
all 76 bytes of `swift --version` output and the subsequent wait
never returns. Workaround applied to all three call sites
(captureSwiftVersion, compileHelpers, runSwift):
let semaphore = DispatchSemaphore(value: 0)
process.terminationHandler = { _ in semaphore.signal() }
try process.run()
// ... drain pipes ...
semaphore.wait()
runSwift additionally drains both stdout + stderr pipes concurrently
via DispatchGroup + a PipeDataBox class (boxing for Swift 6 strict
concurrency without `@unchecked Sendable` on local vars). Sequential
reads would deadlock when either pipe (~64 KB) fills before the child
exits.
Other Linux-portability changes:
* Switch `import CryptoKit` to `import Crypto` in Helpers.swift +
OutputCache.swift; add swift-crypto 3.0.0 as a skitrun dep so the
same SHA256 API works on both platforms.
* dylibFilename(forLibrary:) returns libX.dylib on Apple / libX.so
on Linux. Threaded through isLibDir, libStamp, the helpers cache
hit check, and `swiftc -emit-library -o ...`.
* `-Xlinker -install_name @rpath/...` is Mach-O specific. Wrapped in
#if !os(Linux). `-Xlinker -rpath` works on both and is what
actually locates the dylib at runtime.
Demo script `Docs/research/poc-step7.sh` self-reruns inside the swift
container when invoked from the host; uses .build-linux/ as a separate
build path so the host's .build/ stays untouched (gitignored).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lives at Sources/skitrun/README.md (per user's "may be temp" note — keep it local to the target, not the repo front door). Covers usage, the portable bundle, input shape, helpers, caches, the flag table, platform notes (including the Linux waitUntilExit gotcha), and the open scope decisions from codegen-cli-design.md §7. Cross-links into Docs/research/ so the design + per-step results are one click away. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- poc-step4-release.sh: explicitly build the SyntaxKit product. skitrun doesn't depend on SyntaxKit (it spawns swift on user input that does), so --product skitrun alone never produced libSyntaxKit.dylib and the bundle ended up with whatever swiftmodule happened to be cached — which surfaced as a 6.3 vs 6.3.2 module-version mismatch after a toolchain bump. - Examples/Completed/blackjack/dsl.swift: update to the current DSL API (ComputedProperty now requires type:, Init's builder takes ParameterExp, VariableDecl → Variable), escape \(…) interpolations so the literal appears in the generated code, and drop the let/print wrapper so the file is a top-level CodeBlock expression compatible with skitrun. - .vscode/launch.json: add Debug/Release launch configs for skitrun. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings 6 more examples up to a state where skitrun renders them
end-to-end (joining blackjack from the previous commit). 7/11 examples
now work; the remaining 4 (concurrency, errors_async, macro_tutorial,
enum_generator) need deeper rewrites and are left for follow-up.
Common patterns applied:
- Drop `let <name> = …; print(<name>.generateCode())` wrappers — skitrun
wraps the input in `Group { … }` itself and rejects top-level let/print.
- `ComputedProperty(_:)` → `ComputedProperty(_:type:)` (type is now required).
- `Parameter(name:value:)` inside `Init { … }` → `ParameterExp(name:value:)`.
- `Call("fn", "literal")` → `Call("fn") { ParameterExp(unlabeled: Literal.string("literal")) }`.
- `Infix("a", "op", v)` → `Infix("op", lhs: VariableExp("a"), rhs: <expr>)`.
- `Function("f", parameters: […])` → trailing parameter-builder closure form.
- `.access("public")` → `.access(.public)` (now takes AccessModifier enum).
- `Literal("\"…\"")` → `Literal.string(…)`.
- `VariableDecl(.let, …)` → `Variable(.let, …)` (rename).
- `Variable(…, defaultValue:)` → `Variable(…, equals:)`.
- `.let("x")` pattern shorthand → `Pattern.let("x")`.
- `.reference("weak")` → `.reference(.weak)` (now takes CaptureReferenceType).
- `.property(name:)` → `.property(_:)` (label removed).
- Escape `\(…)` interpolations so the literal appears in the *generated*
code rather than being evaluated against non-existent DSL-scope names.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Per codegen-cli-design.md §7. The spawned `swift` process is the only unknown-runtime piece in skitrun (helpers compile and version capture are bounded and cached); a hung user script previously had no upper bound, so the CLI could sit forever. - New flag: `--timeout <seconds>` on `runSwift`. Default 60. Pass 0 to disable. - On expiry: `process.terminate()` (SIGTERM), 5s grace, then `kill(pid, SIGKILL)`. Exit code 124 (matches POSIX `timeout(1)`), with a `skitrun: timed out after Xs` prefix on stderr. - Folder mode propagates the per-input timeout to each parallel worker. - Drive-by: bad CLI args now exit cleanly with the usage text instead of the Swift runtime's "Fatal error: ..." trace. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Swift swiftmodules aren't reliably compatible across compiler versions (observed today: a 6.3-built SyntaxKit.swiftmodule was rejected by a 6.3.2 swift). When the bundled module won't load, the spawned `swift` emits a cryptic "module compiled with Swift X cannot be imported by Y" diagnostic that doesn't point at the actual fix. - poc-step4-release.sh now writes lib/swift-version.txt at bundle time, recording the build toolchain's `swift --version`. - skitrun reads the stamp on startup and compares to a local `swift --version` capture (existing captureSwiftVersion helper). Strict exact-string match — patch-level drift broke today. - On mismatch: exit 2 with a stderr message that names both versions and points at the rebuild script. New --no-toolchain-check flag bypasses the check (debugging / forward-compat experiments). - On missing stamp (older bundles): one-line warn and continue, so existing prebuilt bundles keep working through the transition. Auto-rebuild on mismatch is the natural follow-up; tracked as #157. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings concurrency, errors_async, and macro_tutorial up to a state
where skitrun renders them end-to-end (joining the 7 already updated).
10/11 examples now work; enum_generator remains broken — it's a fully
programmatic Swift program (struct definitions, JSON loading, demo
runner) rather than a single DSL expression, so it needs structural
restructuring into the Helpers/+dsl pattern. Left for follow-up.
Notable per-example fixes:
- concurrency: rewritten Guard/Throw/Function shapes; Dictionary
values are external `Init` expressions of an `Item` type that
Literal.dictionary's typed cases can't represent, so the inventory
is emitted via the raw VariableExp escape hatch.
- errors_async: dropped the TupleAssignment line — that type became
internal in the current API — and substituted two single Variable
bindings (same observable behaviour for the catch block).
- macro_tutorial: collapsed 11 per-example `let` bindings + a final
`print` into a single top-level Group (skitrun's input shape).
Some sub-examples used `Init { Parameter(...) }` to *declare*
initializers; the current public DSL only has Init-as-expression,
so those are emitted as raw Swift via VariableExp.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Examples/Completed/ holds single-file rendered-DSL examples that skitrun can take as input. enum_generator never fit that shape — it's a full demo project (Package.swift, main.swift, before/after dirs, INTEGRATION_GUIDE.md, JSON config). Move it under a new Examples/Demos/ bucket so the Completed/ contract stays clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`skit` is now a single CLI with two verbs: skit run <input> # was `skitrun` — render DSL → Swift skit parse # was `skit` — stdin Swift → JSON Default subcommand is `run`, so `skit Input.swift` works without a verb. CLI parsing moves to swift-argument-parser, replacing the hand-rolled CLIArgs parser. The declarative @Option/@Flag/@argument surface produces better-formatted help for free and validates inputs at parse time. - Sources/skitrun/ folded into Sources/skit/ (Main.swift → Runner.swift, Helpers + OutputCache unchanged in substance). - skitrun product/target removed from Package.swift; skit gains SwiftSyntax / SwiftParser / Crypto / ArgumentParser deps. - Env vars: SKITRUN_LIB_DIR → SKIT_LIB_DIR; cache-key env prefix SKITRUN_* → SKIT_*. Bundle dir: .build/skitrun-release/ → .build/skit-release/. Homebrew fallback path: lib/skitrun/ → lib/skit/. - Release script moves to Scripts/build-skit-release.sh — promoted from Docs/research/poc-step4-release.sh to reflect that it's now a shipping build script, not a POC step. - Wrapper internal name: __skitrun_root → __skit_root (only visible in spawned-swift error messages on wrapper-line failures). All 10 working Examples/Completed/*/dsl.swift still render through `skit run` (and the default-subcommand form). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drop the "research POC for issue #154" framing. Replace `skitrun` with `skit run`, env var prefixes with SKIT_*, and the release-script path with Scripts/build-skit-release.sh. The "Open scope decisions" section is gone — items either shipped (timeout) or have follow-up issues (#157 auto-rebuild, #158 if-in-Group), and the rest are explicitly out-of-scope. Point at Docs/skit.md for the deeper dive (forthcoming). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The chronological poc-step{1..7}-results.md log and the codegen-cli-design.md
sketch were the trail by which `skit` reached its current shape — useful at
review time, noise for ongoing maintenance. Their substance is now folded
into a single forward-looking explainer at Docs/skit.md (the architecture,
caches, toolchain stamp, timeout, sharp edges, deferred items, references
to #157/#158).
Docs/research/tuist-manifest-pipeline.md stays — it's reference material
for the manifest-pipeline pattern, not a step log.
Git history retains the deleted files for anyone wanting to trace how a
particular decision came about.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The two cache keys (helpers cache, output cache) only need a stable, deterministic, cross-platform hash for content addressing — they aren't security-critical and there's no adversary trying to forge collisions. Drop swift-crypto's SHA-256 in favour of a ~10-line pure-Swift FNV-1a 64-bit hasher. - 64-bit output gives ~10⁻⁹ collision probability at 10⁶ cache entries (well past anything we'll see). - Cache keys go from 64-char hex to 16-char hex. - Drops the swift-crypto dep entirely. On macOS the binary barely changes (~4KB) since swift-crypto used CryptoKit there anyway; on Linux we no longer statically link boringssl, which was the dep's real cost. - New file: Sources/skit/ContentHasher.swift. Same streaming API (update/finalize) so the two call sites are nearly identical to the SHA-256 version. All 10 working examples re-rendered with fresh keys after cache clear; output identical. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
fe0ed83 to
e3e243f
Compare
Code Review — PR #156:
|
Run 25759444849 broke skit's build on every non-macOS-host target. Foundation.Process is unavailable on watchOS/tvOS and on Ubuntu's wasm + wasm-embedded toolchains, and POSIX kill/SIGKILL aren't in scope on Windows. The previous skit implementation worked around Linux Foundation.Process quirks with a DispatchSemaphore + DispatchGroup pipe-drain ladder and a manual kill(pid, SIGKILL) timeout watchdog — none of which port. This commit replaces all three Foundation.Process call sites (captureSwiftVersion, compileHelpers, runSwift) with swift-subprocess (v0.4.0). The timeout watchdog becomes a withThrowingTaskGroup race against Task.sleep + cancelAll; swift-subprocess's teardown sequence handles SIGTERM → grace → SIGKILL on POSIX and the WM_CLOSE/CTRL_C_EVENT/TerminateProcess equivalent on Windows. ~70 lines of platform workaround dropped. swift-subprocess requires Swift 6.1, so swift-tools-version goes 6.0 → 6.1 (and CLAUDE.md follows). skit itself is gated by `#if canImport(Subprocess)`; on platforms where the Subprocess module isn't built (iOS, watchOS, tvOS, visionOS, Android, WASI) the new SkitStub.swift provides a caseless-enum @main that exits 1 with a clear message. The target still links — just with no usable subcommands. A regression test in Tests/SyntaxKitTests/Integration/ covers swift-subprocess #256 (stream-read hang on cancellation when grandchildren inherit pipe fds). skit's runSwift hits this scenario because swift forks the frontend + linker as grandchildren. Test passes locally in ~1s, bounded at 15s. The iOS/visionOS simulator failures in the same CI run were a separate cause: the May-13 workflow sync bumped osVersion to 26.5, but the macos-26 runner image still only ships OS 26.4 simulators, so download-platform: true couldn't fetch what the matrix asked for. Revert osVersion 26.5 → 26.4 on the iOS/watchOS/tvOS/visionOS rows; Xcode pins unchanged. Tracked for re-bump in #160. Scripts/build-skit-debug.sh is a debug-mode counterpart to the existing release-bundle script, for fast local end-to-end iteration on the DSL transformation (~10s vs. 5-15 min). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code Review — PR #156: skit CLI for v0.0.5OverviewThis PR unifies the old Issues1. Dead launch configurations in
|
| Area | Finding |
|---|---|
| Correctness | Dead skitrun launch configs; wrap() has no tests |
| Performance | captureSwiftVersion() called 3× per run |
| Robustness | Build-script Package.swift patching is brittle |
| Security | No new concerns; arbitrary-code-execution is documented and intentional |
| Test coverage | Only subprocess timeout is tested; wrap(), ContentHasher, cache logic are untested |
The architecture is sound and well-documented. The items above are fixable without structural changes — the wrap() test gap is the most important one to close before shipping.
🤖 Generated with Claude Code
Code Review:
|
Code Review: PR #156 —
|
| For(VariableExp("number"), in: VariableExp("numbers"), where: { | ||
| try Infix("==") { | ||
| try Infix("%") { | ||
| VariableExp("number") | ||
| Literal.integer(2) | ||
| For(VariableExp("number"), in: VariableExp("numbers"), then: { | ||
| If(VariableExp("number % 2 == 0"), then: { | ||
| Call("print") { | ||
| ParameterExp(unlabeled: "\"Even number: \\(number)\"") | ||
| } | ||
| Literal.integer(0) | ||
| } | ||
| }, then: { | ||
| Call("print") { | ||
| ParameterExp(unlabeled: "\"Even number: \\(number)\"") | ||
| } | ||
| }) | ||
| }) |
There was a problem hiding this comment.
this is change in functionationality
| // single-variable bindings instead — same observable behaviour for the | ||
| // catch block below. | ||
| Variable(.let, name: "fetchedData") { VariableExp("data") } | ||
| Variable(.let, name: "fetchedPosts") { VariableExp("posts") } |
There was a problem hiding this comment.
The original was correct
- for_loops/dsl.swift: restore `For(_, in:, where:, then:)` with where clause filtering; the earlier rewrite to an inner-body `If` was a semantic change (iterate-all-then-branch vs filter-at-iteration). - errors_async/dsl.swift: restore the original `TupleAssignment(...).async().throwing()` line emitting `let (fetchedData, fetchedPosts) = try await (data, posts)`. - Promote `TupleAssignment` (struct, syntax property, init, `.async()`/`.throwing()`/`.asyncSet()`) to `public` so the example compiles via skit, and switch its `import SwiftSyntax` to `public import SwiftSyntax`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…files [skip ci] Drop the file-level `#if canImport(Subprocess)` around the entire Skit type and the separate SkitStub `@main`. The single `@main Skit` now covers all platforms; only the body of `Run.run()` is guarded, so `skit parse` works without a Subprocess backend. Each subcommand lives in its own extension file (Skit+Run.swift, Skit+Parse.swift), and Run's options shed their verbose `discussion:` text in favor of ArgumentParser's default `--help` rendering. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…p ci] Documents the `skit run` pipeline so a reader can trace it end-to-end from the source files alone: top-of-Runner.swift lifecycle map, numbered phases in Skit.Run.run(), docstrings on every previously undocumented orchestrator, and inline step labels inside processFile, runDirectory, runSwift, wrap, buildHelpers, compileHelpers, outputCacheKey, and storeCachedOutput. No behaviour changes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
This branch lands
skit, a SyntaxKit CLI for config-driven Swift codegen. Two verbs:runis the default subcommand, soskit Input.swiftworks without a verb.The PR unifies the previous
skit(stdin → JSON parser, ~50 lines) and the POCskitrun(DSL renderer, ~700 lines) into one binary with ArgumentParser subcommands. Net replacement: one CLI, one product, declarative arg parsing, productized docs.What's in
skitCLI withrun+parsesubcommands (runis the default). Built onswift-argument-parser.run: parse the input with SwiftSyntax, hoistimports, wrap the rest inGroup { … }, spawn script-modeswift, capture stdout, splice the output.#sourceLocationremaps body diagnostics back to the input file.Helpers/directories auto-discovered up-tree from the input, compiled once intolibSyntaxKitHelpers.{dylib,so}and cached by content hash.--no-helpers/--helpers <dir>overrides.(input, helpers, libSyntaxKit stamp, swift version, sorted SKIT_*/SYNTAXKIT_* env)hash. Warm hit ≈ 0.14s, no swift spawn.--no-cacheto skip.lib/swift-version.txtat build time;skit runcompares to localswift --versionon startup and refuses to spawnswifton mismatch with a clear error and the rebuild command.--no-toolchain-checkbypass.swift. SIGTERM → 5s grace → SIGKILL. Exit code 124.--timeout 0disables. Implementation usesDispatchSemaphore.wait(timeout:)to avoid LinuxFoundation.Process.waitUntilExit()hangs.Scripts/build-skit-release.shproduces.build/skit-release/{skit, lib/}. Portable:cp -rit anywhere, runs zero-config.swift:6.0-jammy/aarch64.swift-cryptoreplaces CryptoKit; the Mach-Oinstall_namerewrite is skipped. Concurrent pipe drain viaDispatchGroupto avoid the 64KB pipe-buffer deadlock.Examples/Completed/*/dsl.swiftfiles render end-to-end throughskit. The 11th (enum_generator) was a full demo project shape rather than single-file DSL; moved toExamples/Demos/enum_generator/where it doesn't masquerade as a completed example.Docs/skit.mdis the deep dive (architecture, caches, toolchain stamp, timeout, sharp edges, deferred items).Sources/skit/README.mdis the per-target quick reference.What's deferred
Tracked as follow-up issues, not blockers:
skitbundles SyntaxKit sources and rebuilds transparently per-Swift-version when the stamp doesn't match. The stamp-and-detect path in this PR is the foundation.if/elseinsideGroup { … }builder triggers Swift type-checker crash #158 —if-in-GroupSwift type-checker crash. Independent SyntaxKit bug (notskit's); the workaround is documented inDocs/skit.md(hoist the conditional into a helper that uses plain Swiftif/else).Explicitly out of scope for v0.0.5: multi-file output from a single input, sandboxing the spawned
swift, an HTTP/web-server form. All discussed inDocs/skit.mdunder "What's deferred".Sharp edges documented for users
@mainand other top-level decl attributes don't work on DSL expressions (the wrapper places them inside a result builder where attributes can't bind to function calls). Users wanting@availableetc. in the output should use the DSL's.attribute(…)method..comment { Line(.doc, …) }.Test plan
Reviewer focus
Docs/skit.md— the design + as-shipped explainer. Best read first.Sources/skit/Skit.swift— CLI surface via ArgumentParser.Sources/skit/Runner.swift— the wrap-and-spawn pipeline + toolchain check.Scripts/build-skit-release.sh— bundle layout and how the dylib + swiftmodules get staged.The deferred items (#157, #158) and the documented sharp edges (
@main, comments) are the natural discussion points; everything else should be a straight read.🤖 Generated with Claude Code