From 57aa915dc24597f8f9af3b58b7b9ecc209eb444a Mon Sep 17 00:00:00 2001 From: plz12345 <132735020+plz12345@users.noreply.github.com> Date: Wed, 16 Sep 2026 19:09:38 -0400 Subject: [PATCH 1/3] ci: add pull-request gate for format, lint, and tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Until now release.yml was the only workflow, and it fires on v* tags only — a tag push was the first time SwiftLint or the test suite ran anywhere but a developer's machine. This runs on every pull request and every push to main: - Format & lint (blocking): swiftformat --lint, then swiftlint --strict. Ordered before any xcodebuild call, since the app target's pre-build phase runs SwiftFormat in write mode and would repair the violations the check exists to catch. The tree is at zero violations today, so --strict only ever fires on a regression. - Test (blocking): the same xcodebuild invocation release.yml uses, minus -quiet so the log can feed the analyzer step. - SwiftLint analyzer rules (advisory): the analyzer_rules block in .swiftlint.yml has never actually run — analyzer rules need a full compiler log, which neither `swiftlint lint` nor the pre-build phase provides. It reports 16 pre-existing unused_import violations; advisory until those are cleared, then it can go strict. - Duplication (advisory): jscpd on a Linux runner, since it is token analysis with no need for Xcode. Currently 10.39% against a 2.5% threshold. - Dead code (advisory): Periphery on push to main only — it runs its own full build, which would roughly double PR wall-clock for output nobody blocks on. --- .github/workflows/ci.yml | 146 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 146 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c0c86e4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,146 @@ +name: CI + +# The everyday gate: formatting, lint, and the Swift Testing suite on every pull +# request and every push to main. Signed/notarized release builds live in +# release.yml and fire on v* tags only — before this workflow existed, a tag +# push was the first time lint or tests ran anywhere but a developer's machine. +on: + pull_request: + push: + branches: [main] + +# A newer push to the same branch supersedes an in-flight run. +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +env: + SCHEME: "MLXBits Image Studio" + PROJECT: "MLXBits Image Studio.xcodeproj" + +jobs: + lint: + name: Format & lint + runs-on: macos-26 + steps: + - uses: actions/checkout@v7 + + - name: Install SwiftFormat and SwiftLint + run: brew install --quiet swiftformat swiftlint + + # Must run before anything invokes xcodebuild: the app target's pre-build + # phase (project.yml) runs SwiftFormat in write mode, which would silently + # repair the very violations this step exists to catch. + - name: SwiftFormat (lint only) + run: swiftformat --lint --config .swiftformat . + + # --strict promotes warnings to errors. The tree is at zero violations as + # of this workflow's first commit, so any new one is a real regression. + - name: SwiftLint (strict) + run: | + swiftlint lint \ + --config .swiftlint.yml \ + --strict \ + --reporter github-actions-logging + + test: + name: Test + runs-on: macos-26 + steps: + - uses: actions/checkout@v7 + + - name: Install SwiftLint + run: brew install --quiet swiftlint + + # Same invocation release.yml uses. CODE_SIGNING_ALLOWED=NO because + # runners carry no Mac Development identity and these are pure-logic + # tests. Deliberately not -quiet: this log is the analyzer step's input. + - name: Test (Debug) + run: | + set -o pipefail + mkdir -p build + xcodebuild test \ + -project "$PROJECT" \ + -scheme "$SCHEME" \ + -destination "platform=macOS" \ + -resultBundlePath build/TestResults.xcresult \ + CODE_SIGNING_ALLOWED=NO \ + | tee build/xcodebuild.log + + # The analyzer_rules block in .swiftlint.yml (unused_import) does NOT run + # under plain `swiftlint lint` — not here, and not in the pre-build phase + # in project.yml. Analyzer rules need a full compiler log, which only + # exists once something has actually built. This is the only place it does. + # + # Advisory for now: 16 pre-existing unused_import violations as of + # 2026-09-16. Once those are cleared, drop the `|| true` and add --strict + # so new ones fail the build. + - name: SwiftLint analyzer rules (advisory) + run: | + swiftlint analyze \ + --config .swiftlint.yml \ + --compiler-log-path build/xcodebuild.log \ + --reporter github-actions-logging || true + + - name: Upload test results + if: failure() + uses: actions/upload-artifact@v7 + with: + name: test-results + path: build/TestResults.xcresult + retention-days: 7 + + duplication: + name: Duplication (advisory) + # jscpd is pure token analysis — no Xcode, no build — so it runs on Linux + # rather than burning a macOS runner. + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-node@v7 + with: + node-version: "22" + + # Config (thresholds, ignores, Swift-only format) lives in .jscpd.json. + - name: jscpd + run: | + npx --yes jscpd@4 --reporters console . > jscpd.txt 2>&1 || true + { + echo "### Duplication" + echo "" + echo '```' + tail -25 jscpd.txt + echo '```' + } >> "$GITHUB_STEP_SUMMARY" + + deadcode: + name: Dead code (advisory) + # Periphery runs its own full build, so it would roughly double PR + # wall-clock for output nobody blocks on. Post-merge hygiene instead. + if: github.event_name == 'push' + runs-on: macos-26 + steps: + - uses: actions/checkout@v7 + + - name: Install Periphery + run: brew install --quiet peripheryapp/periphery/periphery + + # Project, scheme, and retention rules come from .periphery.yml. Without + # --strict, findings exit 0 and surface as annotations — so a non-zero + # exit here means the scan itself broke, not that it found dead code. + - name: Scan + run: | + if periphery scan \ + --disable-update-check \ + --quiet \ + --format github-actions \ + -- CODE_SIGNING_ALLOWED=NO + then + echo "Periphery scan completed — findings, if any, are in the annotations." >> "$GITHUB_STEP_SUMMARY" + else + echo "Periphery failed to run (build or config problem), see the step log." >> "$GITHUB_STEP_SUMMARY" + fi From 348663ac36346ba2611421c3f128801e7faac8ed Mon Sep 17 00:00:00 2001 From: plz12345 <132735020+plz12345@users.noreply.github.com> Date: Wed, 16 Sep 2026 19:14:00 -0400 Subject: [PATCH 2/3] ci: gate SwiftLint against a baseline, not a clean tree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first CI run failed with 139 violations. The claim that the tree was clean came from a local `swiftlint lint` that crashed on a missing sourcekitdInProc (xcode-select pointed at CommandLineTools) with stderr redirected to /dev/null — an empty result read as zero violations. With DEVELOPER_DIR set, local reproduces CI exactly: 139 violations, all warning-severity, 0 errors. These predate this workflow. The SwiftLint pre-build phase in project.yml emits them as build warnings, so nothing ever surfaced them. Baseline them rather than block on a 139-violation cleanup: the gate now fails on new violations only. The file records relative paths, so it resolves on both a developer machine and a runner. Largest buckets, if someone wants to shrink it later (several are autocorrectable with `swiftlint --fix`): 79 type_contents_order 21 legacy_swiftui_aspect_ratio 16 closure_parameter_position 7 force_unwrapping --- .github/workflows/ci.yml | 15 ++++++++++++--- .swiftlint-baseline.json | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 .swiftlint-baseline.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0c86e4..75e686b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,12 +37,21 @@ jobs: - name: SwiftFormat (lint only) run: swiftformat --lint --config .swiftformat . - # --strict promotes warnings to errors. The tree is at zero violations as - # of this workflow's first commit, so any new one is a real regression. - - name: SwiftLint (strict) + # --strict promotes warnings to errors, measured against a baseline of the + # 139 warning-severity violations that predate this workflow. New + # violations fail the build; the pre-existing backlog does not. The + # pre-build phase in project.yml emits those 139 as build warnings, which + # is why they were never noticed. + # + # After clearing some (many are autocorrectable with `swiftlint --fix`), + # regenerate with: + # swiftlint lint --config .swiftlint.yml \ + # --write-baseline .swiftlint-baseline.json + - name: SwiftLint (strict, against baseline) run: | swiftlint lint \ --config .swiftlint.yml \ + --baseline .swiftlint-baseline.json \ --strict \ --reporter github-actions-logging diff --git a/.swiftlint-baseline.json b/.swiftlint-baseline.json new file mode 100644 index 0000000..451a53a --- /dev/null +++ b/.swiftlint-baseline.json @@ -0,0 +1 @@ +[{"text":" _, running in if !running {","violation":{"reason":"Closure parameters should be on the same line as opening brace","ruleIdentifier":"closure_parameter_position","ruleName":"Closure Parameter Position","location":{"file":"App\/ContentView.swift","character":17,"line":409},"severity":"warning","ruleDescription":"Closure parameters should be on the same line as opening brace"}},{"text":" _, running in if !running {","violation":{"ruleName":"Closure Parameter Position","severity":"warning","location":{"file":"App\/ContentView.swift","line":409,"character":20},"ruleDescription":"Closure parameters should be on the same line as opening brace","reason":"Closure parameters should be on the same line as opening brace","ruleIdentifier":"closure_parameter_position"}},{"text":" _, running in if !running {","violation":{"ruleDescription":"Closure parameters should be on the same line as opening brace","ruleName":"Closure Parameter Position","severity":"warning","location":{"line":414,"character":17,"file":"App\/ContentView.swift"},"ruleIdentifier":"closure_parameter_position","reason":"Closure parameters should be on the same line as opening brace"}},{"text":" _, running in if !running {","violation":{"severity":"warning","ruleIdentifier":"closure_parameter_position","ruleDescription":"Closure parameters should be on the same line as opening brace","ruleName":"Closure Parameter Position","reason":"Closure parameters should be on the same line as opening brace","location":{"file":"App\/ContentView.swift","line":414,"character":20}}},{"text":" _, running in if !running {","violation":{"ruleIdentifier":"closure_parameter_position","location":{"line":419,"character":17,"file":"App\/ContentView.swift"},"reason":"Closure parameters should be on the same line as opening brace","ruleDescription":"Closure parameters should be on the same line as opening brace","ruleName":"Closure Parameter Position","severity":"warning"}},{"text":" _, running in if !running {","violation":{"ruleIdentifier":"closure_parameter_position","ruleDescription":"Closure parameters should be on the same line as opening brace","ruleName":"Closure Parameter Position","severity":"warning","reason":"Closure parameters should be on the same line as opening brace","location":{"line":419,"file":"App\/ContentView.swift","character":20}}},{"text":" _, running in if !running {","violation":{"ruleDescription":"Closure parameters should be on the same line as opening brace","location":{"line":424,"character":17,"file":"App\/ContentView.swift"},"ruleName":"Closure Parameter Position","reason":"Closure parameters should be on the same line as opening brace","ruleIdentifier":"closure_parameter_position","severity":"warning"}},{"text":" _, running in if !running {","violation":{"ruleName":"Closure Parameter Position","reason":"Closure parameters should be on the same line as opening brace","ruleIdentifier":"closure_parameter_position","location":{"character":20,"line":424,"file":"App\/ContentView.swift"},"ruleDescription":"Closure parameters should be on the same line as opening brace","severity":"warning"}},{"text":" _, running in if !running {","violation":{"reason":"Closure parameters should be on the same line as opening brace","ruleIdentifier":"closure_parameter_position","ruleDescription":"Closure parameters should be on the same line as opening brace","location":{"character":17,"file":"App\/ContentView.swift","line":429},"ruleName":"Closure Parameter Position","severity":"warning"}},{"violation":{"ruleName":"Closure Parameter Position","severity":"warning","ruleIdentifier":"closure_parameter_position","ruleDescription":"Closure parameters should be on the same line as opening brace","location":{"file":"App\/ContentView.swift","character":20,"line":429},"reason":"Closure parameters should be on the same line as opening brace"},"text":" _, running in if !running {"},{"violation":{"reason":"Initializer body should span 80 lines or less excluding comments and whitespace: currently spans 97 lines","severity":"warning","ruleIdentifier":"function_body_length","location":{"line":587,"file":"Stores\/AppSettings.swift","character":5},"ruleDescription":"Function bodies should not span too many lines","ruleName":"Function Body Length"},"text":" init() {"},{"violation":{"severity":"warning","ruleName":"Type Contents Order","ruleIdentifier":"type_contents_order","reason":"A 'type_property' should not be placed amongst the type content(s) 'subtype'","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","location":{"line":24,"file":"Stores\/BackendModelStore.swift","character":5}},"text":" static let pollInterval: TimeInterval = 5"},{"violation":{"severity":"warning","reason":"A 'type_property' should not be placed amongst the type content(s) 'subtype'","location":{"line":29,"file":"Stores\/BackendModelStore.swift","character":5},"ruleName":"Type Contents Order","ruleIdentifier":"type_contents_order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type."},"text":" static let residencyFloorBytes: Int64 = 1_073_741_824"},{"violation":{"ruleName":"Type Contents Order","location":{"file":"Stores\/BackendModelStore.swift","character":18,"line":53},"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","reason":"An 'instance_property' should not be placed amongst the type content(s) 'subtype'","ruleIdentifier":"type_contents_order","severity":"warning"},"text":" private(set) var comfy = ComfyStatus()"},{"violation":{"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order","severity":"warning","ruleIdentifier":"type_contents_order","location":{"line":56,"file":"Stores\/BackendModelStore.swift","character":18},"reason":"An 'instance_property' should not be placed amongst the type content(s) 'subtype'"},"text":" private(set) var isComfyEjecting = false"},{"violation":{"location":{"line":69,"file":"Stores\/BackendModelStore.swift","character":18},"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_method'","ruleName":"Type Contents Order","ruleIdentifier":"type_contents_order","severity":"warning"},"text":" private(set) var lm = LMServerStatus()"},{"violation":{"reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_method'","ruleIdentifier":"type_contents_order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order","location":{"character":18,"file":"Stores\/BackendModelStore.swift","line":70},"severity":"warning"},"text":" private(set) var isEjecting = Set() \/\/ model keys with an in-flight unload"},{"violation":{"location":{"character":18,"file":"Stores\/BackendModelStore.swift","line":73},"reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_method'","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleIdentifier":"type_contents_order","ruleName":"Type Contents Order","severity":"warning"},"text":" private(set) var isEjectingAll = false"},{"violation":{"ruleIdentifier":"type_contents_order","ruleName":"Type Contents Order","location":{"file":"Stores\/BackendModelStore.swift","line":77,"character":18},"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","severity":"warning","reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_method'"},"text":" private weak var settings: AppSettings?"},{"violation":{"reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_method'","ruleIdentifier":"type_contents_order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","location":{"character":5,"line":80,"file":"Stores\/BackendModelStore.swift"},"severity":"warning","ruleName":"Type Contents Order"},"text":" var localRunInFlight = false"},{"violation":{"reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_method'","ruleIdentifier":"type_contents_order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","location":{"character":13,"line":82,"file":"Stores\/BackendModelStore.swift"},"severity":"warning","ruleName":"Type Contents Order"},"text":" private var pollTask: Task?"},{"text":" init(settings: AppSettings? = nil) {","violation":{"ruleIdentifier":"type_contents_order","ruleName":"Type Contents Order","location":{"line":84,"file":"Stores\/BackendModelStore.swift","character":5},"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","reason":"An 'initializer' should not be placed amongst the type content(s) 'type_method'","severity":"warning"}},{"text":" func attach(_ settings: AppSettings) {","violation":{"ruleIdentifier":"type_contents_order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order","location":{"file":"Stores\/BackendModelStore.swift","line":88,"character":5},"severity":"warning","reason":"An 'other_method' should not be placed amongst the type content(s) 'type_method'"}},{"text":" func restart() {","violation":{"ruleIdentifier":"type_contents_order","reason":"An 'other_method' should not be placed amongst the type content(s) 'type_method'","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order","severity":"warning","location":{"line":98,"character":5,"file":"Stores\/BackendModelStore.swift"}}},{"text":" func stop() {","violation":{"ruleName":"Type Contents Order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","reason":"An 'other_method' should not be placed amongst the type content(s) 'type_method'","ruleIdentifier":"type_contents_order","severity":"warning","location":{"file":"Stores\/BackendModelStore.swift","line":114,"character":5}}},{"text":" private func pollOnce() async {","violation":{"ruleName":"Type Contents Order","severity":"warning","reason":"An 'other_method' should not be placed amongst the type content(s) 'type_method'","ruleIdentifier":"type_contents_order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","location":{"line":121,"file":"Stores\/BackendModelStore.swift","character":13}}},{"text":" private func pollComfy(url: String) async {","violation":{"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order","severity":"warning","reason":"An 'other_method' should not be placed amongst the type content(s) 'type_method'","ruleIdentifier":"type_contents_order","location":{"character":13,"file":"Stores\/BackendModelStore.swift","line":161}}},{"text":" private func isNotNativeAPI(_ error: Error) -> Bool {","violation":{"ruleIdentifier":"type_contents_order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order","severity":"warning","location":{"file":"Stores\/BackendModelStore.swift","character":13,"line":183},"reason":"An 'other_method' should not be placed amongst the type content(s) 'type_method'"}},{"text":" var comfyResidentGB: Double? {","violation":{"reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_method'","ruleIdentifier":"type_contents_order","ruleName":"Type Contents Order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","severity":"warning","location":{"file":"Stores\/BackendModelStore.swift","line":192,"character":5}}},{"text":" var lmLoadedEntries: [LMSessionStatus.Entry] {","violation":{"severity":"warning","ruleIdentifier":"type_contents_order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","location":{"line":198,"file":"Stores\/BackendModelStore.swift","character":5},"reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_method'","ruleName":"Type Contents Order"}},{"text":" var lmHasResidency: Bool {","violation":{"ruleIdentifier":"type_contents_order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","location":{"file":"Stores\/BackendModelStore.swift","line":205,"character":5},"ruleName":"Type Contents Order","severity":"warning","reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_method'"}},{"text":" var canEjectComfy: Bool {","violation":{"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleIdentifier":"type_contents_order","ruleName":"Type Contents Order","severity":"warning","location":{"file":"Stores\/BackendModelStore.swift","character":5,"line":210},"reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_method'"}},{"text":" func ejectComfy() {","violation":{"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleIdentifier":"type_contents_order","ruleName":"Type Contents Order","severity":"warning","location":{"file":"Stores\/BackendModelStore.swift","character":5,"line":219},"reason":"An 'other_method' should not be placed amongst the type content(s) 'type_method'"}},{"text":" func ejectAllLM() {","violation":{"reason":"An 'other_method' should not be placed amongst the type content(s) 'type_method'","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order","severity":"warning","ruleIdentifier":"type_contents_order","location":{"line":235,"file":"Stores\/BackendModelStore.swift","character":5}}},{"text":" key.components(separatedBy: \"\/\").last(where: { !$0.isEmpty }) ?? key","violation":{"ruleDescription":"Trailing closure syntax should be used whenever possible","severity":"warning","location":{"character":58,"file":"Stores\/BackendModelStore.swift","line":282},"ruleName":"Trailing Closure","reason":"Trailing closure syntax should be used whenever possible","ruleIdentifier":"trailing_closure"}},{"text":" var unets: [String] = []","violation":{"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","reason":"An 'instance_property' should not be placed amongst the type content(s) 'subtype'","severity":"warning","ruleIdentifier":"type_contents_order","ruleName":"Type Contents Order","location":{"file":"Stores\/ComfyModelStore.swift","character":5,"line":9}}},{"text":" var clips: [String] = []","violation":{"severity":"warning","ruleIdentifier":"type_contents_order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","location":{"file":"Stores\/ComfyModelStore.swift","line":10,"character":5},"reason":"An 'instance_property' should not be placed amongst the type content(s) 'subtype'","ruleName":"Type Contents Order"}},{"text":" var vaes: [String] = []","violation":{"location":{"line":11,"character":5,"file":"Stores\/ComfyModelStore.swift"},"reason":"An 'instance_property' should not be placed amongst the type content(s) 'subtype'","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleIdentifier":"type_contents_order","ruleName":"Type Contents Order","severity":"warning"}},{"text":" var samplers: [String] = []","violation":{"location":{"file":"Stores\/ComfyModelStore.swift","character":5,"line":12},"ruleName":"Type Contents Order","reason":"An 'instance_property' should not be placed amongst the type content(s) 'subtype'","ruleIdentifier":"type_contents_order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","severity":"warning"}},{"text":" var loras: [String] = []","violation":{"ruleIdentifier":"type_contents_order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order","severity":"warning","location":{"character":5,"file":"Stores\/ComfyModelStore.swift","line":15},"reason":"An 'instance_property' should not be placed amongst the type content(s) 'subtype'"}},{"text":" var schedulers: [String] = []","violation":{"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order","ruleIdentifier":"type_contents_order","severity":"warning","location":{"line":16,"file":"Stores\/ComfyModelStore.swift","character":5},"reason":"An 'instance_property' should not be placed amongst the type content(s) 'subtype'"}},{"text":" guard inFlight != nil, !inFlight!.isCancelled else { return false }","violation":{"reason":"Force unwrapping should be avoided","ruleName":"Force Unwrapping","location":{"line":95,"file":"Stores\/ComfyModelStore.swift","character":41},"severity":"warning","ruleIdentifier":"force_unwrapping","ruleDescription":"Force unwrapping should be avoided"}},{"text":"}","violation":{"severity":"warning","location":{"character":1,"line":529,"file":"Stores\/GalleryStore.swift"},"reason":"File should contain 500 lines or less: currently contains 529","ruleDescription":"Files should not span too many lines.","ruleIdentifier":"file_length","ruleName":"File Length"}},{"text":" var unets: [String] = []","violation":{"ruleIdentifier":"type_contents_order","severity":"warning","location":{"character":5,"line":77,"file":"Utilities\/ComfyUIClient.swift"},"ruleName":"Type Contents Order","reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_property'","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type."}},{"text":" var clips: [String] = []","violation":{"ruleIdentifier":"type_contents_order","severity":"warning","location":{"character":5,"line":78,"file":"Utilities\/ComfyUIClient.swift"},"ruleName":"Type Contents Order","reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_property'","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type."}},{"violation":{"location":{"line":79,"character":5,"file":"Utilities\/ComfyUIClient.swift"},"ruleIdentifier":"type_contents_order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_property'","ruleName":"Type Contents Order","severity":"warning"},"text":" var vaes: [String] = []"},{"violation":{"severity":"warning","reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_property'","ruleName":"Type Contents Order","location":{"file":"Utilities\/ComfyUIClient.swift","line":80,"character":5},"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleIdentifier":"type_contents_order"},"text":" var loras: [String] = []"},{"violation":{"ruleIdentifier":"type_contents_order","reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_property'","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order","severity":"warning","location":{"line":81,"character":5,"file":"Utilities\/ComfyUIClient.swift"}},"text":" var samplers: [String] = []"},{"violation":{"reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_property'","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","severity":"warning","ruleName":"Type Contents Order","ruleIdentifier":"type_contents_order","location":{"file":"Utilities\/ComfyUIClient.swift","line":82,"character":5}},"text":" var schedulers: [String] = []"},{"violation":{"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","location":{"line":98,"file":"Utilities\/ComfyUIClient.swift","character":5},"reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_property'","ruleName":"Type Contents Order","ruleIdentifier":"type_contents_order","severity":"warning"},"text":" var currentStep: Int = 0"},{"violation":{"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order","severity":"warning","ruleIdentifier":"type_contents_order","location":{"file":"Utilities\/ComfyUIClient.swift","line":99,"character":5},"reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_property'"},"text":" var totalSteps: Int = 0"},{"violation":{"reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_property'","ruleName":"Type Contents Order","severity":"warning","location":{"character":5,"line":100,"file":"Utilities\/ComfyUIClient.swift"},"ruleIdentifier":"type_contents_order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type."},"text":" var isDenoising: Bool = false"},{"violation":{"ruleName":"Type Contents Order","severity":"warning","reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_property'","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleIdentifier":"type_contents_order","location":{"line":101,"file":"Utilities\/ComfyUIClient.swift","character":5}},"text":" var phaseLabel: String?"},{"violation":{"reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_property'","ruleIdentifier":"type_contents_order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","severity":"warning","location":{"line":103,"file":"Utilities\/ComfyUIClient.swift","character":5},"ruleName":"Type Contents Order"},"text":" var currentNode: Int = 0"},{"violation":{"severity":"warning","ruleIdentifier":"type_contents_order","location":{"line":105,"file":"Utilities\/ComfyUIClient.swift","character":5},"reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_property'","ruleName":"Type Contents Order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type."},"text":" var totalNodes: Int = 0"},{"violation":{"severity":"warning","reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_method'","ruleName":"Type Contents Order","location":{"line":115,"file":"Utilities\/ComfyUIClient.swift","character":9},"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleIdentifier":"type_contents_order"},"text":" var baseURL: String"},{"violation":{"severity":"warning","reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_method'","ruleName":"Type Contents Order","location":{"line":117,"file":"Utilities\/ComfyUIClient.swift","character":9},"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleIdentifier":"type_contents_order"},"text":" var apiKey: String?"},{"text":" private let config: Config","violation":{"ruleIdentifier":"type_contents_order","severity":"warning","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order","location":{"file":"Utilities\/ComfyUIClient.swift","character":13,"line":128},"reason":"An 'instance_property' should not be placed amongst the type content(s) 'subtype'"}},{"text":" let clientID = UUID().uuidString","violation":{"ruleIdentifier":"type_contents_order","ruleName":"Type Contents Order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","severity":"warning","location":{"file":"Utilities\/ComfyUIClient.swift","character":5,"line":130},"reason":"An 'instance_property' should not be placed amongst the type content(s) 'subtype'"}},{"text":" private let session: URLSession","violation":{"ruleName":"Type Contents Order","severity":"warning","location":{"file":"Utilities\/ComfyUIClient.swift","line":131,"character":13},"reason":"An 'instance_property' should not be placed amongst the type content(s) 'subtype'","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleIdentifier":"type_contents_order"}},{"text":" static let maxConsecutiveHistoryFailures = 60","violation":{"reason":"A 'type_property' should not be placed amongst the type content(s) 'subtype'","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order","severity":"warning","ruleIdentifier":"type_contents_order","location":{"line":134,"file":"Utilities\/ComfyUIClient.swift","character":5}}},{"text":" init(config: Config) {","violation":{"ruleName":"Type Contents Order","ruleIdentifier":"type_contents_order","severity":"warning","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","reason":"An 'initializer' should not be placed amongst the type content(s) 'subtype'","location":{"character":5,"line":136,"file":"Utilities\/ComfyUIClient.swift"}}},{"text":" private var base: String {","violation":{"severity":"warning","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order","location":{"line":145,"file":"Utilities\/ComfyUIClient.swift","character":13},"reason":"An 'instance_property' should not be placed amongst the type content(s) 'subtype'","ruleIdentifier":"type_contents_order"}},{"text":" private func headers() -> [String: String] {","violation":{"severity":"warning","location":{"file":"Utilities\/ComfyUIClient.swift","line":149,"character":13},"reason":"An 'other_method' should not be placed amongst the type content(s) 'subtype'","ruleIdentifier":"type_contents_order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order"}},{"text":" func systemStats() async throws -> SystemStats {","violation":{"reason":"An 'other_method' should not be placed amongst the type content(s) 'subtype'","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order","severity":"warning","location":{"line":181,"file":"Utilities\/ComfyUIClient.swift","character":5},"ruleIdentifier":"type_contents_order"}},{"text":" func discoverModels() async throws -> ComfyModelInfo {","violation":{"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","severity":"warning","ruleIdentifier":"type_contents_order","reason":"An 'other_method' should not be placed amongst the type content(s) 'subtype'","ruleName":"Type Contents Order","location":{"character":5,"line":236,"file":"Utilities\/ComfyUIClient.swift"}}},{"text":" func fetchLoras() async throws -> [String] {","violation":{"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order","severity":"warning","reason":"An 'other_method' should not be placed amongst the type content(s) 'subtype'","ruleIdentifier":"type_contents_order","location":{"character":5,"file":"Utilities\/ComfyUIClient.swift","line":273}}},{"text":" func buildKrea2Workflow(_ input: WorkflowInput) -> Any {","violation":{"location":{"line":319,"file":"Utilities\/ComfyUIClient.swift","character":5},"ruleIdentifier":"type_contents_order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order","severity":"warning","reason":"An 'other_method' should not be placed amongst the type content(s) 'subtype'"}},{"text":" func generate(","violation":{"location":{"line":401,"file":"Utilities\/ComfyUIClient.swift","character":5},"ruleIdentifier":"function_body_length","ruleDescription":"Function bodies should not span too many lines","ruleName":"Function Body Length","severity":"warning","reason":"Function body should span 80 lines or less excluding comments and whitespace: currently spans 95 lines"}},{"violation":{"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","reason":"An 'other_method' should not be placed amongst the type content(s) 'subtype'","ruleIdentifier":"type_contents_order","location":{"line":401,"character":5,"file":"Utilities\/ComfyUIClient.swift"},"ruleName":"Type Contents Order","severity":"warning"},"text":" func generate("},{"violation":{"location":{"character":21,"file":"Utilities\/ComfyUIClient.swift","line":404},"ruleName":"Unneeded Escaping","ruleDescription":"The `@escaping` attribute should only be used when the closure actually escapes.","ruleIdentifier":"unneeded_escaping","severity":"warning","reason":"@escaping attribute not required as 'onProgress' does not escape"},"text":" onProgress: @escaping (ComfyUIProgress) -> Void,"},{"violation":{"ruleName":"Force Unwrapping","ruleDescription":"Force unwrapping should be avoided","ruleIdentifier":"force_unwrapping","severity":"warning","location":{"file":"Utilities\/ComfyUIClient.swift","character":73,"line":445},"reason":"Force unwrapping should be avoided"},"text":" ? (snap.executedNodes.firstIndex(of: snap.nodeID!)! + 1)"},{"violation":{"severity":"warning","location":{"line":445,"file":"Utilities\/ComfyUIClient.swift","character":75},"ruleIdentifier":"force_unwrapping","ruleDescription":"Force unwrapping should be avoided","ruleName":"Force Unwrapping","reason":"Force unwrapping should be avoided"},"text":" ? (snap.executedNodes.firstIndex(of: snap.nodeID!)! + 1)"},{"violation":{"ruleDescription":"Force unwrapping should be avoided","ruleIdentifier":"force_unwrapping","severity":"warning","ruleName":"Force Unwrapping","location":{"line":509,"file":"Utilities\/ComfyUIClient.swift","character":59},"reason":"Force unwrapping should be avoided"},"text":" .isEmpty == false ? detail! : \"the server reported an execution failure with no details\")"},{"violation":{"severity":"warning","ruleIdentifier":"force_unwrapping","reason":"Force unwrapping should be avoided","ruleName":"Force Unwrapping","ruleDescription":"Force unwrapping should be avoided","location":{"line":511,"character":79,"file":"Utilities\/ComfyUIClient.swift"}},"text":" throw ComfyUIError.executionFailed(record.errorMessage!)"},{"violation":{"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","severity":"warning","location":{"file":"Utilities\/ComfyUIClient.swift","line":534,"character":13},"ruleName":"Type Contents Order","reason":"An 'other_method' should not be placed amongst the type content(s) 'subtype'","ruleIdentifier":"type_contents_order"},"text":" private func submit(data: Data) async throws -> String {"},{"violation":{"location":{"file":"Utilities\/ComfyUIClient.swift","line":566,"character":13},"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order","severity":"warning","ruleIdentifier":"type_contents_order","reason":"An 'other_method' should not be placed amongst the type content(s) 'subtype'"},"text":" private func fetchHistory(promptID: String) async throws -> HistoryRecord {"},{"violation":{"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleIdentifier":"type_contents_order","severity":"warning","location":{"file":"Utilities\/ComfyUIClient.swift","line":627,"character":5},"ruleName":"Type Contents Order","reason":"A 'type_method' should not be placed amongst the type content(s) 'subtype'"},"text":" static func formatErrorMessage(kind: String, payload: Any) -> String {"},{"violation":{"severity":"warning","location":{"file":"Utilities\/ComfyUIClient.swift","line":645,"character":108},"ruleDescription":"Force unwrapping should be avoided","ruleIdentifier":"force_unwrapping","reason":"Force unwrapping should be avoided","ruleName":"Force Unwrapping"},"text":" let type = (obj[\"node_type\"] as? String ?? \"\").isEmpty ? \"\" : \" \\((obj[\"node_type\"] as? String)!)\""},{"violation":{"severity":"warning","location":{"character":5,"file":"Utilities\/ComfyUIClient.swift","line":663},"ruleIdentifier":"type_contents_order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","reason":"An 'other_method' should not be placed amongst the type content(s) 'subtype'","ruleName":"Type Contents Order"},"text":" func downloadOutput(filename: String, subfolder: String? = nil, type: String = \"output\", to localPath: String) async -> Bool {"},{"violation":{"severity":"warning","location":{"character":52,"file":"Utilities\/ComfyUIClient.swift","line":666},"ruleIdentifier":"force_unwrapping","ruleDescription":"Force unwrapping should be avoided","reason":"Force unwrapping should be avoided","ruleName":"Force Unwrapping"},"text":" let slash = filename.lastIndex(of: \"\/\")!"},{"violation":{"ruleName":"Type Contents Order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","severity":"warning","location":{"line":678,"file":"Utilities\/ComfyUIClient.swift","character":13},"reason":"An 'other_method' should not be placed amongst the type content(s) 'subtype'","ruleIdentifier":"type_contents_order"},"text":" private func fetchView(filename: String, subfolder: String?, type: String, to localPath: String) async -> Bool {"},{"violation":{"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order","location":{"character":5,"file":"Utilities\/ComfyUIClient.swift","line":698},"reason":"An 'other_method' should not be placed amongst the type content(s) 'subtype'","severity":"warning","ruleIdentifier":"type_contents_order"},"text":" func interrupt() async {"},{"violation":{"reason":"An 'other_method' should not be placed amongst the type content(s) 'subtype'","ruleIdentifier":"type_contents_order","severity":"warning","ruleName":"Type Contents Order","location":{"file":"Utilities\/ComfyUIClient.swift","line":711,"character":5},"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type."},"text":" func freeModels(unload: Bool = true, clearMemory: Bool = false) async {"},{"violation":{"ruleName":"Type Contents Order","location":{"character":17,"file":"Utilities\/ComfyUIClient.swift","line":765},"ruleIdentifier":"type_contents_order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","severity":"warning","reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_method'"},"text":" private let baseURL: String"},{"violation":{"severity":"warning","ruleIdentifier":"type_contents_order","location":{"line":766,"character":17,"file":"Utilities\/ComfyUIClient.swift"},"reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_method'","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order"},"text":" private var task: URLSessionWebSocketTask?"},{"violation":{"location":{"line":767,"character":17,"file":"Utilities\/ComfyUIClient.swift"},"reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_method'","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","severity":"warning","ruleIdentifier":"type_contents_order","ruleName":"Type Contents Order"},"text":" private let continuation: AsyncStream.Continuation"},{"violation":{"reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_method'","ruleName":"Type Contents Order","location":{"line":771,"character":9,"file":"Utilities\/ComfyUIClient.swift"},"ruleIdentifier":"type_contents_order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","severity":"warning"},"text":" let progressStream: AsyncStream"},{"violation":{"location":{"file":"Utilities\/ComfyUIClient.swift","character":9,"line":773},"reason":"An 'initializer' should not be placed amongst the type content(s) 'type_method'","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order","severity":"warning","ruleIdentifier":"type_contents_order"},"text":" init(baseURL: String) {"},{"violation":{"ruleIdentifier":"type_contents_order","severity":"warning","reason":"An 'other_method' should not be placed amongst the type content(s) 'type_method'","ruleName":"Type Contents Order","location":{"line":783,"character":9,"file":"Utilities\/ComfyUIClient.swift"},"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type."},"text":" func start(clientId: String) -> Bool {"},{"violation":{"ruleIdentifier":"type_contents_order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_method'","location":{"character":17,"file":"Utilities\/ComfyUIClient.swift","line":808},"severity":"warning","ruleName":"Type Contents Order"},"text":" private var lastNodeID: String?"},{"violation":{"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleIdentifier":"type_contents_order","severity":"warning","location":{"file":"Utilities\/ComfyUIClient.swift","character":17,"line":810},"reason":"An 'instance_property' should not be placed amongst the type content(s) 'type_method'","ruleName":"Type Contents Order"},"text":" private var executedNodes: [String] = []"},{"violation":{"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleIdentifier":"type_contents_order","severity":"warning","location":{"file":"Utilities\/ComfyUIClient.swift","character":17,"line":813},"reason":"An 'other_method' should not be placed amongst the type content(s) 'type_method'","ruleName":"Type Contents Order"},"text":" private func receiveLoop(task: URLSessionWebSocketTask) {"},{"text":"}","violation":{"ruleDescription":"Files should not span too many lines.","ruleName":"File Length","reason":"File should contain 500 lines or less: currently contains 962","severity":"warning","ruleIdentifier":"file_length","location":{"character":1,"line":962,"file":"Utilities\/ComfyUIClient.swift"}}},{"text":" private static let requestTimeout: TimeInterval = 120","violation":{"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleIdentifier":"type_contents_order","severity":"warning","location":{"line":31,"character":13,"file":"Utilities\/OpenAIChatClient.swift"},"ruleName":"Type Contents Order","reason":"A 'type_property' should not be placed amongst the type content(s) 'subtype'"}},{"text":" static func normalizedBase(_ raw: String) -> String {","violation":{"ruleIdentifier":"type_contents_order","severity":"warning","location":{"file":"Utilities\/OpenAIChatClient.swift","character":5,"line":36},"reason":"A 'type_method' should not be placed amongst the type content(s) 'subtype'","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order"}},{"text":" static func chat(_ call: OpenAIChatCall) async throws -> String {","violation":{"severity":"warning","location":{"file":"Utilities\/OpenAIChatClient.swift","character":5,"line":49},"ruleIdentifier":"type_contents_order","reason":"A 'type_method' should not be placed amongst the type content(s) 'subtype'","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order"}},{"text":" static func fetchModels(baseURL: String, apiKey: String) async throws -> [String] {","violation":{"severity":"warning","ruleIdentifier":"type_contents_order","ruleName":"Type Contents Order","location":{"line":95,"character":5,"file":"Utilities\/OpenAIChatClient.swift"},"reason":"A 'type_method' should not be placed amongst the type content(s) 'subtype'","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type."}},{"text":" enum CodingKeys: String, Swift.CodingKey { case models }","violation":{"severity":"warning","location":{"character":9,"line":122,"file":"Utilities\/OpenAIChatClient.swift"},"ruleIdentifier":"nesting","ruleDescription":"Types should be nested at most 1 level deep, and functions should be nested at most 2 levels deep.","ruleName":"Nesting","reason":"Types should be nested at most 1 level deep"}},{"text":" let models: [Entry]","violation":{"reason":"An 'instance_property' should not be placed amongst the type content(s) 'subtype'","severity":"warning","location":{"file":"Utilities\/OpenAIChatClient.swift","character":9,"line":124},"ruleIdentifier":"type_contents_order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order"}},{"text":" struct Entry: Decodable {","violation":{"ruleDescription":"Types should be nested at most 1 level deep, and functions should be nested at most 2 levels deep.","severity":"warning","location":{"character":9,"line":126,"file":"Utilities\/OpenAIChatClient.swift"},"ruleName":"Nesting","reason":"Types should be nested at most 1 level deep","ruleIdentifier":"nesting"}},{"text":" enum CodingKeys: String, Swift.CodingKey {","violation":{"ruleIdentifier":"nesting","ruleDescription":"Types should be nested at most 1 level deep, and functions should be nested at most 2 levels deep.","ruleName":"Nesting","location":{"file":"Utilities\/OpenAIChatClient.swift","line":127,"character":13},"severity":"warning","reason":"Types should be nested at most 1 level deep"}},{"text":" struct Instance: Decodable {","violation":{"reason":"Types should be nested at most 1 level deep","severity":"warning","ruleIdentifier":"nesting","location":{"character":9,"file":"Utilities\/OpenAIChatClient.swift","line":138},"ruleName":"Nesting","ruleDescription":"Types should be nested at most 1 level deep, and functions should be nested at most 2 levels deep."}},{"text":" let id: String","violation":{"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","reason":"An 'instance_property' should not be placed amongst the type content(s) 'subtype'","ruleName":"Type Contents Order","location":{"character":13,"line":139,"file":"Utilities\/OpenAIChatClient.swift"},"severity":"warning","ruleIdentifier":"type_contents_order"}},{"text":" let config: Config?","violation":{"ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","reason":"An 'instance_property' should not be placed amongst the type content(s) 'subtype'","ruleName":"Type Contents Order","location":{"character":13,"line":140,"file":"Utilities\/OpenAIChatClient.swift"},"severity":"warning","ruleIdentifier":"type_contents_order"}},{"text":" struct Config: Decodable {","violation":{"ruleIdentifier":"nesting","ruleName":"Nesting","reason":"Types should be nested at most 1 level deep","ruleDescription":"Types should be nested at most 1 level deep, and functions should be nested at most 2 levels deep.","severity":"warning","location":{"character":13,"line":142,"file":"Utilities\/OpenAIChatClient.swift"}}},{"text":" enum CodingKeys: String, Swift.CodingKey { case contextLength = \"context_length\" }","violation":{"location":{"line":143,"file":"Utilities\/OpenAIChatClient.swift","character":17},"ruleName":"Nesting","ruleIdentifier":"nesting","severity":"warning","reason":"Types should be nested at most 1 level deep","ruleDescription":"Types should be nested at most 1 level deep, and functions should be nested at most 2 levels deep."}},{"text":" DragGesture(minimumDistance: 0, coordinateSpace: .named(BBoxEditorView.canvasSpace))","violation":{"ruleName":"Prefer Self in Static References","ruleIdentifier":"prefer_self_in_static_references","reason":"Use `Self` to refer to the surrounding type name","ruleDescription":"Use `Self` to refer to the surrounding type name","severity":"warning","location":{"character":65,"file":"Views\/Ideogram4\/BBoxEditorView+Gestures.swift","line":186}}},{"text":" DragGesture(minimumDistance: 0, coordinateSpace: .named(BBoxEditorView.canvasSpace))","violation":{"ruleDescription":"Use `Self` to refer to the surrounding type name","location":{"line":244,"file":"Views\/Ideogram4\/BBoxEditorView+Gestures.swift","character":65},"reason":"Use `Self` to refer to the surrounding type name","severity":"warning","ruleName":"Prefer Self in Static References","ruleIdentifier":"prefer_self_in_static_references"}},{"text":" _, _ in if anchorA != nil || anchorB != nil {","violation":{"ruleName":"Closure Parameter Position","ruleIdentifier":"closure_parameter_position","severity":"warning","ruleDescription":"Closure parameters should be on the same line as opening brace","location":{"character":13,"file":"Views\/Ideogram4\/BBoxEditorView+Subviews.swift","line":142},"reason":"Closure parameters should be on the same line as opening brace"}},{"text":" _, _ in if anchorA != nil || anchorB != nil {","violation":{"location":{"character":16,"line":142,"file":"Views\/Ideogram4\/BBoxEditorView+Subviews.swift"},"ruleIdentifier":"closure_parameter_position","ruleDescription":"Closure parameters should be on the same line as opening brace","severity":"warning","reason":"Closure parameters should be on the same line as opening brace","ruleName":"Closure Parameter Position"}},{"text":" _, _ in if anchorA != nil || anchorB != nil {","violation":{"ruleName":"Closure Parameter Position","ruleIdentifier":"closure_parameter_position","location":{"character":13,"file":"Views\/Ideogram4\/BBoxEditorView+Subviews.swift","line":147},"ruleDescription":"Closure parameters should be on the same line as opening brace","severity":"warning","reason":"Closure parameters should be on the same line as opening brace"}},{"text":" _, _ in if anchorA != nil || anchorB != nil {","violation":{"ruleIdentifier":"closure_parameter_position","severity":"warning","reason":"Closure parameters should be on the same line as opening brace","ruleDescription":"Closure parameters should be on the same line as opening brace","ruleName":"Closure Parameter Position","location":{"file":"Views\/Ideogram4\/BBoxEditorView+Subviews.swift","character":16,"line":147}}},{"text":" .aspectRatio(contentMode: .fill)","violation":{"ruleName":"Legacy SwiftUI Aspect Ratio","reason":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","location":{"line":274,"file":"Views\/Krea2\/Krea2ParamsPanelView.swift","character":30},"severity":"warning","ruleIdentifier":"legacy_swiftui_aspect_ratio","ruleDescription":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode"}},{"text":" .aspectRatio(contentMode: .fit)","violation":{"ruleDescription":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","reason":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","severity":"warning","ruleName":"Legacy SwiftUI Aspect Ratio","location":{"character":30,"line":24,"file":"Views\/Krea2\/Krea2PreviewViews.swift"},"ruleIdentifier":"legacy_swiftui_aspect_ratio"}},{"text":" Image(nsImage: img).resizable().aspectRatio(contentMode: .fit)","violation":{"ruleDescription":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","severity":"warning","location":{"character":53,"file":"Views\/Krea2\/Krea2PreviewViews.swift","line":139},"reason":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","ruleIdentifier":"legacy_swiftui_aspect_ratio","ruleName":"Legacy SwiftUI Aspect Ratio"}},{"text":" .aspectRatio(contentMode: .fill)","violation":{"ruleDescription":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","severity":"warning","location":{"character":30,"file":"Views\/ParamsPanel\/ParamsPanelView.swift","line":457},"reason":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","ruleIdentifier":"legacy_swiftui_aspect_ratio","ruleName":"Legacy SwiftUI Aspect Ratio"}},{"text":" .aspectRatio(contentMode: .fill)","violation":{"severity":"warning","ruleName":"Legacy SwiftUI Aspect Ratio","ruleDescription":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","location":{"file":"Views\/ParamsPanel\/ParamsPanelView.swift","line":701,"character":22},"reason":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","ruleIdentifier":"legacy_swiftui_aspect_ratio"}},{"text":" .aspectRatio(contentMode: .fit)","violation":{"ruleDescription":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","ruleName":"Legacy SwiftUI Aspect Ratio","reason":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","ruleIdentifier":"legacy_swiftui_aspect_ratio","severity":"warning","location":{"line":788,"character":26,"file":"Views\/ParamsPanel\/ParamsPanelView.swift"}}},{"text":" .aspectRatio(contentMode: .fill)","violation":{"severity":"warning","ruleIdentifier":"legacy_swiftui_aspect_ratio","ruleDescription":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","ruleName":"Legacy SwiftUI Aspect Ratio","reason":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","location":{"line":162,"file":"Views\/ParamsPanel\/PromptTemplatePickerView.swift","character":26}}},{"text":" .aspectRatio(contentMode: .fill)","violation":{"severity":"warning","ruleDescription":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","reason":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","location":{"character":26,"line":168,"file":"Views\/ParamsPanel\/PromptTemplatePickerView.swift"},"ruleName":"Legacy SwiftUI Aspect Ratio","ruleIdentifier":"legacy_swiftui_aspect_ratio"}},{"text":" .aspectRatio(contentMode: .fill)","violation":{"ruleDescription":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","reason":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","ruleName":"Legacy SwiftUI Aspect Ratio","ruleIdentifier":"legacy_swiftui_aspect_ratio","severity":"warning","location":{"line":371,"file":"Views\/ParamsPanel\/PromptTemplatePickerView.swift","character":26}}},{"text":" .resizable().aspectRatio(contentMode: .fit)","violation":{"ruleDescription":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","location":{"file":"Views\/PreviewPane\/CompareView.swift","line":176,"character":42},"reason":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","severity":"warning","ruleName":"Legacy SwiftUI Aspect Ratio","ruleIdentifier":"legacy_swiftui_aspect_ratio"}},{"text":" .aspectRatio(contentMode: .fit)","violation":{"ruleDescription":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","severity":"warning","reason":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","ruleIdentifier":"legacy_swiftui_aspect_ratio","ruleName":"Legacy SwiftUI Aspect Ratio","location":{"character":26,"file":"Views\/PreviewPane\/CompletedImageView.swift","line":21}}},{"text":" .aspectRatio(contentMode: .fit)","violation":{"reason":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","ruleDescription":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","ruleName":"Legacy SwiftUI Aspect Ratio","ruleIdentifier":"legacy_swiftui_aspect_ratio","severity":"warning","location":{"character":22,"line":44,"file":"Views\/PreviewPane\/FullSizeImageView.swift"}}},{"text":" .aspectRatio(contentMode: .fit)","violation":{"severity":"warning","location":{"character":26,"file":"Views\/PreviewPane\/GalleryItemDetailView.swift","line":42},"ruleDescription":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","ruleName":"Legacy SwiftUI Aspect Ratio","ruleIdentifier":"legacy_swiftui_aspect_ratio","reason":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode"}},{"text":" .aspectRatio(contentMode: .fit)","violation":{"reason":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","severity":"warning","ruleDescription":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","ruleName":"Legacy SwiftUI Aspect Ratio","ruleIdentifier":"legacy_swiftui_aspect_ratio","location":{"file":"Views\/PreviewPane\/SeedVR2PreviewViews.swift","line":25,"character":30}}},{"text":" Image(nsImage: img).resizable().aspectRatio(contentMode: .fit)","violation":{"ruleIdentifier":"legacy_swiftui_aspect_ratio","severity":"warning","reason":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","ruleDescription":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","ruleName":"Legacy SwiftUI Aspect Ratio","location":{"file":"Views\/PreviewPane\/SeedVR2PreviewViews.swift","line":137,"character":53}}},{"text":" .aspectRatio(contentMode: .fit)","violation":{"ruleIdentifier":"legacy_swiftui_aspect_ratio","severity":"warning","reason":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","ruleDescription":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","ruleName":"Legacy SwiftUI Aspect Ratio","location":{"file":"Views\/PreviewPane\/StepwisePreviewView.swift","line":23,"character":30}}},{"text":" .aspectRatio(contentMode: .fit)","violation":{"reason":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","severity":"warning","ruleIdentifier":"legacy_swiftui_aspect_ratio","ruleDescription":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","ruleName":"Legacy SwiftUI Aspect Ratio","location":{"file":"Views\/PreviewPane\/StepwisePreviewView.swift","character":30,"line":156}}},{"text":" Image(nsImage: img).resizable().aspectRatio(contentMode: .fit)","violation":{"location":{"line":248,"file":"Views\/PreviewPane\/StepwisePreviewView.swift","character":53},"ruleDescription":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","ruleName":"Legacy SwiftUI Aspect Ratio","ruleIdentifier":"legacy_swiftui_aspect_ratio","reason":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","severity":"warning"}},{"text":" func krea2FormContent(models: ComfyModelStore) -> some View {","violation":{"ruleName":"Function Body Length","severity":"warning","location":{"file":"Views\/Settings\/ModelDefaultsView+Krea2Form.swift","line":11,"character":5},"reason":"Function body should span 80 lines or less excluding comments and whitespace: currently spans 107 lines","ruleDescription":"Function bodies should not span too many lines","ruleIdentifier":"function_body_length"}},{"text":" private func ensureComfyDiscovery() async {","violation":{"location":{"file":"Views\/Settings\/ModelDefaultsView.swift","character":13,"line":118},"severity":"warning","ruleIdentifier":"type_contents_order","ruleDescription":"Specifies the order of subtypes, properties, methods & more within a type.","ruleName":"Type Contents Order","reason":"An 'other_method' should not be placed amongst the type content(s) 'instance_property'"}},{"text":" _, v in if !v.isEmpty {","violation":{"ruleIdentifier":"closure_parameter_position","ruleDescription":"Closure parameters should be on the same line as opening brace","ruleName":"Closure Parameter Position","severity":"warning","location":{"line":287,"character":33,"file":"Views\/Settings\/SettingsView.swift"},"reason":"Closure parameters should be on the same line as opening brace"}},{"text":" _, v in if !v.isEmpty {","violation":{"ruleIdentifier":"closure_parameter_position","reason":"Closure parameters should be on the same line as opening brace","ruleDescription":"Closure parameters should be on the same line as opening brace","severity":"warning","location":{"file":"Views\/Settings\/SettingsView.swift","line":287,"character":36},"ruleName":"Closure Parameter Position"}},{"text":"}","violation":{"ruleName":"File Length","ruleIdentifier":"file_length","reason":"File should contain 500 lines or less: currently contains 511","ruleDescription":"Files should not span too many lines.","severity":"warning","location":{"file":"Views\/Settings\/SettingsView.swift","line":511,"character":1}}},{"text":" .aspectRatio(contentMode: .fill)","violation":{"ruleIdentifier":"legacy_swiftui_aspect_ratio","severity":"warning","reason":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","ruleDescription":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","ruleName":"Legacy SwiftUI Aspect Ratio","location":{"character":30,"line":278,"file":"Views\/ZImage\/ZImageParamsPanelView.swift"}}},{"text":" .aspectRatio(contentMode: .fit)","violation":{"ruleIdentifier":"legacy_swiftui_aspect_ratio","ruleDescription":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","severity":"warning","ruleName":"Legacy SwiftUI Aspect Ratio","reason":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","location":{"line":24,"file":"Views\/ZImage\/ZImagePreviewViews.swift","character":30}}},{"text":" Image(nsImage: img).resizable().aspectRatio(contentMode: .fit)","violation":{"severity":"warning","ruleName":"Legacy SwiftUI Aspect Ratio","location":{"file":"Views\/ZImage\/ZImagePreviewViews.swift","line":139,"character":53},"reason":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode","ruleIdentifier":"legacy_swiftui_aspect_ratio","ruleDescription":"Prefer `scaledToFit()` or `scaledToFill()` over `aspectRatio(contentMode:)` with a constant content mode"}}] \ No newline at end of file From 0ae2e5555d6674752b7c098719bbee6515c1c536 Mon Sep 17 00:00:00 2001 From: plz12345 <132735020+plz12345@users.noreply.github.com> Date: Wed, 16 Sep 2026 19:30:56 -0400 Subject: [PATCH 3/3] ci: fail the Periphery job when the scan itself breaks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The if/else wrote a message to the step summary and fell out of the branch, so the trailing echo returned 0. A Periphery that could not build the project at all reported as a green job — the same silent-failure shape as the crashed SwiftLint that produced a bogus clean tree earlier on this branch. Findings stay advisory (no --strict, so they exit 0 and surface as annotations). A scan that cannot run is a broken check, and now says so. --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 75e686b..4e4711d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -152,4 +152,7 @@ jobs: echo "Periphery scan completed — findings, if any, are in the annotations." >> "$GITHUB_STEP_SUMMARY" else echo "Periphery failed to run (build or config problem), see the step log." >> "$GITHUB_STEP_SUMMARY" + # Findings are advisory; the scan breaking is not. Without this the + # trailing echo returns 0 and a dead tool reports as a green job. + exit 1 fi