Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ Dates are `YYYY-MM-DD` in the project's local timezone.
- Stray `docs/mockups/preview.png` (Android emulator screenshot from a
pre-SwiftUI prototype).

### Fixed
- Stale verification instructions: `docs/TESTING.md` and `CLAUDE.md` still
described the removed `check` SwiftPM command plugin (`swift test` +
`swift run coverage` replaced it), and `Package.swift` comments still
referenced the removed `scripts/bundle.sh`. Coverage table in
`docs/TESTING.md` re-measured; `ContainerImage` helpers now unit-tested.

## [0.1.0] — 2026-06-19

Initial release. Menu-bar-first macOS companion for Apple's `container`
Expand Down
2 changes: 1 addition & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ On this machine `xcodebuild`/Xcode 26 was not detected and `container-compose` i
installed, so the app **cannot be compile-verified locally yet**. `container` *is* present.
**Mitigation:** treat "builds in Xcode 26" as a manual gate the developer runs. There is no
hosted CI (Apple's SDK graph can't build on hosted runners) — verify locally with `swift
test` / `swift package check` (the `check` SwiftPM command plugin). See `docs/TESTING.md`.
test` (coverage via `swift run coverage`). See `docs/TESTING.md`.

### R8 — Crowded ecosystem (LOW / product)
Many compose tools and full GUIs already exist; the menu-bar-first niche is the
Expand Down
33 changes: 33 additions & 0 deletions ConsaiCore/Tests/ConsaiCoreTests/ContainerImageTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Testing
@testable import ConsaiCore

@Suite struct ContainerImageTests {

@Test func idIsTheReference() {
let image = ContainerImage(reference: "alpine:3.20", digest: "sha256:abcd")
#expect(image.id == "alpine:3.20")
}

@Test func shortDigestStripsPrefixAndTruncatesTo12() {
let image = ContainerImage(reference: "alpine", digest: "sha256:abcd1234efgh5678ijkl")
#expect(image.shortDigest == "abcd1234efgh")
}

@Test func shortDigestHandlesBareHex() {
let image = ContainerImage(reference: "alpine", digest: "abcdef1234567890")
#expect(image.shortDigest == "abcdef123456")
}

@Test func formattedSizeIsNilWithoutAUsableSize() {
#expect(ContainerImage(reference: "a", digest: "d", sizeBytes: nil).formattedSize == nil)
#expect(ContainerImage(reference: "a", digest: "d", sizeBytes: 0).formattedSize == nil)
#expect(ContainerImage(reference: "a", digest: "d", sizeBytes: -5).formattedSize == nil)
}

@Test func formattedSizeUsesSIBinaryBoundaries() {
#expect(formatImageBytes(2_500_000_000) == "2.50 GB")
#expect(formatImageBytes(1_000_000_000) == "1.00 GB")
#expect(formatImageBytes(234_000_000) == "234 MB")
#expect(formatImageBytes(512_000) == "512 KB")
}
}
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import PackageDescription
// integration fails to wire up the container SDK's transitive package modules
// (ServiceContextModule, SwiftASN1, ...); SwiftPM builds the same graph cleanly.
// Open this Package.swift directly in Xcode for GUI development, or use `swift build`
// + scripts/bundle.sh to produce Consai.app.
// + `swift run bundle` to produce Consai.app.
let package = Package(
name: "Consai",
platforms: [.macOS(.v26)],
Expand All @@ -32,7 +32,7 @@ let package = Package(
dependencies: ["ConsaiCore"],
path: "ConsaiKit/Sources/ConsaiKit"
),
// The menu bar app (SwiftUI). Bundled into Consai.app by scripts/bundle.sh.
// The menu bar app (SwiftUI). Bundled into Consai.app by `swift run bundle`.
.executableTarget(
name: "Consai",
dependencies: ["ConsaiCore", "ConsaiKit"],
Expand Down
25 changes: 10 additions & 15 deletions docs/TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,36 @@ differently, and coverage should be read per tier — not as a single blended nu

Everything builds and runs **locally** — there is no GitHub Actions / hosted CI (Apple's
`container` SDK can't build on hosted runners, and the project targets a developer's
macOS 26 / Xcode 26 machine). The pre-flight check is a native SwiftPM command plugin
(`Plugins/Check`), not a shell script:

```bash
swift package --disable-sandbox check # build + test
swift package --disable-sandbox --allow-writing-to-package-directory check coverage # + llvm-cov report
```

(`--disable-sandbox` lets the plugin spawn `swift`/`xcrun`; the write flag is needed only
for the `coverage` run, which writes profdata under `.build`.)

Or invoke the toolchain directly — `swift test` is itself the native test runner:
macOS 26 / Xcode 26 machine). `swift test` is the native test runner; the coverage
report is a small Swift executable (`Tools/coverage`) that shells out to `xcrun
llvm-cov` — it never re-invokes SwiftPM, so it can't hit the build-lock deadlock a
command plugin does:

```bash
swift test # fast, no daemon required
swift test --enable-code-coverage # adds llvm-cov instrumentation
swift run coverage # prints the logic-tier llvm-cov report
```

Or invoke llvm-cov directly:
PROF=$(find .build -name default.profdata | head -1)
BIN=$(find .build -name ConsaiPackageTests -type f | head -1)
xcrun llvm-cov report "$BIN" -instr-profile "$PROF" ConsaiCore/Sources ConsaiKit/Sources
```

## Tier 1 — pure logic (unit-tested here, ~93% line coverage)
## Tier 1 — pure logic (unit-tested here, ~90% line coverage)

No daemon, no subprocess, no UI. All I/O sits behind protocols, so these are tested with
in-memory fakes and a `SpyProcessRunner` that records argv/cwd instead of spawning.

| Area | File | Line cov |
|------|------|---------:|
| App orchestration | `ConsaiKit/AppState.swift` | ~93% |
| App orchestration | `ConsaiKit/AppState.swift` | ~86% |
| Stack assembly | `ConsaiCore/Engines/ProjectRegistry.swift` | ~94% |
| Registry persistence | `ConsaiCore/Engines/RegistryStore.swift` | ~93% |
| Service health | `ConsaiCore/Engines/CLIServiceHealth.swift` | ~91% |
| Compose CLI wrapper | `ConsaiCore/Engines/CLIComposeEngine.swift` | ~88% |
| Container creation | `ConsaiCore/Engines/CLIContainerCreator.swift` | ~84% |
| Container creation | `ConsaiCore/Engines/CLIContainerCreator.swift` | ~95% |
| Vitals math | `ConsaiCore/Engines/VitalsSampler.swift` | 100% |
| Models | `ConsaiCore/Models/*` | 90–100% |

Expand Down