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
21 changes: 21 additions & 0 deletions .codex/environments/swift-package.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Copy to .codex/environments/swift-package.toml and adjust action names if needed.
version = 1
name = "swift-package"

[setup]
script = "swift package resolve"

[[actions]]
name = "resolve"
icon = "tool"
command = "swift package resolve"

[[actions]]
name = "build"
icon = "tool"
command = "swift build"

[[actions]]
name = "test"
icon = "tool"
command = "swift test"
21 changes: 11 additions & 10 deletions .github/workflows/validate-repo-maintenance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,20 @@ on:
branches:
- main

permissions:
contents: read

concurrency:
group: validate-repo-maintenance-${{ github.ref }}
cancel-in-progress: true

jobs:
validate:
name: validate
runs-on: ubuntu-latest
timeout-minutes: 5
runs-on: macos-26
steps:
- uses: actions/checkout@v5
# This is a validated floor, not a ceiling; update to newer stable official versions when validated.
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
persist-credentials: false
- name: Report selected Xcode
run: xcode-select --print-path
- name: Report Swift toolchain
run: xcrun swift --version
- name: Install Swift repo-maintenance tools
run: brew install swiftformat swiftlint
- name: Run repo-maintenance validation
run: bash scripts/repo-maintenance/validate-all.sh
39 changes: 39 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,42 @@ xcodebuild docbuild -scheme SwiftASB -destination generic/platform=macOS -derive
- Keep `CodexWireInitializeResponse` hand-owned in its own dedicated Swift file next to the promoted generated v2 snapshot until the upstream v2 schema exposes that type directly.
- Do not reintroduce a promoted generated v1 batch unless Gale explicitly asks for that compatibility surface again.
- Treat the generated wire layer as an internal scaffolding surface, not the final public Swift API.

## Swift Package Workflow

- Use `swift build` and `swift test` as the default first-pass validation commands for this package.
- Use `bootstrap-swift-package` when a new Swift package repo still needs to be created from scratch.
- Use `sync-swift-package-guidance` when the repo guidance for this package drifts and needs to be refreshed or merged forward.
- Re-run `sync-swift-package-guidance` after substantial package-workflow or plugin updates so local guidance stays aligned.
- Use `swift-package-build-run-workflow` for manifest, dependency, plugin, resource, Metal-distribution, build, and run work when `Package.swift` is the source of truth.
- Use `swift-package-testing-workflow` for Swift Testing, XCTest holdouts, `.xctestplan`, fixtures, and package test diagnosis.
- Use `scripts/repo-maintenance/validate-all.sh` for local maintainer validation and `scripts/repo-maintenance/sync-shared.sh` for repo-local sync steps.
- Use `scripts/repo-maintenance/release.sh --mode standard --version vX.Y.Z` from 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 in `ROADMAP.md`, merge to protected `main`, fast-forward local `main`, and clean up stale branches.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Align the agent release steps with release.sh

This newly added guidance tells agents to tag and push the tag before opening the release PR, but the same commit changed scripts/repo-maintenance/release.sh so standard releases create and push the tag only after CI/review, merge, and local fast-forward (run_standard_release now calls merge_pr, fast_forward_base_branch, then create_release_tag/push_release_tag). For protected-main releases where an agent follows AGENTS.md instead of just invoking the script, this reintroduces pre-review tags that the script is explicitly avoiding; update this sequence to match the post-merge tagging flow.

Useful? React with 👍 / 👎.

- Treat `scripts/repo-maintenance/config/profile.env` as the installed `maintain-project-repo` profile marker, and keep it on the `swift-package` profile 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-manager` docset first; fall back to the canonical `swiftlang/swift-package-manager` GitHub 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 `struct` request, 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.swift` as the source of truth for package structure, targets, products, and dependencies.
- Prefer `swift package` subcommands for structural package edits before manually editing `Package.swift`.
- Edit `Package.swift` intentionally 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.swift` explicit about its package-wide Swift language mode. On current Swift 6-era manifests, prefer `swiftLanguageModes: [.v6]` as the default declaration, treat `swiftLanguageVersions` as 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 Swift `6.2` as 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:` below `6.2` without an explicit repo policy and a matching guidance update.
- Keep `swift-configuration` as the default configuration dependency for Swift packages unless the package has a concrete reason to remove it. The preferred manifest shape depends on `https://github.com/apple/swift-configuration` from `1.2.0`, enables the `.defaults`, `Reloading`, `YAML`, and `CommandLineArguments` package traits, and adds the `Configuration` product to the primary target. Add the `PropertyList` trait when the package should parse property-list configuration, and add the `Logging` trait when configuration access should integrate with `SwiftLog.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.resolved` and 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-workflow` when 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 through `Bundle.module`.
- Keep test fixtures as test-target resources instead of relying on the working directory.
- Bundle precompiled Metal artifacts such as `.metallib` files as explicit resources when they ship with the package, and prefer `xcode-build-run-workflow` when 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_models` on Gale's live TTS service before the heavy run and `reload_models` after 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-workflow` or `xcode-testing-workflow` only when package work needs Xcode-managed SDK, toolchain, or test behavior.
- Keep runtime UI accessibility verification and XCUITest follow-through in `xcode-testing-workflow` rather than treating package-side testing as a substitute for live UI verification.
6 changes: 5 additions & 1 deletion Sources/SwiftASB/SwiftASB.docc/CodexThread.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ Use ``startPlanningTurn(_:approvalPolicy:approvalsReviewer:currentDirectoryPath:

Use ``makeAgenda()`` when a UI wants current goal and plan state for this thread. Agenda also exposes UI-friendly goal actions. Use ``readGoal()``, ``setGoal(_:)``, and ``clearGoal()`` when a non-UI caller needs direct goal reads or mutation.

Plan and goal actions are separate controls. Use plan mode first to shape complex or ambiguous work, then set a goal from the accepted objective when a user or host app is ready to track execution. SwiftASB does not currently auto-create goals from plan prompts or auto-promote completed plans into goals.
Goals, plans, and plan collaboration mode are related but separate concepts. A Codex goal is a durable objective and progress container. It can span turns, can optionally have a token budget, and is explicitly marked complete or blocked when that status is known. A Codex plan is the active working checklist for a task or turn. It is lower-level than a goal and can be updated as work progresses. Plan collaboration mode is a separate turn-start mode that changes the planning and decision-making flow, including structured user-input prompts during planning.

These controls compose, but they are not fused. A goal can exist without a current plan, a plan can exist without a goal, and plan collaboration mode can be active or inactive independently of both. A useful mental model is: goal is the durable mission, plan is the active checklist, and plan collaboration mode is the planning cadence.

Plan and goal actions are separate controls. Use plan mode first to shape complex or ambiguous work, then set a goal from the accepted objective when a user or host app is ready to track execution. SwiftASB does not currently auto-create goals from plan prompts or auto-promote completed plans into goals. Keep goal creation explicit; plans may still be created or updated when an agent needs a checklist for multi-step work.

Use ``startReview(against:placement:)`` to ask the app-server to review repository state associated with this thread. The `against` subject can be uncommitted changes, a base branch, one commit, or custom instructions. ``ReviewPlacement/inline`` runs the review turn on this thread. ``ReviewPlacement/detached`` runs the review turn on a new review thread returned in ``CodexReviewHandle/reviewThreadID``.

Expand Down
25 changes: 25 additions & 0 deletions docs/maintainers/thread-plan-goal-companion-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,31 @@ That is useful but too raw for routine app UI. Consumers should not need to
reconcile thread-goal reads with live goal notifications, and they should not
need to treat experimental plan deltas as a stable user-facing data source.

## Concept Boundary

Use these distinctions when naming APIs, docs, tests, and UI affordances:

- A Codex Goal is a durable objective and progress container. It can span
multiple turns, can optionally carry a token budget, and should be marked
complete or blocked only when that state is explicit.
- A Codex Plan is the current working checklist for a task or turn. It is
lower-level than a goal, can be updated as work progresses, and can exist
whether plan collaboration mode is active.
- Plan collaboration mode is a separate collaboration flow. It changes how
planning and decisions are handled, especially by allowing structured
user-input prompts during planning. It is not the same thing as a plan.

These concepts compose, but they are not fused. A goal can exist without a
current plan, a plan can exist without a goal, and plan collaboration mode can
be active or inactive independently of both. The short mental model is: goal is
the durable mission, plan is the active checklist, and plan collaboration mode is
the planning and decision-making cadence.

Operationally, SwiftASB should create or mutate goals only when the user or host
app explicitly asks for persistent objective tracking. Agent-facing plans may be
created or updated whenever they help organize multi-step work, but a plan is
not a request to persist a goal.

## Proposed Public Shape

Add one thread-scoped observable companion that owns plan and goal current state.
Expand Down
15 changes: 13 additions & 2 deletions scripts/repo-maintenance/config/release.env
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# Repo-maintenance release defaults.
REPO_MAINTENANCE_DEFAULT_RELEASE_MODE=standard
REPO_MAINTENANCE_RELEASE_BRANCH=main
REPO_MAINTENANCE_CI_REGISTRATION_TIMEOUT_SECONDS=120
REPO_MAINTENANCE_CI_REGISTRATION_POLL_SECONDS=5
REPO_MAINTENANCE_REMOTE_CI_MODE=full

# GitHub can accept branch, tag, PR, check, review, and release mutations before
# those surfaces are immediately readable. These defaults keep release scripts
# explicit about intentional waits instead of failing on transient indexing gaps.
REPO_MAINTENANCE_GH_WAIT_TIMEOUT_SECONDS=120
REPO_MAINTENANCE_GH_WAIT_POLL_SECONDS=5

# Keep full local validation as the default release gate. For repositories whose
# GitHub CI is intentionally heavy, use --remote-ci-mode defer so release.sh
# pauses after branch push, PR creation, and initial check discovery. Codex can
# then use a native thread Timer/Wakeup or heartbeat automation to resume later
# instead of leaving a long-running shell process open just to poll GitHub.
142 changes: 142 additions & 0 deletions scripts/repo-maintenance/lib/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,148 @@ load_profile_env() {
load_env_file "$REPO_MAINTENANCE_ROOT/config/profile.env"
}

positive_integer_or_default() {
value="$1"
default_value="$2"

case "$value" in
''|*[!0-9]*)
printf '%s\n' "$default_value"
;;
0)
printf '%s\n' "$default_value"
;;
*)
printf '%s\n' "$value"
;;
esac
}

is_semver_prerelease_tag() {
tag_name="$1"
case "$tag_name" in
v[0-9]*.[0-9]*.[0-9]*-*)
return 0
;;
*)
return 1
;;
esac
}

expected_github_prerelease_value() {
tag_name="$1"
if is_semver_prerelease_tag "$tag_name"; then
printf '%s\n' "true"
else
printf '%s\n' "false"
fi
}

github_release_create_prerelease_flag() {
tag_name="$1"
if is_semver_prerelease_tag "$tag_name"; then
printf '%s\n' "--prerelease"
fi
}

verify_github_release_prerelease_metadata() {
tag_name="$1"
expected_value="$(expected_github_prerelease_value "$tag_name")"

actual_value="$(gh release view "$tag_name" --json isPrerelease --jq .isPrerelease 2>/dev/null || true)"
case "$actual_value" in
true|false)
;;
*)
die "GitHub release $tag_name exists, but its prerelease metadata was not readable. Confirm gh can read release JSON metadata before rerunning release.sh."
;;
esac

[ "$actual_value" = "$expected_value" ] || die "GitHub release $tag_name prerelease metadata mismatch: tag implies isPrerelease=$expected_value but GitHub reports isPrerelease=$actual_value. Update the release metadata or delete and recreate the release before rerunning release.sh."
}

github_wait_timeout() {
value="$1"
default_timeout="$(positive_integer_or_default "${REPO_MAINTENANCE_GH_WAIT_TIMEOUT_SECONDS:-120}" 120)"
positive_integer_or_default "$value" "$default_timeout"
}

github_wait_poll_seconds() {
value="$1"
default_poll_seconds="$(positive_integer_or_default "${REPO_MAINTENANCE_GH_WAIT_POLL_SECONDS:-5}" 5)"
positive_integer_or_default "$value" "$default_poll_seconds"
}

wait_for_remote_branch() {
branch_name="$1"
timeout_seconds="$(github_wait_timeout "${REPO_MAINTENANCE_REMOTE_BRANCH_TIMEOUT_SECONDS:-}")"
poll_seconds="$(github_wait_poll_seconds "${REPO_MAINTENANCE_REMOTE_BRANCH_POLL_SECONDS:-}")"
elapsed_seconds="0"

log "Waiting up to ${timeout_seconds}s for remote branch origin/$branch_name to become visible."

while :; do
if git -C "$REPO_ROOT" ls-remote --exit-code --heads origin "$branch_name" >/dev/null 2>&1; then
log "Remote branch origin/$branch_name is visible."
return 0
fi

if [ "$elapsed_seconds" -ge "$timeout_seconds" ]; then
die "Remote branch origin/$branch_name was not visible after ${timeout_seconds}s. Confirm the branch push succeeded and that the origin remote is reachable before rerunning release.sh."
fi

sleep "$poll_seconds"
elapsed_seconds=$((elapsed_seconds + poll_seconds))
done
}

wait_for_remote_tag() {
tag_name="$1"
timeout_seconds="$(github_wait_timeout "${REPO_MAINTENANCE_REMOTE_TAG_TIMEOUT_SECONDS:-}")"
poll_seconds="$(github_wait_poll_seconds "${REPO_MAINTENANCE_REMOTE_TAG_POLL_SECONDS:-}")"
elapsed_seconds="0"

log "Waiting up to ${timeout_seconds}s for remote tag $tag_name to become visible."

while :; do
if git -C "$REPO_ROOT" ls-remote --exit-code --tags origin "refs/tags/$tag_name" >/dev/null 2>&1; then
log "Remote tag $tag_name is visible."
return 0
fi

if [ "$elapsed_seconds" -ge "$timeout_seconds" ]; then
die "Remote tag $tag_name was not visible after ${timeout_seconds}s. Confirm the tag push succeeded and that GitHub has indexed the tag before rerunning release.sh."
fi

sleep "$poll_seconds"
elapsed_seconds=$((elapsed_seconds + poll_seconds))
done
}

wait_for_github_release() {
tag_name="$1"
timeout_seconds="$(github_wait_timeout "${REPO_MAINTENANCE_GH_RELEASE_TIMEOUT_SECONDS:-}")"
poll_seconds="$(github_wait_poll_seconds "${REPO_MAINTENANCE_GH_RELEASE_POLL_SECONDS:-}")"
elapsed_seconds="0"

log "Waiting up to ${timeout_seconds}s for GitHub release $tag_name to become readable."

while :; do
if gh release view "$tag_name" >/dev/null 2>&1; then
log "GitHub release $tag_name is readable."
return 0
fi

if [ "$elapsed_seconds" -ge "$timeout_seconds" ]; then
die "GitHub release $tag_name was not readable after ${timeout_seconds}s. Confirm release creation succeeded and GitHub has indexed the release before rerunning release.sh."
fi

sleep "$poll_seconds"
elapsed_seconds=$((elapsed_seconds + poll_seconds))
done
}

ensure_git_repo() {
git -C "$REPO_ROOT" rev-parse --is-inside-work-tree >/dev/null 2>&1 || die "maintain-project-repo must run inside a git worktree rooted at $REPO_ROOT."
}
Expand Down
Loading
Loading