Repo-local guidance for the standalone SpeakSwiftly Swift package.
- This file is the repo-local guidance surface for the standalone
SpeakSwiftlySwift package. - Treat this repository as the source of truth for
SpeakSwiftlypackage development, tags, and releases. - Treat
../speak-to-user/packages/SpeakSwiftlyas the integration submodule copy, not the primary development home.
- Start with
Package.swift,Package.resolved,README.md,CONTRIBUTING.md, andROADMAP.mdfor package shape, dependency state, public docs, contributor workflow, and planned work. - Read
Sources/SpeakSwiftly/API,Sources/SpeakSwiftly/Generation,Sources/SpeakSwiftly/Playback,Sources/SpeakSwiftly/Normalization,Sources/SpeakSwiftlyNormalization, andSources/SpeakSwiftly/Runtimeaccording to the feature surface being changed. - Mirror package-owned normalization API and state coverage in
Tests/SpeakSwiftlyTests/Normalization, and pure normalization and summarization coverage inTests/SpeakSwiftlyNormalizationTests. - Use
docs/maintainers/validation-lanes.mdandscripts/repo-maintenance/for validation, release, runtime publishing, and maintainer operations.
- Keep
README.mdshort, nontechnical, and focused on end users and agents deciding whether SpeakSwiftly fits a local speech workflow. - Keep contributor workflow, maintainer operations, validation lanes, release workflow, and technical reference pointers in
CONTRIBUTING.mdordocs/maintainers/, not inREADME.md. - Keep agent-facing maintainer guidance in this
AGENTS.mdfile so future agents do not need to infer operating rules from public-facing docs. - Leave
README.mdOverview subsections that need Gale's own wording asTBDuntil Gale provides the replacement text.
- Use Swift Package Manager as the source of truth for package structure and dependencies.
- Prefer
swift packagesubcommands for dependency, target, and manifest-adjacent changes before hand-editingPackage.swift. - Keep package graph updates cohesive across
Package.swift,Package.resolved, and related source or test targets. - Use
xcodebuildonly when Apple-platform configuration details, test plans, SDK behavior, or Metal-toolchain behavior matter in a way plain SwiftPM cannot validate well. - Treat
mlx-audio-swiftas a current compatibility dependency to shrink and eventually remove, not as the default implementation home for new model families. - For new speech model research and backend work, prefer first-party Swift-owned Apple-platform pipelines using Core AI, Accelerate, CoreMedia, CoreAudio, and AVFoundation where those frameworks fit the stage.
- Treat community ports such as
mlx-audioandmlx-audio-swiftas comparison evidence unless Gale explicitly asks to adopt or port them directly. - Treat the local
../speak-to-usercheckout as a clean base checkout only. It must stay onmain, and it must stay clean. - Never change the local branch of the base
../speak-to-usercheckout for feature work, experiments, release bumps, or submodule updates. - For any monorepo change, create a new branch in a new
git worktreeand do the work there instead of touching the base../speak-to-usercheckout. - After a monorepo branch is merged, fast-forward the base
../speak-to-usercheckout back tomainand delete the merged worktree and branch. - When
speak-to-useradopts a newSpeakSwiftlyversion, prefer updating the submodule pointer to a taggedSpeakSwiftlyrelease rather than an arbitrary branch tip. - Land monorepo submodule bumps through a pull request against the monorepo instead of pushing those pointer updates directly to monorepo
main. - Use tagged releases for the monorepo when publishing coordinated umbrella states that depend on specific submodule versions.
- Keep
Sources/SpeakSwiftly/APIas the single home for publicSpeakSwiftly.Runtimeconcern-handle accessors,SpeakSwiftly.Name, and other operator-facing library surface declarations. - Keep feature logic in its feature directory, not in
Runtime/. Public normalization API, state, persistence lifecycle, and generation integration belong inSources/SpeakSwiftly/Normalization; pure normalization and summarization algorithms plus their value models belong in the internal, non-vendedSpeakSwiftlyNormalizationtarget. Generation and voice-profile logic belongs inGeneration/, and playback logic belongs inPlayback/. - Keep
Sources/SpeakSwiftly/Runtimefor runtime-only internals such as worker request handling, queue orchestration, lifecycle management, event emission, and other machinery that is genuinely part of the worker runtime itself. - Do not split one feature across three places when two will do. For any given feature, prefer one API file in
API/plus one logic file in the relevant feature directory. - Mirror the source tree by feature area in tests so API, generation, playback, normalization, runtime, support, and e2e coverage are easy to find. Keep algorithm-focused normalization tests in
SpeakSwiftlyNormalizationTestsand public state/lifecycle tests inSpeakSwiftlyTests/Normalization. - For the JSONL worker surface, keep operation names snake_case and verb-first.
- For JSONL reads, use
get_*for one resource or snapshot andlist_*for collections and queue snapshots. - For JSONL writes, prefer
create_*,update_*,replace_*, anddelete_*when those verbs fit the real semantics. - Keep literal lifecycle and control verbs like
queue_*,set_*,reload_*,unload_*,pause,resume,clear_*,cancel_*,load_*,save_*, andreset_*when the operation is not best modeled as CRUD. - When adding or renaming a JSONL operation, update
Sources/SpeakSwiftly/SpeakSwiftly.docc/WorkerContract.mdandCONTRIBUTING.mdin the same pass so the wire naming convention stays documented without makingREADME.mdtechnical. - Keep
SpeakSwiftly.liftoff(configuration:)as the single public startup entry point, with optional configuration carrying startup-time choices such asspeechBackendand an optionaltextNormalizer. - Expose stored concern handles such as
generate,playback,voices,normalizer,jobs, andartifactsfromSpeakSwiftly.Runtimeinstead of growing one monolithic method namespace. - Keep typed Swift observation surfaces aligned around
Event,State,Update, andSnapshotmodel families.RequestEvent,RequestState,RequestUpdate, andRequestSnapshotare the baseline shape; Generate, Playback, Runtime, and per-request Synthesis observation should rhyme with that model. - Keep those concern handles lightweight views over shared runtime state, not separate subsystems with their own lifecycle or duplicated ownership.
- Prefer separate public generation verbs for live playback and file output, such as
Generate.speech(...)andGenerate.audio(...), instead of exposing a public job-type switch. - Keep generation-queue inspection on
Generate/Jobsand playback-queue inspection onPlayback; do not mirror internal queue routing details in the public typed surface when the domain handle already makes ownership clearer. - Public transport and result model types may stay public for inspection, but keep memberwise construction internal unless callers have a concrete reason to author those values directly.
- Use
SpeakSwiftly.Nameas the semantic name type for stored voice-profile names and similar stable operator-facing resources. - For the voice-profile library surface, prefer one
Voices.create(...)verb with explicit overloaded first labels, such ascreate(design named: Name, ...)andcreate(clone named: Name, ...), instead of multiplying unrelated creation verbs. - For first-party bundled built-in voices, use the
create-system-voice-profileSwiftPM command plugin from this checkout with--target SpeakSwiftly; it writes reviewed system profile resources underSources/SpeakSwiftly/Resources/SystemProfiles/profiles/, which theSpeakSwiftlytarget already bundles.
- Read the relevant Apple or Swift documentation first for any Swift, Apple-framework, Apple-platform, SwiftUI, SwiftData, Observation, AppKit, UIKit, Foundation-on-Apple, or Xcode-related task before planning or changing code.
- Use Dash or local Apple documentation first, then official Apple or Swift web docs when local docs are insufficient.
- Before proposing an architecture or implementation, state the documented API behavior, lifecycle rule, or workflow requirement being relied on.
- If documentation and the current code disagree, stop and report the conflict before continuing.
- Before adding a new layer, abstraction, wrapper, manager, bridge, coordinator, repository, store, helper type, service, dependency, or package, explain which near-term use cases it unlocks here in
SpeakSwiftly, which real pain or duplication it removes, and which simpler extension path was considered first. - Do not change this repository's core architecture casually or silently. If the design starts needing a new queue, subsystem, storage model, or ownership boundary, stop and make that pivot explicit to Gale before implementing it, or as soon as the need becomes clear.
- When future scope is already visible and the current model will not compose cleanly, prefer strengthening the core primitives on purpose over shipping narrow stopgaps that will soon block momentum.
- Use
bootstrap-swift-packageonly when a new Swift package repository still needs to be created from scratch. - Use
sync-swift-package-guidancewhen this repository's package workflow guidance drifts and needs to be refreshed or merged forward. - Re-run
sync-swift-package-guidanceafter substantial package-workflow or Apple Dev Skills plugin updates so the local guidance and repo-maintenance contract stay aligned. - Keep the repo-owned Codex local environment in
.codex/environments/swift-package.tomlportable and repo-relative so shared setup and action buttons work the same way across worktrees.
swift package resolveswift build
swift test
bash scripts/repo-maintenance/validate-all.shsh scripts/repo-maintenance/publish-runtime.sh --configuration Debug
sh scripts/repo-maintenance/run-e2e.sh --suite quick
sh scripts/repo-maintenance/run-e2e-full.sh
SPEAKSWIFTLY_E2E=1 swift test --filter GeneratedFileE2ETestsUse SPEAKSWIFTLY_PLAYBACK_TRACE=1 for chunk, scheduling, and rebuffer trace events during deep-trace playback work. For audible deep-trace playback verification, use:
SPEAKSWIFTLY_E2E=1 SPEAKSWIFTLY_DEEP_TRACE_E2E=1 SPEAKSWIFTLY_PLAYBACK_TRACE=1 swift test --filter longCodeHeavyUse scripts/repo-maintenance/sync-shared.sh for repo-local shared sync tasks, scripts/repo-maintenance/release.sh for releases, and the maintained scripts under scripts/repo-maintenance/ for publish, verify, vendored-bundle refresh, and repo validation work instead of reconstructing those flows ad hoc.
- Prefer the simplest correct Swift that is easiest to read, reason about, and maintain.
- Treat idiomatic Swift and Cocoa conventions as tools in service of readability, not goals by themselves.
- Do not add ceremony, abstraction, or boilerplate just to make code look more architectural, more generic, or more "Swifty".
- Strongly prefer synthesized, implicit, and framework-provided behavior over handwritten setup code.
- Prefer synthesized conformances, memberwise initializers, default property values, and framework defaults whenever they satisfy the real requirements.
- Do not add
CodingKeys, manualCodable, custom initializers, wrappers, helper types, protocols, coordinators, or extra layers unless they are required by a concrete constraint or make the final code clearly easier to understand. - Prefer stable, source-of-truth naming across layers when the data and meaning have not changed.
- Preserve raw wire and persistence shapes by default; do not add DTO, domain, or view-model conversion layers unless meaning actually changes or a concrete boundary requires it.
- Keep code compliant with Swift 6 language mode and strict concurrency checking.
- Prefer modern structured concurrency (
async/await, task groups, actors,AsyncSequence) when it keeps the flow clearer and more direct. - Prefer first-party and top-tier Swift ecosystem packages from Apple,
swiftlang, the Swift Server Work Group, and similar trusted core Swift projects when they simplify the code and make it easier to reason about. - For packages, server-side, or cross-platform Swift, prefer Swift Logging as the primary logging API.
- Prefer Swift OpenTelemetry for telemetry and instrumentation when telemetry is needed, and prefer existing ecosystem integrations over bespoke wrappers.
- Prefer Nick Lockwood's SwiftFormat and/or SwiftLint as the baseline Swift formatting and linting tools; at least one should stay configured and used in this repository.
- Never run multiple build toolchains, package managers, test runners, or other heavy validation commands at the same time on Gale's machine.
- Never run multiple SwiftPM or Xcode build or test processes concurrently for this repository.
- Treat
swift buildandswift testas the fast inner-loop checks for this package. - For MLX-backed package tests, stay in the plain
swift testlane by default. The test target bundlesdefault.metalliband the shared test bootstrap stages it into the direct SwiftPM probe path under.build/...before the first MLX-backed test model is created. - The older vendored
mlx-audio-swiftparser failure inEnglishG2P.swiftis historical with the current dependency pin. If a futureswift buildorswift testrun hits that same failure again, stop retrying the same SwiftPM lane and switch to the Xcode-backed validation path documented inCONTRIBUTING.mdanddocs/maintainers/validation-lanes.md. - Treat the GitHub Actions package lane as its own documented CI surface: keep
swift package dump-package, then use the current Xcode-backedbuild-for-testingplus targetedtest-without-buildingmatrix shown in.github/workflows/swift.yml. - Use Swift Testing (
import Testing) as the default package test framework, and keep XCTest only when an external dependency or platform constraint requires it. - Treat
sh scripts/repo-maintenance/run-e2e.sh --suite quickas the smallest opt-in real-model E2E path for this package. - For release-grade standalone-worker validation, Marvis overlap investigation, or any validation pass that is actually blocked by a renewed SwiftPM parser regression, prefer running
xcodebuild build-for-testingfrom the repo root with-scheme SpeakSwiftly-Package, then follow it with targetedxcodebuild test-without-buildingruns instead of ad hoc retries through plain SwiftPM. - Before worker-backed E2E, use the repo-maintenance wrappers so
scripts/repo-maintenance/unload-live-service-resident-models.shcan ask the liveSpeakSwiftlyServerservice to unload resident models without uninstalling or stopping the LaunchAgent-backed service, andscripts/repo-maintenance/reload-live-service-resident-models.shcan restore residency after testing completes. The helpers useSPEAKSWIFTLY_LIVE_SERVICE_BASE_URLwhen set and only skip deliberately whenSPEAKSWIFTLY_SKIP_LIVE_SERVICE_UNLOAD=1andSPEAKSWIFTLY_SKIP_LIVE_SERVICE_RELOAD=1. - Keep the shared test profile convention stable unless Gale explicitly changes it:
profile_name:testing-profilevoice_description:A generic, warm, masculine, slow speaking voice.
- Expect generated
*.profrawcoverage artifacts from local test runs and do not commit them.
- Do not use a plain SwiftPM-built worker product for real MLX-backed command-line runs and real-model E2E coverage. Use the Xcode-built worker product for those standalone executable lanes because upstream
mlx-swiftdoes not make the Metal shader bundle available to the plain SwiftPM command-line build. - When launching the real worker from the shell, prefer the deterministic Xcode runtime launcher under
.local/derived-data/runtime-debug/run-speakswiftlyor.local/derived-data/runtime-release/run-speakswiftlyinstead of reconstructingDYLD_FRAMEWORK_PATHanddefault.metallibpaths by hand. - If a real worker run fails with
default.metallibormlx-swift_Cmlx.bundleerrors, treat that as a build-and-launch-path problem first, not as evidence that the worker runtime itself is broken. - For direct deep-trace worker captures, prefer the held-open stdin pattern instead of sending one JSONL file and allowing stdin to close immediately.
- The current known worker behavior is that if stdin closes before queued work drains, the worker can cancel queued requests that are still waiting behind resident-model warmup. Treat that as a current shutdown-and-queue quirk to be fixed, not as normal playback failure.
- Ask before widening a package change into a coordinated
speak-to-usermonorepo submodule bump or coordinated umbrella tag. - Ask before changing the shared test profile convention, release validation lane, runtime publishing layout, or resident-model queue behavior.
- Ask before replacing the SwiftPM-first package workflow with an Xcode-first workflow for ordinary package work.
This repository does not currently define deeper AGENTS.md files. If a future subdirectory adds one, the closer file refines this root guidance for work inside that subtree.
- Use
swift buildandswift testas the default first-pass validation commands for this package. - Use
bootstrap-swift-packagewhen a new Swift package repo still needs to be created from scratch. - Use
sync-swift-package-guidancewhen the repo guidance for this package drifts and needs to be refreshed or merged forward. - Re-run
sync-swift-package-guidanceafter substantial package-workflow or plugin updates so local guidance stays aligned. - Use
swift-package-build-run-workflowfor manifest, dependency, plugin, resource, Metal-distribution, build, and run work whenPackage.swiftis the source of truth. - Use
swift-package-testing-workflowfor Swift Testing, XCTest holdouts,.xctestplan, fixtures, and package test diagnosis. - Use
scripts/repo-maintenance/validate-all.shfor local maintainer validation andscripts/repo-maintenance/sync-shared.shfor repo-local sync steps. - Use
scripts/repo-maintenance/release.sh --mode standard --version vX.Y.Zfrom a feature branch or worktree only when the task is actually a protected-main release, publish, merge, tag, or release-PR preparation. - Do not run the standard release workflow from
main; when a protected-main release is explicitly requested, let it validate, bump versions, tag, push the branch and tag, open the release PR, watch CI, address valid PR comments or record out-of-scope concerns inROADMAP.md, merge to protectedmain, fast-forward localmain, and clean up stale branches. - Treat
scripts/repo-maintenance/config/profile.envas the installedmaintain-project-repoprofile marker, and keep it on theswift-packageprofile for plain package repos. - Read relevant SwiftPM, Swift, and Apple documentation before proposing package-structure, dependency, manifest, concurrency, or architecture changes.
- Prefer Dash or local Swift docs first, then official Swift or Apple docs when local docs are insufficient.
- When SwiftPM behavior, manifest syntax, package plugins, resources, products, targets, or dependency rules matter, prefer the Dash.app docset workflow with the
swiftlang/swift-package-managerdocset first; fall back to the canonicalswiftlang/swift-package-managerGitHub repository only when the local docset is unavailable or insufficient. - Prefer the simplest correct Swift that is easiest to read and reason about.
- Prefer synthesized and framework-provided behavior over extra wrappers and boilerplate.
- For public Swift APIs, treat streamlined, compact, ergonomic call sites as the only acceptable default; prefer optional parameters with explicit default values over additional methods or overloads when the difference is optional behavior on the same operation.
- When a public function, initializer, or method reaches four or more arguments or parameters, strongly prefer a named typed
structrequest, options, or configuration value so call sites stay readable and future additions do not multiply overloads. - Prefer enums, enum cases with associated values, and narrow typed values over strings, booleans, sentinel values, or parallel parameters whenever the domain has a closed or meaningful set of choices.
- Keep data flow straight and dependency direction unidirectional.
- Treat
Package.swiftas the source of truth for package structure, targets, products, and dependencies. - Prefer
swift packagesubcommands for structural package edits before manually editingPackage.swift. - Edit
Package.swiftintentionally and keep it readable; agents may modify it when package structure, targets, products, or dependencies need to change, and should try to keep package graph updates consolidated in one change when possible. - Keep
Package.swiftexplicit about its package-wide Swift language mode. On current Swift 6-era manifests, preferswiftLanguageModes: [.v6]as the default declaration, treatswiftLanguageVersionsas a legacy alias used only when an older manifest surface requires it, and keep the supported Swift toolchain window focused on the latest stable minor and previous stable minor. Treat Swift6.2as the current minimum floor for trait-enabled manifests, not as a ceiling; use newer stable Swift toolchains when available and validated, and refresh this guidance when the maintained floor or window changes. Do not lower// swift-tools-version:below6.2without an explicit repo policy and a matching guidance update. - Keep
swift-configurationas the default configuration dependency for Swift packages unless the package has a concrete reason to remove it. The preferred manifest shape depends onhttps://github.com/apple/swift-configurationfrom1.2.0, enables the.defaults,Reloading,YAML, andCommandLineArgumentspackage traits, and adds theConfigurationproduct to the primary target. Add thePropertyListtrait when the package should parse property-list configuration, and add theLoggingtrait when configuration access should integrate withSwiftLog.Logger. - Keep dependency provenance concise but explicit enough for another contributor to fetch the same package: use package-manager, package-registry, GitHub URL, or other real remote repository requirements, and do not commit machine-local dependency paths such as
/Users/...,~/...,../..., local worktrees, or private checkout paths. Avoid branch- or revision-based requirements unless the user explicitly asks for that level of control. - Treat
Package.resolvedand similar package-manager outputs as generated files; do not hand-edit them. - Prefer Swift Testing by default unless an external constraint requires XCTest.
- Use
apple-ui-accessibility-workflowwhen the package work crosses into SwiftUI accessibility semantics, Apple UI accessibility review, or UIKit/AppKit accessibility bridge behavior. - Keep package resources under the owning target tree, declare them intentionally with
Resource.process(...),Resource.copy(...),Resource.embedInCode(...), and load them throughBundle.module. - Keep test fixtures as test-target resources instead of relying on the working directory.
- Bundle precompiled Metal artifacts such as
.metallibfiles as explicit resources when they ship with the package, and preferxcode-build-run-workflowwhen shader compilation or Apple-managed Metal toolchain behavior matters. - Prefer normal SwiftPM parallel test execution for ordinary Swift Testing and XCTest runs. Do not serialize regular package tests just because they use Swift, XCTest, async tests, fixtures, or test plans.
- Treat tests that load large local AI or ML models, especially models over 500 million parameters, as heavy system-resource tests. Run those tests sequentially, one at a time, and call
unload_modelson Gale's live TTS service before the heavy run andreload_modelsafter it ends, even when the run fails or is interrupted. - Validate both Debug and Release paths when optimization or packaging differences matter, and treat tagged releases as a cue to verify the Release artifact path before publishing.
- Prefer
xcode-build-run-workfloworxcode-testing-workflowonly when package work needs Xcode-managed SDK, toolchain, or test behavior. - Keep runtime UI accessibility verification and XCUITest follow-through in
xcode-testing-workflowrather than treating package-side testing as a substitute for live UI verification.