本文件記錄:
- 架構決策
- workflow 邊界
- 長期設計理由
- trust boundary
- 未來維護時仍重要的「為什麼」
不記錄:
- operational tuning
- sleep 數值
- 一次性 workaround
- debug 步驟
- 日常操作流程
那些請放在 RUNBOOK.md。
背景:
Threads Graph API 對圖片來源有限制。Medium 原始圖片(miro.medium.com)常被拒絕,外部圖片來源也不可控。
決定:
所有圖片統一走:
本機圖片 → AWS S3 → public URL → Threads API
S3 bucket 內以 threads-assets/<uuid>-<filename> 方式儲存。
理由:
- 避免 Threads API 拒絕外站圖片
- 統一圖片來源
- 避免 Medium hotlink 問題
- 發文流程更 deterministic
- 未來可擴充 CDN / cache policy
未解:
- 未來是否改成 CloudFront
- 是否需要圖片壓縮 pipeline
- 是否需要自動清理舊 assets
- 是否要支援 Cloudflare R2 / Imgur 等其他圖床(見 D-007)
背景:
要把 Medium 長文完整本機化,包括 Markdown、圖片、文章結構。
決定:
使用 ZMediumToMarkdown 作為第一階段 import tool。
流程:
Medium URL
→ ZMediumToMarkdown
→ output/zmediumtomarkdown/
→ import_zmedium.py
→ drafts/article.md + assets/
理由:
- 可完整保留文章內容
- 可下載圖片資產
- Markdown 結構穩定
- 比手動複製更可重複
- 可 script 化
未解:
- Medium 結構變動時的相容性
- 是否要替換成自建 parser
- 是否支援其他平台(Substack / Hashnode)
背景:
「內容改寫」與「實際發文」屬於不同性質的工作。
- 內容改寫需要:語意理解、摘要能力、Threads 語氣調整
- 發文涉及:token、API、retry、sleep timing、S3 upload、deterministic execution
決定:
AI 只負責:
drafts/article.md → output/threads_draft.json
真正發文由 scripts/post_threads.py 負責。
理由:
- 降低 AI 誤發文風險
- 避免 AI 接觸
.env - upload / retry 可程式化驗證
- draft 可先經過 check 與 preview
- 人工 review 可保留最後控制權
未解:
- 是否要加入 scheduled publish
- 是否要加入 retry queue
- 是否要支援 multi-platform publishing
背景:
Threads 每篇 post 都可以帶自己的 topic。若每篇使用不同 topic,容易讓整串內容失去一致性。
決定:
同一串 thread 的所有 post 共用同一個 topic_tag。
topic 應代表:整篇文章主題、長期可搜尋主題、核心技術或概念;而不是單篇局部內容。
理由:
- 維持 thread 主題一致性
- 降低 topic fragmentation
- 提升搜尋與推薦穩定性
- 避免 AI 每篇亂換 topic
未解:
- 是否要加入 topic suggestion system
- 是否要建立 topic whitelist
- 是否要加入 analytics 驗證 topic 效果
背景:
若允許 AI 自由產生 image_url,可能出現:幻覺圖片、不存在檔案、外部 hotlink、使用 miro.medium.com、不可控來源。
決定:
AI:
- 只能使用 Markdown 中已存在圖片
- 必須輸出
local_image_path或local_image_paths - 禁止輸出
image_url
並由 scripts/check_draft.py 額外驗證檔案實際存在。
理由:
- 建立 trust boundary
- 防止 hallucinated assets
- 保持發文 deterministic
- 避免外部依賴
- 提升 draft 可驗證性
未解:
- 是否要加入 image metadata validation
- 是否要支援 AI 自動選圖
- 是否要加入 image ranking
背景:
Threads pipeline 會產生大量 preview、publish result、import output、cache 中介檔。若全部 commit,repo 會快速污染。
決定:
專案區分:
永久素材(可追蹤):source code、文件、scripts
衍生產物(不追蹤):publish result、cache、preview、import 中介輸出、使用者自己的 drafts/ 與 assets/
由 .gitignore 控制。
理由:
- 保持 repo 乾淨
- 避免 commit 噪音
- 降低衍生檔污染
- 保持 pipeline 可重建
未解:
- 是否要定期清理 assets
- 是否要建立 release artifacts
- 是否需要 cache policy
背景:
最初版本在私有環境中假設使用 Claude Code。開源後,使用者可能用 Codex CLI、Cursor、Aider,甚至完全沒有付費 LLM CLI、只想用 ChatGPT 免費版。
初版做法是同時放 AGENTS.md 與內容相同的 CLAUDE.md,讓 Claude Code 預設讀取路徑也能命中。後來反轉這個決策,理由見下。
決定:
- AI 助理規格只寫一份
AGENTS.md(2025 年後逐漸成為跨工具慣例) - 不在 repo 內維護
CLAUDE.md;它被定位為「個人本地 AI 指引」,比照~/.claude/CLAUDE.md的角色,不入版本控制 .gitignore明確排除CLAUDE.md- Claude Code 使用者啟動後,請手動告知它「依照
AGENTS.md規則」 - 不在 repo 內 hard-code 任何特定廠商 API 呼叫
- README 同時列出「Agentic CLI」與「網頁手動模式」兩條路徑
理由:
- 兩份內容相同的檔案實質上是維護負擔(要手動同步、容易漂移)
CLAUDE.md在語意上偏向「個人/本地的 AI 偏好」,跟~/.claude/CLAUDE.md的角色一致;公開 repo 的規格走AGENTS.md更乾淨- 受眾依舊從「會用 Claude Code 的人」涵蓋到「會跑 Python 的人」,不會因為拿掉
CLAUDE.md而少 - 跨工具 prompt 標準演進中,
AGENTS.md是目前最廣的共識
代價:
- Claude Code 使用者每次啟動要多一句「請依照 AGENTS.md」,無法靠預設讀取路徑自動套用
- 對「我以為 Claude Code 進來就會自動懂規則」的使用者體驗較差
未解:
AGENTS.md慣例是否會穩定下來- 是否在 README 的 Quick Start 加更顯眼的「Claude Code 使用者請執行 X」提示
- 是否提供一個 opt-in 腳本讓使用者自行產生本地
CLAUDE.md(已 ignored)
背景:
初版要求所有使用者都要設 AWS S3,即使只發純文字串文。對個人使用者門檻過高(要開 AWS 帳號、設 IAM、設 bucket policy)。
決定:
post_threads.py只在偵測到 IMAGE 或 CAROUSEL 貼文時才檢查 S3 環境變數boto3延遲到實際上傳時才 import.env.example將 S3 三個欄位標示為「只在發圖時才需要」
理由:
- 純文字使用者零門檻
- 想發圖的使用者再花時間設 S3
- 維持「圖片走 S3」的決策(D-001),不改變圖片處理架構
未解:
- 是否要支援 Cloudflare R2、Imgur 等替代圖床
- 是否要把 uploader 抽象成 interface(目前只有單一函式
upload_local_file_to_s3())
背景:
threads_draft.json 是由 AI 或使用者手動產生,不能假設內容完全可信。公開 repo 的使用者也需要一個不碰 token、不呼叫 Threads API、不上傳 S3 的方式來確認發文計畫。
決定:
check_draft.py、render_preview.py、post_threads.py共用scripts/draft_validation.py- 發文前拒絕
image_url/image_urls - 圖片路徑只能指向
assets/底下既有圖片 post_threads.py --dry-run只列出 container、reply 順序與圖片上傳計畫,不讀.env、不呼叫 API、不上傳圖片published_result.json保存 draft fingerprint;接續發文前若 draft 被改過,就拒絕 resume
理由:
- 避免 AI 產出的 draft 繞過本機圖片檢查
- 避免任意本機檔案被上傳到公開圖床
- 降低使用者第一次操作時誤發文的風險
- 避免中途失敗後把新 draft 接到舊 thread 後面
背景:
Threads 與 Instagram 都走 Meta 的 Graph API,圖片同樣需要公開 URL(共用 S3 圖床、共用本機素材檢查)。但兩者規則差異夠大,硬塞進同一個發文器只會讓分支邏輯爆炸。
差異:
- IG 不支援純文字貼文,每篇都要 IMAGE / CAROUSEL
- IG feed 貼文彼此獨立,沒有 Threads 的 reply 串接
- IG caption 上限 2200、沒有 topic_tag(hashtag 寫在 caption)
- IG 只吃 JPEG,非 JPEG 來源需先轉檔
- IG 一定要圖床(Threads 純文字可不必)
決定:
- 圖片路徑驗證、fingerprint、resume、
--dry-run、token 遮罩等共用慣例兩邊一致 scripts/draft_validation.py內 Threads 用validate_draft、IG 用validate_instagram_draft,各自一套規則- 發文器分開:
post_threads.py與post_instagram.py,進度檔也分開(published_result.jsonvspublished_ig_result.json) - IG 預設讀
output/instagram_draft.json,與 Threads 的output/threads_draft.json分流 Pillow設為可選依賴,只在 IG 需轉檔時才 import
理由:
- 共用安全與可靠性慣例,不重複造輪子
- 平台規則各自獨立,避免單一函式塞滿 if/else 難維護
- 進度檔分流讓兩平台可獨立 resume,互不污染