Imported from donaldfilimon/gama (
AGENTS.md). Install upstream withnpx skills add donaldfilimon/gama. Copyright stays with the author.
Gama Agent Guide
Repository Identity
- This is the SwiftPM Gama Framework checkout.
~/dev/active/gama-qtis an unrelated Qt browser app. - The package is a retained UI core plus plugin, drawing, TUI, Apple, WASM, C/Android, MLIR, macro, and demo targets.
Toolchain And Commands
- Run
unset TOOLCHAINSbefore Swift commands. Useswiftly run swift ...;.swift-versionpinsmain-snapshot-2026-08-21(Swift 6.5-dev). Package.swiftdeliberately staysswift-tools-version: 6.4so Xcode's SwiftPM can resolve platform gates.check-boundaries.shenforces this; do not upgrade it with the compiler.Toolchains.tomlis the pin authority.scripts/check-toolchain-pins.sh, chained from the boundary gate, rejects drift in compiler/SDK revisions, URLs, and checksums; it also discovers everyGAMA_TOOLCHAIN_IDdefault rather than listing scripts, and fails on any checked-in home-directory path underscripts/.- Scripts derive the pinned snapshot's location from
Toolchains.tomlthroughscripts/lib/toolchain.sh. Do not write an absolute toolchain path into a script; override withGAMA_SWIFT_64/GAMA_SWIFTC_64/GAMA_EMBEDDED_TOOLCHAINinstead. - This checkout is iCloud/FileProvider-managed. Direct tests must use a scratch path outside the repository:
unset TOOLCHAINS
swiftly run swift --version # must report 6.5-dev
swiftly run swift build
swiftly run swift test --scratch-path /private/tmp/gama-framework-swiftpm
swiftly run swift test --scratch-path /private/tmp/gama-framework-swiftpm --filter SceneGraphTests
swiftly run swift build --target GamaCore
swiftly run swift run gama-demo
.agents/skills/run-gama/driver.sh smoke # tmux-driven TUI proof; do not pipe gama-demo
- Test filters match Swift source identifiers, not
@Suitedisplay names. A non-matching filter prints a warning and exits 0; confirm the test count. - Tests use Swift Testing (
import Testing) only. Do not add XCTest; macro expansion tests useSwiftSyntaxMacrosGenericTestSupport. - Gate scratch override is
GAMA_SCRATCH_ROOT, notSCRATCH_ROOT.check-mlir.shhardcodes/private/tmp/gama-framework-swiftpmwith no override, so a filteredswift testand a concurrent MLIR gate collide. - Drive
gama-demowith therun-gamaskill (tmux). It is mirrored at.agents/skills/run-gama/and.claude/skills/run-gama/; change both.gama-demostill ownsTUIRendereritself because of its plugin loop;App.runAdaptive()is the TTY-versus-pipe entry for ordinary apps.
Verification
- Fast Apple gate:
./scripts/check-apple.sh(debug build, all tests, release build). - Portable ownership/import/symbol rules:
./scripts/check-boundaries.sh. - Documentation gates:
./scripts/check-docs.sh && ./scripts/check-doc-coverage.sh. New public declarations need///; do not expand the coverage allowlist without a genuine baseline exception. - Android cross-build/JNI packaging requires
ANDROID_NDK_HOME=... ./scripts/check-android.sh. - Full acceptance is
./scripts/check.sh. Itsgatesarray is authoritative and currently runs 15 fail-closed gates: Apple, Apple platforms, boundaries, concurrency negatives, C ABI, Embedded, Linux, WASM, Android, Android emulator, MLIR, DocC, doc coverage, evidence freshness, and package graph. - python3 is a prerequisite of the documentation and boundary helpers; node is a prerequisite of the WASM gate.
- Some full-matrix gates require pinned SDKs, the NDK, Node/browser tooling, MLIR, or hosted non-macOS runners. Missing proof is a failure; do not weaken or skip gates to make the matrix green.
- CI truth is
.github/workflows/ci.yml. Windows deliberately uses the pinned Swift 6.4.x exception; other jobs use the 6.5-dev snapshot family.
Architecture Boundaries
- Flow:
App -> SceneBuilder -> RenderNode -> LayoutEngine -> CellPainter -> CellBuffer -> DrawList -> backend; platform events return throughFrameHost. - Every app declares exactly one primary scene. All backends except
GamaAppleShellrender only that primary scene; the shell owns macOS auxiliary/multi-window surfaces. GamaCoreandGamaPluginare stdlib-only. The platform-import ban covers all five portable targets —GamaCore,GamaPlugin,GamaDraw,GamaEmbed,GamaMLIR— which may not import Foundation, platform UI/POSIX modules, WinSDK, or Synchronization, and framework state must not move into process-global registries. Both bans are enforced byscripts/portable-global-state.pyover its singleTARGETSlist, so there is no second list to keep in step; it fails closed on a target directory that is missing or empty.FrameHostandAppRuntimeare~Copyable; each host uniquely owns focus, actions,@Reactivestate, subscriptions, dirty state, and frames. Out-of-band changes use host subscriptions or explicitinvalidate().GamaPlatformServicescontains Foundation-backed host-service implementations. Only apps, demos, examples, and tests may import it; portable/framework targets must depend on service interfaces instead.GamaMacrosImplis a host compiler plugin.swift-syntaxis revision-pinned and build-time-only; shipped products must retain zero runtime package dependencies.- Backends translate events and present shared
DrawListoutput; do not fork layout, paint, or application semantics. Keep Cgama_embed_v1_*and WASMgama_web_v1_*/gama_web_v2_*symbols versioned and separately namespaced; the WASM backend ships both tiers,v2being the argument-compatible status-reporting form (docs/backends/WASM.md). CellPresenter(mutating, swaps planes: TUIAnsiPresenter/StreamPresenter) andCellSerializer(non-mutating, no swap: WASM, Embed, and AppleDrawListSerializer) are distinct families. Do not unify them (docs/superpowers/specs/2026-09-06-cell-serializer-design.md).App.runAdaptive()selects interactive versus stream from stdout (--gama-plain/--gama-tui). An input-less stream run ends at the first quiescent frame; async work must declareCompletionStatusviacomplete(_:).FailureExitCodeis an opt-in1...255constructor used withfailure(exitCode:_:); it rejects0, negatives, and256+by failing construction. The unvalidatedfailure(code:_:)factory still accepts zero.
State And Documentation Traps
@Reactiveis per-surface; aSignalon theAppis shared (ADR 0011). Scene content closures run every frame, and a component constructed inline keeps its@Reactivestate because@Component's synthesizedrender(in:)binds each slot to the host's identity-keyed store; two windows of oneWindowGroupget independent state, and a hoisted instance still writes per surface. RawSignalproperties inside components are unsupported; use@Reactiveand_name.binding().- The binding cannot be skipped silently:
@Reactiveoutside a struct marked@Componentis errorreactive.requires-component, and a hand-writtenrender(in:)beside@Reactiveproperties is errorcomponent.render-collision.FrameHost.transientStateIDsreports storage replaced at an existing(NodeID, slot)key; it does not report new/removed keys or positional storage reuse. PositionalForEachstate follows indices through reordering; useIdentifiedForEachor.stateScope(_:)when state must follow an element. - Read
docs/README.md, the relevantdocs/adr/record, anddocs/backends/<Backend>.mdbefore changing a settled backend contract. Plugin tier/capability work starts withdocs/Plugins.md. docs/Capabilities.mdis the evidence ledger. Distinguish implemented, locally proven, hosted proven, provisional, and blocked behavior; implementation presence alone is not platform proof.#expectcannot read a bare property off a~Copyablehost (#expect(host.needsFrame)does not compile). Bind first:let dirty = host.needsFrame; #expect(dirty).GEMINI.mdis a tracked empty placeholder; do not fill it. No gate reads it.
Repository Safety
- Preserve
Package.resolved. Never commit credentials or runner configuration, force-pushmain, or merge before required checks are green. - Never run
git gc,git prune,git fsck, orgit repackin this FileProvider-managed checkout.