From 87cb21e6a0034392adf76607e90666f360485240 Mon Sep 17 00:00:00 2001 From: Gale W Date: Fri, 8 May 2026 18:56:44 -0400 Subject: [PATCH 1/7] docs: add next roadmap surface candidates --- ROADMAP.md | 51 +++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index decb2cd..7d03f77 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -142,24 +142,42 @@ That means the current priority order is: special attention to workspace, filesystem, Git/repository, and app-server action families that let sandboxed clients ask Codex for facts instead of reading disk directly. -2. Continue promoting app-server-owned workspace and Git facts beyond the +2. Add a deliberate `codex mcp-server` support plan as a separate integration + lane from `codex app-server`. The current MCP mode should be treated as an + external-agent bridge with a smaller stdio tool surface, not as a replacement + for the app-server lifecycle SwiftASB already wraps. Verify the live + `initialize`, `tools/list`, `resources/list`, and `prompts/list` surface + before deciding whether SwiftASB should expose client helpers, examples, or a + dedicated package module for it. +3. Continue promoting app-server-owned workspace and Git facts beyond the current cwd, origin metadata, and runtime permission-profile provenance: Git worktree root if upstream exposes it, branch/SHA observables, and any workspace listing/search/status actions that upstream already owns. -3. Finish the next descriptor increment beyond the current list, history, and +4. Evaluate a Worktrunk-based worktree system only after the workspace and Git + fact boundary is clearer. The useful shape is a SwiftASB-supported way for + clients to ask Codex-owned services for workspace/worktree identity, + branch/status facts, and safe handoff points, without committing + machine-local paths or turning SwiftASB into a Worktrunk clone. +5. Explore a custom approval auto-reviewer after the answerable + server-request model is stable enough to distinguish advisory review from + action approval. The first useful slice should classify approval requests and + produce review recommendations; automatically answering requests should wait + for an explicit policy model and tests that prove dangerous actions stay + user-controlled. +6. Finish the next descriptor increment beyond the current list, history, and recent-activity descriptors: broader public cursor semantics, any selection-centered reads that become necessary, and later search-hit hydration. -4. Finish the next `CodexAppServer.Library` slice around richer Git observables +7. Finish the next `CodexAppServer.Library` slice around richer Git observables and app-wide settings/actions, using promoted app-server facts and descriptor values where they make list and selection behavior explicit. -5. Keep tuning `RecentTurns`, `RecentFiles`, and `RecentCommands` after v1 as +8. Keep tuning `RecentTurns`, `RecentFiles`, and `RecentCommands` after v1 as real UI usage teaches better calibration. The v1 review keeps the separate turn/file/command companions, current cache-policy names and defaults, selection/visibility protection, slimming behavior, and rehydration model as stable enough; remaining work is calibration and richer previews, not proving the model exists. -6. Keep future Codex CLI schema additions classified before public promotion: +9. Keep future Codex CLI schema additions classified before public promotion: `excludeTurns` remains public on resume/fork request models because it directly supports the existing paged history model; permission-profile families stay internal until SwiftASB owns a deliberate public permission @@ -168,11 +186,11 @@ That means the current priority order is: sessions, marketplace/account-management families, and guardian denied-action approval remain post-v1 until their consumer workflows are clearer. -5. Flesh out archive-aware retention and eviction beyond the current list-driven +10. Flesh out archive-aware retention and eviction beyond the current list-driven archive-state drift correction. -6. Add any sharper binary-discovery diagnostics we want alongside the +11. Add any sharper binary-discovery diagnostics we want alongside the current-reviewed compatibility window before a broader compatibility release. -7. Revisit whether a convenience `run(...)` API is earned only after the +12. Revisit whether a convenience `run(...)` API is earned only after the lower-level lifecycle has more production mileage. ## V1 Readiness Checklist @@ -233,6 +251,15 @@ workflow earns them in a later feature release. descriptors, prioritizing workspace, filesystem, Git/repository, and app-server action surfaces that let sandboxed clients ask Codex for facts instead of reading local disk directly. +- [ ] Add `codex mcp-server` support as a separate external-agent bridge from + `codex app-server`, starting with live surface verification and a clear + boundary between MCP tools and SwiftASB's app-server lifecycle API. +- [ ] Plan a custom approval auto-reviewer that can classify approval requests + and recommend responses without silently approving actions before SwiftASB has + an explicit policy model. +- [ ] Plan a Worktrunk-based worktree system around Codex-owned workspace, + Git, and worktree facts, keeping machine-local paths out of public dependency + or package metadata. - [ ] Finish the `CodexAppServer` app-wide observable companion with derived repository-root grouping, richer Git observables, and any broader app-wide settings/actions that earn public models. @@ -1243,6 +1270,13 @@ Completed - [ ] Promote an upstream app-server fuzzy file-search endpoint later if Codex owns indexing, ignore rules, pagination, and result stability clearly enough for SwiftASB to wrap it as a separate public API. +- [ ] Add `codex mcp-server` examples or helpers after the live MCP tool surface + is verified and its relationship to the app-server lifecycle is documented. +- [ ] Add a custom approval auto-reviewer after policy, logging, and + user-control boundaries are explicit enough to avoid accidental approvals. +- [ ] Add a Worktrunk-based worktree system once SwiftASB can lean on + app-server-owned workspace and Git facts instead of local filesystem + inference. - [ ] Add archive-aware retention/eviction and rollback forensic archival for removed turn payloads. - [x] Add live rollback coverage once the disposable-thread path is reliable enough to assert explicit local rollback markers. - [x] Add a local-only startup mode for recent history observables when live upstream paging is unavailable because the thread is ephemeral or not yet materialized. @@ -1263,3 +1297,4 @@ Completed - 2026-05-06: Promoted bounded file discovery and fuzzy file lookup through `CodexFS.FileDiscoveryQD` and `CodexFS.discoverFiles(_:)`, keeping traversal on app-server `fs/readDirectory` while SwiftASB owns local ranking over returned entries. - 2026-05-06: Expanded deterministic coverage for promoted file discovery, config, extension inventory, and workspace-permission request descriptors. - 2026-05-07: Added UI-ready `CodexFS.FileDiscoveryHit` search metadata for match kind, matched file-name and relative-path character ranges, and stable ranking reasons. +- 2026-05-08: Added post-v1 roadmap candidates for `codex mcp-server` support, a custom approval auto-reviewer, and a Worktrunk-based worktree system. From 26de290334b3c59ed719c4f35f8349db468c80c1 Mon Sep 17 00:00:00 2001 From: Gale W Date: Fri, 8 May 2026 19:47:49 -0400 Subject: [PATCH 2/7] workspace: expose app-server worktree snapshots --- README.md | 4 +- ROADMAP.md | 12 ++-- .../Public/CodexAppServer+Library.swift | 5 ++ .../CodexAppServer+ThreadLifecycle.swift | 5 ++ Sources/SwiftASB/Public/CodexWorkspace.swift | 58 ++++++++++++++++++- .../SwiftUIObservableCompanions.md | 2 +- .../Public/CodexAppServerLibraryTests.swift | 15 +++-- .../Public/CodexWorkspaceTests.swift | 40 +++++++++++++ docs/maintainers/v1-public-api-audit.md | 10 ++++ .../v1-public-api-symbol-inventory.md | 21 +++---- 10 files changed, 147 insertions(+), 25 deletions(-) create mode 100644 Tests/SwiftASBTests/Public/CodexWorkspaceTests.swift diff --git a/README.md b/README.md index f2a745e..f177a2f 100644 --- a/README.md +++ b/README.md @@ -45,9 +45,9 @@ For copy-pasteable startup code, open the DocC getting-started guide: Use SwiftASB when an app needs to show what Codex is doing right now, keep recent command and file activity visible, answer interactive requests, or build SwiftUI state around a running Codex turn. -For app-wide sidebars and launchers, `CodexAppServer.makeLibrary()` provides observable stored-thread lists, cwd or repository grouping, refresh actions, library-local selection state, and app-wide model, MCP, and hook diagnostics snapshots. Thread handles can also name, archive, unarchive, compact, and roll back stored threads through thread-scoped methods. +For app-wide sidebars and launchers, `CodexAppServer.makeLibrary()` provides observable stored-thread lists, cwd or repository grouping, refresh actions, library-local selection state, app-server-owned worktree snapshots, and app-wide model, MCP, and hook diagnostics snapshots. Thread handles can also name, archive, unarchive, compact, and roll back stored threads through thread-scoped methods. -Use `CodexAppServer.fs` when a sandboxed client needs filesystem metadata, directory listings, file bytes, file discovery, fuzzy file lookup, or file-change watches through the Codex app-server instead of reading local disk directly. File-discovery hits include match kind, matched character ranges, and ranking reasons for picker highlighting and result explanations. `CodexWorkspace` carries app-server-owned workspace permission selections, active permission-profile provenance, and runtime filesystem/network permission facts for started threads and turns. Use `CodexAppServer.config` for effective config reads, and `CodexAppServer.extensions` for app, skill, plugin, and collaboration-mode inventory. +Use `CodexAppServer.fs` when a sandboxed client needs filesystem metadata, directory listings, file bytes, file discovery, fuzzy file lookup, or file-change watches through the Codex app-server instead of reading local disk directly. File-discovery hits include match kind, matched character ranges, and ranking reasons for picker highlighting and result explanations. `CodexWorkspace` carries app-server-owned worktree, Git, workspace permission selection, active permission-profile provenance, and runtime filesystem/network permission facts for started threads and turns. Use `CodexAppServer.config` for effective config reads, and `CodexAppServer.extensions` for app, skill, plugin, and collaboration-mode inventory. Use `CodexAppServer.ThreadListQD`, `CodexFS.FileDiscoveryQD`, `CodexThread.HistoryWindowQD`, `CodexThread.RecentFilesQD`, and `CodexThread.RecentCommandsQD` when a client needs to preserve repeatable list, file-discovery, history-window, or recent-activity intent without depending on Core Data, SwiftData, direct filesystem reads, or raw app-server paging details. diff --git a/ROADMAP.md b/ROADMAP.md index decb2cd..74deca6 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -71,8 +71,8 @@ | Thread-scoped recent-turn observable | `Partially shipped` | `CodexThread.makeRecentTurns(limit:)` now vends a bounded recent-turn observable that prewarms from the local history store, supports explicit older/newer whole-turn window expansion, seeds upstream paging cursors even when the visible initial window came from local history, and falls back to `thread/turns/list` when needed. Live probing showed that upstream turn paging is available only after a non-ephemeral thread has materialized at least one user turn, so recent observable startup now degrades to an empty local-only view for the known ephemeral and pre-materialized live runtime responses instead of surfacing raw protocol text. `RecentTurns` now ships named cache-policy presets for chat UIs, full inspectors, and compact history rails; tracks both resident item counts and weighted resident item cost; slims low-value payloads out of older non-visible completed turns before evicting whole turns; rehydrates slimmed turns when they become visible again; and uses scroll-position, visibility, phase, and velocity signals to drive protected residency plus earlier prefetch. Richer weighting heuristics and deeper policy tuning are still open. | | Thread-scoped recent-file observable | `Partially shipped` | `CodexThread.makeRecentFiles(limit:)` and `makeRecentFiles(_:)` now vend a file-centric recent-files observable that hydrates from persisted file-change items, keeps one resident entry per file-change item, enriches live entries from `item/fileChange/outputDelta` and `item/fileChange/patchUpdated`, can load older file entries from the same turn before stepping farther back through older turns, and supports selection-aware shell-versus-payload slimming with automatic payload rehydration for protected files. `CodexThread.RecentFilesQD` gives callers a repeatable descriptor for the initial resident file window and cache policy. Live probing exercises a real create/edit/delete scenario, and recent-file startup now inherits the same empty local-only degradation as recent-turns for the known live history-unavailable responses. The current weighting now accounts for diff structure and line volume, and shell summaries prefer concise edit summaries over raw terminal status when sealed payload is available. The remaining open work is better payload-cost calibration at the margins and richer structured patch presentation beyond the current text preview. | | Thread-scoped recent-command observable | `Partially shipped` | `CodexThread.makeRecentCommands(limit:)` and `makeRecentCommands(_:)` now vend a command-centric recent-commands observable that hydrates from persisted `commandExecution` items, keeps one resident entry per command item, enriches live entries from `item/commandExecution/outputDelta`, can load older command entries from the same turn before stepping farther back through older turns, and supports selection-aware shell-versus-output slimming with automatic output rehydration for protected commands. `CodexThread.RecentCommandsQD` gives callers a repeatable descriptor for the initial resident command window and cache policy. Recent-command startup now inherits the same empty local-only degradation as recent-turns for the known live history-unavailable responses. Current output weighting accounts for output size and line structure, and shell summaries prefer concise command and output summaries over raw transport detail. The remaining open work is better output-cost calibration and sharper shell-summary heuristics. | -| App-wide observable companion | `In Progress` | `CodexAppServer.makeLibrary()` and `CodexAppServer.Library` now expose Core Data-backed value snapshots for unarchived, archived, cwd-grouped, and repository-grouped threads, `CodexWorkspace.ProjectInfo` identity for thread and group displays, `CodexAppServer.ThreadSource` values for source badges, bindable sort/grouping policies, thread-list query descriptors, scoped refresh actions, library-local selection, recently selected ordering, local reloads after app-wide thread/turn events, and app-wide model/MCP/hook snapshots for launcher and sidebar UI. `CodexWorkspace` now promotes active permission-profile provenance, runtime filesystem/network permission facts, and app-server-owned project identity from thread sessions, but the library still needs broader app-wide settings/actions. | -| Public query descriptors | `Partially shipped` | `CodexAppServer.ThreadListQD` now provides repeatable thread-list intent for direct app-server `thread/list` reads and app-wide `Library` loading, `CodexFS.FileDiscoveryQD` provides repeatable bounded file-discovery intent over app-server `fs/readDirectory` reads, `CodexThread.HistoryWindowQD` provides repeatable local completed-turn window intent for recent, older, newer, turn-centered, and item-centered reads, and `CodexThread.RecentFilesQD` plus `CodexThread.RecentCommandsQD` describe recent-activity companion startup. Repository grouping now uses `CodexWorkspace.ProjectInfo`, which identifies a project by Codex-reported Git origin when available and falls back to cwd. Remaining descriptor work includes broader public cursor semantics, selection-centered reads if a concrete caller needs them, and later search-hit hydration. | +| App-wide observable companion | `In Progress` | `CodexAppServer.makeLibrary()` and `CodexAppServer.Library` now expose Core Data-backed value snapshots for unarchived, archived, cwd-grouped, and repository-grouped threads, `CodexWorkspace.ProjectInfo` identity for thread and group displays, `CodexWorkspace.WorktreeSnapshot` values for Codex-reported cwd plus optional Git facts, `CodexAppServer.ThreadSource` values for source badges, bindable sort/grouping policies, thread-list query descriptors, scoped refresh actions, library-local selection, recently selected ordering, local reloads after app-wide thread/turn events, and app-wide model/MCP/hook snapshots for launcher and sidebar UI. `CodexWorkspace` now promotes active permission-profile provenance, runtime filesystem/network permission facts, app-server-owned project identity, and worktree snapshots from thread sessions, but the library still needs broader app-wide settings/actions. | +| Public query descriptors | `Partially shipped` | `CodexAppServer.ThreadListQD` now provides repeatable thread-list intent for direct app-server `thread/list` reads and app-wide `Library` loading, `CodexFS.FileDiscoveryQD` provides repeatable bounded file-discovery intent over app-server `fs/readDirectory` reads, `CodexThread.HistoryWindowQD` provides repeatable local completed-turn window intent for recent, older, newer, turn-centered, and item-centered reads, and `CodexThread.RecentFilesQD` plus `CodexThread.RecentCommandsQD` describe recent-activity companion startup. Repository grouping now uses `CodexWorkspace.ProjectInfo`, and per-thread UI state can read `CodexWorkspace.WorktreeSnapshot`, both of which identify a project by Codex-reported Git origin when available and fall back to cwd. Remaining descriptor work includes broader public cursor semantics, selection-centered reads if a concrete caller needs them, and later search-hit hydration. | | Non-UI local history-reading helpers | `Partially shipped` | `CodexThread` now exposes a lightweight `HistoryWindow` page shape for recent local history, older or newer local windows around a known boundary turn id, centered `windowAroundTurn(...)` reads, centered `windowAroundItem(...)` reads, direct `ClosedTurn` reads for one turn, and convenience array helpers over those same windows. This gives non-UI callers an intentional path into the local history store without binding a UI-oriented observable, while still deferring a broader public cursor model, transcript search surface, and richer history-query helpers. | | Public API curation | `Shipped / ongoing` | The source-organization pass has split app-wide model, MCP, thread-management, history, and observable companion values into focused public files while preserving `CodexAppServer`, `CodexThread`, and `CodexTurnHandle` as the three real owners. The connected public-surface review closed the v1 ownership model; post-v1 curation now includes app-server-owned project identity and thread source facts for launcher UI without exposing generated wire models. Future curation should stay tied to concrete public API additions. | | DocC documentation | `Shipped / ongoing` | `Sources/SwiftASB/SwiftASB.docc/` contains a package landing page, public-handle extension pages, conceptual articles for app-wide capabilities, interactive lifecycle, thread management, history/observable companions, generated-wire boundary notes, and copy-pasteable walkthroughs for startup, progress/approval handling, diagnostics/history, and SwiftUI observable companions. The catalog is validated through Xcode `docbuild`; future work is ordinary stale-link, prose, and symbol-comment refinement as the public API grows. | @@ -143,9 +143,10 @@ That means the current priority order is: action families that let sandboxed clients ask Codex for facts instead of reading disk directly. 2. Continue promoting app-server-owned workspace and Git facts beyond the - current cwd, origin metadata, and runtime permission-profile provenance: Git - worktree root if upstream exposes it, branch/SHA observables, and any - workspace listing/search/status actions that upstream already owns. + current cwd, origin metadata, runtime permission-profile provenance, and + `CodexWorkspace.WorktreeSnapshot`: Git worktree root if upstream exposes it, + branch/SHA observables, and any workspace listing/search/status actions that + upstream already owns. 3. Finish the next descriptor increment beyond the current list, history, and recent-activity descriptors: broader public cursor semantics, any selection-centered reads that become necessary, and later search-hit @@ -1263,3 +1264,4 @@ Completed - 2026-05-06: Promoted bounded file discovery and fuzzy file lookup through `CodexFS.FileDiscoveryQD` and `CodexFS.discoverFiles(_:)`, keeping traversal on app-server `fs/readDirectory` while SwiftASB owns local ranking over returned entries. - 2026-05-06: Expanded deterministic coverage for promoted file discovery, config, extension inventory, and workspace-permission request descriptors. - 2026-05-07: Added UI-ready `CodexFS.FileDiscoveryHit` search metadata for match kind, matched file-name and relative-path character ranges, and stable ranking reasons. +- 2026-05-08: Added `CodexWorkspace.WorktreeSnapshot` so thread, session, and library snapshots expose a single app-server-owned cwd plus Git-facts value without inferring repository roots from local disk. diff --git a/Sources/SwiftASB/Public/CodexAppServer+Library.swift b/Sources/SwiftASB/Public/CodexAppServer+Library.swift index e2749df..90d3ae3 100644 --- a/Sources/SwiftASB/Public/CodexAppServer+Library.swift +++ b/Sources/SwiftASB/Public/CodexAppServer+Library.swift @@ -342,6 +342,11 @@ public extension CodexAppServer { public let source: CodexAppServer.ThreadSource public let status: CodexAppServer.ThreadStatus public let updatedAt: Int + + /// Codex-reported cwd plus optional Git facts for this stored thread. + public var worktree: CodexWorkspace.WorktreeSnapshot { + projectInfo.worktree + } } public struct ThreadGroup: Sendable, Equatable, Identifiable { diff --git a/Sources/SwiftASB/Public/CodexAppServer+ThreadLifecycle.swift b/Sources/SwiftASB/Public/CodexAppServer+ThreadLifecycle.swift index 89c4f81..39479be 100644 --- a/Sources/SwiftASB/Public/CodexAppServer+ThreadLifecycle.swift +++ b/Sources/SwiftASB/Public/CodexAppServer+ThreadLifecycle.swift @@ -217,6 +217,11 @@ extension CodexAppServer { public let source: ThreadSource public let status: ThreadStatus public let updatedAt: Int + + /// Codex-reported cwd plus optional Git facts for this thread. + public var worktree: CodexWorkspace.WorktreeSnapshot { + projectInfo.worktree + } } /// Request used to read a stored thread snapshot. diff --git a/Sources/SwiftASB/Public/CodexWorkspace.swift b/Sources/SwiftASB/Public/CodexWorkspace.swift index 0dd5d04..ad13ca4 100644 --- a/Sources/SwiftASB/Public/CodexWorkspace.swift +++ b/Sources/SwiftASB/Public/CodexWorkspace.swift @@ -143,16 +143,46 @@ public enum CodexWorkspace { public let displayName: String public let currentDirectoryPath: String public let repository: RepositoryInfo? + public let worktree: WorktreeSnapshot /// Creates project identity from app-server-owned cwd and optional Git metadata. + public init( + currentDirectoryPath: String, + repository: RepositoryInfo? = nil + ) { + let worktree = WorktreeSnapshot( + currentDirectoryPath: currentDirectoryPath, + repository: repository + ) + self.id = worktree.id + self.identitySource = worktree.identitySource + self.displayName = worktree.displayName + self.currentDirectoryPath = worktree.currentDirectoryPath + self.repository = worktree.repository + self.worktree = worktree + } + } + + /// Codex-reported workspace plus optional Git facts for one thread worktree. + /// + /// This value is intentionally a snapshot of app-server payloads. It does + /// not infer a repository root, run Git commands, or inspect local disk. + public struct WorktreeSnapshot: Sendable, Equatable, Identifiable { + public let id: String + public let identitySource: ProjectInfo.IdentitySource + public let displayName: String + public let currentDirectoryPath: String + public let repository: RepositoryInfo? + + /// Creates a worktree snapshot from an app-server cwd and optional Git facts. public init( currentDirectoryPath: String, repository: RepositoryInfo? = nil ) { self.currentDirectoryPath = currentDirectoryPath - self.repository = repository + self.repository = repository?.normalized - if let originURL = repository?.originURL, !originURL.isEmpty { + if let originURL = self.repository?.originURL, !originURL.isEmpty { self.id = originURL self.identitySource = .gitOrigin self.displayName = Self.displayName(forGitOriginURL: originURL) @@ -163,6 +193,11 @@ public enum CodexWorkspace { } } + /// True when the app-server reported any Git metadata for this worktree. + public var hasRepositoryFacts: Bool { + repository?.hasFacts == true + } + private static func displayName(forGitOriginURL originURL: String) -> String { guard let url = URL(string: originURL), let host = url.host, @@ -194,9 +229,24 @@ public enum CodexWorkspace { self.sha = sha } + /// True when Codex reported at least one Git fact for this thread. + public var hasFacts: Bool { + !isEmpty + } + + /// Short display form for the reported commit SHA. + public var shortSHA: String? { + guard let sha, !sha.isEmpty else { return nil } + return String(sha.prefix(12)) + } + internal var isEmpty: Bool { originURL == nil && branch == nil && sha == nil } + + internal var normalized: Self? { + isEmpty ? nil : self + } } /// Thread-session workspace snapshot built from app-server-owned facts. @@ -207,6 +257,7 @@ public enum CodexWorkspace { public let permissionProfile: PermissionProfile? public let projectInfo: ProjectInfo public let sandboxPolicy: CodexAppServer.SandboxPolicy + public let worktree: WorktreeSnapshot } } @@ -388,7 +439,8 @@ extension CodexWorkspace.SessionSnapshot { instructionSources: session.instructionSources, permissionProfile: session.permissionProfile, projectInfo: session.thread.projectInfo, - sandboxPolicy: session.sandboxPolicy + sandboxPolicy: session.sandboxPolicy, + worktree: session.thread.projectInfo.worktree ) } } diff --git a/Sources/SwiftASB/SwiftASB.docc/SwiftUIObservableCompanions.md b/Sources/SwiftASB/SwiftASB.docc/SwiftUIObservableCompanions.md index 4e6bac9..8455fc6 100644 --- a/Sources/SwiftASB/SwiftASB.docc/SwiftUIObservableCompanions.md +++ b/Sources/SwiftASB/SwiftASB.docc/SwiftUIObservableCompanions.md @@ -87,7 +87,7 @@ final class ThreadInspectorModel { ## Selection And Cache Behavior -`CodexAppServer.Library` is the app-wide companion for launchers, sidebars, and project browsers. It publishes value snapshots for unarchived threads, archived threads, cwd groups, ``CodexWorkspace/ProjectInfo`` values for thread and repository-group identity, and ``CodexAppServer/ThreadSource`` values for source badges; it also reloads from local persistence after app-wide thread and turn events such as archive, unarchive, name changes, status changes, and completed turns. +`CodexAppServer.Library` is the app-wide companion for launchers, sidebars, and project browsers. It publishes value snapshots for unarchived threads, archived threads, cwd groups, ``CodexWorkspace/ProjectInfo`` values for thread and repository-group identity, ``CodexWorkspace/WorktreeSnapshot`` values for Codex-reported cwd plus optional Git facts, and ``CodexAppServer/ThreadSource`` values for source badges; it also reloads from local persistence after app-wide thread and turn events such as archive, unarchive, name changes, status changes, and completed turns. Use ``CodexAppServer/Library/selectedThreadID`` and ``CodexAppServer/Library/selectThread(_:)-(String?)`` for library-local selection. The selection timestamp stays inside the library and can drive ``CodexAppServer/Library/SortedBy/selectedNewestFirst`` without writing UI preference state into Codex's stored thread metadata. diff --git a/Tests/SwiftASBTests/Public/CodexAppServerLibraryTests.swift b/Tests/SwiftASBTests/Public/CodexAppServerLibraryTests.swift index c86e3f8..5b03a23 100644 --- a/Tests/SwiftASBTests/Public/CodexAppServerLibraryTests.swift +++ b/Tests/SwiftASBTests/Public/CodexAppServerLibraryTests.swift @@ -110,6 +110,7 @@ extension CodexAppServerTests { cwd: "/tmp/package-a", gitBranch: "main", gitOriginURL: "https://github.com/gaelic-ghost/SwiftASB.git", + gitSHA: "abcdef1234567890", name: "Package A", preview: "First repo thread", statusType: "notLoaded", @@ -167,7 +168,12 @@ extension CodexAppServerTests { #expect(repositoryGroup.projectInfo?.identitySource == .gitOrigin) #expect(repositoryGroup.projectInfo?.repository?.originURL == "https://github.com/gaelic-ghost/SwiftASB.git") #expect(repositoryGroup.projectInfo?.repository?.branch == nil) + #expect(repositoryGroup.projectInfo?.worktree.id == "https://github.com/gaelic-ghost/SwiftASB.git") + #expect(repositoryGroup.projectInfo?.worktree.identitySource == .gitOrigin) + #expect(repositoryGroup.projectInfo?.worktree.hasRepositoryFacts == true) #expect(repositoryGroup.threads.map(\.id) == ["thread-package-a", "thread-package-b"]) + #expect(repositoryGroup.threads.first?.worktree.id == "https://github.com/gaelic-ghost/SwiftASB.git") + #expect(repositoryGroup.threads.first?.worktree.repository?.shortSHA == "abcdef123456") #expect(repositoryGroup.threads.first?.projectInfo.repository?.originURL == "https://github.com/gaelic-ghost/SwiftASB.git") #expect(repositoryGroup.threads.first?.projectInfo.repository?.branch == "main") #expect(repositoryGroup.threads.first?.source == .cli) @@ -609,6 +615,7 @@ private func storedThread( cwd: String, gitBranch: String? = nil, gitOriginURL: String? = nil, + gitSHA: String? = nil, name: String, preview: String, statusType: String, @@ -632,13 +639,13 @@ private func storedThread( thread["gitInfo"] = [ "branch": gitBranch, "originUrl": gitOriginURL as Any? ?? NSNull(), - "sha": NSNull(), + "sha": gitSHA as Any? ?? NSNull(), ] - } else if let gitOriginURL { + } else if gitOriginURL != nil || gitSHA != nil { thread["gitInfo"] = [ "branch": NSNull(), - "originUrl": gitOriginURL, - "sha": NSNull(), + "originUrl": gitOriginURL as Any? ?? NSNull(), + "sha": gitSHA as Any? ?? NSNull(), ] } return thread diff --git a/Tests/SwiftASBTests/Public/CodexWorkspaceTests.swift b/Tests/SwiftASBTests/Public/CodexWorkspaceTests.swift new file mode 100644 index 0000000..5df19c4 --- /dev/null +++ b/Tests/SwiftASBTests/Public/CodexWorkspaceTests.swift @@ -0,0 +1,40 @@ +import Testing +@testable import SwiftASB + +@Suite("Codex workspace facts") +struct CodexWorkspaceTests { + @Test("worktree snapshot uses Codex-reported Git origin when available") + func worktreeSnapshotUsesGitOriginWhenAvailable() { + let repository = CodexWorkspace.RepositoryInfo( + originURL: "https://github.com/gaelic-ghost/SwiftASB.git", + branch: "workspace/git-facts", + sha: "abcdef1234567890" + ) + let snapshot = CodexWorkspace.WorktreeSnapshot( + currentDirectoryPath: "/tmp/SwiftASB-wt", + repository: repository + ) + + #expect(snapshot.id == "https://github.com/gaelic-ghost/SwiftASB.git") + #expect(snapshot.identitySource == .gitOrigin) + #expect(snapshot.displayName == "SwiftASB (github.com)") + #expect(snapshot.currentDirectoryPath == "/tmp/SwiftASB-wt") + #expect(snapshot.repository?.branch == "workspace/git-facts") + #expect(snapshot.repository?.shortSHA == "abcdef123456") + #expect(snapshot.hasRepositoryFacts) + } + + @Test("worktree snapshot falls back to Codex-reported cwd without Git facts") + func worktreeSnapshotFallsBackToCurrentDirectoryWithoutGitFacts() { + let snapshot = CodexWorkspace.WorktreeSnapshot( + currentDirectoryPath: "/tmp/standalone", + repository: .init() + ) + + #expect(snapshot.id == "/tmp/standalone") + #expect(snapshot.identitySource == .currentDirectory) + #expect(snapshot.displayName == "/tmp/standalone") + #expect(snapshot.repository == nil) + #expect(!snapshot.hasRepositoryFacts) + } +} diff --git a/docs/maintainers/v1-public-api-audit.md b/docs/maintainers/v1-public-api-audit.md index 7dc1c1c..a615b78 100644 --- a/docs/maintainers/v1-public-api-audit.md +++ b/docs/maintainers/v1-public-api-audit.md @@ -489,6 +489,16 @@ Use these decisions for every public symbol: and full runtime filesystem/network permission facts reported by the app-server. SwiftASB still does not infer repository roots or permission policy by walking local directories. +- [x] Record the worktree snapshot promotion. + Decision: `CodexWorkspace.WorktreeSnapshot` is the public value for one + app-server-reported cwd plus optional Git origin, branch, and SHA facts. + `CodexWorkspace.ProjectInfo`, `CodexAppServer.ThreadInfo`, + `CodexAppServer.ThreadSession.workspace`, and + `CodexAppServer.Library.ThreadSnapshot` expose the same snapshot so UI and + Worktrunk-oriented callers can use one identity/display shape without running + Git or deriving repository roots locally. The current app-server schema does + not expose a Git worktree root or working-tree status surface, so those remain + future promotion candidates instead of inferred SwiftASB fields. - [x] Add DocC examples for app-server startup, thread/turn start, progress observation, approval response, diagnostics, recent history, and SwiftUI observable companions. diff --git a/docs/maintainers/v1-public-api-symbol-inventory.md b/docs/maintainers/v1-public-api-symbol-inventory.md index 770bedb..3cfa63d 100644 --- a/docs/maintainers/v1-public-api-symbol-inventory.md +++ b/docs/maintainers/v1-public-api-symbol-inventory.md @@ -1,15 +1,15 @@ # V1 Public API Symbol Inventory -Generated from `swift package dump-symbol-graph --minimum-access-level public --skip-synthesized-members` on 2026-05-02 after the v0.128 generated-wire promotion and final pre-v1 public-surface tightening, then updated on 2026-05-05 for the post-v1 app-wide library snapshot, on 2026-05-06 for the public query descriptor, filesystem, config, extension-inventory, thread-goal, recent-activity descriptor, repository-grouping, workspace permission-profile, and file-discovery slices, and on 2026-05-08 for the `CodexWorkspace.ProjectInfo` cleanup, `CodexAppServer.ThreadSource` promotion, and v0.129 hook compact event names. This is a maintainer ledger for the v1 public API freeze plus accepted post-v1 app-wide additions; it records public/open declarations visible through the `SwiftASB` library product, excluding synthesized members. +Generated from `swift package dump-symbol-graph --minimum-access-level public --skip-synthesized-members` on 2026-05-02 after the v0.128 generated-wire promotion and final pre-v1 public-surface tightening, then updated on 2026-05-05 for the post-v1 app-wide library snapshot, on 2026-05-06 for the public query descriptor, filesystem, config, extension-inventory, thread-goal, recent-activity descriptor, repository-grouping, workspace permission-profile, and file-discovery slices, and on 2026-05-08 for the `CodexWorkspace.ProjectInfo` cleanup, `CodexWorkspace.WorktreeSnapshot` promotion, `CodexAppServer.ThreadSource` promotion, and v0.129 hook compact event names. This is a maintainer ledger for the v1 public API freeze plus accepted post-v1 app-wide additions; it records public/open declarations visible through the `SwiftASB` library product, excluding synthesized members. ## Summary -- Public/open symbols: 1869 -- Public/open types: 293 -- Public/open initializers: 130 +- Public/open symbols: 1883 +- Public/open types: 294 +- Public/open initializers: 131 - Public/open methods and type methods: 129 - Public/open enum cases: 363 -- Public/open properties: 951 +- Public/open properties: 963 ## Public Types @@ -232,6 +232,7 @@ Generated from `swift package dump-symbol-graph --minimum-access-level public -- - `CodexWorkspace.ProjectInfo.IdentitySource` (`enum`) - Sources/SwiftASB/Public/CodexWorkspace.swift - `CodexWorkspace.RepositoryInfo` (`struct`) - Sources/SwiftASB/Public/CodexWorkspace.swift - `CodexWorkspace.SessionSnapshot` (`struct`) - Sources/SwiftASB/Public/CodexWorkspace.swift +- `CodexWorkspace.WorktreeSnapshot` (`struct`) - Sources/SwiftASB/Public/CodexWorkspace.swift ## Public Initializers And Methods @@ -762,9 +763,9 @@ The 2026-05-06 app-server schema promotion added several hand-owned public names - `CodexThreadEvent` now includes `.goalUpdated(_:)` and `.goalCleared(_:)` for app-server goal notifications. - `CodexThread.RecentFilesQD` and `CodexThread.RecentCommandsQD` describe repeatable recent-activity companion startup intent. - `CodexAppServer.Library.GroupedBy.repository` groups app-wide library snapshots by `CodexWorkspace.ProjectInfo` identity: app-server Git origin metadata with cwd fallback. -- `CodexWorkspace` owns app-server-owned permission selections, runtime workspace permission facts, and project identity: `PermissionSelection`, `PermissionSelectionModification`, `ActivePermissionProfile`, `ActivePermissionModification`, `PermissionProfile`, `FileSystemPermissions`, `FileSystemSandboxEntry`, `FileSystemAccessMode`, `FileSystemPath`, `FileSystemSpecialPath`, `NetworkPermissions`, `ProjectInfo`, `RepositoryInfo`, and `SessionSnapshot`. +- `CodexWorkspace` owns app-server-owned permission selections, runtime workspace permission facts, project identity, and worktree snapshots: `PermissionSelection`, `PermissionSelectionModification`, `ActivePermissionProfile`, `ActivePermissionModification`, `PermissionProfile`, `FileSystemPermissions`, `FileSystemSandboxEntry`, `FileSystemAccessMode`, `FileSystemPath`, `FileSystemSpecialPath`, `NetworkPermissions`, `ProjectInfo`, `RepositoryInfo`, `SessionSnapshot`, and `WorktreeSnapshot`. - `CodexAppServer.ThreadStartRequest`, `ThreadResumeRequest`, `ThreadForkRequest`, `TurnStartRequest`, `CodexThread.TurnStartRequest`, and `CodexThread.startTextTurn(...)` now accept optional `CodexWorkspace.PermissionSelection` values. -- `CodexAppServer.ThreadSession` and `CodexThread` now expose active permission-profile provenance, runtime permission facts, app-server-owned project identity, and a `CodexWorkspace.SessionSnapshot`. +- `CodexAppServer.ThreadSession` and `CodexThread` now expose active permission-profile provenance, runtime permission facts, app-server-owned project identity, app-server-owned worktree snapshots, and a `CodexWorkspace.SessionSnapshot`. - `CodexAppServer.ThreadInfo` and `CodexAppServer.Library.ThreadSnapshot` now expose `CodexAppServer.ThreadSource` so launcher UIs can badge CLI, app-server, editor, custom, and sub-agent threads without reading generated wire values. ## Public Property Counts By Source File @@ -773,11 +774,11 @@ The 2026-05-06 app-server schema promotion added several hand-owned public names - `Sources/SwiftASB/Public/CodexAppServer+CodexExtensions.swift`: 113 public properties - `Sources/SwiftASB/Public/CodexAppServer+Compatibility.swift`: 10 public properties - `Sources/SwiftASB/Public/CodexAppServer+Hooks.swift`: 32 public properties -- `Sources/SwiftASB/Public/CodexAppServer+Library.swift`: 56 public properties +- `Sources/SwiftASB/Public/CodexAppServer+Library.swift`: 57 public properties - `Sources/SwiftASB/Public/CodexAppServer+LoadedThreads.swift`: 4 public properties - `Sources/SwiftASB/Public/CodexAppServer+MCP.swift`: 43 public properties - `Sources/SwiftASB/Public/CodexAppServer+Models.swift`: 23 public properties -- `Sources/SwiftASB/Public/CodexAppServer+ThreadLifecycle.swift`: 105 public properties +- `Sources/SwiftASB/Public/CodexAppServer+ThreadLifecycle.swift`: 106 public properties - `Sources/SwiftASB/Public/CodexAppServer+ThreadManagement.swift`: 10 public properties - `Sources/SwiftASB/Public/CodexAppServer+TurnLifecycle.swift`: 24 public properties - `Sources/SwiftASB/Public/CodexConfig.swift`: 18 public properties @@ -791,4 +792,4 @@ The 2026-05-06 app-server schema promotion added several hand-owned public names - `Sources/SwiftASB/Public/CodexThread+RecentTurns.swift`: 54 public properties - `Sources/SwiftASB/Public/CodexThread.swift`: 71 public properties - `Sources/SwiftASB/Public/CodexTurnHandle.swift`: 108 public properties -- `Sources/SwiftASB/Public/CodexWorkspace.swift`: 34 public properties +- `Sources/SwiftASB/Public/CodexWorkspace.swift`: 44 public properties From f4a69c5157325bb73459c8810de22443f970c5de Mon Sep 17 00:00:00 2001 From: Gale W Date: Fri, 8 May 2026 20:16:52 -0400 Subject: [PATCH 3/7] library: expose worktree grouping helpers --- README.md | 2 +- ROADMAP.md | 9 +- .../Public/CodexAppServer+Library.swift | 51 ++++++++++ .../SwiftUIObservableCompanions.md | 4 +- .../Public/CodexAppServerLibraryTests.swift | 92 +++++++++++++++++++ docs/maintainers/v1-public-api-audit.md | 9 +- .../v1-public-api-symbol-inventory.md | 14 ++- 7 files changed, 167 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index f177a2f..af051e3 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ For copy-pasteable startup code, open the DocC getting-started guide: Use SwiftASB when an app needs to show what Codex is doing right now, keep recent command and file activity visible, answer interactive requests, or build SwiftUI state around a running Codex turn. -For app-wide sidebars and launchers, `CodexAppServer.makeLibrary()` provides observable stored-thread lists, cwd or repository grouping, refresh actions, library-local selection state, app-server-owned worktree snapshots, and app-wide model, MCP, and hook diagnostics snapshots. Thread handles can also name, archive, unarchive, compact, and roll back stored threads through thread-scoped methods. +For app-wide sidebars and launchers, `CodexAppServer.makeLibrary()` provides observable stored-thread lists, cwd or repository grouping, stable worktree groups, repository/worktree thread filters, refresh actions, library-local selection state, app-server-owned worktree snapshots, and app-wide model, MCP, and hook diagnostics snapshots. Thread handles can also name, archive, unarchive, compact, and roll back stored threads through thread-scoped methods. Use `CodexAppServer.fs` when a sandboxed client needs filesystem metadata, directory listings, file bytes, file discovery, fuzzy file lookup, or file-change watches through the Codex app-server instead of reading local disk directly. File-discovery hits include match kind, matched character ranges, and ranking reasons for picker highlighting and result explanations. `CodexWorkspace` carries app-server-owned worktree, Git, workspace permission selection, active permission-profile provenance, and runtime filesystem/network permission facts for started threads and turns. Use `CodexAppServer.config` for effective config reads, and `CodexAppServer.extensions` for app, skill, plugin, and collaboration-mode inventory. diff --git a/ROADMAP.md b/ROADMAP.md index 68d4c31..537343f 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -71,7 +71,7 @@ | Thread-scoped recent-turn observable | `Partially shipped` | `CodexThread.makeRecentTurns(limit:)` now vends a bounded recent-turn observable that prewarms from the local history store, supports explicit older/newer whole-turn window expansion, seeds upstream paging cursors even when the visible initial window came from local history, and falls back to `thread/turns/list` when needed. Live probing showed that upstream turn paging is available only after a non-ephemeral thread has materialized at least one user turn, so recent observable startup now degrades to an empty local-only view for the known ephemeral and pre-materialized live runtime responses instead of surfacing raw protocol text. `RecentTurns` now ships named cache-policy presets for chat UIs, full inspectors, and compact history rails; tracks both resident item counts and weighted resident item cost; slims low-value payloads out of older non-visible completed turns before evicting whole turns; rehydrates slimmed turns when they become visible again; and uses scroll-position, visibility, phase, and velocity signals to drive protected residency plus earlier prefetch. Richer weighting heuristics and deeper policy tuning are still open. | | Thread-scoped recent-file observable | `Partially shipped` | `CodexThread.makeRecentFiles(limit:)` and `makeRecentFiles(_:)` now vend a file-centric recent-files observable that hydrates from persisted file-change items, keeps one resident entry per file-change item, enriches live entries from `item/fileChange/outputDelta` and `item/fileChange/patchUpdated`, can load older file entries from the same turn before stepping farther back through older turns, and supports selection-aware shell-versus-payload slimming with automatic payload rehydration for protected files. `CodexThread.RecentFilesQD` gives callers a repeatable descriptor for the initial resident file window and cache policy. Live probing exercises a real create/edit/delete scenario, and recent-file startup now inherits the same empty local-only degradation as recent-turns for the known live history-unavailable responses. The current weighting now accounts for diff structure and line volume, and shell summaries prefer concise edit summaries over raw terminal status when sealed payload is available. The remaining open work is better payload-cost calibration at the margins and richer structured patch presentation beyond the current text preview. | | Thread-scoped recent-command observable | `Partially shipped` | `CodexThread.makeRecentCommands(limit:)` and `makeRecentCommands(_:)` now vend a command-centric recent-commands observable that hydrates from persisted `commandExecution` items, keeps one resident entry per command item, enriches live entries from `item/commandExecution/outputDelta`, can load older command entries from the same turn before stepping farther back through older turns, and supports selection-aware shell-versus-output slimming with automatic output rehydration for protected commands. `CodexThread.RecentCommandsQD` gives callers a repeatable descriptor for the initial resident command window and cache policy. Recent-command startup now inherits the same empty local-only degradation as recent-turns for the known live history-unavailable responses. Current output weighting accounts for output size and line structure, and shell summaries prefer concise command and output summaries over raw transport detail. The remaining open work is better output-cost calibration and sharper shell-summary heuristics. | -| App-wide observable companion | `In Progress` | `CodexAppServer.makeLibrary()` and `CodexAppServer.Library` now expose Core Data-backed value snapshots for unarchived, archived, cwd-grouped, and repository-grouped threads, `CodexWorkspace.ProjectInfo` identity for thread and group displays, `CodexWorkspace.WorktreeSnapshot` values for Codex-reported cwd plus optional Git facts, `CodexAppServer.ThreadSource` values for source badges, bindable sort/grouping policies, thread-list query descriptors, scoped refresh actions, library-local selection, recently selected ordering, local reloads after app-wide thread/turn events, and app-wide model/MCP/hook snapshots for launcher and sidebar UI. `CodexWorkspace` now promotes active permission-profile provenance, runtime filesystem/network permission facts, app-server-owned project identity, and worktree snapshots from thread sessions, but the library still needs broader app-wide settings/actions. | +| App-wide observable companion | `In Progress` | `CodexAppServer.makeLibrary()` and `CodexAppServer.Library` now expose Core Data-backed value snapshots for unarchived, archived, cwd-grouped, and repository-grouped threads, stable worktree groups independent of the visible grouping mode, repository/worktree thread filters, `CodexWorkspace.ProjectInfo` identity for thread and group displays, `CodexWorkspace.WorktreeSnapshot` values for Codex-reported cwd plus optional Git facts, `CodexAppServer.ThreadSource` values for source badges, bindable sort/grouping policies, thread-list query descriptors, scoped refresh actions, library-local selection, selected worktree/repository context, recently selected ordering, local reloads after app-wide thread/turn events, and app-wide model/MCP/hook snapshots for launcher and sidebar UI. `CodexWorkspace` now promotes active permission-profile provenance, runtime filesystem/network permission facts, app-server-owned project identity, and worktree snapshots from thread sessions, but the library still needs broader app-wide settings/actions. | | Public query descriptors | `Partially shipped` | `CodexAppServer.ThreadListQD` now provides repeatable thread-list intent for direct app-server `thread/list` reads and app-wide `Library` loading, `CodexFS.FileDiscoveryQD` provides repeatable bounded file-discovery intent over app-server `fs/readDirectory` reads, `CodexThread.HistoryWindowQD` provides repeatable local completed-turn window intent for recent, older, newer, turn-centered, and item-centered reads, and `CodexThread.RecentFilesQD` plus `CodexThread.RecentCommandsQD` describe recent-activity companion startup. Repository grouping now uses `CodexWorkspace.ProjectInfo`, and per-thread UI state can read `CodexWorkspace.WorktreeSnapshot`, both of which identify a project by Codex-reported Git origin when available and fall back to cwd. Remaining descriptor work includes broader public cursor semantics, selection-centered reads if a concrete caller needs them, and later search-hit hydration. | | Non-UI local history-reading helpers | `Partially shipped` | `CodexThread` now exposes a lightweight `HistoryWindow` page shape for recent local history, older or newer local windows around a known boundary turn id, centered `windowAroundTurn(...)` reads, centered `windowAroundItem(...)` reads, direct `ClosedTurn` reads for one turn, and convenience array helpers over those same windows. This gives non-UI callers an intentional path into the local history store without binding a UI-oriented observable, while still deferring a broader public cursor model, transcript search surface, and richer history-query helpers. | | Public API curation | `Shipped / ongoing` | The source-organization pass has split app-wide model, MCP, thread-management, history, and observable companion values into focused public files while preserving `CodexAppServer`, `CodexThread`, and `CodexTurnHandle` as the three real owners. The connected public-surface review closed the v1 ownership model; post-v1 curation now includes app-server-owned project identity and thread source facts for launcher UI without exposing generated wire models. Future curation should stay tied to concrete public API additions. | @@ -169,9 +169,9 @@ That means the current priority order is: recent-activity descriptors: broader public cursor semantics, any selection-centered reads that become necessary, and later search-hit hydration. -7. Finish the next `CodexAppServer.Library` slice around richer Git observables - and app-wide settings/actions, using promoted app-server facts and descriptor - values where they make list and selection behavior explicit. +7. Finish the next `CodexAppServer.Library` slice around app-wide + settings/actions, using promoted app-server facts and descriptor values + where they make list and selection behavior explicit. 8. Keep tuning `RecentTurns`, `RecentFiles`, and `RecentCommands` after v1 as real UI usage teaches better calibration. The v1 review keeps the separate turn/file/command companions, current cache-policy names and defaults, @@ -1300,3 +1300,4 @@ Completed - 2026-05-07: Added UI-ready `CodexFS.FileDiscoveryHit` search metadata for match kind, matched file-name and relative-path character ranges, and stable ranking reasons. - 2026-05-08: Added `CodexWorkspace.WorktreeSnapshot` so thread, session, and library snapshots expose a single app-server-owned cwd plus Git-facts value without inferring repository roots from local disk. - 2026-05-08: Added post-v1 roadmap candidates for `codex mcp-server` support, a custom approval auto-reviewer, and a Worktrunk-based worktree system. +- 2026-05-08: Added stable `CodexAppServer.Library.worktreeGroups`, selected worktree/repository context, and repository/worktree thread filters for app-wide sidebars without changing the caller-selected visible grouping mode. diff --git a/Sources/SwiftASB/Public/CodexAppServer+Library.swift b/Sources/SwiftASB/Public/CodexAppServer+Library.swift index 90d3ae3..e1cd3bf 100644 --- a/Sources/SwiftASB/Public/CodexAppServer+Library.swift +++ b/Sources/SwiftASB/Public/CodexAppServer+Library.swift @@ -354,6 +354,11 @@ public extension CodexAppServer { public let projectInfo: CodexWorkspace.ProjectInfo? public let title: String public let threads: [ThreadSnapshot] + + /// Codex-reported cwd plus optional Git facts for this group. + public var worktree: CodexWorkspace.WorktreeSnapshot? { + projectInfo?.worktree + } } public private(set) var archivedThreads: [ThreadSnapshot] @@ -388,6 +393,7 @@ public extension CodexAppServer { } } public private(set) var unarchivedThreads: [ThreadSnapshot] + public private(set) var worktreeGroups: [ThreadGroup] public private(set) var snapshotCurrentDirectoryPaths: [String]? public private(set) var snapshotPhase: SnapshotPhase @@ -408,6 +414,14 @@ public extension CodexAppServer { return allThreads.first { $0.id == selectedThreadID } } + public var selectedWorktree: CodexWorkspace.WorktreeSnapshot? { + selectedThread?.worktree + } + + public var selectedRepository: CodexWorkspace.RepositoryInfo? { + selectedThread?.worktree.repository + } + @ObservationIgnored private let appServer: CodexAppServer @@ -475,6 +489,7 @@ public extension CodexAppServer { self.snapshotPhase = .idle self.sortedBy = configuration.sortedBy self.unarchivedThreads = [] + self.worktreeGroups = [] applyVisibleState() if configuration.reconcilesOnCreation { @@ -618,6 +633,29 @@ public extension CodexAppServer { selectThread(nil) } + public func threads( + in worktree: CodexWorkspace.WorktreeSnapshot, + includeArchived: Bool = false + ) -> [ThreadSnapshot] { + threads(inWorktreeID: worktree.id, includeArchived: includeArchived) + } + + public func threads( + inWorktreeID worktreeID: String, + includeArchived: Bool = false + ) -> [ThreadSnapshot] { + sortedVisibleThreads(includeArchived: includeArchived) + .filter { $0.worktree.id == worktreeID } + } + + public func threads( + inRepositoryOriginURL originURL: String, + includeArchived: Bool = false + ) -> [ThreadSnapshot] { + sortedVisibleThreads(includeArchived: includeArchived) + .filter { $0.worktree.repository?.originURL == originURL } + } + private func refreshArchiveScope(_ archived: Bool) async { if isReconciling || isLoadingLocalSnapshot { return @@ -697,6 +735,10 @@ public extension CodexAppServer { from: unarchivedThreads, groupedBy: groupedBy ) + worktreeGroups = Self.groups( + from: unarchivedThreads, + groupedBy: .repository + ) } private func recordSelection(threadID: String) { @@ -724,6 +766,15 @@ public extension CodexAppServer { return uniquePaths.isEmpty ? nil : uniquePaths } + private func sortedVisibleThreads(includeArchived: Bool) -> [ThreadSnapshot] { + let threads = includeArchived ? allThreads : allThreads.filter { !$0.isArchived } + return Self.sort( + threads, + by: sortedBy, + selectionOrderByThreadID: selectionOrderByThreadID + ) + } + private static func sort( _ threads: [ThreadSnapshot], by sortedBy: SortedBy, diff --git a/Sources/SwiftASB/SwiftASB.docc/SwiftUIObservableCompanions.md b/Sources/SwiftASB/SwiftASB.docc/SwiftUIObservableCompanions.md index 8455fc6..1995632 100644 --- a/Sources/SwiftASB/SwiftASB.docc/SwiftUIObservableCompanions.md +++ b/Sources/SwiftASB/SwiftASB.docc/SwiftUIObservableCompanions.md @@ -87,10 +87,12 @@ final class ThreadInspectorModel { ## Selection And Cache Behavior -`CodexAppServer.Library` is the app-wide companion for launchers, sidebars, and project browsers. It publishes value snapshots for unarchived threads, archived threads, cwd groups, ``CodexWorkspace/ProjectInfo`` values for thread and repository-group identity, ``CodexWorkspace/WorktreeSnapshot`` values for Codex-reported cwd plus optional Git facts, and ``CodexAppServer/ThreadSource`` values for source badges; it also reloads from local persistence after app-wide thread and turn events such as archive, unarchive, name changes, status changes, and completed turns. +`CodexAppServer.Library` is the app-wide companion for launchers, sidebars, and project browsers. It publishes value snapshots for unarchived threads, archived threads, cwd groups, stable worktree groups, ``CodexWorkspace/ProjectInfo`` values for thread and repository-group identity, ``CodexWorkspace/WorktreeSnapshot`` values for Codex-reported cwd plus optional Git facts, and ``CodexAppServer/ThreadSource`` values for source badges; it also reloads from local persistence after app-wide thread and turn events such as archive, unarchive, name changes, status changes, and completed turns. Use ``CodexAppServer/Library/selectedThreadID`` and ``CodexAppServer/Library/selectThread(_:)-(String?)`` for library-local selection. The selection timestamp stays inside the library and can drive ``CodexAppServer/Library/SortedBy/selectedNewestFirst`` without writing UI preference state into Codex's stored thread metadata. +Use ``CodexAppServer/Library/worktreeGroups`` when a sidebar needs repository/workspace sections independent of the current visible grouping mode. Use ``CodexAppServer/Library/threads(inWorktreeID:includeArchived:)`` or ``CodexAppServer/Library/threads(inRepositoryOriginURL:includeArchived:)`` when a project browser needs the sorted threads for one Codex-reported worktree or Git origin without reading local disk. + Use ``CodexAppServer/Library/refreshAppSnapshots()`` when the same app-wide UI needs model capabilities, MCP server status, and hook diagnostics. Library derives hook `cwd` requests from its stored thread snapshots unless configuration provides explicit hook current-directory paths. Recent companions keep caller-owned UI inputs mutable. For example, views can update selected file or command identifiers and visible item identifiers. SwiftASB uses that information to protect visible or selected payloads while slimming older low-value entries when the resident cache exceeds its budget. diff --git a/Tests/SwiftASBTests/Public/CodexAppServerLibraryTests.swift b/Tests/SwiftASBTests/Public/CodexAppServerLibraryTests.swift index 5b03a23..611d1c5 100644 --- a/Tests/SwiftASBTests/Public/CodexAppServerLibraryTests.swift +++ b/Tests/SwiftASBTests/Public/CodexAppServerLibraryTests.swift @@ -182,6 +182,98 @@ extension CodexAppServerTests { await tearDownTemporarySQLiteHistoryStore(historyStore, directory: temporaryDirectory) } + @MainActor + @Test("library exposes worktree views independent of visible grouping") + func libraryExposesWorktreeViewsIndependentOfVisibleGrouping() async throws { + let transport = FakeCodexAppServerTransport( + threadListResultQueue: [ + [ + "data": [ + storedThread( + id: "thread-active", + cwd: "/tmp/package-active", + gitBranch: "main", + gitOriginURL: "https://github.com/gaelic-ghost/SwiftASB.git", + gitSHA: "abcdef1234567890", + name: "Active package", + preview: "Active repo thread", + statusType: "notLoaded", + updatedAt: 1713350030 + ), + storedThread( + id: "thread-standalone", + cwd: "/tmp/standalone", + name: "Standalone", + preview: "Standalone thread", + statusType: "notLoaded", + updatedAt: 1713350020 + ), + ], + "nextCursor": NSNull(), + ], + [ + "data": [ + storedThread( + id: "thread-archived", + cwd: "/tmp/package-archived", + gitBranch: "main", + gitOriginURL: "https://github.com/gaelic-ghost/SwiftASB.git", + name: "Archived package", + preview: "Archived repo thread", + statusType: "notLoaded", + updatedAt: 1713350010 + ), + ], + "nextCursor": NSNull(), + ], + ] + ) + let (historyStore, temporaryDirectory) = try temporarySQLiteHistoryStore() + let client = CodexAppServer(transport: transport, historyStore: historyStore) + + try await client.start() + _ = try await client.initialize( + .init(clientInfo: .init(name: "SwiftASBTests", title: "SwiftASB Tests", version: "0.1.0")) + ) + + let library = try await client.makeLibrary( + configuration: .init( + pageSize: 10, + groupedBy: .none, + reconcilesOnCreation: false, + loadsAppSnapshotsOnCreation: false + ) + ) + + await library.refresh() + + #expect(library.groups.isEmpty) + #expect(library.worktreeGroups.map(\.id) == [ + "/tmp/standalone", + "https://github.com/gaelic-ghost/SwiftASB.git", + ]) + #expect(library.worktreeGroups.first(where: { + $0.id == "https://github.com/gaelic-ghost/SwiftASB.git" + })?.worktree?.repository?.shortSHA == "abcdef123456") + #expect(library.threads(inRepositoryOriginURL: "https://github.com/gaelic-ghost/SwiftASB.git").map(\.id) == [ + "thread-active", + ]) + #expect(library.threads( + inRepositoryOriginURL: "https://github.com/gaelic-ghost/SwiftASB.git", + includeArchived: true + ).map(\.id) == [ + "thread-active", + "thread-archived", + ]) + + library.selectThread("thread-active") + #expect(library.selectedWorktree?.id == "https://github.com/gaelic-ghost/SwiftASB.git") + #expect(library.selectedRepository?.originURL == "https://github.com/gaelic-ghost/SwiftASB.git") + + await client.stop() + await tearDownTemporarySQLiteHistoryStore(historyStore, directory: temporaryDirectory) + } + @MainActor @Test("library can sort local snapshots by name without changing persistence") func librarySortsLocalSnapshotsByName() async throws { diff --git a/docs/maintainers/v1-public-api-audit.md b/docs/maintainers/v1-public-api-audit.md index a615b78..05d2b88 100644 --- a/docs/maintainers/v1-public-api-audit.md +++ b/docs/maintainers/v1-public-api-audit.md @@ -496,9 +496,12 @@ Use these decisions for every public symbol: `CodexAppServer.ThreadSession.workspace`, and `CodexAppServer.Library.ThreadSnapshot` expose the same snapshot so UI and Worktrunk-oriented callers can use one identity/display shape without running - Git or deriving repository roots locally. The current app-server schema does - not expose a Git worktree root or working-tree status surface, so those remain - future promotion candidates instead of inferred SwiftASB fields. + Git or deriving repository roots locally. `CodexAppServer.Library` also keeps + stable worktree groups, selected worktree/repository context, and sorted + worktree/repository thread filters available independent of the caller's + visible grouping mode. The current app-server schema does not expose a Git + worktree root or working-tree status surface, so those remain future + promotion candidates instead of inferred SwiftASB fields. - [x] Add DocC examples for app-server startup, thread/turn start, progress observation, approval response, diagnostics, recent history, and SwiftUI observable companions. diff --git a/docs/maintainers/v1-public-api-symbol-inventory.md b/docs/maintainers/v1-public-api-symbol-inventory.md index 3cfa63d..702d8a6 100644 --- a/docs/maintainers/v1-public-api-symbol-inventory.md +++ b/docs/maintainers/v1-public-api-symbol-inventory.md @@ -1,15 +1,15 @@ # V1 Public API Symbol Inventory -Generated from `swift package dump-symbol-graph --minimum-access-level public --skip-synthesized-members` on 2026-05-02 after the v0.128 generated-wire promotion and final pre-v1 public-surface tightening, then updated on 2026-05-05 for the post-v1 app-wide library snapshot, on 2026-05-06 for the public query descriptor, filesystem, config, extension-inventory, thread-goal, recent-activity descriptor, repository-grouping, workspace permission-profile, and file-discovery slices, and on 2026-05-08 for the `CodexWorkspace.ProjectInfo` cleanup, `CodexWorkspace.WorktreeSnapshot` promotion, `CodexAppServer.ThreadSource` promotion, and v0.129 hook compact event names. This is a maintainer ledger for the v1 public API freeze plus accepted post-v1 app-wide additions; it records public/open declarations visible through the `SwiftASB` library product, excluding synthesized members. +Generated from `swift package dump-symbol-graph --minimum-access-level public --skip-synthesized-members` on 2026-05-02 after the v0.128 generated-wire promotion and final pre-v1 public-surface tightening, then updated on 2026-05-05 for the post-v1 app-wide library snapshot, on 2026-05-06 for the public query descriptor, filesystem, config, extension-inventory, thread-goal, recent-activity descriptor, repository-grouping, workspace permission-profile, and file-discovery slices, and on 2026-05-08 for the `CodexWorkspace.ProjectInfo` cleanup, `CodexWorkspace.WorktreeSnapshot` promotion, `CodexAppServer.Library` worktree-group helpers, `CodexAppServer.ThreadSource` promotion, and v0.129 hook compact event names. This is a maintainer ledger for the v1 public API freeze plus accepted post-v1 app-wide additions; it records public/open declarations visible through the `SwiftASB` library product, excluding synthesized members. ## Summary -- Public/open symbols: 1883 +- Public/open symbols: 1890 - Public/open types: 294 - Public/open initializers: 131 -- Public/open methods and type methods: 129 +- Public/open methods and type methods: 132 - Public/open enum cases: 363 -- Public/open properties: 963 +- Public/open properties: 967 ## Public Types @@ -326,6 +326,9 @@ Generated from `swift package dump-symbol-graph --minimum-access-level public -- - `CodexAppServer.Library.selectThread(_:)` - `@MainActor func selectThread(_ threadID: String?)` - Sources/SwiftASB/Public/CodexAppServer+Library.swift - `CodexAppServer.Library.selectThread(_:)` - `@MainActor func selectThread(_ thread: CodexAppServer.Library.ThreadSnapshot)` - Sources/SwiftASB/Public/CodexAppServer+Library.swift - `CodexAppServer.Library.clearSelection()` - `@MainActor func clearSelection()` - Sources/SwiftASB/Public/CodexAppServer+Library.swift +- `CodexAppServer.Library.threads(in:includeArchived:)` - `@MainActor func threads(in worktree: CodexWorkspace.WorktreeSnapshot, includeArchived: Bool = false) -> [CodexAppServer.Library.ThreadSnapshot]` - Sources/SwiftASB/Public/CodexAppServer+Library.swift +- `CodexAppServer.Library.threads(inWorktreeID:includeArchived:)` - `@MainActor func threads(inWorktreeID worktreeID: String, includeArchived: Bool = false) -> [CodexAppServer.Library.ThreadSnapshot]` - Sources/SwiftASB/Public/CodexAppServer+Library.swift +- `CodexAppServer.Library.threads(inRepositoryOriginURL:includeArchived:)` - `@MainActor func threads(inRepositoryOriginURL originURL: String, includeArchived: Bool = false) -> [CodexAppServer.Library.ThreadSnapshot]` - Sources/SwiftASB/Public/CodexAppServer+Library.swift - `CodexAppServer.cliExecutableDiagnostics()` - `func cliExecutableDiagnostics() async throws -> CodexAppServer.CLIExecutableDiagnostics` - Sources/SwiftASB/Public/CodexAppServer.swift - `CodexAppServer.compactThread(_:)` - `func compactThread(_ request: CodexAppServer.ThreadCompactRequest) async throws` - Sources/SwiftASB/Public/CodexAppServer.swift - `CodexAppServer.diagnosticEvents()` - `func diagnosticEvents() -> AsyncThrowingStream` - Sources/SwiftASB/Public/CodexAppServer.swift @@ -763,6 +766,7 @@ The 2026-05-06 app-server schema promotion added several hand-owned public names - `CodexThreadEvent` now includes `.goalUpdated(_:)` and `.goalCleared(_:)` for app-server goal notifications. - `CodexThread.RecentFilesQD` and `CodexThread.RecentCommandsQD` describe repeatable recent-activity companion startup intent. - `CodexAppServer.Library.GroupedBy.repository` groups app-wide library snapshots by `CodexWorkspace.ProjectInfo` identity: app-server Git origin metadata with cwd fallback. +- `CodexAppServer.Library` exposes stable worktree groups, selected worktree/repository context, and sorted repository/worktree thread filters for sidebar and project-browser UIs independent of the caller-selected visible grouping mode. - `CodexWorkspace` owns app-server-owned permission selections, runtime workspace permission facts, project identity, and worktree snapshots: `PermissionSelection`, `PermissionSelectionModification`, `ActivePermissionProfile`, `ActivePermissionModification`, `PermissionProfile`, `FileSystemPermissions`, `FileSystemSandboxEntry`, `FileSystemAccessMode`, `FileSystemPath`, `FileSystemSpecialPath`, `NetworkPermissions`, `ProjectInfo`, `RepositoryInfo`, `SessionSnapshot`, and `WorktreeSnapshot`. - `CodexAppServer.ThreadStartRequest`, `ThreadResumeRequest`, `ThreadForkRequest`, `TurnStartRequest`, `CodexThread.TurnStartRequest`, and `CodexThread.startTextTurn(...)` now accept optional `CodexWorkspace.PermissionSelection` values. - `CodexAppServer.ThreadSession` and `CodexThread` now expose active permission-profile provenance, runtime permission facts, app-server-owned project identity, app-server-owned worktree snapshots, and a `CodexWorkspace.SessionSnapshot`. @@ -774,7 +778,7 @@ The 2026-05-06 app-server schema promotion added several hand-owned public names - `Sources/SwiftASB/Public/CodexAppServer+CodexExtensions.swift`: 113 public properties - `Sources/SwiftASB/Public/CodexAppServer+Compatibility.swift`: 10 public properties - `Sources/SwiftASB/Public/CodexAppServer+Hooks.swift`: 32 public properties -- `Sources/SwiftASB/Public/CodexAppServer+Library.swift`: 57 public properties +- `Sources/SwiftASB/Public/CodexAppServer+Library.swift`: 61 public properties - `Sources/SwiftASB/Public/CodexAppServer+LoadedThreads.swift`: 4 public properties - `Sources/SwiftASB/Public/CodexAppServer+MCP.swift`: 43 public properties - `Sources/SwiftASB/Public/CodexAppServer+Models.swift`: 23 public properties From 7e91d53d932190ee3357ca0235b56755180b1923 Mon Sep 17 00:00:00 2001 From: Gale W Date: Fri, 8 May 2026 20:25:07 -0400 Subject: [PATCH 4/7] docs: add app access roadmap layer --- ROADMAP.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/ROADMAP.md b/ROADMAP.md index 537343f..a90a41e 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -158,7 +158,12 @@ That means the current priority order is: fact boundary is clearer. The useful shape is a SwiftASB-supported way for clients to ask Codex-owned services for workspace/worktree identity, branch/status facts, and safe handoff points, without committing - machine-local paths or turning SwiftASB into a Worktrunk clone. + machine-local paths or turning SwiftASB into a Worktrunk clone. Richer local + file-write, repository-root, and working-tree status enrichment should live + behind a separate optional app-access layer where a consuming macOS app can + report user-granted directory access or pass a security-scoped bookmark, + following Apple's sandbox model instead of treating local disk access as an + implicit SwiftASB capability. 5. Explore a custom approval auto-reviewer after the answerable server-request model is stable enough to distinguish advisory review from action approval. The first useful slice should classify approval requests and @@ -261,6 +266,10 @@ workflow earns them in a later feature release. - [ ] Plan a Worktrunk-based worktree system around Codex-owned workspace, Git, and worktree facts, keeping machine-local paths out of public dependency or package metadata. +- [ ] Plan an optional macOS app-access layer for user-granted directory access + and security-scoped bookmark handoff, so consuming apps can explicitly tell + SwiftASB when richer local file-write, repository-root, and working-tree + status enrichment is allowed. - [ ] Finish the `CodexAppServer` app-wide observable companion with derived repository-root grouping, richer Git observables, and any broader app-wide settings/actions that earn public models. @@ -1278,6 +1287,10 @@ Completed - [ ] Add a Worktrunk-based worktree system once SwiftASB can lean on app-server-owned workspace and Git facts instead of local filesystem inference. +- [ ] Add an optional sandbox-friendly app-access capability layer for + consuming macOS apps that have explicit user-granted directory access, + including security-scoped bookmark handoff if that proves to be the right + integration shape. - [ ] Add archive-aware retention/eviction and rollback forensic archival for removed turn payloads. - [x] Add live rollback coverage once the disposable-thread path is reliable enough to assert explicit local rollback markers. - [x] Add a local-only startup mode for recent history observables when live upstream paging is unavailable because the thread is ephemeral or not yet materialized. @@ -1301,3 +1314,4 @@ Completed - 2026-05-08: Added `CodexWorkspace.WorktreeSnapshot` so thread, session, and library snapshots expose a single app-server-owned cwd plus Git-facts value without inferring repository roots from local disk. - 2026-05-08: Added post-v1 roadmap candidates for `codex mcp-server` support, a custom approval auto-reviewer, and a Worktrunk-based worktree system. - 2026-05-08: Added stable `CodexAppServer.Library.worktreeGroups`, selected worktree/repository context, and repository/worktree thread filters for app-wide sidebars without changing the caller-selected visible grouping mode. +- 2026-05-08: Added a future optional macOS app-access layer for user-granted directory access and security-scoped bookmark handoff, keeping richer local repository and file-write enrichment separate from app-server-reported workspace facts. From 153ae904609cd46c7e3731543a59717988564e81 Mon Sep 17 00:00:00 2001 From: Gale W Date: Fri, 8 May 2026 20:27:11 -0400 Subject: [PATCH 5/7] release: bump versions for v1.2.0 --- README.md | 4 ++-- ROADMAP.md | 20 ++++++++++---------- docs/maintainers/v1-public-api-audit.md | 6 +++--- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index af051e3..f00c285 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ SwiftASB helps Swift apps work with the local Codex app-server without making ap ### Status -SwiftASB has a supported v1 public API for the core local Codex app-server lifecycle. `v1.1.4` is the current released baseline. +SwiftASB has a supported v1 public API for the core local Codex app-server lifecycle. `v1.2.0` is the current released baseline. ### What This Project Is @@ -33,7 +33,7 @@ Add SwiftASB from the GitHub package URL: https://github.com/gaelic-ghost/SwiftASB -Use release `v1.1.4` or newer unless your project intentionally pins an older version. +Use release `v1.2.0` or newer unless your project intentionally pins an older version. You also need a local Codex CLI installation with app-server support. SwiftASB looks for `codex` in the usual command-line locations, and apps can provide an exact executable path when they need stricter control. diff --git a/ROADMAP.md b/ROADMAP.md index a90a41e..e28c9f6 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -76,7 +76,7 @@ | Non-UI local history-reading helpers | `Partially shipped` | `CodexThread` now exposes a lightweight `HistoryWindow` page shape for recent local history, older or newer local windows around a known boundary turn id, centered `windowAroundTurn(...)` reads, centered `windowAroundItem(...)` reads, direct `ClosedTurn` reads for one turn, and convenience array helpers over those same windows. This gives non-UI callers an intentional path into the local history store without binding a UI-oriented observable, while still deferring a broader public cursor model, transcript search surface, and richer history-query helpers. | | Public API curation | `Shipped / ongoing` | The source-organization pass has split app-wide model, MCP, thread-management, history, and observable companion values into focused public files while preserving `CodexAppServer`, `CodexThread`, and `CodexTurnHandle` as the three real owners. The connected public-surface review closed the v1 ownership model; post-v1 curation now includes app-server-owned project identity and thread source facts for launcher UI without exposing generated wire models. Future curation should stay tied to concrete public API additions. | | DocC documentation | `Shipped / ongoing` | `Sources/SwiftASB/SwiftASB.docc/` contains a package landing page, public-handle extension pages, conceptual articles for app-wide capabilities, interactive lifecycle, thread management, history/observable companions, generated-wire boundary notes, and copy-pasteable walkthroughs for startup, progress/approval handling, diagnostics/history, and SwiftUI observable companions. The catalog is validated through Xcode `docbuild`; future work is ordinary stale-link, prose, and symbol-comment refinement as the public API grows. | -| Swift Package Index readiness | `Shipped` | `.spi.yml` declares `SwiftASB` as the documentation target, and Swift Package Index lists `gaelic-ghost/SwiftASB` with a documentation link, compatibility/build results, Package ID `9B5839D9-9551-473F-A939-841534A3FC55`, and a 2026-05-06 update timestamp for the latest confirmed indexed release. Recheck SPI after the `v1.1.4` tag is published. | +| Swift Package Index readiness | `Shipped` | `.spi.yml` declares `SwiftASB` as the documentation target, and Swift Package Index lists `gaelic-ghost/SwiftASB` with a documentation link, compatibility/build results, Package ID `9B5839D9-9551-473F-A939-841534A3FC55`, and a 2026-05-06 update timestamp for the latest confirmed indexed release. Recheck SPI after the `v1.2.0` tag is published. | | Contributor documentation split | `Shipped` | `README.md` is now focused on Swift and SwiftUI package users, while `CONTRIBUTING.md` owns contributor setup, validation, DocC, live-test flags, generated-wire refresh, and PR expectations. | | `CodexTurnHandle` live observable companion | `Partially shipped` | `CodexTurnHandle` owns a live `Minimap` companion that is attached when the handle is created and maintains current-state call snapshots for command, file-edit, dynamic-tool, collab-tool, and MCP item activity. It also now mirrors whether thread context compaction is active for the turn and supports explicit `complete()` handoff into a caller-owned sealed turn snapshot. | | Additional turn event mapping | `Partially shipped` | The public event layer covers the current interactive lifecycle plus the item-start and item-complete events needed for observable call-state mirrors. Raw command-output and file-change-output deltas now stay internal as transport detail but drive the shipped `RecentCommands` and `RecentFiles` companions, and streamed or patch-updated payloads are preserved when later completed snapshots are thinner. Richer MCP-progress detail still remains internal, while warning, guardian-warning, config-warning, deprecation, MCP-server-status, remote-control-status, model-reroute, and model-verification notifications now surface through hand-owned diagnostic events. | @@ -104,7 +104,7 @@ The next meaningful package step is no longer proving the v1 interactive lifecycle, SPI visibility, basic history hydration, first-pass reconciliation, or command-approval completion. Those slices now exist and shipped in the -`v1.1.4` baseline. +`v1.2.0` baseline. The next meaningful work is to widen the reviewed app-server schema and protocol coverage before adding more public query descriptors. Descriptors should compile @@ -201,7 +201,7 @@ That means the current priority order is: ## V1 Readiness Checklist -This checklist records the work that made `SwiftASB` ready for the `v1.1.4` +This checklist records the work that made `SwiftASB` ready for the `v1.2.0` tag. The goal was not to make every possible app-server feature public before v1. The goal was to make the supported lifecycle honest, durable, well documented, and intentionally shaped. @@ -380,8 +380,8 @@ workflow earns them in a later feature release. ### Documentation And Examples -- [x] Update stale release references after the `v1.1.4` release. - Decision: README now names `v1.1.4` as the current released baseline and no +- [x] Update stale release references after the `v1.2.0` release. + Decision: README now names `v1.2.0` as the current released baseline and no longer describes the package as early development. - [x] Finish DocC symbol comments for the supported lifecycle, not just the conceptual articles. @@ -557,10 +557,10 @@ workflow earns them in a later feature release. the `release/v1.0.0` branch on 2026-05-02 and on the `release/v1.0.1-prep` branch on 2026-05-02. - [x] Decide whether another targeted `v0.9.x` patch release is needed before - `v1.1.4`, or whether the remaining work should go straight into the v1 + `v1.2.0`, or whether the remaining work should go straight into the v1 release branch. Decision: no additional `v0.9.x` patch is needed. The remaining work should go - straight into the `v1.1.4` release branch. + straight into the `v1.2.0` release branch. - [x] Prepare v1 release notes with explicit sections for public surface, intentionally internal surfaces, compatibility window, migration notes, validation performed, and known post-v1 work. @@ -614,7 +614,7 @@ workflow earns them in a later feature release. #### Migration Notes - Existing `v0.9.x` consumers should update the SwiftPM dependency to - `from: "1.1.4"` once the tag is published. + `from: "1.2.0"` once the tag is published. - The v1 API surface has removed stale pre-v1 compatibility shims and phantom fields that no longer exist in the reviewed `v0.128.0` schema. - Same-thread overlapping turns are rejected client-side with @@ -639,7 +639,7 @@ workflow earns them in a later feature release. - Keep an eye on future Swift Package Index builds after compatibility-window or DocC changes; the `v1.1.1` listing and documentation link are live, and - `v1.1.4` should be rechecked after the patch tag is indexed. + `v1.2.0` should be rechecked after the patch tag is indexed. - Add broader live server-request coverage for permissions and MCP elicitation if those become stronger public runtime guarantees. - Continue tuning recent companion cache calibration, richer file previews, @@ -1214,7 +1214,7 @@ Completed - [x] Add version-compatibility policy notes for the local Codex binary. - [x] Refresh the compatibility window and promoted generated snapshot against the current `v0.124.0` schema dump once the added endpoint, notification, and field families have been classified. - [x] Curate the public API before v1 by splitting large source files along existing responsibility boundaries where still helpful, tightening public names/defaults, and finishing targeted source-level symbol documentation for the supported lifecycle. - Decision: completed for the `v1.1.4` boundary through the public API audit, + Decision: completed for the `v1.2.0` boundary through the public API audit, symbol inventory, source-comment pass, and focused public file organization. - [x] Add the first DocC documentation catalog before v1, including a package landing page, public-handle topic groups, and conceptual articles for the interactive lifecycle, history companions, and generated-wire boundary. - [x] Validate the DocC catalog through Xcode `docbuild` and document the maintainer command. diff --git a/docs/maintainers/v1-public-api-audit.md b/docs/maintainers/v1-public-api-audit.md index 05d2b88..863de96 100644 --- a/docs/maintainers/v1-public-api-audit.md +++ b/docs/maintainers/v1-public-api-audit.md @@ -2,7 +2,7 @@ This document is the working checklist for the `SwiftASB` v1 public API curation pass. The goal is to freeze a compact, Swift-native surface for the -supported app-server lifecycle before `v1.1.4`, not to expose every generated +supported app-server lifecycle before `v1.2.0`, not to expose every generated wire family. ## Current Public Source Inventory @@ -429,7 +429,7 @@ Use these decisions for every public symbol: - [x] Add symbol comments for every stable v1 public type and method that is not self-explanatory from its declaration. - Decision: complete for the `v1.1.4` release boundary. Default-bearing public + Decision: complete for the `v1.2.0` release boundary. Default-bearing public initializers and methods now document whether omission delegates to Codex, chooses a SwiftASB local-history/UI default, or applies an explicit safety default such as `.turn` or `.unchanged`. The source-level pass also covers the @@ -508,7 +508,7 @@ Use these decisions for every public symbol: Decision: covered by the startup, progress/approval, diagnostics/history, and SwiftUI observable companion walkthroughs in `Sources/SwiftASB/SwiftASB.docc/`. - [x] Update stale README release references before the next release. - Decision: README now names `v1.1.4` as the current released baseline. + Decision: README now names `v1.2.0` as the current released baseline. - [x] Confirm README, DocC, and this audit use the same v1 release boundary. Decision: README, DocC, and this audit now describe the same narrow v1 promise: app-server lifecycle, app-wide capability reads, stored-thread From c612eb931e4cf9bce4943e307eefc69591519e14 Mon Sep 17 00:00:00 2001 From: Gale W Date: Fri, 8 May 2026 20:29:01 -0400 Subject: [PATCH 6/7] docs: add git action roadmap item --- ROADMAP.md | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index e28c9f6..e857ad4 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -164,26 +164,32 @@ That means the current priority order is: report user-granted directory access or pass a security-scoped bookmark, following Apple's sandbox model instead of treating local disk access as an implicit SwiftASB capability. -5. Explore a custom approval auto-reviewer after the answerable +5. Plan command-execution-backed Git and GitHub actions for consuming apps that + want Codex-like repository operations through SwiftASB. The first useful + shape should route explicit user-reviewed actions through installed `git` + and optional `gh` binaries when available, keep command output and approval + decisions observable, and reuse the app-access/perms model instead of + silently expanding filesystem authority. +6. Explore a custom approval auto-reviewer after the answerable server-request model is stable enough to distinguish advisory review from action approval. The first useful slice should classify approval requests and produce review recommendations; automatically answering requests should wait for an explicit policy model and tests that prove dangerous actions stay user-controlled. -6. Finish the next descriptor increment beyond the current list, history, and +7. Finish the next descriptor increment beyond the current list, history, and recent-activity descriptors: broader public cursor semantics, any selection-centered reads that become necessary, and later search-hit hydration. -7. Finish the next `CodexAppServer.Library` slice around app-wide +8. Finish the next `CodexAppServer.Library` slice around app-wide settings/actions, using promoted app-server facts and descriptor values where they make list and selection behavior explicit. -8. Keep tuning `RecentTurns`, `RecentFiles`, and `RecentCommands` after v1 as +9. Keep tuning `RecentTurns`, `RecentFiles`, and `RecentCommands` after v1 as real UI usage teaches better calibration. The v1 review keeps the separate turn/file/command companions, current cache-policy names and defaults, selection/visibility protection, slimming behavior, and rehydration model as stable enough; remaining work is calibration and richer previews, not proving the model exists. -9. Keep future Codex CLI schema additions classified before public promotion: +10. Keep future Codex CLI schema additions classified before public promotion: `excludeTurns` remains public on resume/fork request models because it directly supports the existing paged history model; permission-profile families stay internal until SwiftASB owns a deliberate public permission @@ -270,6 +276,10 @@ workflow earns them in a later feature release. and security-scoped bookmark handoff, so consuming apps can explicitly tell SwiftASB when richer local file-write, repository-root, and working-tree status enrichment is allowed. +- [ ] Plan command-execution-backed Git and GitHub actions through installed + `git` and optional `gh`, including capability diagnostics, user-reviewed + command intents, observable output, and permission/access boundaries for + repository mutations. - [ ] Finish the `CodexAppServer` app-wide observable companion with derived repository-root grouping, richer Git observables, and any broader app-wide settings/actions that earn public models. @@ -1291,6 +1301,9 @@ Completed consuming macOS apps that have explicit user-granted directory access, including security-scoped bookmark handoff if that proves to be the right integration shape. +- [ ] Add command-execution-backed Git and GitHub action helpers for consuming + apps that want Codex-like repository operations through `git` and `gh` when + those tools are installed and the app has the required access grant. - [ ] Add archive-aware retention/eviction and rollback forensic archival for removed turn payloads. - [x] Add live rollback coverage once the disposable-thread path is reliable enough to assert explicit local rollback markers. - [x] Add a local-only startup mode for recent history observables when live upstream paging is unavailable because the thread is ephemeral or not yet materialized. @@ -1315,3 +1328,4 @@ Completed - 2026-05-08: Added post-v1 roadmap candidates for `codex mcp-server` support, a custom approval auto-reviewer, and a Worktrunk-based worktree system. - 2026-05-08: Added stable `CodexAppServer.Library.worktreeGroups`, selected worktree/repository context, and repository/worktree thread filters for app-wide sidebars without changing the caller-selected visible grouping mode. - 2026-05-08: Added a future optional macOS app-access layer for user-granted directory access and security-scoped bookmark handoff, keeping richer local repository and file-write enrichment separate from app-server-reported workspace facts. +- 2026-05-08: Added future command-execution-backed Git and GitHub actions through installed `git` and optional `gh`, scoped by explicit user-reviewed command intents and the app-access permission model. From f97497d36c3ea9cc4dde9a499a91198f40284dad Mon Sep 17 00:00:00 2001 From: Gale W Date: Fri, 8 May 2026 20:36:49 -0400 Subject: [PATCH 7/7] workspace: tighten worktree fact handling --- ROADMAP.md | 6 +++--- Sources/SwiftASB/Public/CodexWorkspace.swift | 14 +++++++++++--- .../Public/CodexAppServerLibraryTests.swift | 17 +++++++++++++++++ .../Public/CodexWorkspaceTests.swift | 17 +++++++++++++++++ 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index e857ad4..c8333ea 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -198,11 +198,11 @@ That means the current priority order is: sessions, marketplace/account-management families, and guardian denied-action approval remain post-v1 until their consumer workflows are clearer. -10. Flesh out archive-aware retention and eviction beyond the current list-driven +11. Flesh out archive-aware retention and eviction beyond the current list-driven archive-state drift correction. -11. Add any sharper binary-discovery diagnostics we want alongside the +12. Add any sharper binary-discovery diagnostics we want alongside the current-reviewed compatibility window before a broader compatibility release. -12. Revisit whether a convenience `run(...)` API is earned only after the +13. Revisit whether a convenience `run(...)` API is earned only after the lower-level lifecycle has more production mileage. ## V1 Readiness Checklist diff --git a/Sources/SwiftASB/Public/CodexWorkspace.swift b/Sources/SwiftASB/Public/CodexWorkspace.swift index ad13ca4..0a95ec7 100644 --- a/Sources/SwiftASB/Public/CodexWorkspace.swift +++ b/Sources/SwiftASB/Public/CodexWorkspace.swift @@ -224,9 +224,9 @@ public enum CodexWorkspace { branch: String? = nil, sha: String? = nil ) { - self.originURL = originURL - self.branch = branch - self.sha = sha + self.originURL = Self.normalizedFact(originURL) + self.branch = Self.normalizedFact(branch) + self.sha = Self.normalizedFact(sha) } /// True when Codex reported at least one Git fact for this thread. @@ -247,6 +247,14 @@ public enum CodexWorkspace { internal var normalized: Self? { isEmpty ? nil : self } + + private static func normalizedFact(_ value: String?) -> String? { + guard let trimmed = value?.trimmingCharacters(in: .whitespacesAndNewlines), + !trimmed.isEmpty else { + return nil + } + return trimmed + } } /// Thread-session workspace snapshot built from app-server-owned facts. diff --git a/Tests/SwiftASBTests/Public/CodexAppServerLibraryTests.swift b/Tests/SwiftASBTests/Public/CodexAppServerLibraryTests.swift index 611d1c5..b472617 100644 --- a/Tests/SwiftASBTests/Public/CodexAppServerLibraryTests.swift +++ b/Tests/SwiftASBTests/Public/CodexAppServerLibraryTests.swift @@ -255,9 +255,18 @@ extension CodexAppServerTests { #expect(library.worktreeGroups.first(where: { $0.id == "https://github.com/gaelic-ghost/SwiftASB.git" })?.worktree?.repository?.shortSHA == "abcdef123456") + let repositoryWorktree = try #require(library.worktreeGroups.first(where: { + $0.id == "https://github.com/gaelic-ghost/SwiftASB.git" + })?.worktree) #expect(library.threads(inRepositoryOriginURL: "https://github.com/gaelic-ghost/SwiftASB.git").map(\.id) == [ "thread-active", ]) + #expect(library.threads(in: repositoryWorktree).map(\.id) == [ + "thread-active", + ]) + #expect(library.threads(inWorktreeID: repositoryWorktree.id).map(\.id) == [ + "thread-active", + ]) #expect(library.threads( inRepositoryOriginURL: "https://github.com/gaelic-ghost/SwiftASB.git", includeArchived: true @@ -265,6 +274,14 @@ extension CodexAppServerTests { "thread-active", "thread-archived", ]) + #expect(library.threads(in: repositoryWorktree, includeArchived: true).map(\.id) == [ + "thread-active", + "thread-archived", + ]) + #expect(library.threads(inWorktreeID: repositoryWorktree.id, includeArchived: true).map(\.id) == [ + "thread-active", + "thread-archived", + ]) library.selectThread("thread-active") #expect(library.selectedWorktree?.id == "https://github.com/gaelic-ghost/SwiftASB.git") diff --git a/Tests/SwiftASBTests/Public/CodexWorkspaceTests.swift b/Tests/SwiftASBTests/Public/CodexWorkspaceTests.swift index 5df19c4..7c9dfd3 100644 --- a/Tests/SwiftASBTests/Public/CodexWorkspaceTests.swift +++ b/Tests/SwiftASBTests/Public/CodexWorkspaceTests.swift @@ -37,4 +37,21 @@ struct CodexWorkspaceTests { #expect(snapshot.repository == nil) #expect(!snapshot.hasRepositoryFacts) } + + @Test("worktree snapshot ignores blank Git facts") + func worktreeSnapshotIgnoresBlankGitFacts() { + let snapshot = CodexWorkspace.WorktreeSnapshot( + currentDirectoryPath: "/tmp/blank-git", + repository: .init( + originURL: " ", + branch: "\n\t", + sha: "" + ) + ) + + #expect(snapshot.id == "/tmp/blank-git") + #expect(snapshot.identitySource == .currentDirectory) + #expect(snapshot.repository == nil) + #expect(!snapshot.hasRepositoryFacts) + } }