我用 AI 做了第一款 macOS 应用,也重新学了一遍怎样做软件
我用 AI 做了第一款 macOS 应用,也重新学了一遍怎样做软件
dong4j前言
几个月没更新博客,这段时间基本都花在开发一款原生 macOS 应用上。
这是我做的第一款 macOS 应用。我从 2015 年开始使用 Mac,很喜欢 macOS 简洁的界面和系统体验,也一直想亲手做一款原生应用。
过去十年我主要写 Java 后端,熟悉的是 Spring Boot、数据库和服务器,对 SwiftUI、App Sandbox、签名、公证和 StoreKit 基本没有接触。这次只能边做边学,从界面和数据存储,一路做到签名、公证、支付和上架。
这款应用就是 Starcat。现在它已经以 Starcat for GitHub 的名字上架 Mac App Store,也可以通过官网 DMG 和 Homebrew 安装。写这篇文章时,App Store 和 Direct 的公开版本都是 1.3.0。
最初准备写这篇文章时,我想从架构入手,把 SQLite、本地搜索、RAG、双渠道分发和海外支付梳理一遍。但回头翻完整个开发记录,很多问题并不属于架构本身。需求没有讨论清楚,后面的设计和实现就会反复调整;UI 规范没有统一,不同 AI 会按照各自的理解完成页面;自动化测试通过,也只能说明代码覆盖到的部分没有问题,真实 App、账号和服务仍然需要重新验证。
随着应用接近发布,开发者账号、App Store 审核、备案、支付和结算又陆续出现。它们不属于某个具体功能,却可能直接卡住上架和收款。如果只写 SQLite、RAG 和支付架构,Starcat 从开发到交付的过程就少了一大截。
下面就从 Starcat 最初要解决的问题写起,依次讲需求和架构怎样确定、开发中踩过哪些坑,以及最后如何完成 App Store 上架、Direct 分发和支付对接。过程中形成的文档、规范、工具和 Skills,会结合对应的问题一起说明。
👀 Starcat 是什么?
Starcat 是一款 macOS 原生 GitHub Stars 管理工具。它把 starred repositories 同步到本地 SQLite,支持标签、私有笔记、阅读状态、README、全文搜索、Release 追踪和仓库健康度。用户可以把真正想长期保留的仓库显式加入知识库,再使用 RAG 做带引用的问答。
1.3.0 增加了「我的项目」、全局与单仓库洞察、macOS 桌面小组件,以及 Alfred、uTools、Raycast 搜索入口。界面支持 18 种语言。AI 功能依赖 Pro 与用户配置的 provider;不配置 AI,Stars 管理和本地搜索仍可使用。
- 官网与 Direct 下载:starcat.ink
- Mac App Store:Starcat for GitHub
- Homebrew:homebrew-starcat
- 公开问题反馈:starcat-pro
Starcat 的起点
我是 GitHub 的重度使用者,平时经常在上面翻项目。看到一个项目做得不错,或者认同作者的思路,我会点个 Star;遇到以后工作中可能用到、自己也想继续学习的项目,同样会 Star。时间长了,我的 Stars 已经接近两千个。
GitHub 对 Stars 的管理比较简单,主要依靠自定义 Lists 分组。数量少的时候还够用,积累到一定程度以后,找项目就变得很麻烦。工作中遇到一个问题,明明记得以前在 GitHub 看过类似的项目,却想不起仓库名和 owner,只能换着关键词搜索,再到 Stars 列表里一页页翻。
我长期用过 OhMyStar,后来又看了 Starship 和 Starflare。这些工具改善了 Stars 的浏览和整理,但仍然没有完全解决我的问题。我的 Star 实际上包含两种不同的意思:一种是对项目和作者表示认同,另一种才是把项目当成以后可能使用的工作资料。
第一类项目点完 Star 以后,也许不会再打开,没有必要逐个分类和补笔记。第二类项目才需要认真整理,记录它解决的问题、自己的判断和可能用到的场景。把所有 Stars 都当成待整理的收藏,最后只会多出一项长期维护工作。
我用过的这些工具,重点仍然是把 GitHub Stars 同步下来,再提供分组、标签、搜索和 README 浏览。它们可以改善 Stars 的管理,却没有把「表示认同」和「准备在工作中使用」分开。第二类项目需要记录用途、补充笔记,并且能够按照问题和使用场景重新找到,这正是我想通过 Starcat 解决的问题。
Starcat 因此在基础的 Stars 管理之外增加了独立知识库。只有准备学习、使用或长期保留的项目才需要进入知识库,成为后续整理、检索和 RAG 问答的数据来源。
知识库里的项目可以来自自己的 Stars,也可以直接从发现和探索中加入。看到一个值得研究的项目,不需要先去 GitHub 点 Star,再回到 Starcat 整理;Star 表达认同,加入知识库表示准备继续使用,两种操作各自保留原来的含义。
这就是 Starcat 最初的产品思路。它先解决 Stars 的日常管理,再把真正有用的项目整理成本地知识库。既然我本来就想做一款原生 macOS 应用,这个困扰了自己很久的问题,正好成了开始动手的理由。
从 Java 到 SwiftUI
2026 年离职后,时间空了出来。我想验证一件事:靠现在的 AI 工具,一个后端开发能不能把原生 macOS 应用做到上架。
我一开始就决定把 Starcat 做成原生 macOS 应用,所以没有选择 Electron 或 Tauri 这类跨平台框架。除了做出一个自己需要的 Stars 管理工具,我也想借这个项目完整了解 Apple 平台的开发方式,从 SwiftUI、AppKit、窗口和菜单,到 Keychain、Sandbox、签名、公证、StoreKit、App Store 和桌面小组件。这些技术当时大多没有接触过,正好可以在一个真实项目里逐步学下来。
最终技术栈是 SwiftUI + 少量 AppKit、GRDB.swift + SQLite、URLSession 和 Keychain,最低系统是 macOS 15。
AI 在这里确实有帮助。我不需要先把整本 Swift 教程读完,遇到问题可以顺着当前代码学。但页面能够运行,只说明最外面的一层已经出来了。
SwiftUI 开发中,状态生命周期很容易引起问题。.task 重复执行、ViewModel 跟随 View 重建、列表 identity 不稳定,都会造成重复请求、界面闪烁,或者数据需要第二次操作才能显示。菜单栏、窗口位置、标题栏、WebView 和焦点管理涉及系统层行为时,仍然需要通过 AppKit 处理。
Keychain 的问题主要出现在开发和测试环境。开发构建的签名发生变化以后,历史 Keychain Item 的访问控制可能不再匹配,GitHub Token 就无法正常读取。单元测试启动时,如果 test host 访问 Keychain,macOS 还可能弹出授权窗口。测试进程没有可以交互的窗口,授权无法完成,最后导致 testmanagerd 超时。
项目后来统一通过 TestEnvironment.isRunning 识别测试环境。测试期间跳过启动阶段的 Keychain 检查和 Session 恢复,也不触发系统授权。这项约束随后写入项目文档,新增启动逻辑时必须继续遵守。
后来不少规则都是这样产生的:先踩到具体问题,修完以后再决定要不要把防线留在代码和文档里。
总体架构
确定原生技术栈以后,Starcat 的整体架构按照本地优先的思路分成三层。
最上面是用户直接接触的功能,包括知识库管理、搜索与发现、AI / RAG 工作台,以及仍在开发中的 Agent 工作台。中间是应用与领域服务,负责依赖装配、GitHub 同步、仓库管理、AI Pipeline,以及 RAG 和 Agent 的运行逻辑。底层负责本地数据,主要使用 GRDB、SQLite、FTS5、Embedding、Keychain 和磁盘缓存。
Agent 工作台目前还在开发,Release 构建暂时隐藏了入口。架构图先标出了它在系统中的位置,相关功能完成开发和验收后,会随下一个版本发布。
GitHub API 提供 Stars、仓库信息、README 和 Release 数据。Trending、Weekly、Discovery、Wiki 和 Recommend 这类公共内容通过 Starcat API 获取。AI 请求发送到用户自己配置的 Provider,Starcat 不统一托管用户的 AI Key。
标签、笔记、阅读状态和知识库等用户数据目前保存在本机数据库。数据模型已经为 CloudKit 预留同步字段和边界,但生产版本还没有接入 CloudKit Adapter,架构图中的虚线表示后续同步方向。StoreKit 和系统通知继续使用 Apple 提供的服务。后面的本地存储、RAG、支撑 API 和双渠道分发,都是在这套结构上继续展开。
需求的开发流程
AI 时代的开发流程
AI 可以很快完成页面和功能,但大需求如果没有经过整理就开始开发,后面很容易反复修改。支付、RAG、Agent 工作台会跨越多个模块,也包含不少需要提前确认的取舍。需求只留在聊天记录里,目标、决定、改动范围和验收条件就会混在一起。Agent 拿到局部上下文后直接实现,等其他模块接进来,才发现前面的设计缺少边界,只能重新讨论并修改已经完成的代码。
UI 也遇到过同样的问题。项目里没有统一规范时,不同 Agent 会按照各自的理解处理关闭按钮、刷新状态、标题字号和页面间距。每个页面单独运行都没有问题,放到主窗口、RAG 工作台和 Agent 工作台里却明显不一致。功能做完以后,还要重新统一组件和布局。
多个 Agent 接力开发时,如果后一个 Agent 不知道前面的设计背景,也会按照自己的理解补代码。已经准备废弃的接口被继续保留,临时兼容又变成新的调用路径;自动化测试刚通过,进度文档就被标成完成,到了真实支付或 App Store Connect 验收时才发现还有工作没有做。已经结束的任务只能重新打开。
这些重复修改不是因为 AI 写代码太慢,而是开发前没有管理好需求,项目里没有统一的 UI 规范,多个 Agent 之间也缺少完整、可以继续使用的上下文。为了减少这类返工,大需求需要一套固定流程,把需求讨论、方案确认、开发任务和最终验收分开,并把每个阶段的结论保存在项目里。
在 AI 编程工具出现以前,我就一直坚持一件事:产品和开发文档应该跟着代码一起走。需求、设计、技术决策和操作说明都放在仓库里,随着代码版本一起修改和提交。这样回到某个版本时,既能看到当时的代码,也能知道为什么要这样实现。
实际开发中却很难一直做到。代码每天都在变化,文档往往只在立项或者准备交付时集中写一次。功能已经调整了几轮,设计文档还停在最初的方案。时间久了,文档只能说明项目曾经打算怎么做,无法反映代码现在是什么状态。
AI 编程工具出现以后,文档的地位发生了变化。以前可以先把代码写出来,再补充设计和使用说明;现在让 AI 开发一个大需求,必须先把目标、范围、业务规则、技术方案和验收条件写清楚。这些文档会直接成为 AI 的提示词和上下文。描述不准确,AI 就会按照错误的理解继续开发;缺少必要边界,它也会自己补全,而且通常补得很快。
对 AI-Native 项目来说,文档已经不只是代码完成后的说明,也是开发开始前的输入。代码质量很大程度上取决于前面的需求和设计是否准确。大需求开始时,最先投入时间的应该是问题整理、需求分析和方案设计,等这些内容确认以后,再让 AI 根据文档完成实现。
这也是我开始寻找 Spec-Driven Development 工具的原因。我需要的不只是一个保存 Markdown 的目录,而是一套能把需求、设计、开发和验收串起来,同时让文档与代码在同一个仓库里迭代的流程。
Spec Kit 与 SuperSpec
GitHub Spec Kit 和 SuperSpec 的出发点很接近:先把需求写成结构化文档,再让 AI 按文档推进设计、任务拆分和实现。这样比丢给 AI 一句话就开写稳得多,需求、计划和代码之间也有东西可以核对。
Spec Kit 给出的是一套完整流程:Spec → Plan → Tasks → Implement。每一步都会生成 Markdown,后面一步以前面的产物为输入,还带 Checklist、跨文档一致性检查和可扩展 Workflow。它的优势是标准化,换一个开发者或换一个 AI 工具,仍能沿着同一条路径工作。新项目、多人团队,以及需要需求追踪和合规记录的项目,会比较适合这种做法。
完整也意味着更多步骤。一个需求要先进入它的命令和模板,再按既定阶段生成文档。如果项目已经有自己的设计规范、进度索引和验收流程,还要决定 Spec Kit 的 spec.md、plan.md、tasks.md 与现有文档谁说了算。它的官方文档也把文档保存方式留给团队选择:可以持续修改,可以每次新建一份历史记录,也可以只把 spec.md 当长期来源。工具提供了流程,知识怎样留下来仍要自己定。
SuperSpec 针对这类负担做了不少收缩。Standard 模式只有 proposal.md、Checklist 和 tasks.md,大需求再切到 Boost,补上详细 Spec、设计和交叉校验。它还提供 sync、resume、依赖管理、状态查看、搜索和归档,中文模板与多种 AI 工具也考虑到了。对还没有建立文档规范、又希望快速接入 SDD 的个人开发者或小团队,SuperSpec 比从零发明流程省事;中断会话以后恢复上下文,也是很实用的功能。
Starcat 的文档流程
两套工具都能把需求、方案、任务和实现串起来,但也会把 CLI、Slash Commands、配置文件、文档命名、分支和归档状态一起带进项目。完整走一遍,步骤和文档都不少。我看完以后,还是觉得太麻烦。
最后我没有接入 Spec Kit 或 SuperSpec,而是借用 SDD 的基本思路,按自己的方式搭了一套更简洁的流程:编码前先澄清需求,把方案和任务分开,开发过程中持续核对,结束后保存可以复盘的结果。后来的文档目录、大需求开发流程,以及 CLAUDE.md、AGENTS.md 和 DESIGN.md,都是沿着这套思路逐步形成的。
小需求写一份简单文档就够了,把目标、改动范围和验收方式记清楚,做完以后一起归档。支付、RAG、Agent 工作台这类大需求会跨多个模块和开发阶段,过程中还会出现方案取舍与需求调整,不能把所有内容挤在同一份简短文档里。
Starcat 因此把大需求拆成不同阶段,对应文档全部留在项目里。开发时它们给 AI 提供上下文,完成后用于复盘,后续也可以进入项目知识库继续检索。
Starcat 后来把文档按生命周期分开:
1 | 原始想法 |
以支付对接为例,最初的问题其实很散:App Store 用 StoreKit,官网要不要卖 License,Creem 能不能用,以后要不要换网关,退款后怎样撤权。第一份「需求讨论」只整理目标、用户流程和待决策项,不急着写类名。
这个阶段我会使用 grill-me Skill 完善讨论内容。它会沿着设计中的决策分支逐项追问,每次只处理一个问题,同时给出推荐答案;能从现有代码和文档中确认的内容,先由 AI 自己查清楚。等目标、边界、依赖和例外情况基本说透,再把结论整理回「需求讨论」文档,作为后续方案设计的输入。
讨论收口以后才有「正式方案」。这里会明确 App Store 和 Direct 的渠道边界,确定业务层只依赖统一权益,不让 StoreKit 或 Creem 判断散落到每个页面。到了「详细设计」,才继续写客户端协议、后端接口、本地存储、webhook 和测试策略。
随后是 Checklist。它把文档、客户端、后端和验证拆成能逐项确认的工作。开发中发现方案缺口,就回到对应文档修正,不在聊天里悄悄改变设计。
Starcat 的大专项通常会留下审查报告、验收步骤和结果报告。审查报告记录这一轮到底发现了什么;验收步骤列出必须在真实 App、真实账号或真实服务里点击的内容;结果报告则把「已经完成」「仍需上线配置」「没有纳入本期」分开。
支付专项的结果报告就是这样写的:客户端和 License API 的首期代码、测试已经完成,Creem 的 test/live 产品、API key、webhook secret 和真实购买退款仍待联调。两句话不能合并成「支付已经上线」。
根目录的 docs/功能实现总览.md 是最后一层。它只记录已经确认的进度。Agent 不能看到代码过了测试就自己勾选;需要先给出拟写入内容,等我确认后再更新。
这套流程看起来比直接写代码慢。对于两小时能做完的小改动,我也不会强行建八份文档。大需求则完全不同。前面多花一点时间把边界写清楚,后面少的是跨文件返工,而且新的 Agent 可以接着做,不必重新把整段历史讲一遍。
我还参考了对 Karpathy 四条 AI 编程原则的拆解。文章总结了四个原则:明确假设和分歧、优先选择简单方案、限制修改范围,以及为任务设置可验证的目标。Starcat 将这些原则落实到需求讨论、正式方案、开发 Checklist 和验收报告中。更换 Agent 或重新开始会话时,可以继续使用项目里已经保存的结论,不需要依赖上一段聊天的上下文。
DESIGN.md
开发一段时间后,UI 不一致的问题开始反复出现。同一个关闭按钮、刷新图标、标题字号和页面间距,在不同功能里经常出现不同的实现。单独修改某个页面只能解决眼前的问题,后续让 AI 开发新界面时,同类问题还会再次出现。
项目里当时还没有统一、可执行的 UI 规范。需求中写「使用 Apple 原生风格」或者「和现有页面保持一致」,不同 Agent 的理解并不相同,做出来的字号、间距、颜色和交互方式也会有差异。
为了解决这个问题,我把开发过程中已经确认的 UI 规则整理到根目录的 DESIGN.md。这份文档是给 AI 执行的设计约束,里面写明 Starcat 应该像 Finder、Mail 和 Xcode Organizer 这类 macOS 工具:信息密度高,状态清楚,少用大卡片、渐变和科技感装饰。主窗口、Agent 工作台和 RAG 工作台必须属于同一套设计系统。
它规定字号、间距、圆角、分栏职责和常见组件。不可违反的细则仍放在 docs/5-规范/,例如 .buttonStyle(.plain) 后必须关闭 Focus Ring、设置页独立按钮右对齐、Sheet 关闭统一使用共享组件。以后 Agent 接到 UI 任务,要先读 DESIGN.md,再读相关强制规范和现有代码。
DESIGN.md 成为 UI 开发和验收的统一依据。新增页面或调整组件时,可以按照文档检查布局、字号、间距、颜色和状态表达;发现问题时,也能定位到具体规则并统一修改。不同 Agent 开发的页面因此可以遵循同一套视觉和交互规范。
AI 协作规则
需求流程固定下来以后,还要让不同 AI 工具沿着这套流程接着做。我平时会用 Codex 调研、整理需求和制定开发计划,方案确认以后,再交给 Claude Code 或 Cursor 编写代码。实现完成后,可以重新用 Codex 或另一个 Agent 检查 diff、测试和 Checklist,最后由我处理 UI、真实账号、支付和发布等人工验收。
如果这些步骤只存在于各自的聊天记录里,每换一次工具都要重新介绍背景,还容易把已经确认的决定重新讨论一遍。Starcat 的做法是把交接内容写回项目。工具可以更换,后一个 Agent 直接读取前一个阶段留下的文档和代码状态。
这套协作依赖交接材料。前一个阶段要留下已经确认的目标、边界和验收条件,后一个阶段完成以后再补上代码状态、测试结果和未处理事项。换了工具,仍然可以从项目里的当前结论继续。
工具分工
Codex 通常负责前期工作。它先读项目文档和现有代码,把需求里的目标、边界、依赖和风险整理出来,再形成正式方案、详细设计和开发 Checklist。这个阶段仍然是讨论,只有方案确认以后才进入实现。
Claude Code 或 Cursor 接手具体开发。它们读取已经确认的方案和 Checklist,在约定范围内修改代码、补测试,并记录没有完成的部分。我不会把某个工具固定在某一类任务上,实际选择取决于当时要处理的代码和使用环境,交接材料保持一致就可以。
实现完成后,我会让 Codex 或另一个 Agent 再检查一遍。复核时不重新设计需求,而是对照原方案查看修改范围、测试结果和遗漏项。发现实现和设计不一致,就回到对应文档或代码修正。最后保留人工验收,自动化测试无法替代真实界面、Apple 账号、支付回调和发布结果。
共享上下文
需求讨论、正式方案、详细设计和 Checklist 保存这个需求本身的上下文。DESIGN.md 负责 UI 约束,测试规范、Git 规范和发版 SOP 记录具体操作要求。一个 Agent 完成当前阶段以后,后续工具从这些文件继续,不需要依赖上一段聊天记录。
Starcat 根目录还维护 CLAUDE.md 和 AGENTS.md。CLAUDE.md 主要供 Claude Code 使用,AGENTS.md 供 Codex、Cursor 和其他 Agent 读取。不同工具默认寻找的入口文件不同,数据库迁移、修改授权、发布操作和验收边界等规则会同时写进两个入口,避免换工具以后失效。
入口文件里只保留项目概况、必须遵守的边界和文档索引。模块设计、UI 规范、测试要求和发版流程仍由各自的文档维护。我参考过《写好 CLAUDE.md 的 8 条经验》,其中提到入口文件要短、规则要能执行,详细内容通过链接指向独立文档,必须执行的检查交给 Hook 或脚本。Starcat 后来也是按这个方式整理的。
任务交接
只写一句「按方案实现」无法完成任务交接。Codex 完成开发计划以后,要留下已经确认的目标、修改范围、技术决定、开发顺序和验收条件。Claude Code 或 Cursor 开发结束时,则要记录改了哪些文件、运行了哪些测试、哪些步骤仍需人工处理,以及有没有发现原方案没有覆盖的问题。
遇到新问题时,先判断它属于需求变化、设计遗漏还是实现错误。需要改变方案的内容回到对应文档讨论,确认后再继续写代码。单纯的实现错误直接修复,并补上可以复现问题的测试或验收步骤。这样下一次切换工具时,看到的是当前结论,而不是几段互相矛盾的聊天记录。
并行开发与文件冲突
多个 AI 同时开发时,最稳妥的做法是先把工作空间隔离开。对于中大型需求,我会使用 Git worktree。每个 Agent 在独立的目录和分支中开发,完成后再检查改动并合并回目标分支。这样可以避免不同任务直接修改同一批文件,也方便分别运行测试和审查代码。
Git worktree 也会带来额外成本。它会在另一个目录检出独立工作树,并与主仓库共享 Git 对象库。开发结束后,还要处理分支同步、代码合并和 worktree 清理。对于一个按钮样式、页面间距或者几行 UI 代码的调整,完整走一遍这套流程反而更麻烦。
所以我把并行开发分成两种情况:中大型需求使用 Git worktree 隔离,小需求直接在当前工作空间修改。后者操作更快,但多个 AI 会话可能同时碰到同一个文件。Codex 正在修改一个 SwiftUI 页面时,Cursor 或 Claude Code 并不知道,稍后保存就可能覆盖或者混入另一份改动。git status 只能看到文件发生了变化,无法说明现在是谁正在编辑。
AI File Wall 就是为这个场景做的。它在 Git 状态之外增加了一层 Agent 编辑状态。Codex 和 Claude Code 通过 Hook 自动登记正在处理的文件,Cursor 在修改前通过 MCP 执行 claim_files;长任务持续发送 heartbeat,结束后再执行 release_files。同一个文件被多个会话同时声明时,文件墙会直接标红。
文件墙不会阻止 Agent 写入,也不会替代 Git 合并。它只负责告诉我当前有哪些文件正在被其他 AI 修改。看到冲突以后,再根据任务范围决定暂停哪一个会话,或者让其中一个 Agent 完成后再继续。这样,小需求不必每次创建 worktree,同一个工作空间里的并行修改也不再完全不可见。
验证与验收
写代码的 Agent 先完成和改动范围对应的自动化测试,并说明测试覆盖了什么。复核 Agent 再对照正式方案和 Checklist 查看 diff,确认没有扩大修改范围,也没有把待配置、待联调的内容写成已经完成。
自动化验证之后还会保留人工步骤。UI 是否符合 DESIGN.md、登录是否能在真实 Apple 环境完成、Creem webhook 是否收到回调、退款以后 License 是否撤销,这些都需要在对应环境里验证。最终报告会把代码完成、环境配置和真实联调分开记录。
这些规则都是在项目推进中补出来的。哪个环节出现过理解偏差,就把对应边界写进入口文档、专项规范或 Hook。新项目开始时先写数据安全、修改授权、外部操作和验收边界,其他规则等遇到实际问题再补,不需要第一天就把入口文件写成几十页。
协作方式稳定以后,接下来要处理的是产品本身的数据边界。Starcat 保存的是用户长期积累的资料,这决定了它不能把网络服务当成唯一的数据来源。
本地优先
Starcat 处理的是用户长期积累的 GitHub 数据。标签、笔记、阅读状态和知识库选择可能会保存很多年,其中还包含个人判断和工作记录。如果这些内容全部依赖服务端,搜索和整理会受到网络与服务状态影响,用户也必须把自己的数据交给 Starcat 托管。即使公共服务暂时不可用,用户仍然应该能够打开、搜索和整理自己的资料。
因此 Starcat 采用本地优先的设计。搜索、筛选、阅读和知识库管理直接使用本机数据,网络服务负责补充和更新内容。本地优先减少了对服务端的依赖,也带来了缓存失效、数据迁移、索引同步和磁盘清理等问题。
数据边界
实现本地优先时,先要区分可重建数据和用户数据。Stars、仓库信息、README、Release 和公开指标会同步到本地 SQLite,GitHub 上仍然存在的内容可以重新拉取。标签、笔记、阅读状态、置顶和知识库选择由用户创建,升级、数据库迁移和清理缓存时必须保留。
GitHub Token、AI Key 和 Direct License 保存在 Keychain。Trending、Weekly 和公开分享页不属于个人数据,由 supports 服务统一采集和提供。
缓存与同步
本机和远端同时保存数据以后,每次读取都要判断缓存是否存在、是否过期,以及远端失败时还能不能继续使用旧数据。Starcat 的缓存分布在客户端本地、HTTP 请求和支撑服务三个位置,每一层解决的问题不同。
本地应用缓存
仓库信息、README、Trending、Weekly、Health、OpenSSF 和 Embedding 等内容会写入 SQLite 或磁盘。页面读取时先检查缓存状态:缓存仍在有效期内就直接显示;缓存已经过期则先显示旧内容,同时在后台刷新;没有缓存才等待网络返回。这样断网或接口暂时失败时,已经保存的内容仍然可以查看。
SWR 也会带来界面问题。Explore 的热门和新发布列表曾经出现过数据上屏后又刷新一次的情况。排查以后确认,第一次来自本地缓存,第二次来自远端更新,并不是同一个请求执行了两遍。后来把列表状态放到窗口会话层,通过完整查询条件保存有界快照,只让受影响的数据失效,避免切换页面时重复创建 ViewModel 和重新加载整张列表。
手动刷新、TTL 到期、数据版本变化、账号切换和缓存清理都会触发失效。缓存键必须包含账号和完整查询条件;切换数据库或账号时,还要清掉内存快照和正在执行的请求,避免把上一位用户或上一个筛选条件的数据显示出来。
第三方服务的网络缓存
调用 GitHub、README、Activity 和公共 API 时,Starcat 会保存 ETag 或 Last-Modified。下一次请求带上 If-None-Match 或 If-Modified-Since,远端内容没有变化时返回 304 Not Modified,客户端继续使用本地 payload,只更新缓存时间。这样可以减少重复下载,也能节省 GitHub API 配额。
304 只表示远端内容没有变化,并不会重新返回正文。如果用户刚好清除了本地 payload,只留下校验值,客户端必须再执行一次无条件请求。网络失败时也不会先删除已有缓存,页面继续显示旧数据,并保留下一次重试所需的状态。
支撑服务的内存缓存
ETag 和 Last-Modified 可以减少重复传输,但请求仍然会到达服务端。如果每个请求都重新查询 SQLite、组装 JSON 和压缩响应,多个客户端同时刷新时,服务端仍会重复执行相同的工作。
因此 Trending、Weekly 和 Discovery 等支撑服务还增加了进程内缓存。请求到达后先查内存,命中时直接返回已经组装好的响应;没有命中或者缓存过期时,才查询 SQLite,重新生成 payload 并写回缓存。Weekly 的全量数据还会提前生成 gzip,命中以后不需要再次压缩几千条项目数据。
不同数据使用不同的缓存粒度。Trending 按周期、语言和数量分桶,daily、weekly 和 monthly 分别使用对应的更新时间;Weekly 和 Discovery 的 bulk 接口返回完整快照,进程内只保留一个全量 entry。缓存条目同时保存 ETag 和 Last-Modified,客户端带着相同校验值请求时,可以直接返回 304 Not Modified。
服务端同步完成后会主动清除相关缓存,下一次请求重新读取 SQLite。TTL 只负责处理遗漏的失效情况,不是判断数据是否更新的主要方式。服务重启以后,内存缓存自然清空,再由下一次请求重新建立。它只减少数据库查询、JSON 编码和压缩开销,SQLite 仍然是数据来源。
数据库升级
数据边界确定以后,数据库升级就不能依赖删库重建。已经随正式版本发出的 migration 不再回改,新增字段和索引只能追加新的 migration。新安装会一次执行完整迁移链,老用户可能从几个月前的版本直接升级,两条路径最后必须得到相同的 schema。
服务端数据库出现问题时,可以在服务器上统一修复。桌面应用的数据库保存在每位用户的 Mac 上,里面可能已经积累了大量标签、笔记和知识库数据。版本升级必须通过数据库迁移保留这些内容,不能把删除本地数据库作为问题处理方案。
所以每次数据库变化都要验证三条路径:全新数据库能否建立,旧版本能否逐步升级,已有标签、笔记、知识库和订阅数据能否保留。缓存表可以重建,用户表必须经过迁移继续使用。
磁盘占用与缓存清理
本地缓存会持续占用磁盘。README、图片、公开指标、Embedding、Wiki、外部搜索结果和调试文件的增长速度不同,不能使用同一个过期时间,也不能通过一条删除语句统一清理。
Starcat 把缓存清理和用户数据重置分成两个操作。清理缓存只删除可以重新下载或重新生成的内容,标签、笔记、阅读状态、知识库选择、License 和 Keychain 凭据不会跟着删除。彻底重置本地数据属于单独的破坏性操作,需要明确提示影响范围。
多设备同步
本地优先先解决了单台 Mac 上的数据归属,多设备同步仍然需要单独实现。Starcat 当前的数据模型已经为 CloudKit 准备了 UUID、修改时间和同步边界,但生产版本还没有 CloudKit Adapter,用户数据仍以当前 Mac 上的 SQLite 为准。
后续接入 CloudKit 时,还要处理离线修改冲突、删除记录的 tombstone、账号切换、设备本地状态和隐私范围。标签、笔记等用户数据可以进入同步范围,GitHub 缓存、AI Key、私有项目数据和临时附件则需要分别限制。这个能力在真实同步和冲突测试完成以前,不会写成已经上线。
本地知识库
GitHub Star 表达的意思很多。有些项目会在工作中直接使用,有些准备以后研究,还有一些只是觉得做得不错,顺手点个 Star。后一类项目留在 Stars 列表里就够了,没有必要全部进入知识库。
Starcat 的本地知识库,是用户主动挑选出来的一组仓库资料。里面放的是工作中可能查询、项目里可能复用,或者准备继续研究的内容。早期的 RAG 设计曾把全部 Starred 当成默认数据源,知识库状态独立出来以后,这个前提也随之修改。现在只有明确加入知识库的项目才进入默认问答范围。
本地知识库从确定知识范围开始,接着整理资料、生成分片和索引,再处理问题理解、检索与上下文组装,最后让模型基于证据回答。任何一个环节处理得不好,最后看到的现象都可能只是「回答不准」。
资料怎样进入知识库
GitHub 的 Star 状态和 Starcat 的知识库状态分别保存。项目可以从自己的 Stars 中选择,也可以来自搜索中心、发现、趋势、热门、新发布和 Weekly。一个没有 Star 的项目同样可以加入知识库,Starcat 会把仓库元数据保存到本地,并保持 isStarred = false,不会替用户修改 GitHub 上的 Star 状态。
这个拆分也限制了 RAG 的检索范围。索引器和候选查询只认 repo_notes.library_state = 'in_library',不会因为一个项目出现在 Stars 或探索列表里就自动使用它。用户在问题里通过 @repo 指定项目时,候选范围还会进一步缩小;显式选择了一个仓库,就不能在没有命中的情况下擅自扩大到整个知识库。
项目入库以后,Starcat 从本地已有数据生成 RAG 资料。README 是主要内容,私有笔记记录用户自己的判断,已有的 AI 摘要提供压缩后的项目介绍,仓库元数据则包含名称、描述、语言、Topics、标签、阅读状态,以及本地缓存的 Release、Repo Health 和 OpenSSF 信息。
这几类内容不能混成一段文本。私有笔记通常很短,而且优先级高,完整保留更合适;README 有标题、代码块和表格,需要按原有结构处理;Stars、Forks、版本号和状态更适合精确查询。元数据因此只进入 FTS5,不生成向量,避免频繁变化的数字反复消耗 Embedding 请求。
索引构建也不会为了补齐内容临时调用 AI 或抓取网络。没有摘要就跳过摘要,没有 README 就先使用笔记和元数据。私有仓库只能使用本地已有且当前账号有权访问的缓存。
按固定字符数切 README 实现简单,但标题和正文容易分开,代码块可能从中间断掉,引用最后只能指向一段缺少上下文的文字。Starcat 使用 repo-aware 的 Parent-child 结构:仓库是最外层对象,README 再按 Markdown 标题形成章节和子章节,检索命中子分片以后,可以把所属章节的相邻内容一起带入上下文。
默认分片目标约 700 tokens,普通上限是 1100,单个代码块或表格的硬上限是 1600。很短的相邻章节会合并,过长章节先按子标题拆,再按段落处理,只有被拆开的长章节保留少量 overlap。代码块尽量保持完整,大表格拆分时会重复表头。私有笔记固定保留为完整分片,不套用 README 的切分规则。
分片还需要稳定身份。README 中间插入一个章节以后,后续 chunk_index 都会变化,所以 Starcat 另外使用由来源、章节路径和章节内序号组成的 chunk_key。chunk_index 只负责展示顺序,chunk_key 和内容哈希负责判断分片是否真的发生了变化。
README、笔记、摘要和元数据分别维护。某条笔记修改以后,只更新 notes 分片;README 变化只比较 readme 分片;元数据变化只刷新 FTS5。内容哈希没有变化时,旧向量可以继续复用,不需要重建整个知识库。
文本写入和向量生成也不是同一个时间点。分片会记录 pending、ready、failed、stale 和 keyword_only 等状态。FTS5 可以查询所有未排除的文本分片,向量检索只读取当前模型下的 ready 数据。切换 Embedding 模型以后,旧向量会进入待重建状态,但关键词检索仍然可用。
项目移出知识库时,已有分片不会立刻删除,Retriever 会根据知识库状态排除它。重新入库时可以复用未变化的缓存。账号切换则需要先暂停索引器、取消尚未完成的更新,并等待已经开始的写入退出,再切换 SQLite,避免旧账号的异步任务写进新账号数据库。
问题怎样被找到
用户的问题经常同时包含自然语言和结构化条件,比如「最近加入知识库、还没读过的 Swift 数据库项目」。只把整句话生成一个查询向量,很难稳定处理时间、状态、语言和排除条件。
Starcat 在检索前先生成一份 Query Plan。Planner 把问题拆成适合语义检索的 semanticQuery、适合字面召回的 keywordQueries,以及语言、状态、时间和项目范围等过滤条件。中文问题会同时准备中英文核心词,关键词分支使用经过转义的 FTS5 OR 查询,项目范围始终由 repo id 控制,不把仓库名混进分词结果。
涉及数量、分组和排名的问题会进入受限的本地统计 DSL,再由固定的参数化 SQL 执行。模型不能直接生成 SQL,也不能绕开 in_library 范围。Planner 返回的字段还会经过本地校验,格式错误或条件越界时按受控规则降级。
关键词和语义检索各自保留适用场景。仓库名、API、文件路径、错误码和笔记原句更适合 FTS5;只记得用途和大概印象时,向量检索更容易找到相关内容。两路检索并行执行,再通过 RRF 融合、去重和排序,Inspector 会保留每条结果来自 keyword、vector 还是 hybrid。
Embedding 在这里是增强能力。没有配置向量模型、缺少 API Key、当前候选没有可用向量,或者向量服务临时失败时,关键词结果仍然可以继续使用;关键词分支失败时,已有向量结果也不会被一起丢掉。只有所有已启用的检索分支都失败,整轮才会按检索错误处理。
分片完成融合以后,还要按仓库聚合。一个 README 不能凭借大量相似分片占满上下文,每个仓库有自己的命中上限,笔记、摘要、README 和元数据也使用不同权重。可选 Rerank 放在融合之后,默认关闭。没有真实评测数据时直接启用更重的排序模型,只会增加耗时和调用费用。
回答怎样保留证据
检索命中以后,Starcat 不会把 flat topK 直接塞给模型。每个仓库会形成一个 RepoContextBundle,里面依次放入仓库元数据、私有笔记、已有摘要、实际命中的子分片,以及必要的相邻章节。高分仓库可以获得更多上下文,单个仓库仍有硬上限,避免一份很长的 README 挤掉其他候选。
最终 Prompt 还要和系统提示、对话历史、附件、特殊 XML 资料以及输出预留共享模型窗口。Starcat 根据当前模型的 Context Window 分配预算,历史接近可用上限时才压缩较早对话,不按固定轮数机械总结。单项目深度问答使用的 RepoContext XML 和仓库洞察 XML 有独立预算,它们是本轮证据,不会混进普通 rag_chunks。
本地索引适合回答已经保存的内容,最新 Issues、Pull Requests 和 Releases 随时会变化。把这些信息长期写进向量索引,很快就会过期。Starcat 让 Planner 声明实时资料需求,执行层再根据候选仓库和用户授权临时访问 GitHub 或 External Search。
远程正文只参与当前问题,不写入 RAG 分片和长期会话;历史记录只保存资源类型、来源 URL、获取时间和结果状态等审计信息。问题明确要求最新资料而远程请求没有拿到可信结果时,Starcat 会停止生成,直接说明当前证据不足。普通网络失败只降级远程分支,不影响已经命中的本地资料。
这里的本地优先主要指索引、知识库范围、会话和引用保存在本地。用户配置远程 Embedding 或 Chat Provider 后,相应分片、问题和检索结果仍会发送给所选服务。私有仓库名称不会在未经授权时进入外部搜索查询,文本附件也只在当前会话使用。
模型拿到相关分片以后,仍然可能忽略证据或写出上下文里没有的结论。Starcat 在生成 Prompt 前先为每条证据分配 [S1] 这样的编号,并绑定仓库、分片、来源、章节、分数和命中方式。模型只能使用这些已有编号,自己编出的标记不会变成有效引用。
引用解析后来又补了一层可见文本过滤。代码块、行内代码、转义文本和 Markdown 链接标签里的 [S1] 不计入回答引用,避免示例内容干扰统计。没有本地分片、特殊 XML、附件或远程临时资料时,Generator 不会凭模型自身知识继续回答,而是返回知识库为空、没有索引、没有候选项目或没有相关证据等具体状态。
会话保存引用元数据,不复制保存整个分片正文。用户打开历史回答时,Inspector 再从当前本地索引读取内容;原分片已经更新或清理时,仍能看到仓库和来源信息,同时明确提示正文无法回放。
RAG 质量验证
只看最终回答,很难判断问题出在资料、检索还是模型。知识库页因此提供了召回测试,可以直接输入问题,查看候选仓库、命中分片、来源、排序和检索方式,不需要每次执行完整问答。RAG 工作台则把 Query Plan、执行过程、引用证据和索引覆盖率放在同一个界面里。
项目里还准备了脱敏评测模板,用固定的知识库快照、Embedding 模型、Provider 和 TopK 逐条记录 Recall、nDCG、引用覆盖率、拒答准确率和耗时。结构测试可以证明查询范围、分片状态和引用规则没有被破坏,真实数据评测才用于判断召回质量。后者仍是一项持续工作,不能因为单元测试通过就写成 RAG 已经达到稳定准确。
回头看,接入模型只占了 RAG 工作的一小部分。更多时间花在知识范围、索引更新、召回质量和引用证据上,模型负责最后的回答生成。
Agent 工作台
RAG 工作台解决的是检索和回答:从知识库里找材料,整理上下文,再给出带引用的结果。Agent 工作台继续往前走一步,它接收一个目标,调用搜索、仓库分析、知识库和其他工具,保留执行过程与产物,并在写入数据或触发外部操作前等待确认。
Starcat 已经有 Agent Run、消息、步骤、Artifact、Tool Call 和审批记录等底层结构,也完成了工作台原型。目前真正启用的内置 Agent 只有 Weekly Report 和 Repo Insight,其他能力还在整理统一上下文、工具复用和数据迁移。Release 构建仍然隐藏入口,所以它没有作为 1.3.0 的公开功能宣传。
这部分没有急着开放,是因为 Agent 的完成条件比聊天更难判断。生成一段回答只需要检查引用,执行任务还要处理工具权限、中途失败、可重试步骤和写操作确认。等这些边界和真实场景验收完成以后,工作台才适合交给普通用户。
Xcode 工程之外
支撑项目
最开始我以为 Starcat 主要就是一个 Xcode 工程。做着做着,一些能力天然不能放进客户端。
Trending 没有稳定的官方 API,需要独立采集和缓存。Weekly 有自己的数据源和审核节奏。分享页要让没装 Starcat 的人也能打开。Direct 支付需要保存 provider API Key、接 webhook、维护 License,这些更不能塞进 App。
于是 supports/ 下面逐渐出现了一批独立仓库。官网、公开文档和 Homebrew tap 负责产品交付;CLI、MCP、Alfred、uTools、Raycast 和浏览器插件负责外部集成;本地化项目和 starcat-skill 处理多语言与 Agent 接入;公共 Go API 和 License API 承担服务端能力。
这些项目有各自的 remote、CI 和版本号,主仓只把对应目录当作工作区,不把它们强行塞进同一个 Git 历史。
用户自己的 Stars 从 GitHub 进入本机数据库。公共发现数据由 supports 服务采集后提供 DTO。Direct 支付则经过 License API 处理 checkout、webhook 和设备授权。三条数据流分开以后,哪一层可以看到什么数据也更清楚。
starcat-cli 与 MCP
App 之外很快又有了新的入口。终端需要查询 Stars,Alfred、uTools 和 Raycast 需要调用全局搜索,Codex、Claude Code 这类 Agent 还需要读取仓库上下文、标签和知识库。如果每个入口都直接读取 SQLite,再各自实现一遍权限、搜索和写入逻辑,维护成本会越来越高,本地数据也容易被绕过业务规则修改。
为此我做了 starcat-cli。它既是跨平台命令行客户端,也是面向 AI Agent 的 MCP bridge。CLI 不直接读取 Starcat 数据库,所有请求仍然交给 Starcat App 提供的 MCP Tools;权限检查、Pro 校验、审计和写入规则继续由 App 控制。
日常可以在终端里查询统计、搜索仓库、读取 README 和摘要,也可以让 Agent 通过 starcat mcp 使用同一组结构化工具。写操作默认 dry-run,只有明确传入 --apply 才会落库。这样接入 Alfred、uTools、Raycast 或新的 Agent 时,只需要适配输入和展示,不必再复制 Starcat 的业务逻辑。
浏览器插件
Starcat 解决的是 GitHub Stars 的整理问题,但平时发现项目的地方仍然是 GitHub 和 Google。浏览仓库时,如果想查看自己在 Starcat 里保存的笔记、Health、Wiki 和相似项目,每次都切回 App,操作很割裂。
于是我又做了 Chrome 插件 和 Safari 插件。它们会在 GitHub 仓库页面展示相似仓库、Wiki、私有笔记以及 Starcat 已缓存的 Health 和 OpenSSF 数据,也可以直接触发 App 里的 CodeFlow、Codebase 操作。在 Google 搜索结果中,已经收藏的 GitHub 仓库会出现 Open in Starcat 和 Health 标记。
两个插件使用相同的功能边界,只访问 Starcat 在 127.0.0.1 上提供的本机接口,并通过 Local API Key 鉴权。插件不直接请求 GitHub API、Starcat 后端、OpenSSF 或 AI Provider,也不会持有 GitHub Token 和 AI Key。需要新增浏览器能力时,先扩展 App 的本机接口,再让 Chrome 和 Safari 同步接入,数据权限仍由 Starcat 统一处理。
这里也要区分开发完成和商店上架。两个插件的源码和本机接口联调已经完成,Chrome Web Store 与 Safari App Store 仍在准备隐私资料、审核素材和正式包,目前还不能从商店直接安装。
API 聚合
支撑项目变多以后,命令、部署和费用也跟着增加。
独立开发里很容易把注意力全放在“选哪台服务器”。我也参考过《独立开发者的穷鬼套餐》这类从域名、部署、数据库一路算到支付的成本清单。
具体的免费额度随时会变。Starcat 早期尽量选择可以从小规模开始的托管服务,把时间留给产品;涉及用户数据、License 和发版时,监控、备份和恢复仍然要保留。
Starcat 最初有 Trending、Weekly、Sharing、Wiki、Recommend 和 Discovery 六个业务 API,分别部署在 Fly.io。单个服务的配置都不高,但六套 Machine、Volume 和部署流程累积起来,每个月仍然接近 20 美元。
当时比较了三种方案。继续独立部署改动最少,但解决不了常驻实例和多套运维流程的成本;把仓库与部署全部合并最省事,却会破坏各个 API 原有的开源、自托管和独立发布边界。最后决定保留业务仓库,只合并生产部署。
六个 API 分别导出统一的 server 包,再由 starcat-api 装配到同一个 Go 进程。路径冲突通过 X-SC-Svc 请求头分流,鉴权、CORS、GitHub 接口和环境变量处理收敛到公开的 starcat-api-kit。各个服务继续使用独立的 SQLite 文件,只是共用一个 Volume;License API 涉及支付和用户权益,仍然独立部署。
这套聚合已经完成代码、Fly Machine、Volume、Secrets、首轮迁库和只读业务验证,但生产切流还没有发生。验证结束后,聚合服务重新进入维护状态,六个旧 API 仍在生产写入。计划在 1.4.0 切换前完成最终数据同步、写入冻结和全链路验收。技术方案跑通了,不代表生产迁移已经结束。
Makefile Explorer
Starcat 有 App Store 和 Direct 两套运行、构建与打包入口,还有后端启动、Fly.io 状态检查、官网、本地化和 Homebrew 等操作。我把常用命令逐渐收进 Makefile,希望不再记脚本路径和参数。
过了一段时间,Makefile 本身又变成了问题。target 太多,新开的项目还有嵌套 Makefile。每次都要打开文件搜索,或者先运行 make help,找到以后再回终端执行。
于是我做了 Makefile Explorer。它会把工作区里的 Makefile 和 targets 放进 VS Code 的树形面板,双击可以执行,右键能复制命令、带参数运行或跳到定义。插件也已经放到 VS Code Marketplace。
这个插件不是 Starcat 的功能,也不是什么宏大的开发者工具计划。就是我每天操作 Makefile 时觉得麻烦,顺手把麻烦做成了一个可以复用的东西。
Skill 沉淀
Makefile 把常用命令集中到了一起,解决了命令难找、脚本路径记不住的问题。但它只能告诉我有哪些命令,不能告诉 AI 在什么情况下应该使用哪一个,执行前需要检查什么,操作会修改哪些外部状态,失败后又该怎样恢复。
例如发布 Starcat,App Store 和 Direct 使用的是两套不同流程。App Store 需要生成并检查 archive,Direct 还要处理签名、公证、DMG、Sparkle、官网和 Homebrew。Agent 如果只看到几个相似的 Makefile target,很容易选错命令,或者把生成安装包误认为已经完成发布。
所以我开始把这些需要判断、确认和恢复的操作整理成 Skill。Makefile 继续负责执行命令,Skill 负责说明使用场景、前置检查、操作顺序和安全边界。
现在仓库里有一批 Starcat 自己的 Skills:
starcat-release区分 App Store、Direct 和基础内测发布。starcat-supports-ops处理后端服务、Fly secrets、健康检查、备份与恢复。starcat-backend-release管理多个独立 API 仓库的 PR、tag 和部署边界。starcat-localization-sync固化 String Catalog、XCLoc、翻译审核和 18 种语言的同步过程。starcat-public-site-and-promo处理官网、Changelog 和多个公开仓库的推广内容。starcat-weekly-import要求先识别官方仓库、展示候选、等待确认,再写入生产数据。
双渠道发版
拿 starcat-release 来说,它不会只告诉 Agent 执行哪个 shell。它先判断要走 App Store 还是 Direct,再检查分支、dirty worktree、tag 和签名条件,说明会产生哪些外部影响,然后停下来等确认。发生一半失败时,也要知道哪些步骤可以安全重跑。
两条正式路径不能混用。App Store 通过 make package-appstore 生成并检查 archive,确认 Bundle ID、Sandbox、签名和构建内容,再由 Xcode Organizer 完成 Validate 与上传;生成 archive 不能写成已经发布。Direct 则要走 Developer ID 签名、公证、staple、Gatekeeper、DMG、Sparkle appcast、官网和 Homebrew。脚本负责执行步骤,Skill 负责选择入口、检查前提和保留失败后的恢复方法。
本地化同步
Starcat 扩展到 18 种语言以后,本地化也不再是把中文交给 AI 翻译一遍。运行时的单一来源是 Localizable.xcstrings,公开协作使用 Xcode 导出的 XCLoc。新增文案以后,要先同步 key 和 source,再生成翻译初稿,处理占位符、代码、URL 和其他不能改动的字面量,最后导回 App。
项目把本地化分成四个状态:AI 初稿、翻译批准、导入 Catalog 和正式发布。AI 生成完成只能进入 needs-review-translation,不能直接算成已经支持该语言。即使维护者接受 AI 翻译风险,编译、界面截断和 Arabic RTL 仍要单独验收。
这些步骤后来写进 starcat-localization-sync。它会先审计 Catalog 和公开翻译包,区分这次要改 XCLoc、主 Catalog、locales.json 还是 AppLocale,能 dry-run 的操作先 dry-run。这样“18 种语言已经有翻译”和“18 种语言已经可以发布”不会被写成同一个状态。
Weekly 情报入库
Weekly 的 AI 情报最初来自新闻摘要、文章和项目清单,很多内容只有产品名,没有 GitHub 地址。手动录入时,需要逐条搜索发布方、确认官方仓库、检查 canonical 地址,再排除 fork、镜像、模型权重页和同名的第三方实现。
仓库确认以后,还要整理 owner/repo、批内去重、生成导入 JSON、检查 Weekly 当前允许人工写入的来源,先做 dry-run,再带管理员权限提交。接口返回 202 Accepted 时任务刚刚入队,还要继续查询批次状态,直到 success、partial_success 或 failed。这套流程重复而且容易漏步骤。
为了减少这些手工操作,我把它整理成了 starcat-weekly-import Skill。Agent 收到一批新闻后,先提取已有链接,再联网搜索缺失地址;核验完成后把项目分成「已确认」「待确认」和「未找到」,展示最终列表并等待确认。用户确认以后,Skill 才会执行 dry-run、读取受控的管理员配置、整批提交并轮询结果。
有一批 AI 情报中出现了 ReDesign、Kimi K3、OpenAI Astra 等项目。核验后发现 sonjt00/ReDesign 已重定向到 jintae-00/ReDesign;MoonshotAI/kimi-code 实际是 Kimi Code CLI,不能当成 Kimi K3;Astra 等项目没有对应的官方 GitHub 仓库,也没有用第三方实现代替。最终展示并确认了 14 个仓库,dry-run 通过后写入 ai_intelligence,批次轮询结果为 14/14 成功,0 重复、0 剔除。
一开始,我把 Skill 理解成一份可以重复使用的 Prompt。真正用到发版、本地化和 Weekly 导入以后,我发现它更像一份给 AI 执行的 SOP。它会先判断当前任务属于哪种场景,再找到需要读取的文档和脚本,检查执行条件,等待必要的人工确认,最后完成操作和结果验证。
Skill 不代替脚本,也不负责实现业务逻辑。脚本负责执行具体命令,项目文档解释设计和约束,Skill 则把两者串成一套 AI 可以重复执行的流程。
后来我开始关注 Claude Code Dynamic Workflows 这类做法。文章里的思路很实用:编排逻辑变复杂以后,把它写成能落盘、能修改、能重跑的代码,不要继续藏在一次聊天里。
Starcat 目前只把发版、本地化和 Weekly 导入这类步骤多、风险高的流程固定下来。第一次跑通以后,我会把其中的检查条件、操作步骤和验证方法写进 Skill 或代码。下次再处理同类任务时,AI 可以按照已有流程执行,不需要重新从聊天记录里整理一遍。普通的小任务没有必要做成复杂的 Workflow。
App Store 上架
代码、测试和打包流程准备好以后,Starcat 还不能直接提交 App Store。开发者账号注册、App Store Connect 配置、审核、备案和付费协议都要逐项完成,其中很多问题无法通过修改代码解决。
开发者账号注册
申请 Apple Developer Program 个人开发者账号时,我在 Apple Developer App 的注册过程中更换了设备。此后注册状态无法继续,同一份个人信息也不能再用于其他 Apple ID 重新申请。
我联系 Apple Developer 技术支持沟通了很多天,问题经过多次转交,仍然无法重置原来的注册状态,也无法重新使用我的个人信息完成申请。Apple 的注册说明要求整个注册流程使用同一台设备。我遇到的情况和《注册苹果个人开发者账号,千万别换设备!!!》记录的问题基本一致。
最后,我重新注册了一个 Apple ID,使用妻子的个人信息申请 Apple Developer Program,才完成开发者账号注册。
第一次审核被拒
Starcat 要读取用户自己的 GitHub Stars,登录是主路径。最早使用 GitHub Device Flow,用户点击以后直接打开默认浏览器,再回到 App 等待授权。
我从开发者角度看,这很正常。OAuth 本来就要去 GitHub,浏览器也是用户熟悉的环境。
第一次审核时,登录流程因为缺少必要提示被拒。用户点击登录后,App 会直接打开浏览器,但界面没有提前说明接下来会发生什么。按照审核要求,应用需要先告知用户将前往 GitHub 完成授权,确认后才能打开浏览器。
最后 App Store 构建的主登录改成了 ASWebAuthenticationSession。它会先显示系统确认界面,告诉用户 App 要与哪个域名进行认证,用户同意后才进入浏览器,完成后回调只交给发起认证的 App。Apple 对这个行为有明确说明。
Starcat 现在把 Web Flow + PKCE 作为默认登录。App Store 构建只保留这条系统认证路径;Device Flow 和 PAT 作为 Direct 版的备选方式。UI 隐藏还不够,Service 层也有渠道门禁,防止 App Store 包从别的入口启动外部登录流程。
App Store 后台
登录修完以后,还有 App Store Connect。
Starcat Pro 有月付、年付和终身三个 IAP。第一次提交时,每个商品都要填写本地化、价格、审核备注和截图,还要关联到具体的 App 版本。
这里很容易被本地测试误导。Xcode 可以读取 Products.storekit,所以商品还没有在 App Store Connect 配好时,本地 Pro 页面也可能正常显示。TestFlight 不读取这份文件,它只使用 App Store Connect 的 sandbox 商品。商品元数据没有补齐、没有关联当前 App 版本,或者 Paid Apps Agreement、税务和银行信息还没生效,TestFlight 里都可能只显示「订阅商品不可用」。
排查这类问题时,先看 App Store Connect 的商品状态和协议,不要看到本地 StoreKit 正常就继续改客户端代码。第一次提交 IAP 还要把商品挂到版本页面,和 App 一起送审;只创建 Product ID 不够。
《过来人给独立开发者的建议(7)——收款篇》把 App Store 收款前的几件事按顺序列了出来:付费协议、税务信息、银行账户,以及符合条件时申请 Small Business Program。
App 准备在中国大陆区上架时,需要提前检查备案要求。手机 App 有单独的 App 备案流程;Starcat 是 macOS App,而且没有使用国内服务器,我当时并不确定这种情况是否必须办理 App 备案,也不确定公钥和证书指纹是不是必交材料。
为了避免提交以后再补材料,我先后办了网站备案和 App 备案,Bundle ID、公钥、证书指纹等能够准备的内容也一次性提交了。公钥和指纹的处理参考了《iOS APP 备案那些事:分析如何通过证书文件获取公钥和指纹》,可以从 App Store Connect、macOS 钥匙串或者证书文件中取得。
如果准备开发应用并上架 App Store,最好提前检查开发者主体、销售地区、网站备案、App 备案和可能需要的资质材料。中国大陆和欧盟还有各自的合规要求,欧盟销售范围需要同时处理 DSA 交易商信息。这些审核都需要时间,不要等到版本提交前再开始办理。
提交审核前,还需要在 App Store Connect 中补全隐私标签、支持页面、隐私政策和 Review Notes。Review Notes 是写给审核人员看的,需要说明如何登录、从哪里进入 Pro 功能、应用为什么访问网络,以及 App Store 版本不包含外部购买入口,方便审核人员按照实际路径检查应用。
Direct 分发
为什么保留 Direct
Starcat 完成 App Store 版本以后,发布和收款已经可以正常进行。如果只是把应用卖出去,做到这里就够了。
我继续做了 Direct 版本,因为我想借着 Starcat 把独立开发者出海的完整链路亲自走一遍:搭建官网,签名和公证安装包,通过 DMG 与 Homebrew 分发,对接海外支付,处理 webhook、退款和 License,再用 Sparkle 完成应用更新。这些事情由 App Store 代为处理时,开发者很难接触到完整过程。
因此 Starcat 最终保留了两套分发渠道:
| 维度 | App Store | Direct |
|---|---|---|
| Bundle ID | com.starcat.app.store | com.starcat.app.direct |
| 分发 | Mac App Store | 官网 DMG / Homebrew Cask |
| 支付 | StoreKit 2 / Apple IAP | Hosted checkout / License Key |
| 更新 | App Store | Sparkle appcast |
| 签名 | Apple Distribution | Developer ID + notarization |
| Pro 权益 | StoreKit entitlement | Direct License entitlement |
双渠道隔离
构建隔离
App Store 和 Direct 版本包含的分发能力不同。App Store 版本使用 StoreKit,不能包含官网支付、License 激活和 Sparkle;Direct 版本使用官网支付、License 和 Sparkle,不需要加载 StoreKit 商品。
这些差异需要同时落实到 UI、Service、配置和打包产物中,不能只隐藏购买按钮。
权益统一
两个版本的付费方式不同,解锁后的业务功能相同。AI、RAG、标签数量和 Release 订阅统一通过 EntitlementGate 查询用户权益,不直接依赖 StoreKit、Creem 或具体的 License 服务。
以后更换支付平台时,只需要调整支付和权益层,不需要逐个修改业务页面。
应用身份隔离
两个版本使用不同的 Bundle ID,Keychain access group、URL Scheme、签名、entitlements、自动更新和调试配置都要分别维护。
如果用户在同一台 Mac 上同时安装两个版本,数据库目录、通知标识和 deep link 也要相互隔离,避免两个版本读写同一份数据或者唤起错误的应用。
Stripe 的门槛
现在做产品出海,很多人会优先考虑 Stripe。它的 API、文档和支付生态已经非常成熟,SaaS、订阅服务和数字产品都能找到现成的接入方案。独立开发者也经常选择 Stripe 旗下的 Lemon Squeezy,由平台作为 Merchant of Record 处理全球收款、销售税和合规。
我一开始也考虑过这两种方案。真正挡住我的不是支付接口,而是商家资格。Stripe 的全球可用地区目前没有中国大陆,国内个人开发者无法直接使用大陆身份和银行账户开通 Payments。要使用 Stripe,通常需要先准备香港、美国或其他受支持地区的公司主体和银行账户。
Lemon Squeezy 在 2024 年被 Stripe 收购。它简化了税务和合规,但仍然需要审核商店和结算资格。它当前支持的银行结算地区同样没有中国大陆,实际使用时还要另外处理 PayPal 或境外银行账户。
Stripe Atlas 可以协助注册美国公司,但注册公司只是开始,后面还有银行开户、税务申报和公司维护。Starcat 当时还在验证阶段,我不想为了接入支付,先增加一套需要长期维护的境外公司体系。
如果选择美国公司这条路线,需要先注册公司、申请 EIN,准备海外手机号和地址,然后申请银行账户并完成 Stripe 开户。这篇实操记录整理了从注册公司到最终收款的完整步骤,也提到了公司成立后的维护费用。
另一篇全球收款记录采用香港 Stripe 和香港银行账户。流程比注册美国公司短一些,但仍然需要准备符合注册地区要求的身份材料和结算账户。
也有开发者使用个人 Stripe 接收订阅付款。VidPilot 的订阅复盘使用 Stripe Checkout 和 webhook 完成订阅功能,同时准备了香港银行账户用于结算。支付接口本身不难接入,前提是先解决 Stripe 账号和结算账户。
这些方案都可以完成收款,但 Starcat 当时还在验证产品阶段。我不想先注册境外公司或者办理香港银行账户,因此没有选择 Stripe。
选择 Creem
Stripe 和 Lemon Squeezy 都需要先解决商户主体或境外结算账户,我只能继续寻找对国内个人开发者更友好的支付平台。
我申请时,Creem 允许中国大陆用户注册商户,也支持通过支付宝接收结算款,不需要先注册境外公司或者办理香港银行账户。Creem 成立时间较晚,当时针对商户地区和结算方式的限制相对少一些,更适合 Starcat 这个还在验证阶段的产品。
Creem 采用 Merchant of Record 模式,由平台处理收款、销售税、退款和拒付。它还提供 License 的 activate、validate 和 deactivate 接口,能够直接接入桌面软件的 License 流程。
确定使用 Creem 后,我参考了一篇从创建产品、商户审核到接通 webhook 的完整记录。Creem 在审核商户时会检查产品介绍、价格、官网、隐私政策和退款政策,这些内容需要在提交申请前准备好。
国内个人开发者还要处理结算。这篇接入记录介绍了商户申请、身份验证和支付宝结算的配置过程。相比先注册境外公司、申请银行账户再接 Stripe,这条路线更适合 Starcat 当时的情况。
支付代码接入以后,Starcat 已经可以创建 Creem Checkout、打开 Customer Portal,并通过 webhook 处理购买、退款和订阅状态变化。License API 负责激活和验证 License,客户端根据服务端返回的状态更新 Pro 权益。
目前这些代码和自动化测试已经完成,真实商品、API Key、webhook secret,以及购买、退款、取消订阅和多设备激活流程还需要在 Creem 环境中完成联调。只有整条链路跑通以后,Direct 版本的付费功能才算真正完成。
支付后的链路
Direct 版本的支付链路统一经过 starcat-license-api。客户端只传 monthly、yearly 或 lifetime 这类稳定 plan alias,真实 product id 留在服务端配置。支付平台的 API Key 也不会进 App。
Hosted checkout 完成后,成功页用 deep link 把 License Key 带回 Starcat。客户端不相信 URL 里的套餐和状态,还会调用 License API 激活,以服务端返回的权益快照为准。
App 重启时先从 Keychain 恢复本地凭据,再按间隔静默 validate。网络超时或服务临时 5xx 时,已有 Pro 不会立即消失;只有服务端明确返回 expired、revoked 或 inactive 才撤权。这里必须区分「暂时无法确认」和「已经确认失效」。
麻烦在浏览器关掉、deep link 没回来、退款、取消订阅和争议。客户端成功页不能成为事实来源,最终状态还得靠 webhook。
Webhook 需要验签和幂等。付款成功建立 License 与 customer 的映射;退款、过期和争议撤销权益;scheduled cancel 先保留到当前周期结束。设备额度、解绑、Customer Portal 和客服流程也都在这条链路里。
一位工具站开发者对一年收款经历的复盘记录了迁移支付网关、账户风控、3DS、欺诈预警、扣款通知、争议处理和发票。这些问题会在开始收款以后逐渐出现。Starcat 第一版不需要照抄整套运营规则,但 webhook、退款撤权、订阅到期和客服可追溯性要提前留好位置。
提现与结算
用户完成付款后,资金会先进入 Creem 账户。要把这笔钱转回国内,还需要配置 payout account,并实际验证提现、到账和换汇流程。支付接口接通和资金完成结算是两条不同的链路。
Starcat 准备验证的结算路线是 Creem → Wise → 支付宝。我参考了这套方案的掘金实操记录和作者独立站整理版。具体操作是在 Wise 中取得收款账户信息,将其配置到 Creem 的 payout account,Creem 完成打款后,再通过 Wise 转到支付宝。
这条路线还需要检查账户姓名、支持币种、提现门槛、手续费和到账时间,平台政策变化后也可能需要重新调整。Starcat 目前完成了 Checkout、webhook 和 License 的代码,Creem → Wise → 支付宝的真实资金结算还需要单独验证。实际款项到达国内账户以后,Direct 版本的海外收款流程才算完整。
项目沉淀
Starcat 从需求整理、技术选型和界面开发,一直走到 App Store 上架、Direct 分发与海外支付。完成这次从 0 到 1 的开发以后,项目中留下了可以继续复用的开发经验、文档流程、配套工具和 Skills。
macOS 开发经验
Starcat 使用 SwiftUI 开发主要界面,并在窗口管理、菜单、快捷键、WebView 和焦点处理等场景中接入 AppKit。项目还实际处理了 Keychain、App Sandbox、签名、公证和 Sparkle 自动更新。
本地优先架构带来了 SQLite 数据迁移、缓存更新、CloudKit 同步、用户数据保护和 RAG 索引维护等问题。App Store 与 Direct 双渠道又涉及 StoreKit、License、Bundle ID 隔离和不同的签名流程。这些实现和排查记录可以继续用于后续的 macOS 项目。
文档与开发流程
大需求进入开发前,先整理需求并完成讨论,再依次形成正式方案、详细设计、开发 Checklist、审查报告和验收记录。每个阶段的文档都保存在项目中,并跟随代码一起更新。
CLAUDE.md 和 AGENTS.md 为不同 AI 工具提供项目入口,记录修改授权、数据库迁移、发布操作和验收边界。DESIGN.md 统一界面与交互规则,模块设计文档保存具体方案,测试规范和发版 SOP 负责约束执行过程。
这套结构可以直接用于后续的大需求。规模较小的需求只保留必要文档,避免为了流程增加额外维护工作。
配套项目与工具
Starcat 的开发范围逐渐扩展到主应用之外。starcat-cli 和 MCP 为终端及 AI Agent 提供操作入口,Chrome 与 Safari 插件负责从网页收集项目。多个独立 API 后来合并到 starcat-api,公共的鉴权、CORS 和 GitHub 接口被提取到 starcat-api-kit。
Makefile 中的 targets 增加以后,项目又开发了 Makefile Explorer,用于在 VS Code 中查看和执行 Makefile 命令。多个 Agent 在同一工作区修改文件时,AI File Wall 会登记文件占用状态,并提示可能发生的编辑冲突。
这些工具最初都用于解决 Starcat 开发中的具体问题,后续也可以独立用于其他项目。
Skills 与自动化
发版、本地化和 Weekly 导入包含固定的检查、确认和验证步骤。项目将这些流程整理成 starcat-release、starcat-localization-sync 和 starcat-weekly-import 等 Skills。
Skill 会先读取项目文档,检查分支、配置和当前状态,再调用对应脚本。发布、上传和生产数据写入必须等待人工确认。执行结束后还要检查结果,并说明失败后哪些步骤可以安全重跑。
脚本负责执行命令,项目文档保存设计与约束,Skill 把两者组织成 AI 可以重复执行的流程。换用不同的 Agent 处理同类任务时,仍然会经过相同的检查和确认步骤。
后续复用
Starcat 已经形成了一套原生 macOS 应用的开发基础,其中包括需求文档、UI 规范、测试规则、数据迁移、发版流程、配套工具和 Skills。后续开发新的 Mac 应用时,可以从这些内容开始,再根据产品规模进行调整。
这次开发也确定了 AI 在项目中的分工。产品方向、需求取舍和最终验收由人完成,AI 参与方案整理、代码实现、问题排查和文档维护,测试、脚本与人工检查共同验证交付结果。
找回你的 Stars
如果 Stars 不多,而且平时会回 GitHub 翻,原版页面通常已经够用。多装一个工具反而增加负担。
如果你的 Stars 已经很多,经常只记得使用场景,却想不起仓库名;或者希望给真正重要的项目补标签、笔记,再用带引用的问答重新找回它们,可以试试 Starcat。
它不会替你把两千个 Stars 自动整理成一套完美知识库。我的做法还是很朴素:先把 Stars 同步到本机,需要长期保留的再手动入库。搜索尽量走本地,AI 回答附上材料来源,修改用户数据前要确认。
这正是我自己想用的工具。













































