Skip to content

V1.0.0 - #28

Merged
leogdion merged 5 commits into
mainfrom
v1.0.0
Jul 23, 2026
Merged

leogdion merged 5 commits into
mainfrom
v1.0.0

Conversation

@leogdion

@leogdion leogdion commented Dec 2, 2024 •

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Added an async Mailchimp client supporting sent-campaign listing, archive HTML, plain text, and combined campaign content retrieval.
    • Added pagination, Basic authentication, typed errors, transport injection, and campaign models.
    • Added Swift 6.4, OpenAPI-based platform support, and improved development-container compatibility.
  • Breaking Changes

    • Removed the previous broad endpoint API; support is now focused on campaign retrieval and content operations.
  • Documentation

    • Updated installation, usage, supported operations, error handling, and development guidance.

@coderabbitai

coderabbitai Bot commented Jun 21, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@leogdion, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 3 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 38f9cba5-ba6d-4833-b97f-c27ad6b2b2dd

📥 Commits

Reviewing files that changed from the base of the PR and between 6e53d34 and d8c50ab.

📒 Files selected for processing (7)
  • .github/workflows/cleanup-caches.yml
  • CLAUDE.md
  • README.md
  • Sources/Spinetail/Campaign.swift
  • Sources/Spinetail/MailchimpClient.swift
  • Tests/SpinetailTests/Fixtures.swift
  • Tests/SpinetailTests/MailchimpClientTests.swift
📝 Walkthrough

Walkthrough

Spinetail migrates from the legacy Prch-based Mailchimp API surface to generated Swift OpenAPI code and a focused async MailchimpClient. The PR adds campaign operations, authentication, pagination, tests, Swift 6.4 tooling, cross-platform CI, repository guidance, and updated documentation.

Changes

Spinetail OpenAPI migration

Layer / File(s) Summary
Repository guidance and skills
.claude/*, CLAUDE.md
Adds maintainer guidance and multiple Claude skill definitions with invocation, handoff, research, ticketing, specification, and workflow instructions.
Swift tooling and CI modernization
.devcontainer/*, .github/*, .mise.toml, .swift-*, .swiftlint.yml, Scripts/*
Adds Swift 6.4 development tooling, lint/format configuration, cached tool setup, multi-platform CI, unsafe-flag checks, cache cleanup, source compatibility checks, and OpenAPI regeneration support.
OpenAPI manifest and generated contract
Package.swift, OpenAPI/*, Sources/SpinetailOpenAPI/*
Adds generated getCampaigns and getCampaignsIdContent operations, OpenAPI runtime dependencies, target wiring, typed responses, and WASI-specific transport scoping.
Legacy endpoint surface removal
Sources/Spinetail/Requests/*
Removes the previous broad Prch-based endpoint and model surface in favor of the two generated campaign operations.
Async Mailchimp wrapper
Sources/Spinetail/AuthenticationMiddleware.swift, Sources/Spinetail/Mailchimp*.swift
Adds Basic authentication, datacenter URL derivation, campaign mapping, paginated campaign retrieval, campaign content methods, and typed client errors.
Transport fixtures and client tests
Tests/SpinetailTests/*
Adds mock transport and fixtures covering authentication, pagination, campaign mapping, content extraction, missing fields, and problem responses.
Project documentation and release notes
README.md, RELEASE_NOTES.md
Documents the focused client API, generated architecture, supported operations, tooling, regeneration workflow, and Swift 6.4 migration.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Application
  participant MailchimpClient
  participant AuthenticationMiddleware
  participant OpenAPIClient
  participant MailchimpAPI
  Application->>MailchimpClient: request campaigns or campaign content
  MailchimpClient->>OpenAPIClient: invoke generated operation
  OpenAPIClient->>AuthenticationMiddleware: process outgoing request
  AuthenticationMiddleware->>MailchimpAPI: send Basic-authenticated HTTP request
  MailchimpAPI-->>OpenAPIClient: JSON or problem response
  OpenAPIClient-->>MailchimpClient: typed operation output
  MailchimpClient-->>Application: campaigns, content, or ClientError
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title is generic and does not describe the main change in the pull request. Use a concise, descriptive title that summarizes the primary change, such as the new v1.0.0 release or API rewrite.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch v1.0.0

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Jun 21, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.41975% with 11 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@6e3a75a). Learn more about missing BASE report.

Files with missing lines Patch % Lines
Sources/Spinetail/MailchimpClient.swift 78.84% 11 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main      #28   +/-   ##
=======================================
  Coverage        ?   43.52%           
=======================================
  Files           ?        7           
  Lines           ?     1050           
  Branches        ?        0           
=======================================
  Hits            ?      457           
  Misses          ?      593           
  Partials        ?        0           
Flag Coverage Δ
macos 43.52% <86.41%> (?)
noble 43.52% <86.41%> (?)
spm 43.52% <86.41%> (?)
swift-6.4 43.52% <86.41%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@leogdion
leogdion force-pushed the v1.0.0 branch 2 times, most recently from cf9e7e8 to ce4d5b6 Compare June 22, 2026 14:33
leogdion and others added 2 commits June 22, 2026 10:34
…CI (#29)

* Phase 4 — OpenAPI & dependency migration (#109)

* Replace legacy Swift-5.x CI with Swift 6.4 template (Spinetail, SwiftTube)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Subrepos: add macOS + Windows nightly-6.4 CI legs across all 5 standalone repos

Extend ButtondownKit/Spinetail/SwiftTube/Contribute/SyndiKit standalone CI to
the BrightDigit multi-platform template now that a self-hosted macOS runner with
/Applications/Xcode-beta.app (Swift 6.4) is available:

- build-macos: [self-hosted, macOS] + swift-build xcode=Xcode-beta. Blocking on
  all 5. Contribute & SyndiKit migrated off macos-15; their Ubuntu + lint legs
  migrated swift:6.3-noble -> swiftlang/swift:nightly-6.4.x-noble.
- build-windows: hosted windows-2022/2025, swift.org nightly snapshot
  6.4.x-DEVELOPMENT-SNAPSHOT-2026-06-01-a. Gated to full-matrix runs via a
  single-package configure job. continue-on-error on the OpenAPI repos +
  Contribute (unverified deps); blocking on SyndiKit.
- SyndiKit also gains build-macos-platforms (iOS/watchOS/tvOS on released
  Xcode_26.4 — not nightly 6.4).

WASM + Android deferred: no nightly 6.4 support yet. WASM is a swift-build
limitation (brightdigit/swift-build#115 — no input to override the auto-derived
-RELEASE wasm SDK URL); Android is blocked upstream (no nightly 6.4 SDK). Each
workflow documents re-adding them. Contribute WASM is permanently N/A (Yams on
the Musl/wasm SDK).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Subrepos: wire WASM + Android nightly-6.4 CI legs via swift-build SDK-bundle inputs

Now that swift.org publishes nightly 6.4 SDK bundles on swift-6.4.x-branch and
brightdigit/swift-build#116 adds inputs to install caller-supplied bundles, add:

- build-wasm to ButtondownKit/Spinetail/SwiftTube/SyndiKit (NOT Contribute — Yams
  fails on the Musl/wasm SDK). Uses wasm-sdk-url + wasm-sdk-checksum pointing at the
  swift-6.4.x-DEVELOPMENT-SNAPSHOT-2026-06-15-a_wasm artifactbundle, with WASI
  emulation + memory flags.
- build-android to all 5 repos. Uses android-sdk-url + android-sdk-id (+ matching
  android-swift-version) for the swift-6.4.x-DEVELOPMENT-SNAPSHOT-2026-06-15-a_android
  artifactbundle via skiptools custom-sdk-url; build-only (android-run-tests: false).

Both legs are full-matrix-gated and continue-on-error: they reference @v1 and are
inert until swift-build#116 is released and the v1 tag moved, after which they
should be confirmed green and promoted to blocking (SyndiKit first). Bump the
snapshot SDK URLs/checksums periodically.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Subrepos: run all Apple-platform suites on self-hosted Xcode-beta (Swift 6.4)

Per the rule that Swift 6.4 builds use the self-hosted runners (the only macOS
Swift 6.4 toolchain is /Applications/Xcode-beta.app = Xcode 27 / Swift 6.4):

- Move SyndiKit + Contribute build-macos-platforms off hosted macos-26/Xcode_26.4
  (which is only Swift 6.2) onto [self-hosted, macOS] + Xcode-beta.
- Add build-macos-platforms (iOS/watchOS/tvOS) to ButtondownKit/Spinetail/SwiftTube
  on the same self-hosted runner — these are swift-tools-version:6.4, so hosted
  released-Xcode runners can't even parse their manifests.

All use the iOS/watchOS/tvOS 27.0 simulator runtimes present on the runner
(iPhone 17 Pro / Apple Watch Ultra 3 (49mm) / Apple TV 4K (3rd generation)),
full-matrix-gated and continue-on-error (simulator-on-nightly is the most fragile
leg; promote once green). lint now needs build-macos-platforms.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Subrepos: give Windows its own (stricter) matrix tier

Three CI tiers instead of two: small set (build-ubuntu/build-macos/lint, always)
< full-matrix (macОS-platforms/wasm/android) < +Windows. configure now emits a
second output, run-windows, equal to full-matrix MINUS PRs into semver branches.
build-windows (the most expensive leg — hosted windows-2022/2025 ×2) gates on
run-windows; the rest keep full-matrix. So PRs into semver branches still exercise
the full non-Windows matrix but skip Windows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Subrepos: test against swift-build feature branch + fold build-wasm into build-ubuntu

- Point every brightdigit/swift-build@v1 reference at @sdk-url-checksum-nightly-6.4
  (PR #116) TEMPORARILY so CI exercises the new wasm-sdk-url/android-sdk-url code
  before it ships in @v1. Revert to @v1 once #116 is merged + the v1 tag moved.
- Fold build-wasm into build-ubuntu as a continue-on-error step in the same
  nightly-6.4.x-noble container (one spin-up; runs on every push/PR now, not just
  full-matrix). Removed the standalone build-wasm job from ButtondownKit/Spinetail/
  SwiftTube/SyndiKit (Contribute has no wasm — Yams on Musl/wasm).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Subrepos: only run push CI on main (drop integration branch from push trigger)

Push events now trigger only on main (+ release tags). Feature/integration
branches like brightdigit-com-260406 get CI through their PRs instead of a
redundant branch-push run.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Subrepos: unify the 5 CI workflows to byte-identical (except name)

- Windows is now blocking everywhere (dropped continue-on-error from build-windows
  in all 5, not just SyndiKit).
- The only per-package functional difference — the wasm step (Contribute can't
  build Yams on wasm) — is externalized to the ENABLE_WASM repo variable:
  `if: ${{ vars.ENABLE_WASM != 'false' }}`. Set ENABLE_WASM=false on Contribute.
- Normalized all comments to a shared template.

Result: the five workflow files are byte-identical except the `name:` line; the
only behavioral knob is each repo's ENABLE_WASM variable. Filed Contribute#10 to
fix Yams-on-wasm and re-enable it there.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Reconcile tree to brightdigit-com-260406 (drop v1.0.0 legacy Prch files)

The rebase onto v1.0.0 carried over legacy files from v1.0.0's superseded
Prch2 commits that the OpenAPI rebuild never touched. Force the tree to match
the original brightdigit-com-260406 content exactly so no stray files land.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Subrepos: remove continue-on-error from CI workflow

Make the WASM step, the Apple-platforms job (iOS/watchOS/tvOS), and the
Android job blocking. Tidy the now-stale continue-on-error comments.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Subrepos: gate watchOS leg on ENABLE_WATCHOS (disable where failing)

Gate the build-macos-platforms watchOS leg on the ENABLE_WATCHOS repo variable
(set false on this repo). watchOS-27 SDK rejects deps inferring an 8.0
deployment target (SwiftPM #10188). Tracked in brightdigit.com#119.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Subrepos: build-ubuntu matrix + guard URLSession behind #if !os(WASI)

build-ubuntu fans out over [standard, wasm, wasm-embedded] (configure →
ubuntu-type, gated by ENABLE_WASM). Make OpenAPIURLSession conditional via
Platform.withoutWASI and guard the URLSession-based initializer behind
#if !os(WASI) so the wasm/embedded legs build. Mirrors brightdigit/MistKit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Subrepos: make wasm/wasm-embedded legs build-only

WasmKit can't mount the Foundation resource bundles the tests load, and
embedded hits OpenAPIRuntime keypath limits at runtime. Build-only validates
wasm/embedded compilation; the standard leg runs the full suite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* CI: revert swift-build action to @v1 (temp sdk-url-checksum-nightly-6.4 branch deleted)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add devcontainer, CI workflows, and update README

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@leogdion
leogdion marked this pull request as draft June 22, 2026 14:35
@leogdion
leogdion marked this pull request as ready for review July 22, 2026 12:35

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🧹 Nitpick comments (3)
.github/workflows/Spinetail.yml (3)

29-241: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add an explicit permissions: block; jobs currently rely on default (broad) GITHUB_TOKEN permissions.

zizmor's excessive-permissions warning fires for every job that lacks a permissions: key (build-ubuntu 29-79, configure 81-117, build-macos 118-144, build-windows 145-170, build-macos-platforms 171-211, build-android 212-240). None of these jobs appear to need more than read access to repo contents. Scoping to contents: read (top-level or per-job) follows least-privilege for the GITHUB_TOKEN.

🔒 Proposed fix
 jobs:
+  # top-level default; override per-job if a job legitimately needs more
+permissions:
+  contents: read
+
 jobs:
   build-ubuntu:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/Spinetail.yml around lines 29 - 241, Add an explicit
GitHub Actions permissions block scoped to contents: read, preferably at
workflow level so it applies to build-ubuntu, configure, build-macos,
build-windows, build-macos-platforms, and build-android. Preserve all existing
job behavior while eliminating reliance on broad default GITHUB_TOKEN
permissions.

Source: Linters/SAST tools


92-116: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Pass github.ref/github.base_ref/vars.ENABLE_WASM via env: instead of interpolating into the script.

REF="${{ github.ref }}" and BASE_REF="${{ github.base_ref }}" are spliced directly into the run: shell script; ref names can contain shell metacharacters, making this a classic GitHub Actions script-injection vector (flagged by zizmor as template-injection, error severity at line 98). Prefer passing these through env: and referencing $REF/$BASE_REF so the values are never shell-interpreted from the workflow expression.

🔒 Proposed fix
       - id: check
         name: Determine matrix scope
+        env:
+          REF: ${{ github.ref }}
+          EVENT_NAME: ${{ github.event_name }}
+          BASE_REF: ${{ github.base_ref }}
+          ENABLE_WASM: ${{ vars.ENABLE_WASM }}
         run: |
           FULL=false; WIN=false
-          REF="${{ github.ref }}"; EVENT="${{ github.event_name }}"; BASE_REF="${{ github.base_ref }}"
+          EVENT="$EVENT_NAME"
           if [[ "$REF" == "refs/heads/main" ]]; then FULL=true; WIN=true
           ...
-          if [[ "${{ vars.ENABLE_WASM }}" != "false" ]]; then
+          if [[ "$ENABLE_WASM" != "false" ]]; then
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/Spinetail.yml around lines 92 - 116, Update the workflow
step that computes FULL, WIN, and ubuntu-type to pass github.ref,
github.base_ref, and vars.ENABLE_WASM through the step’s env configuration, then
reference those environment variables as $REF, $BASE_REF, and an appropriate
variable inside the run script. Remove the direct GitHub expression
interpolations from the shell commands while preserving the existing branch,
event, and wasm-selection behavior.

Source: Linters/SAST tools


44-44: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Set persist-credentials: false on every actions/checkout step across the CI workflows. All three workflows checkout the repo without disabling credential persistence, so the ephemeral GITHUB_TOKEN remains in the local git config for the rest of each job (zizmor artipacked); none of these jobs push back to the repo, so persistence isn't needed.

  • .github/workflows/Spinetail.yml#L44-L44: add with: persist-credentials: false to this and the other six actions/checkout@v6 steps in the file (lines 89, 123, 158, 185, 222, 253).
  • .github/workflows/check-unsafe-flags.yml#L18-L19: add with: persist-credentials: false to this checkout step.
  • .github/workflows/swift-source-compat.yml#L27-L28: add with: persist-credentials: false to this checkout step.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/Spinetail.yml at line 44, Update every actions/checkout@v6
step to set persist-credentials: false: all seven checkout steps in
.github/workflows/Spinetail.yml (lines 44, 89, 123, 158, 185, 222, and 253), the
checkout step in .github/workflows/check-unsafe-flags.yml (lines 18-19), and the
checkout step in .github/workflows/swift-source-compat.yml (lines 27-28). Add
the setting under each action’s with configuration.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/skills/claude-handoff/SKILL.md:
- Around line 8-10: Update the handoff command in the skill instructions to pass
the summary and descriptive name as safely separated argument values, preventing
conversation content from being interpreted as shell syntax. Use a wrapper or
robust shell-escaping approach around the existing background-agent launch,
while preserving the required --bg and --name behavior.

In @.claude/skills/loop-me/SKILL.md:
- Line 8: Update the stateful /grilling workflow in the loop-me skill so
creating, editing, or deleting workflow specs is deferred until the user
confirms shared understanding; alternatively, require explicit confirmation
before each destructive overwrite or deletion. Preserve the
one-question-at-a-time grilling process and workflow-only output.

In @.claude/skills/to-spec/SKILL.md:
- Around line 17-19: Update the workflow instructions after “Check with the
user” to require explicit user approval before publishing the spec to the issue
tracker. If the user rejects the spec, revise it and seek approval again; only
publish after approval, then apply the ready-for-agent label.

In @.github/workflows/cleanup-caches.yml:
- Line 15: Update the ref construction in the cleanup workflow to use
context.payload.ref_type, selecting refs/heads/ for branch deletions and
refs/tags/ for tag deletions. Keep the existing cache cleanup behavior unchanged
after producing the correct ref for either delete event.

In `@CLAUDE.md`:
- Around line 21-27: Update the Build, Build incl. tests, Test (all), and Test
(single) command examples in CLAUDE.md to invoke Swift through the pinned
toolchain using mise exec --, keeping the existing command arguments and test
filter unchanged.

In `@README.md`:
- Around line 19-20: Update the two badge image labels in the README to use
descriptive alt text: identify the Swift versions badge as “Supported Swift
versions” and the platforms badge as “Supported platforms,” while preserving
their existing badge URLs and links.
- Around line 42-59: Update the README’s MailchimpClient API description to
document the public campaignContent(forCampaignID:) and
plainText(forCampaignID:) methods as convenience methods backed by the two
generated operations, while preserving the existing description of listing
campaigns and fetching archive HTML.
- Around line 164-179: Update the Error handling documentation around the “Both
methods throw” statement to acknowledge that generated-client and transport
errors propagate unchanged, rather than claiming every failure is a
MailchimpClient.ClientError. Keep the existing ClientError cases and missingHTML
example, and clearly distinguish mapped errors from propagated transport errors.

In `@Scripts/lint.sh`:
- Line 66: Guard the directory-stack operations in the script: update pushd
before the run_command steps and the corresponding popd near the end to fail
immediately when either command fails. Preserve the existing command flow while
ensuring subsequent formatting, linting, and build operations cannot run from an
unintended directory.

In `@Sources/Spinetail/MailchimpClient.swift`:
- Around line 160-169: The pagination termination logic must not treat
collected.count as the total when body.total_items is absent. Update the loop
around body.total_items and the page.isEmpty check to stop on total_items only
when present, while continuing to fetch subsequent pages until an empty page
when it is nil.
- Around line 107-121: Update serverURL(forAPIKey:) to validate the extracted
datacenter token against the allowed Mailchimp datacenter format, rejecting URL
delimiters and any other invalid characters before interpolation. Preserve
invalidAPIKey errors for rejected tokens, and add a test covering a suffix such
as “key-evil.com/” to ensure no non-Mailchimp URL is constructed.

---

Nitpick comments:
In @.github/workflows/Spinetail.yml:
- Around line 29-241: Add an explicit GitHub Actions permissions block scoped to
contents: read, preferably at workflow level so it applies to build-ubuntu,
configure, build-macos, build-windows, build-macos-platforms, and build-android.
Preserve all existing job behavior while eliminating reliance on broad default
GITHUB_TOKEN permissions.
- Around line 92-116: Update the workflow step that computes FULL, WIN, and
ubuntu-type to pass github.ref, github.base_ref, and vars.ENABLE_WASM through
the step’s env configuration, then reference those environment variables as
$REF, $BASE_REF, and an appropriate variable inside the run script. Remove the
direct GitHub expression interpolations from the shell commands while preserving
the existing branch, event, and wasm-selection behavior.
- Line 44: Update every actions/checkout@v6 step to set persist-credentials:
false: all seven checkout steps in .github/workflows/Spinetail.yml (lines 44,
89, 123, 158, 185, 222, and 253), the checkout step in
.github/workflows/check-unsafe-flags.yml (lines 18-19), and the checkout step in
.github/workflows/swift-source-compat.yml (lines 27-28). Add the setting under
each action’s with configuration.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f521d9dd-263e-4718-bfd1-996a40b41a64

📥 Commits

Reviewing files that changed from the base of the PR and between 6e3a75a and 6e53d34.

⛔ Files ignored due to path filters (2)
  • Package.resolved is excluded by !**/Package.resolved
  • Sources/Spinetail/Spinetail.docc/Resources/logo.png is excluded by !**/*.png
📒 Files selected for processing (321)
  • .circleci/config.yml
  • .claude/agent-notes.md
  • .claude/skills/claude-handoff/SKILL.md
  • .claude/skills/claude-handoff/agents/openai.yaml
  • .claude/skills/grill-me/SKILL.md
  • .claude/skills/grill-me/agents/openai.yaml
  • .claude/skills/grill-with-docs/SKILL.md
  • .claude/skills/grill-with-docs/agents/openai.yaml
  • .claude/skills/grilling/SKILL.md
  • .claude/skills/grilling/agents/openai.yaml
  • .claude/skills/handoff/SKILL.md
  • .claude/skills/handoff/agents/openai.yaml
  • .claude/skills/loop-me/SKILL.md
  • .claude/skills/loop-me/agents/openai.yaml
  • .claude/skills/research/SKILL.md
  • .claude/skills/research/agents/openai.yaml
  • .claude/skills/to-spec/SKILL.md
  • .claude/skills/to-spec/agents/openai.yaml
  • .claude/skills/to-tickets/SKILL.md
  • .claude/skills/to-tickets/agents/openai.yaml
  • .claude/skills/writing-great-skills/GLOSSARY.md
  • .claude/skills/writing-great-skills/SKILL.md
  • .claude/skills/writing-great-skills/agents/openai.yaml
  • .devcontainer/devcontainer.json
  • .github/actions/setup-tools/action.yml
  • .github/workflows/Spinetail.yml
  • .github/workflows/check-unsafe-flags.yml
  • .github/workflows/cleanup-caches.yml
  • .github/workflows/swift-source-compat.yml
  • .hound.yml
  • .mise.toml
  • .spi.yml
  • .swift-format
  • .swift-version
  • .swiftformat
  • .swiftlint.yml
  • CLAUDE.md
  • OpenAPI/openapi.yaml
  • Package.swift
  • Package@swift-5.5.swift
  • README.md
  • RELEASE_NOTES.md
  • Scripts/generate-openapi-spinetail.sh
  • Scripts/lint.sh
  • Sources/Spinetail/AuthenticationMiddleware.swift
  • Sources/Spinetail/Mailchimp.API.swift
  • Sources/Spinetail/Mailchimp.swift
  • Sources/Spinetail/MailchimpCampaign.swift
  • Sources/Spinetail/MailchimpClient.swift
  • Sources/Spinetail/Models/DefaultResponse.swift
  • Sources/Spinetail/Models/Links.swift
  • Sources/Spinetail/Requests/Lite/CampaignFolders/DeleteCampaignFoldersId.swift
  • Sources/Spinetail/Requests/Lite/CampaignFolders/GetCampaignFolders.swift
  • Sources/Spinetail/Requests/Lite/CampaignFolders/GetCampaignFoldersId.swift
  • Sources/Spinetail/Requests/Lite/CampaignFolders/PatchCampaignFoldersId.swift
  • Sources/Spinetail/Requests/Lite/CampaignFolders/PostCampaignFolders.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/DeleteCampaignsId.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/DeleteCampaignsIdFeedbackId.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/GetCampaigns.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/GetCampaignsId.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/GetCampaignsIdContent.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/GetCampaignsIdFeedback.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/GetCampaignsIdFeedbackId.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/GetCampaignsIdSendChecklist.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PatchCampaignsId.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PatchCampaignsIdFeedbackId.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PostCampaigns.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PostCampaignsIdActionsCancelSend.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PostCampaignsIdActionsCreateResend.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PostCampaignsIdActionsPause.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PostCampaignsIdActionsReplicate.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PostCampaignsIdActionsResume.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PostCampaignsIdActionsSchedule.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PostCampaignsIdActionsSend.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PostCampaignsIdActionsTest.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PostCampaignsIdActionsUnschedule.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PostCampaignsIdFeedback.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PutCampaignsIdContent.swift
  • Sources/Spinetail/Requests/Lite/Lists/DeleteListsId.swift
  • Sources/Spinetail/Requests/Lite/Lists/DeleteListsIdInterestCategoriesId.swift
  • Sources/Spinetail/Requests/Lite/Lists/DeleteListsIdInterestCategoriesIdInterestsId.swift
  • Sources/Spinetail/Requests/Lite/Lists/DeleteListsIdMembersId.swift
  • Sources/Spinetail/Requests/Lite/Lists/DeleteListsIdMembersIdNotesId.swift
  • Sources/Spinetail/Requests/Lite/Lists/DeleteListsIdMergeFieldsId.swift
  • Sources/Spinetail/Requests/Lite/Lists/DeleteListsIdSegmentsId.swift
  • Sources/Spinetail/Requests/Lite/Lists/DeleteListsIdSegmentsIdMembersId.swift
  • Sources/Spinetail/Requests/Lite/Lists/DeleteListsIdWebhooksId.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListMemberTags.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetLists.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsId.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdAbuseReports.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdAbuseReportsId.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdActivity.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdClients.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdGrowthHistory.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdGrowthHistoryId.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdInterestCategories.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdInterestCategoriesId.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdInterestCategoriesIdInterests.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdInterestCategoriesIdInterestsId.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdLocations.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdMembers.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdMembersId.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdMembersIdActivity.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdMembersIdActivityFeed.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdMembersIdEvents.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdMembersIdGoals.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdMembersIdNotes.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdMembersIdNotesId.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdMergeFields.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdMergeFieldsId.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdSegmentsId.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdSegmentsIdMembers.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdSignupForms.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdWebhooks.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdWebhooksId.swift
  • Sources/Spinetail/Requests/Lite/Lists/PatchListsId.swift
  • Sources/Spinetail/Requests/Lite/Lists/PatchListsIdInterestCategoriesId.swift
  • Sources/Spinetail/Requests/Lite/Lists/PatchListsIdInterestCategoriesIdInterestsId.swift
  • Sources/Spinetail/Requests/Lite/Lists/PatchListsIdMembersId.swift
  • Sources/Spinetail/Requests/Lite/Lists/PatchListsIdMembersIdNotesId.swift
  • Sources/Spinetail/Requests/Lite/Lists/PatchListsIdMergeFieldsId.swift
  • Sources/Spinetail/Requests/Lite/Lists/PatchListsIdSegmentsId.swift
  • Sources/Spinetail/Requests/Lite/Lists/PatchListsIdWebhooksId.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListMemberEvents.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListMemberTags.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostLists.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListsId.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListsIdInterestCategories.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListsIdInterestCategoriesIdInterests.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListsIdMembers.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListsIdMembersHashActionsDeletePermanent.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListsIdMembersIdNotes.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListsIdMergeFields.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListsIdSegments.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListsIdSegmentsId.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListsIdSegmentsIdMembers.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListsIdSignupForms.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListsIdWebhooks.swift
  • Sources/Spinetail/Requests/Lite/Lists/PreviewASegment.swift
  • Sources/Spinetail/Requests/Lite/Lists/PutListsIdMembersId.swift
  • Sources/Spinetail/Requests/Lite/Lists/SearchTagsByName.swift
  • Sources/Spinetail/Requests/Lite/Reports/GetReports.swift
  • Sources/Spinetail/Requests/Lite/Reports/GetReportsId.swift
  • Sources/Spinetail/Requests/Lite/Reports/GetReportsIdAbuseReportsId.swift
  • Sources/Spinetail/Requests/Lite/Reports/GetReportsIdAbuseReportsIdId.swift
  • Sources/Spinetail/Requests/Lite/Reports/GetReportsIdAdvice.swift
  • Sources/Spinetail/Requests/Lite/Reports/GetReportsIdClickDetails.swift
  • Sources/Spinetail/Requests/Lite/Reports/GetReportsIdClickDetailsId.swift
  • Sources/Spinetail/Requests/Lite/Reports/GetReportsIdClickDetailsIdMembers.swift
  • Sources/Spinetail/Requests/Lite/Reports/GetReportsIdClickDetailsIdMembersId.swift
  • Sources/Spinetail/Requests/Lite/Reports/GetReportsIdDomainPerformance.swift
  • Sources/Spinetail/Requests/Lite/Reports/GetReportsIdEcommerceProductActivity.swift
  • Sources/Spinetail/Requests/Lite/Reports/GetReportsIdEepurl.swift
  • Sources/Spinetail/Requests/Lite/Reports/GetReportsIdEmailActivity.swift
  • Sources/Spinetail/Requests/Lite/Reports/GetReportsIdEmailActivityId.swift
  • Sources/Spinetail/Requests/Lite/Reports/GetReportsIdLocations.swift
  • Sources/Spinetail/Requests/Lite/Reports/GetReportsIdOpenDetails.swift
  • Sources/Spinetail/Requests/Lite/Reports/GetReportsIdOpenDetailsIdMembersId.swift
  • Sources/Spinetail/Requests/Lite/Reports/GetReportsIdSentTo.swift
  • Sources/Spinetail/Requests/Lite/Reports/GetReportsIdSentToId.swift
  • Sources/Spinetail/Requests/Lite/Reports/GetReportsIdSubReportsId.swift
  • Sources/Spinetail/Requests/Lite/Reports/GetReportsIdUnsubscribed.swift
  • Sources/Spinetail/Requests/Lite/Reports/GetReportsIdUnsubscribedId.swift
  • Sources/Spinetail/Requests/Lite/Root/GetRoot.swift
  • Sources/Spinetail/Requests/Lite/SearchCampaigns/GetSearchCampaigns.swift
  • Sources/Spinetail/Requests/Lite/SearchMembers/GetSearchMembers.swift
  • Sources/Spinetail/Requests/Lite/TemplateFolders/DeleteTemplateFoldersId.swift
  • Sources/Spinetail/Requests/Lite/TemplateFolders/GetTemplateFolders.swift
  • Sources/Spinetail/Requests/Lite/TemplateFolders/GetTemplateFoldersId.swift
  • Sources/Spinetail/Requests/Lite/TemplateFolders/PatchTemplateFoldersId.swift
  • Sources/Spinetail/Requests/Lite/TemplateFolders/PostTemplateFolders.swift
  • Sources/Spinetail/Requests/Lite/Templates/DeleteTemplatesId.swift
  • Sources/Spinetail/Requests/Lite/Templates/GetTemplates.swift
  • Sources/Spinetail/Requests/Lite/Templates/GetTemplatesId.swift
  • Sources/Spinetail/Requests/Lite/Templates/GetTemplatesIdDefaultContent.swift
  • Sources/Spinetail/Requests/Lite/Templates/PatchTemplatesId.swift
  • Sources/Spinetail/Requests/Lite/Templates/PostTemplates.swift
  • Sources/Spinetail/Requests/Regular/ActivityFeed/GetActivityFeedChimpChatter.swift
  • Sources/Spinetail/Requests/Regular/AuthorizedApps/GetAuthorizedApps.swift
  • Sources/Spinetail/Requests/Regular/AuthorizedApps/GetAuthorizedAppsId.swift
  • Sources/Spinetail/Requests/Regular/Automations/ArchiveAutomations.swift
  • Sources/Spinetail/Requests/Regular/Automations/DeleteAutomationsIdEmailsId.swift
  • Sources/Spinetail/Requests/Regular/Automations/GetAutomations.swift
  • Sources/Spinetail/Requests/Regular/Automations/GetAutomationsId.swift
  • Sources/Spinetail/Requests/Regular/Automations/GetAutomationsIdEmails.swift
  • Sources/Spinetail/Requests/Regular/Automations/GetAutomationsIdEmailsId.swift
  • Sources/Spinetail/Requests/Regular/Automations/GetAutomationsIdEmailsIdQueue.swift
  • Sources/Spinetail/Requests/Regular/Automations/GetAutomationsIdEmailsIdQueueId.swift
  • Sources/Spinetail/Requests/Regular/Automations/GetAutomationsIdRemovedSubscribers.swift
  • Sources/Spinetail/Requests/Regular/Automations/GetAutomationsIdRemovedSubscribersId.swift
  • Sources/Spinetail/Requests/Regular/Automations/PatchAutomationEmailWorkflowId.swift
  • Sources/Spinetail/Requests/Regular/Automations/PostAutomations.swift
  • Sources/Spinetail/Requests/Regular/Automations/PostAutomationsIdActionsPauseAllEmails.swift
  • Sources/Spinetail/Requests/Regular/Automations/PostAutomationsIdActionsStartAllEmails.swift
  • Sources/Spinetail/Requests/Regular/Automations/PostAutomationsIdEmailsIdActionsPause.swift
  • Sources/Spinetail/Requests/Regular/Automations/PostAutomationsIdEmailsIdActionsStart.swift
  • Sources/Spinetail/Requests/Regular/Automations/PostAutomationsIdEmailsIdQueue.swift
  • Sources/Spinetail/Requests/Regular/Automations/PostAutomationsIdRemovedSubscribers.swift
  • Sources/Spinetail/Requests/Regular/BatchWebhooks/DeleteBatchWebhookId.swift
  • Sources/Spinetail/Requests/Regular/BatchWebhooks/GetBatchWebhook.swift
  • Sources/Spinetail/Requests/Regular/BatchWebhooks/GetBatchWebhooks.swift
  • Sources/Spinetail/Requests/Regular/BatchWebhooks/PatchBatchWebhooks.swift
  • Sources/Spinetail/Requests/Regular/BatchWebhooks/PostBatchWebhooks.swift
  • Sources/Spinetail/Requests/Regular/Batches/DeleteBatchesId.swift
  • Sources/Spinetail/Requests/Regular/Batches/GetBatches.swift
  • Sources/Spinetail/Requests/Regular/Batches/GetBatchesId.swift
  • Sources/Spinetail/Requests/Regular/Batches/PostBatches.swift
  • Sources/Spinetail/Requests/Regular/ConnectedSites/DeleteConnectedSitesId.swift
  • Sources/Spinetail/Requests/Regular/ConnectedSites/GetConnectedSites.swift
  • Sources/Spinetail/Requests/Regular/ConnectedSites/GetConnectedSitesId.swift
  • Sources/Spinetail/Requests/Regular/ConnectedSites/PostConnectedSites.swift
  • Sources/Spinetail/Requests/Regular/ConnectedSites/PostConnectedSitesIdActionsVerifyScriptInstallation.swift
  • Sources/Spinetail/Requests/Regular/Conversations/GetConversations.swift
  • Sources/Spinetail/Requests/Regular/Conversations/GetConversationsId.swift
  • Sources/Spinetail/Requests/Regular/Conversations/GetConversationsIdMessages.swift
  • Sources/Spinetail/Requests/Regular/Conversations/GetConversationsIdMessagesId.swift
  • Sources/Spinetail/Requests/Regular/CustomerJourneys/PostCustomerJourneysJourneysIdStepsIdActionsTrigger.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/DeleteEcommerceStoresId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/DeleteEcommerceStoresIdCartsId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/DeleteEcommerceStoresIdCartsLinesId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/DeleteEcommerceStoresIdCustomersId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/DeleteEcommerceStoresIdOrdersId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/DeleteEcommerceStoresIdOrdersIdLinesId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/DeleteEcommerceStoresIdProductsId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/DeleteEcommerceStoresIdProductsIdImagesId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/DeleteEcommerceStoresIdProductsIdVariantsId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/DeleteEcommerceStoresIdPromocodesId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/DeleteEcommerceStoresIdPromorulesId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceOrders.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStores.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdCarts.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdCartsId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdCartsIdLines.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdCartsIdLinesId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdCustomers.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdCustomersId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdOrders.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdOrdersId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdOrdersIdLines.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdOrdersIdLinesId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdProducts.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdProductsId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdProductsIdImages.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdProductsIdImagesId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdProductsIdVariants.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdProductsIdVariantsId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdPromocodes.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdPromocodesId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdPromorules.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdPromorulesId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PatchEcommerceStoresId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PatchEcommerceStoresIdCartsId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PatchEcommerceStoresIdCartsIdLinesId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PatchEcommerceStoresIdCustomersId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PatchEcommerceStoresIdOrdersId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PatchEcommerceStoresIdOrdersIdLinesId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PatchEcommerceStoresIdProductsId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PatchEcommerceStoresIdProductsIdImagesId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PatchEcommerceStoresIdProductsIdVariantsId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PatchEcommerceStoresIdPromocodesId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PatchEcommerceStoresIdPromorulesId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PostEcommerceStores.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PostEcommerceStoresIdCarts.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PostEcommerceStoresIdCartsIdLines.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PostEcommerceStoresIdCustomers.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PostEcommerceStoresIdOrders.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PostEcommerceStoresIdOrdersIdLines.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PostEcommerceStoresIdProducts.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PostEcommerceStoresIdProductsIdImages.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PostEcommerceStoresIdProductsIdVariants.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PostEcommerceStoresIdPromocodes.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PostEcommerceStoresIdPromorules.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PutEcommerceStoresIdCustomersId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PutEcommerceStoresIdProductsIdVariantsId.swift
  • Sources/Spinetail/Requests/Regular/FacebookAds/GetAllFacebookAds.swift
  • Sources/Spinetail/Requests/Regular/FacebookAds/GetFacebookAdsId.swift
  • Sources/Spinetail/Requests/Regular/FileManager/DeleteFileManagerFilesId.swift
  • Sources/Spinetail/Requests/Regular/FileManager/DeleteFileManagerFoldersId.swift
  • Sources/Spinetail/Requests/Regular/FileManager/GetFileManagerFiles.swift
  • Sources/Spinetail/Requests/Regular/FileManager/GetFileManagerFilesId.swift
  • Sources/Spinetail/Requests/Regular/FileManager/GetFileManagerFolders.swift
  • Sources/Spinetail/Requests/Regular/FileManager/GetFileManagerFoldersId.swift
  • Sources/Spinetail/Requests/Regular/FileManager/PatchFileManagerFilesId.swift
  • Sources/Spinetail/Requests/Regular/FileManager/PatchFileManagerFoldersId.swift
  • Sources/Spinetail/Requests/Regular/FileManager/PostFileManagerFiles.swift
  • Sources/Spinetail/Requests/Regular/FileManager/PostFileManagerFolders.swift
  • Sources/Spinetail/Requests/Regular/LandingPages/DeleteLandingPageId.swift
  • Sources/Spinetail/Requests/Regular/LandingPages/GetAllLandingPages.swift
  • Sources/Spinetail/Requests/Regular/LandingPages/GetLandingPageId.swift
  • Sources/Spinetail/Requests/Regular/LandingPages/GetLandingPageIdContent.swift
  • Sources/Spinetail/Requests/Regular/LandingPages/PatchLandingPageId.swift
  • Sources/Spinetail/Requests/Regular/LandingPages/PostAllLandingPages.swift
  • Sources/Spinetail/Requests/Regular/LandingPages/PostLandingPageIdActionsPublish.swift
  • Sources/Spinetail/Requests/Regular/LandingPages/PostLandingPageIdActionsUnpublish.swift
  • Sources/Spinetail/Requests/Regular/Ping/GetPing.swift
  • Sources/Spinetail/Requests/Regular/Reporting/GetReportingFacebookAds.swift
  • Sources/Spinetail/Requests/Regular/Reporting/GetReportingFacebookAdsId.swift
  • Sources/Spinetail/Requests/Regular/Reporting/GetReportingFacebookAdsIdEcommerceProductActivity.swift
  • Sources/Spinetail/Requests/Regular/Reporting/GetReportingLandingPages.swift
  • Sources/Spinetail/Requests/Regular/Reporting/GetReportingLandingPagesId.swift
  • Sources/Spinetail/Requests/Regular/VerifiedDomains/CreateVerifiedDomain.swift
  • Sources/Spinetail/Requests/Regular/VerifiedDomains/DeleteVerifiedDomain.swift
  • Sources/Spinetail/Requests/Regular/VerifiedDomains/GetVerifiedDomain.swift
  • Sources/Spinetail/Requests/Regular/VerifiedDomains/GetVerifiedDomains.swift
  • Sources/Spinetail/Requests/Regular/VerifiedDomains/VerifyDomain.swift
  • Sources/Spinetail/Spinetail.docc/Spinetail.md
  • Sources/SpinetailOpenAPI/Client.swift
  • Sources/SpinetailOpenAPI/Types.swift
  • Sources/SpinetailOpenAPI/openapi-generator-config.yaml
  • Tests/LinuxMain.swift
  • Tests/SpinetailTests/Fixtures.swift
  • Tests/SpinetailTests/Helpers/Client.Helpers.swift
  • Tests/SpinetailTests/Helpers/Settings.swift
  • Tests/SpinetailTests/Helpers/String.swift
  • Tests/SpinetailTests/MailchimpClientTests.swift
  • Tests/SpinetailTests/MockTransport.swift
  • Tests/SpinetailTests/Tests/CampaignTests.swift
  • Tests/SpinetailTests/Tests/ListsTests.swift
  • Tests/SpinetailTests/XCTestManifests.swift
💤 Files with no reviewable changes (165)
  • .swiftformat
  • Tests/LinuxMain.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PostCampaignsIdActionsResume.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PostCampaignsIdActionsSchedule.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/GetCampaignsIdSendChecklist.swift
  • Sources/Spinetail/Models/Links.swift
  • Sources/Spinetail/Requests/Lite/Lists/PatchListsIdInterestCategoriesIdInterestsId.swift
  • Sources/Spinetail/Requests/Lite/Lists/DeleteListsIdMergeFieldsId.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/DeleteCampaignsIdFeedbackId.swift
  • Sources/Spinetail/Spinetail.docc/Spinetail.md
  • Sources/Spinetail/Mailchimp.API.swift
  • Tests/SpinetailTests/Tests/CampaignTests.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdMembersIdEvents.swift
  • Tests/SpinetailTests/Helpers/String.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/DeleteEcommerceStoresIdOrdersIdLinesId.swift
  • Sources/Spinetail/Requests/Regular/VerifiedDomains/GetVerifiedDomain.swift
  • Sources/Spinetail/Models/DefaultResponse.swift
  • .hound.yml
  • Sources/Spinetail/Requests/Lite/Lists/DeleteListsIdSegmentsIdMembersId.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PostCampaignsIdActionsReplicate.swift
  • Sources/Spinetail/Requests/Regular/BatchWebhooks/DeleteBatchWebhookId.swift
  • Sources/Spinetail/Requests/Lite/CampaignFolders/GetCampaignFolders.swift
  • Sources/Spinetail/Requests/Regular/Batches/GetBatchesId.swift
  • Package@swift-5.5.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdMergeFields.swift
  • Sources/Spinetail/Requests/Regular/ConnectedSites/PostConnectedSites.swift
  • .circleci/config.yml
  • Sources/Spinetail/Requests/Lite/Lists/DeleteListsIdSegmentsId.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PostCampaignsIdActionsUnschedule.swift
  • Sources/Spinetail/Requests/Regular/BatchWebhooks/PatchBatchWebhooks.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdCustomers.swift
  • Sources/Spinetail/Requests/Lite/CampaignFolders/GetCampaignFoldersId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/DeleteEcommerceStoresIdProductsId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/DeleteEcommerceStoresIdProductsIdVariantsId.swift
  • Sources/Spinetail/Requests/Lite/Lists/DeleteListsIdWebhooksId.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PutCampaignsIdContent.swift
  • Sources/Spinetail/Requests/Regular/BatchWebhooks/PostBatchWebhooks.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PostCampaignsIdActionsCancelSend.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdSegmentsId.swift
  • Sources/Spinetail/Requests/Regular/VerifiedDomains/GetVerifiedDomains.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdWebhooks.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/DeleteEcommerceStoresIdPromorulesId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStores.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/GetCampaignsIdFeedbackId.swift
  • Sources/Spinetail/Requests/Lite/CampaignFolders/PostCampaignFolders.swift
  • Sources/Spinetail/Requests/Lite/Lists/PreviewASegment.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/DeleteEcommerceStoresIdCartsLinesId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdProductsIdImagesId.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdMembersIdActivityFeed.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdProducts.swift
  • Sources/Spinetail/Requests/Lite/Lists/DeleteListsIdMembersIdNotesId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdProductsIdVariantsId.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdMembersIdGoals.swift
  • Sources/Spinetail/Requests/Regular/ConnectedSites/DeleteConnectedSitesId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/DeleteEcommerceStoresIdOrdersId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/DeleteEcommerceStoresIdProductsIdImagesId.swift
  • Sources/Spinetail/Requests/Lite/Lists/DeleteListsIdInterestCategoriesIdInterestsId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdPromorulesId.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListsIdMergeFields.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/DeleteEcommerceStoresIdCustomersId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/DeleteEcommerceStoresIdPromocodesId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdCartsId.swift
  • Sources/Spinetail/Requests/Regular/Reporting/GetReportingLandingPagesId.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListsIdWebhooks.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdPromorules.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PostCampaignsIdActionsPause.swift
  • Sources/Spinetail/Requests/Lite/CampaignFolders/DeleteCampaignFoldersId.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListMemberTags.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdMembersId.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdMembersIdNotes.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdInterestCategoriesIdInterestsId.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListMemberTags.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdCartsIdLinesId.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PostCampaignsIdFeedback.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresId.swift
  • Tests/SpinetailTests/XCTestManifests.swift
  • Tests/SpinetailTests/Tests/ListsTests.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdProductsId.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListsIdInterestCategoriesIdInterests.swift
  • Sources/Spinetail/Requests/Regular/VerifiedDomains/VerifyDomain.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdOrdersIdLines.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdCustomersId.swift
  • Sources/Spinetail/Requests/Regular/VerifiedDomains/DeleteVerifiedDomain.swift
  • Sources/Spinetail/Requests/Regular/BatchWebhooks/GetBatchWebhooks.swift
  • Sources/Spinetail/Requests/Regular/ConnectedSites/GetConnectedSitesId.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PatchCampaignsIdFeedbackId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdCarts.swift
  • Sources/Spinetail/Requests/Lite/Lists/PatchListsIdInterestCategoriesId.swift
  • Sources/Spinetail/Requests/Lite/Lists/PatchListsIdMembersId.swift
  • Sources/Spinetail/Requests/Lite/Lists/DeleteListsIdMembersId.swift
  • Sources/Spinetail/Requests/Regular/Batches/DeleteBatchesId.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListsIdInterestCategories.swift
  • Sources/Spinetail/Requests/Lite/Lists/DeleteListsId.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/DeleteCampaignsId.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdLocations.swift
  • Sources/Spinetail/Requests/Lite/Lists/PatchListsId.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdMergeFieldsId.swift
  • Tests/SpinetailTests/Helpers/Client.Helpers.swift
  • Sources/Spinetail/Requests/Regular/VerifiedDomains/CreateVerifiedDomain.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/DeleteEcommerceStoresId.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsId.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PostCampaignsIdActionsTest.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdSegmentsIdMembers.swift
  • Sources/Spinetail/Mailchimp.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdAbuseReports.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/GetCampaignsIdContent.swift
  • Sources/Spinetail/Requests/Lite/Lists/PatchListsIdWebhooksId.swift
  • Sources/Spinetail/Requests/Regular/Batches/PostBatches.swift
  • Tests/SpinetailTests/Helpers/Settings.swift
  • Sources/Spinetail/Requests/Regular/ConnectedSites/PostConnectedSitesIdActionsVerifyScriptInstallation.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdProductsIdImages.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListsIdSegmentsIdMembers.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdMembers.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdAbuseReportsId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdPromocodes.swift
  • Sources/Spinetail/Requests/Regular/Reporting/GetReportingLandingPages.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PatchEcommerceStoresIdCartsId.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PostCampaignsIdActionsSend.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostLists.swift
  • Sources/Spinetail/Requests/Regular/BatchWebhooks/GetBatchWebhook.swift
  • Sources/Spinetail/Requests/Lite/Lists/SearchTagsByName.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/DeleteEcommerceStoresIdCartsId.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdMembersIdNotesId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdOrders.swift
  • Sources/Spinetail/Requests/Regular/ConnectedSites/GetConnectedSites.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdSignupForms.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdOrdersId.swift
  • Sources/Spinetail/Requests/Regular/Conversations/GetConversationsId.swift
  • Sources/Spinetail/Requests/Lite/Lists/PutListsIdMembersId.swift
  • Sources/Spinetail/Requests/Lite/CampaignFolders/PatchCampaignFoldersId.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListsIdMembersHashActionsDeletePermanent.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListsId.swift
  • Sources/Spinetail/Requests/Regular/Conversations/GetConversations.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListsIdMembers.swift
  • Sources/Spinetail/Requests/Lite/Lists/PatchListsIdMergeFieldsId.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PostCampaignsIdActionsCreateResend.swift
  • Sources/Spinetail/Requests/Regular/Conversations/GetConversationsIdMessages.swift
  • OpenAPI/openapi.yaml
  • Sources/Spinetail/Requests/Lite/Lists/DeleteListsIdInterestCategoriesId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceOrders.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdProductsIdVariants.swift
  • Sources/Spinetail/Requests/Regular/Reporting/GetReportingFacebookAdsIdEcommerceProductActivity.swift
  • Sources/Spinetail/Requests/Lite/Lists/PatchListsIdSegmentsId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdPromocodesId.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetLists.swift
  • Sources/Spinetail/Requests/Regular/Batches/GetBatches.swift
  • Sources/Spinetail/Requests/Regular/CustomerJourneys/PostCustomerJourneysJourneysIdStepsIdActionsTrigger.swift
  • Sources/Spinetail/Requests/Regular/Conversations/GetConversationsIdMessagesId.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListMemberEvents.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdOrdersIdLinesId.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListsIdSignupForms.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/PatchEcommerceStoresId.swift
  • Sources/Spinetail/Requests/Lite/Reports/GetReports.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdWebhooksId.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListsIdMembersIdNotes.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListsIdSegmentsId.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/GetCampaigns.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PatchCampaignsId.swift
  • Sources/Spinetail/Requests/Regular/Ecommerce/GetEcommerceStoresIdCartsIdLines.swift
  • Sources/Spinetail/Requests/Lite/Lists/PostListsIdSegments.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/PostCampaigns.swift
  • Sources/Spinetail/Requests/Lite/Lists/GetListsIdMembersIdActivity.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/GetCampaignsId.swift
  • Sources/Spinetail/Requests/Lite/Campaigns/GetCampaignsIdFeedback.swift
  • Sources/Spinetail/Requests/Lite/Lists/PatchListsIdMembersIdNotesId.swift

Comment on lines +8 to +10
Write a handoff summary of the current conversation so a fresh agent can continue the work. Instead of saving it, launch a background agent seeded with the summary as its prompt: `claude --bg --name "<descriptive name>" "<handoff summary>"`. It starts in the current working directory and returns immediately; the user manages it with `claude agents`.

Always pass `-n`/`--name` with a descriptive name (e.g. `--name "Fix login bug"`) — it sets the display name shown in the job list, session picker, and terminal title.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file=".claude/skills/claude-handoff/SKILL.md"

printf '--- file exists: %s ---\n' "$file"
wc -l "$file"
printf '\n--- lines 1-40 ---\n'
sed -n '1,40p' "$file" | cat -n

printf '\n--- search for related references ---\n'
rg -n --hidden --glob '!**/.git/**' 'claude --bg|handoff summary|--name|claude agents|handoff' .claude . 2>/dev/null || true

Repository: brightdigit/Spinetail

Length of output: 250


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file=".claude/skills/claude-handoff/SKILL.md"

echo "--- file exists: $file ---"
wc -l "$file"

echo
echo "--- lines 1-60 ---"
sed -n '1,60p' "$file" | cat -n

echo
echo "--- related references ---"
rg -n --hidden --glob '!**/.git/**' 'claude --bg|handoff summary|--name|claude agents|handoff' .claude . 2>/dev/null || true

Repository: brightdigit/Spinetail

Length of output: 4062


Pass the summary and name as separate argv values. Interpolating conversation text into claude --bg ... "<handoff summary>" lets $(), backticks, quotes, or newlines break quoting or trigger substitution; use a wrapper or robust shell escaping instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/skills/claude-handoff/SKILL.md around lines 8 - 10, Update the
handoff command in the skill instructions to pass the summary and descriptive
name as safely separated argument values, preventing conversation content from
being interpreted as shell syntax. Use a wrapper or robust shell-escaping
approach around the existing background-agent launch, while preserving the
required --bg and --name behavior.

argument-hint: "A workflow to design, or nothing to go find one"
---

Run a stateful `/grilling` session whose only output is **workflow** specs. Use the grilling discipline — relentless, one question at a time, a recommended answer attached to each — aimed at the vocabulary and goal below. Create, edit, and delete specs as the grilling resolves things.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Defer destructive spec changes until confirmation.

This permits creating, editing, and deleting workflow source files while decisions are still unresolved, conflicting with the confirmation gate in .claude/skills/grilling/SKILL.md. Defer writes until the user confirms shared understanding, or require explicit confirmation before each deletion/overwrite.

Suggested wording
-Create, edit, and delete specs as the grilling resolves things.
+Do not modify workflow specs during the interview. After the user confirms shared understanding, create or edit the required specs; require separate explicit confirmation before deleting or overwriting an existing spec.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Run a stateful `/grilling` session whose only output is **workflow** specs. Use the grilling discipline — relentless, one question at a time, a recommended answer attached to each — aimed at the vocabulary and goal below. Create, edit, and delete specs as the grilling resolves things.
Run a stateful `/grilling` session whose only output is **workflow** specs. Use the grilling discipline — relentless, one question at a time, a recommended answer attached to each — aimed at the vocabulary and goal below. Do not modify workflow specs during the interview. After the user confirms shared understanding, create or edit the required specs; require separate explicit confirmation before deleting or overwriting an existing spec.
🧰 Tools
🪛 SkillSpector (2.3.11)

[warning] 27: [EA2] Autonomous Decision Making: Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Remediation: Add human-in-the-loop confirmation for destructive, irreversible, or high-impact operations. Never auto-execute commands that modify files, send data, or alter system state.

(Excessive Agency (EA2))

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/skills/loop-me/SKILL.md at line 8, Update the stateful /grilling
workflow in the loop-me skill so creating, editing, or deleting workflow specs
is deferred until the user confirms shared understanding; alternatively, require
explicit confirmation before each destructive overwrite or deletion. Preserve
the one-question-at-a-time grilling process and workflow-only output.

Source: Linters/SAST tools

Comment on lines +17 to +19
Check with the user that these seams match their expectations.

3. Write the spec using the template below, then publish it to the project issue tracker. Apply the `ready-for-agent` triage label - no need for additional triage.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Require explicit approval before publishing the spec.

“Check with the user” does not clearly require pausing or iterating; the next step publishes to the tracker. State that publication must wait for explicit user approval, and revise the spec if the user rejects it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/skills/to-spec/SKILL.md around lines 17 - 19, Update the workflow
instructions after “Check with the user” to require explicit user approval
before publishing the spec to the issue tracker. If the user rejects the spec,
revise it and seek approval again; only publish after approval, then apply the
ready-for-agent label.

Comment thread .github/workflows/cleanup-caches.yml Outdated
Comment thread CLAUDE.md
Comment on lines +21 to +27
Tooling is pinned in `.mise.toml` and run through `mise exec --`. Install once
with `mise install`.

- **Build:** `swift build`
- **Build incl. tests:** `swift build --build-tests`
- **Test (all):** `swift test`
- **Test (single):** `swift test --filter MailchimpClientTests/serverURLDerivedFromAPIKey`

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Make the toolchain invocation match the documented pinning.

The file says tools run through mise exec --, but the canonical build/test examples invoke raw swift commands. Prefix those examples with mise exec --, or explicitly document the required activation step.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CLAUDE.md` around lines 21 - 27, Update the Build, Build incl. tests, Test
(all), and Test (single) command examples in CLAUDE.md to invoke Swift through
the pinned toolchain using mise exec --, keeping the existing command arguments
and test filter unchanged.

Comment thread README.md
Comment thread README.md
Comment thread Scripts/lint.sh
SWIFTLINT_OPTIONS=""
fi

pushd $PACKAGE_DIR

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Guard pushd (and popd) against failure.

Since set -e is intentionally removed, a failed pushd does not stop the script — subsequent run_command steps (in-place swift-format, swiftlint --fix, swift build) would then run from the wrong directory and either corrupt results or mask real failures. Apply the same guard to popd on Line 97.

🛡️ Proposed fix
-pushd $PACKAGE_DIR
+pushd "$PACKAGE_DIR" || exit 1

And at Line 97:

-popd
+popd || exit 1
🧰 Tools
🪛 Shellcheck (0.11.0)

[warning] 66-66: Use 'pushd ... || exit' or 'pushd ... || return' in case pushd fails.

(SC2164)


[info] 66-66: Double quote to prevent globbing and word splitting.

(SC2086)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Scripts/lint.sh` at line 66, Guard the directory-stack operations in the
script: update pushd before the run_command steps and the corresponding popd
near the end to fail immediately when either command fails. Preserve the
existing command flow while ensuring subsequent formatting, linting, and build
operations cannot run from an unintended directory.

Source: Linters/SAST tools

Comment on lines +107 to +121
internal static func serverURL(forAPIKey apiKey: String) throws -> URL {
guard let datacenter = apiKey.split(separator: "-").last,
!datacenter.isEmpty,
datacenter != apiKey[...]
else {
throw ClientError.invalidAPIKey
}
guard
let url = URL(
string: "https://\(datacenter).api.mailchimp.com/3.0"
)
else {
throw ClientError.invalidAPIKey
}
return url

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Reject URL delimiters in the datacenter suffix.

Line 108 interpolates the entire suffix into the URL. For example, key-evil.com/ produces a URL with evil.com as the host, redirecting requests and their Basic-auth header away from Mailchimp. Allowlist the datacenter token before constructing the URL and add a rejection test.

Proposed fix
   guard let datacenter = apiKey.split(separator: "-").last,
     !datacenter.isEmpty,
-    datacenter != apiKey[...]
+    datacenter != apiKey[...],
+    datacenter.range(
+      of: #"^[A-Za-z0-9]+$"#,
+      options: .regularExpression
+    ) != nil
   else {
     throw ClientError.invalidAPIKey
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
internal static func serverURL(forAPIKey apiKey: String) throws -> URL {
guard let datacenter = apiKey.split(separator: "-").last,
!datacenter.isEmpty,
datacenter != apiKey[...]
else {
throw ClientError.invalidAPIKey
}
guard
let url = URL(
string: "https://\(datacenter).api.mailchimp.com/3.0"
)
else {
throw ClientError.invalidAPIKey
}
return url
internal static func serverURL(forAPIKey apiKey: String) throws -> URL {
guard let datacenter = apiKey.split(separator: "-").last,
!datacenter.isEmpty,
datacenter != apiKey[...],
datacenter.range(
of: #"^[A-Za-z0-9]+$"#,
options: .regularExpression
) != nil
else {
throw ClientError.invalidAPIKey
}
guard
let url = URL(
string: "https://\(datacenter).api.mailchimp.com/3.0"
)
else {
throw ClientError.invalidAPIKey
}
return url
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Sources/Spinetail/MailchimpClient.swift` around lines 107 - 121, Update
serverURL(forAPIKey:) to validate the extracted datacenter token against the
allowed Mailchimp datacenter format, rejecting URL delimiters and any other
invalid characters before interpolation. Preserve invalidAPIKey errors for
rejected tokens, and add a test covering a suffix such as “key-evil.com/” to
ensure no non-Mailchimp URL is constructed.

Comment thread Sources/Spinetail/MailchimpClient.swift
leogdion and others added 2 commits July 22, 2026 08:59
Drops the redundant Mailchimp prefix from the campaign DTO (the module
is already Mailchimp-specific); MailchimpClient keeps its prefix. Updates
all references and doc mentions in CLAUDE.md and README.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…/CI nits.

Keep paging until an empty page if Mailchimp omits total_items, clean tag caches correctly, and document the full MailchimpClient surface plus error propagation.

Co-authored-by: Cursor <cursoragent@cursor.com>
@leogdion
leogdion merged commit 1932c48 into main Jul 23, 2026
16 checks passed
@leogdion
leogdion deleted the v1.0.0 branch July 26, 2026 21:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant