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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -268,3 +268,5 @@ jobs:
grep -E "error:|Testing failed:|XCTAssert.*failed|exceeded execution time allowance" "$LOG" | head -40
exit 1
fi
set -e
scripts/check-swift-warnings.sh "$LOG"
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ jobs:
grep -E "error:|Testing failed:|XCTAssert.*failed|exceeded execution time allowance" "$LOG" | head -40
exit 1
fi
set -e
scripts/check-swift-warnings.sh "$LOG"

# A keychain of its own, unlocked only for this job, so nothing lands in the runner's login keychain.
- name: Import the signing certificates
Expand Down
15 changes: 9 additions & 6 deletions Flowlight.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -2184,7 +2184,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 56;
CURRENT_PROJECT_VERSION = 57;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = YES;
Expand All @@ -2199,7 +2199,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 0.15.9;
MARKETING_VERSION = 0.15.10;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down Expand Up @@ -2245,7 +2245,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 56;
CURRENT_PROJECT_VERSION = 57;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 38RJUJHKZS;
ENABLE_HARDENED_RUNTIME = YES;
Expand All @@ -2260,7 +2260,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 0.15.9;
MARKETING_VERSION = 0.15.10;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
PRODUCT_NAME = "$(TARGET_NAME)";
Expand Down Expand Up @@ -2325,7 +2325,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_STYLE = Automatic;
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 56;
CURRENT_PROJECT_VERSION = 57;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = "";
ENABLE_HARDENED_RUNTIME = YES;
Expand All @@ -2346,7 +2346,7 @@
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 15.0;
MARKETING_VERSION = 0.15.9;
MARKETING_VERSION = 0.15.10;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
Expand All @@ -2371,6 +2371,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.flowlight.app.tests;
SDKROOT = macosx;
SWIFT_TREAT_WARNINGS_AS_ERRORS = YES;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Flowlight.app/Contents/MacOS/Flowlight";
};
name = AppStore;
Expand Down Expand Up @@ -2428,6 +2429,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.flowlight.app.tests;
SDKROOT = macosx;
SWIFT_TREAT_WARNINGS_AS_ERRORS = YES;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Flowlight.app/Contents/MacOS/Flowlight";
};
name = Release;
Expand Down Expand Up @@ -2505,6 +2507,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = com.flowlight.app.tests;
SDKROOT = macosx;
SWIFT_TREAT_WARNINGS_AS_ERRORS = YES;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Flowlight.app/Contents/MacOS/Flowlight";
};
name = Debug;
Expand Down
18 changes: 9 additions & 9 deletions FlowlightTests/InspectScopeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ final class InspectScopeTests: XCTestCase {
/// The exact shape of the bug: a page that merely mentions the app's name.
func testScopingToAnAppExcludesAPageThatOnlyMentionsIt() throws {
let db = try database()
try db.insertExchange(exchange(app: ("claude", "Claude Code"), host: "api.anthropic.com"))
try db.insertExchange(exchange(app: ("com.google.Chrome", "Google Chrome"), host: "github.com",
_ = try db.insertExchange(exchange(app: ("claude", "Claude Code"), host: "api.anthropic.com"))
_ = try db.insertExchange(exchange(app: ("com.google.Chrome", "Google Chrome"), host: "github.com",
body: "<p>Generated with Claude Code</p>"))

let scoped = try db.exchanges(since: Date().addingTimeInterval(-60), app: "claude")
Expand All @@ -39,9 +39,9 @@ final class InspectScopeTests: XCTestCase {

func testScopingToAHostKeepsItsSubdomains() throws {
let db = try database()
try db.insertExchange(exchange(app: ("a", "A"), host: "api.github.com"))
try db.insertExchange(exchange(app: ("b", "B"), host: "github.com"))
try db.insertExchange(exchange(app: ("c", "C"), host: "example.com"))
_ = try db.insertExchange(exchange(app: ("a", "A"), host: "api.github.com"))
_ = try db.insertExchange(exchange(app: ("b", "B"), host: "github.com"))
_ = try db.insertExchange(exchange(app: ("c", "C"), host: "example.com"))

let scoped = try db.exchanges(since: Date().addingTimeInterval(-60), host: "github.com")
XCTAssertEqual(Set(scoped.map(\.host)), ["github.com", "api.github.com"])
Expand All @@ -50,8 +50,8 @@ final class InspectScopeTests: XCTestCase {
/// A scope and a search have to compose, or narrowing a scoped list would silently widen it.
func testAScopeAndASearchBothApply() throws {
let db = try database()
try db.insertExchange(exchange(app: ("claude", "Claude Code"), host: "api.anthropic.com"))
try db.insertExchange(exchange(app: ("claude", "Claude Code"), host: "registry.npmjs.org"))
_ = try db.insertExchange(exchange(app: ("claude", "Claude Code"), host: "api.anthropic.com"))
_ = try db.insertExchange(exchange(app: ("claude", "Claude Code"), host: "registry.npmjs.org"))

let both = try db.exchanges(since: Date().addingTimeInterval(-60), search: "npmjs", app: "claude")
XCTAssertEqual(both.count, 1)
Expand All @@ -60,8 +60,8 @@ final class InspectScopeTests: XCTestCase {

func testNoScopeStillReturnsEverything() throws {
let db = try database()
try db.insertExchange(exchange(app: ("a", "A"), host: "one.example"))
try db.insertExchange(exchange(app: ("b", "B"), host: "two.example"))
_ = try db.insertExchange(exchange(app: ("a", "A"), host: "one.example"))
_ = try db.insertExchange(exchange(app: ("b", "B"), host: "two.example"))
XCTAssertEqual(try db.exchanges(since: Date().addingTimeInterval(-60)).count, 2)
}
}
2 changes: 1 addition & 1 deletion FlowlightTests/InspectionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ final class InspectionStorageTests: XCTestCase {
requestBody: Data("{}".utf8), requestSize: 2, requestTruncated: false, responseHeaders: [], responseBody: Data([0, 1, 2]),
responseSize: 3, responseTruncated: false, contentType: "text/event-stream", pid: 42, bundleID: "claude", appName: "claude",
agent: "claude", agentName: "Claude Code", mcpServer: nil, toolCalls: [call], note: nil)
try db.insertExchange(exchange)
_ = try db.insertExchange(exchange)
let rows = try db.exchanges(since: Date().addingTimeInterval(-60))
XCTAssertEqual(rows.count, 1)
XCTAssertEqual(rows[0].toolCalls, [call])
Expand Down
6 changes: 3 additions & 3 deletions FlowlightTests/MockResponseTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -296,11 +296,11 @@ final class MockRuleStorageTests: XCTestCase {
responseHeaders: [], responseBody: Data(), responseSize: 0, responseTruncated: false, contentType: "", pid: 1,
bundleID: "b", appName: "a", agent: nil, agentName: nil, mcpServer: nil, toolCalls: [])
exchange.mockRule = "down"
try db.insertExchange(exchange)
try db.insertExchange(exchange) // a second row, left real
_ = try db.insertExchange(exchange)
_ = try db.insertExchange(exchange) // a second row, left real
var real = exchange
real.mockRule = nil
try db.insertExchange(real)
_ = try db.insertExchange(real)
let rows = try db.exchanges(since: .distantPast)
XCTAssertEqual(rows.compactMap(\.mockRule), ["down", "down"])
XCTAssertEqual(rows.count, 3)
Expand Down
2 changes: 1 addition & 1 deletion FlowlightTests/ResponseTransformTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ final class ResponseTransformProvenanceTests: XCTestCase {
let url = FileManager.default.temporaryDirectory.appendingPathComponent("response-transform-\(UUID()).sqlite")
defer { try? FileManager.default.removeItem(at: url) }
let db = try TrafficDatabase(url: url)
try db.insertExchange(try XCTUnwrap(exchange))
_ = try db.insertExchange(try XCTUnwrap(exchange))
let stored = try XCTUnwrap(db.exchanges(since: .distantPast).first)
XCTAssertEqual(stored.responseTransform, "Rewrite status")
XCTAssertNil(stored.mockRule)
Expand Down
4 changes: 1 addition & 3 deletions FlowlightTests/RuleTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -553,15 +553,13 @@ final class RuleTests: XCTestCase {
generation: store.ruleSet.generation
)
let completed = expectation(description: "retirement completed")
var saved: Bool?

store.retireBoundedClaim(claim) { result in
saved = result
XCTAssertFalse(result)
completed.fulfill()
}
await fulfillment(of: [completed], timeout: 2)

XCTAssertEqual(saved, false)
XCTAssertEqual(store.rules.first(where: { $0.id == allow.id })?.hits, 1)
XCTAssertNil(store.live.flowDecision(facts()).externalClaim)
}
Expand Down
6 changes: 3 additions & 3 deletions FlowlightTests/UpdateTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ final class UpdateTests: XCTestCase {

@MainActor
func testCleanRelaunchPromotionOpensWindowForNewerRelease() async {
let release = await fixtureChecker(version: nextMajorVersion())
let release = fixtureChecker(version: nextMajorVersion())

await release.checker.check(userInitiated: false, promoteIfAvailable: true)

Expand All @@ -105,7 +105,7 @@ final class UpdateTests: XCTestCase {

@MainActor
func testCleanRelaunchPromotionSkipsSkippedRelease() async {
let release = await fixtureChecker(version: nextMajorVersion())
let release = fixtureChecker(version: nextMajorVersion())
UserDefaults.standard.set(release.version, forKey: UpdateChecker.Keys.skipped)

await release.checker.check(userInitiated: false, promoteIfAvailable: true)
Expand All @@ -119,7 +119,7 @@ final class UpdateTests: XCTestCase {

@MainActor
func testCleanRelaunchPromotionDoesNotOpenForCurrentRelease() async {
let checker = await fixtureChecker(version: currentVersion())
let checker = fixtureChecker(version: currentVersion())

await checker.checker.check(userInitiated: false, promoteIfAvailable: true)

Expand Down
2 changes: 1 addition & 1 deletion docs/404.html
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ <h1>That page isn't here</h1><p class="lede">Try the <a href="/">home page</a>,
<div class="legal">
<span>© 2026 The Flowlight contributors. Flowlight is free software, released under the
<a href="https://github.com/xinbetween/flowlight/blob/main/LICENSE">GNU General Public License v3.0</a>.</span>
<span>Version 0.15.9 · Not affiliated with Apple or any AI provider named on this site.</span>
<span>Version 0.15.10 · Not affiliated with Apple or any AI provider named on this site.</span>
</div>
</div>
</footer>
Expand Down
4 changes: 2 additions & 2 deletions docs/about/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
<link rel="apple-touch-icon" href="../assets/apple-touch-icon.png">

<link rel="stylesheet" href="../assets/site.css?v=807a8a77">
<script type="application/ld+json">{"@context": "https://schema.org", "@graph": [{"@type": "WebSite", "@id": "https://flowlight.xinbetween.com/#website", "url": "https://flowlight.xinbetween.com/", "name": "Flowlight", "inLanguage": "en", "publisher": {"@id": "https://flowlight.xinbetween.com/#publisher"}}, {"@type": "Organization", "@id": "https://flowlight.xinbetween.com/#publisher", "name": "xinbetween", "url": "https://flowlight.xinbetween.com/", "logo": "https://flowlight.xinbetween.com/assets/icon.png", "sameAs": ["https://github.com/xinbetween/flowlight"]}, {"@type": "WebPage", "@id": "https://flowlight.xinbetween.com/about/#webpage", "url": "https://flowlight.xinbetween.com/about/", "name": "About Flowlight", "description": "Why Flowlight exists, the principles it's built on, and who makes it.", "inLanguage": "en", "isPartOf": {"@id": "https://flowlight.xinbetween.com/#website"}, "dateModified": "2026-10-27"}, {"@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Flowlight", "item": "https://flowlight.xinbetween.com/"}, {"@type": "ListItem", "position": 2, "name": "About Flowlight", "item": "https://flowlight.xinbetween.com/about/"}]}]}</script>
<script type="application/ld+json">{"@context": "https://schema.org", "@graph": [{"@type": "WebSite", "@id": "https://flowlight.xinbetween.com/#website", "url": "https://flowlight.xinbetween.com/", "name": "Flowlight", "inLanguage": "en", "publisher": {"@id": "https://flowlight.xinbetween.com/#publisher"}}, {"@type": "Organization", "@id": "https://flowlight.xinbetween.com/#publisher", "name": "xinbetween", "url": "https://flowlight.xinbetween.com/", "logo": "https://flowlight.xinbetween.com/assets/icon.png", "sameAs": ["https://github.com/xinbetween/flowlight"]}, {"@type": "WebPage", "@id": "https://flowlight.xinbetween.com/about/#webpage", "url": "https://flowlight.xinbetween.com/about/", "name": "About Flowlight", "description": "Why Flowlight exists, the principles it's built on, and who makes it.", "inLanguage": "en", "isPartOf": {"@id": "https://flowlight.xinbetween.com/#website"}, "dateModified": "2026-10-28"}, {"@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Flowlight", "item": "https://flowlight.xinbetween.com/"}, {"@type": "ListItem", "position": 2, "name": "About Flowlight", "item": "https://flowlight.xinbetween.com/about/"}]}]}</script>
</head>
<body>
<a class="skip" href="#main" style="position:absolute;left:-999px">Skip to content</a>
Expand Down Expand Up @@ -153,7 +153,7 @@ <h2 id="thanks">Thanks</h2>
<div class="legal">
<span>© 2026 The Flowlight contributors. Flowlight is free software, released under the
<a href="https://github.com/xinbetween/flowlight/blob/main/LICENSE">GNU General Public License v3.0</a>.</span>
<span>Version 0.15.9 · Not affiliated with Apple or any AI provider named on this site.</span>
<span>Version 0.15.10 · Not affiliated with Apple or any AI provider named on this site.</span>
</div>
</div>
</footer>
Expand Down
4 changes: 2 additions & 2 deletions docs/de/about/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
<link rel="apple-touch-icon" href="../../assets/apple-touch-icon.png">

<link rel="stylesheet" href="../../assets/site.css?v=807a8a77">
<script type="application/ld+json">{"@context": "https://schema.org", "@graph": [{"@type": "WebSite", "@id": "https://flowlight.xinbetween.com/#website-de", "url": "https://flowlight.xinbetween.com/de/", "name": "Flowlight", "inLanguage": "de", "publisher": {"@id": "https://flowlight.xinbetween.com/#publisher"}}, {"@type": "Organization", "@id": "https://flowlight.xinbetween.com/#publisher", "name": "xinbetween", "url": "https://flowlight.xinbetween.com/", "logo": "https://flowlight.xinbetween.com/assets/icon.png", "sameAs": ["https://github.com/xinbetween/flowlight"]}, {"@type": "WebPage", "@id": "https://flowlight.xinbetween.com/de/about/#webpage", "url": "https://flowlight.xinbetween.com/de/about/", "name": "Über Flowlight", "description": "Warum es Flowlight gibt, auf welchen Grundsätzen es steht und wer es macht.", "inLanguage": "de", "isPartOf": {"@id": "https://flowlight.xinbetween.com/#website-de"}, "dateModified": "2026-10-27"}, {"@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Flowlight", "item": "https://flowlight.xinbetween.com/de/"}, {"@type": "ListItem", "position": 2, "name": "Über Flowlight", "item": "https://flowlight.xinbetween.com/de/about/"}]}]}</script>
<script type="application/ld+json">{"@context": "https://schema.org", "@graph": [{"@type": "WebSite", "@id": "https://flowlight.xinbetween.com/#website-de", "url": "https://flowlight.xinbetween.com/de/", "name": "Flowlight", "inLanguage": "de", "publisher": {"@id": "https://flowlight.xinbetween.com/#publisher"}}, {"@type": "Organization", "@id": "https://flowlight.xinbetween.com/#publisher", "name": "xinbetween", "url": "https://flowlight.xinbetween.com/", "logo": "https://flowlight.xinbetween.com/assets/icon.png", "sameAs": ["https://github.com/xinbetween/flowlight"]}, {"@type": "WebPage", "@id": "https://flowlight.xinbetween.com/de/about/#webpage", "url": "https://flowlight.xinbetween.com/de/about/", "name": "Über Flowlight", "description": "Warum es Flowlight gibt, auf welchen Grundsätzen es steht und wer es macht.", "inLanguage": "de", "isPartOf": {"@id": "https://flowlight.xinbetween.com/#website-de"}, "dateModified": "2026-10-28"}, {"@type": "BreadcrumbList", "itemListElement": [{"@type": "ListItem", "position": 1, "name": "Flowlight", "item": "https://flowlight.xinbetween.com/de/"}, {"@type": "ListItem", "position": 2, "name": "Über Flowlight", "item": "https://flowlight.xinbetween.com/de/about/"}]}]}</script>
</head>
<body>
<a class="skip" href="#main" style="position:absolute;left:-999px">Zum Inhalt springen</a>
Expand Down Expand Up @@ -152,7 +152,7 @@ <h2 id="thanks">Dank</h2>
</div>
<div class="legal">
<span>© 2026 Die Flowlight-Mitwirkenden. Flowlight ist freie Software, veröffentlicht unter der <a href="https://github.com/xinbetween/flowlight/blob/main/LICENSE">GNU General Public License v3.0</a>.</span>
<span>Version 0.15.9 · Nicht verbunden mit Apple oder einem der hier genannten KI-Anbieter.</span>
<span>Version 0.15.10 · Nicht verbunden mit Apple oder einem der hier genannten KI-Anbieter.</span>
</div>
</div>
</footer>
Expand Down
Loading
Loading