merge: issue #16 tasks.md⇄GitHub Project 單向投影(optional 模組)

前 session 寫完未 commit,本次補 commit(已過總管 review、http_request 修正落地)。
本地端讀 md/git diff、遠端 workflow 只打 GitHub API。真跑 acr push/run 留給人。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-28 16:28:08 +08:00
9 changed files with 293 additions and 3 deletions
+14
View File
@@ -10,6 +10,20 @@
---
## 1.13.0 — tasks.md ⇄ GitHub Project 單向投影(optional 模組,需 Arcrunissue #16
裝了 SDD 的專案可把 `system-dev/docs/3-specs/*/tasks.md` 的待辦**單向投影**成唯讀 GitHub Project(看板/dashboard 好抓)。md 唯一真相源、Project 永遠唯讀,不存在反向同步、不會兩個真相源打架。
- **新增投影 workflow**`template/system-dev/workflows/tasks-project-sync.yaml`Arcrun workflow`foreach` 增量 → `switch` 動作 → `http_request` 零件打 GitHub API:新 task→issue create / `[ ]→[x]`→close / 文字改→edit / 行刪→archive,並投影進 Projects v2)。auth 走 `{{creds.github_token}}`acr creds push)。**每個 component 經 `acr parts` 核實存在**(防複發:registry 沒有 `github` 零件,用 `http_request` 打 REST/GraphQL)。`acr validate --offline` 通過。
- **新增本地觸發端**`tasks-project-sync.local.sh`——因 Arcrun workflow 跑在遠端 CF Workers、沒本地 fs/git,「讀 tasks.md / git diff / 回寫 `<!-- gh:id -->`」由本地端做完再 `acr run` 餵增量。職責邊界清楚:本地一半 + 遠端一半。
- **啟用判準=對話 + 能力,不掃檔**:原設計想抄 `HAS_WIKI`/`HAS_SDD` 掃檔指紋,但 Arcrun workflow 存遠端 KV、零本地檔也能在用 → 掃檔會 false negative。改成裝/init 時 CC 問一句「要不要同步」→ 查環境有沒有 Arcrun(mcp/`acr`)→ 有就 `acr push` 啟用、沒有就**一次性溫和廣告**、答不要就閉嘴。**帶檔 ≠ 啟用**。
- **守 flag 紅線**:push 後本機觸發單次,禁定期輪詢、禁 GitHub Actions fan-out。
- **預設不逼**:沒裝 Arcrun/答「不好」的用戶完全 no-op,純 md 不受影響。
- install/update 隨 SDD 模組帶下 workflow 檔(`add_if_missing`,覆蓋不會關掉誰的同步——啟用狀態存遠端)。
- ⚠️ **端到端(`acr push` 真部署 + `acr run` 真投影)待 leo21c 驗證**;本版為 code-done 骨架,GitHub Projects v2 GraphQL 的欄位細節真部署時可能微調。SDD:`docs/3-specs/tasks-project-projection/`
---
## 1.12.0 — 跨 repo issue/comment 署名鐵律:`[<本 repo> CC]`issue #12
當生態系裡多個 repo 共用**同一個 GitHub 帳號**發 issue/comment 時,author 全顯示同一帳號、看不出是哪個 repo 的 CC 發的。GitHub 沒有 per-repo 身份設定,`git config user.name` 只影響 commit、不影響 issue/comment author,多開帳號又會踩「避免被 flag」鐵律——身份只能在**內容層**自報。
+14
View File
@@ -120,6 +120,20 @@ This way, even with a note vault, the original structure stays intact and your n
---
## Mirror tasks to GitHub (optional, needs Arcrun)
If you installed SDD, you can **one-way project** the tasks in `system-dev/docs/3-specs/*/tasks.md` into a read-only GitHub Project (handy for boards/dashboards).
- **One-way, md is the source of truth**: the only write point is `tasks.md` — CC edits md when it finishes a task, you ask the AI to edit md when you want changes, **nobody touches the Project directly**. The Project is always read-only, so there's no two-sources-of-truth conflict.
- **Needs Arcrun** (a free AI-friendly workflow toolkit). No Arcrun → plain md, full no-op, unaffected.
- **How to turn it on**: after install, CC asks you once "do you want tasks mirrored to GitHub?" Answer "yes" and if Arcrun is present, CC `acr push`es the projection workflow to enable it; if Arcrun isn't installed, CC tells you "ask Claude to install it; you can also enable sync later." Answer "no" and nothing happens — no nagging.
- **Plays by the rules**: triggered once locally after a push (no periodic polling, no GitHub Actions), staying within the anti-flag red line.
> The workflow lives at `system-dev/workflows/tasks-project-sync.yaml` (+ local trigger `.local.sh`). **Shipped ≠ enabled** — enabling means you said yes and `acr push`ed.
> ⚠️ End-to-end flow is still being verified (issue #16).
---
## Cowork can curate wikis too
The wiki curator is no longer limited to Claude Code in the terminal — **claude.ai's Cowork** can do it too.
+14
View File
@@ -119,6 +119,20 @@ LLM Wiki 原本假設「原始文件放在 `docs/`」,但 Logseq、Obsidian
---
## 待辦同步到 GitHuboptional,需 Arcrun
裝了 SDD 的專案,可以把 `system-dev/docs/3-specs/*/tasks.md` 的待辦**單向投影**成一個唯讀 GitHub Project(看板/dashboard 好抓進度)。
- **單向、md 當家**:唯一寫入點是 `tasks.md`——CC 完成 task 改 md,你要動就叫 AI 改 md**人不直接碰 Project**。Project 永遠唯讀,不會有兩個真相源打架。
- **需要 Arcrun**(免費的 AI-friendly 工作流套件)。沒裝 Arcrun → 純 md,完全 no-op,不受影響。
- **怎麼開**:安裝後 CC 會問你一句「要不要把待辦同步到 GitHub」。答「好」且環境有 Arcrun → CC `acr push` 那份投影 workflow 啟用;沒裝 Arcrun → CC 告訴你「想裝跟 Claude 說就行,之後也可手動啟用」。答「不好」就不做、不再追問。
- **守規矩**:push 後本機觸發一次(非定期輪詢、非 GitHub Actions),守避免被 flag 的紅線。
> workflow 檔在 `system-dev/workflows/tasks-project-sync.yaml`+ 本地觸發端 `.local.sh`)。**帶檔 ≠ 啟用**——啟用=你答好且 `acr push`。
> ⚠️ 端到端流程仍在驗證中(issue #16)。
---
## Cowork 也能整理 wiki
整理 wiki 的人不再只有終端機裡的 Claude Code——**claude.ai 的 Cowork** 也可以。
+21
View File
@@ -296,6 +296,13 @@ if $WANT_SDD; then
download_if_missing ".claude/commands/sdd-check.md" "$REPO_URL/.claude/commands/sdd-check.md"
download_if_missing ".claude/hooks/sdd-guard.sh" "$REPO_URL/.claude/hooks/sdd-guard.sh"
# ── tasks⇄Project 投影(optionalissue #16)──────────────────
# 帶檔 ≠ 啟用:workflow yaml 只是「留記錄+手動啟用素材」,啟用=對話答好且 acr push。
# 投影邏輯依附 tasks.md(住 3-specs),故隨 SDD 模組帶下來;裝了不代表開。
create_dir "system-dev/workflows"
download_if_missing "system-dev/workflows/tasks-project-sync.yaml" "$REPO_URL/system-dev/workflows/tasks-project-sync.yaml"
download_if_missing "system-dev/workflows/tasks-project-sync.local.sh" "$REPO_URL/system-dev/workflows/tasks-project-sync.local.sh"
fi
# ── 安裝/更新腳本:一開始就放進 system-dev/scripts/ ──
@@ -312,6 +319,7 @@ download_if_missing ".claude/hooks/pre-write-guard.sh" "$REPO_URL/.claude/hooks/
download_if_missing ".claude/commands/issue-handle.md" "$REPO_URL/.claude/commands/issue-handle.md"
chmod +x .claude/hooks/*.sh 2>/dev/null || true
chmod +x system-dev/workflows/*.sh 2>/dev/null || true
# ── 依模組產生 settings.json 的 hooks 區塊 ────────
# settings.json 因模組而異,不能直接下載單一靜態檔,改條件組裝。
@@ -466,6 +474,19 @@ fi
if $WANT_SDD; then
t " 動 code 前先在 system-dev/docs/3-specs/[子系統]/ 建 design.md(可用 /sdd-check 協助)" \
" Before touching code, create design.md under system-dev/docs/3-specs/[subsystem]/ (use /sdd-check to help)"
# ── tasks⇄Project 投影:裝/init 對話 + 一次性廣告(issue #16)──
# 判準=能力(arcrun 裝了沒)+意願,不掃檔。落地成「CC 問一句」,install 只是交代 CC 去問。
echo ""
t " ❓ 待辦同步(optional,需 Arcrun):請你的 CC 問你一句——" \
" ❓ Task sync (optional, needs Arcrun): have your CC ask you once —"
t " 「您需要把本專案的待辦事項(tasks.md)同步到 GitHub 嗎?」" \
" \"Do you want this project's tasks (tasks.md) mirrored to GitHub?\""
t " 答「好」→ CC 查環境有沒有 Arcrunmcp / acr 在 PATH):有就設定同步、沒有就一次性告知" \
" Yes → CC checks for Arcrun (mcp / acr in PATH): set it up if present, otherwise inform you once"
t " 「Arcrun 是免費的 AI-friendly 工作流套件,想裝跟 Claude 說就行;之後也可手動啟用」。" \
" \"Arcrun is a free AI-friendly workflow toolkit — ask Claude to install it; you can also enable sync later.\""
t " 答「不好」→ 不做、不再追問。投影 workflow 在 system-dev/workflows/(帶檔≠啟用)。" \
" No → nothing happens, no nagging. The projection workflow sits in system-dev/workflows/ (shipped ≠ enabled)."
fi
t " GitHub issueCC 可直接 /issue-handle 讀回自己 repo 的 issue(禁自動輪詢)" \
" GitHub issues: CC can use /issue-handle to read issues from its own repo (no auto-polling)"
+6 -1
View File
@@ -246,6 +246,11 @@ if $HAS_SDD; then
update_file "system-dev/docs/2-architecture/decisions/TEMPLATE-adr.md" "$TEMPLATE_URL/system-dev/docs/2-architecture/decisions/TEMPLATE-adr.md"
update_file ".claude/commands/sdd-check.md" "$TEMPLATE_URL/.claude/commands/sdd-check.md"
update_file ".claude/hooks/sdd-guard.sh" "$TEMPLATE_URL/.claude/hooks/sdd-guard.sh"
# tasks⇄Project 投影(issue #16):邏輯檔,可覆蓋。舊版沒有 → add_if_missing 補。
# 啟用狀態存遠端(acr push),不在這些檔裡,覆蓋不會關掉誰的同步。
add_if_missing "system-dev/workflows/tasks-project-sync.yaml" "$TEMPLATE_URL/system-dev/workflows/tasks-project-sync.yaml"
add_if_missing "system-dev/workflows/tasks-project-sync.local.sh" "$TEMPLATE_URL/system-dev/workflows/tasks-project-sync.local.sh"
fi
# ── 自我更新:把最新的 update.sh / install.sh 抓到 system-dev/scripts/ ──
@@ -253,7 +258,7 @@ fi
update_file "system-dev/scripts/update.sh" "$REPO_RAW/scripts/update.sh"
update_file "system-dev/scripts/install.sh" "$REPO_RAW/scripts/install.sh"
chmod +x .claude/hooks/*.sh system-dev/scripts/*.sh 2>/dev/null || true
chmod +x .claude/hooks/*.sh system-dev/scripts/*.sh system-dev/workflows/*.sh 2>/dev/null || true
# ── 使用者資料檔:絕不碰,但提醒「設定可能有新欄位要手動補」──
keep_file ".claude/settings.json"
+1 -1
View File
@@ -1 +1 @@
1.12.0
1.13.0
+1 -1
View File
@@ -1 +1 @@
1.12.0
1.13.0
@@ -0,0 +1,70 @@
#!/usr/bin/env bash
# tasks-project-sync.local.sh — 本地觸發端(投影的「本地一半」)
#
# 來源:issue #16;設計:system-dev/docs/3-specs/tasks-project-projection/design.md
#
# ── 為什麼有這支(職責邊界)──────────────────────────────────────
# arcrun workflow 跑在遠端 CF Workers,沒有本地 fs / git / shell。
# 所以「讀 tasks.md / git diff / 回寫 <!-- gh:id -->」這三件本地事
# 不能放進 workflow(arcrun 也沒有對應零件,這是架構邊界、不是缺口)。
# 這支就是那「本地一半」:分類好增量 → 交給 acr run 打 GitHub。
#
# 這支(本地):讀 tasks.md → git diff → 分類四動作 → acr run 投影 → 回寫新 id
# │
# ▼
# tasks-project-sync.yaml(遠端):foreach → switch → github API
#
# ── 怎麼觸發(守 flag 紅線)─────────────────────────────────────
# push 後「本機觸發單次」,非 cron、非輪詢、非 GitHub Actions。
# 掛載點建議:本機 git post-push 類 hook,或叫 CC 在 push 後跑這支一次。
# ⚠️ 具體掛載點與「分類四動作」的精確 parse,待 leo21c 端到端驗證後定稿;
# 目前是 code-done 骨架,標出該做什麼、邊界在哪,不假裝已通。
#
# ── 用法 ───────────────────────────────────────────────────────
# tasks-project-sync.local.sh <owner> <repo> <project_id>
#
# 前置:acr 在 PATH、github_token 已 acr creds push、workflow 已 acr push。
set -euo pipefail
OWNER="${1:?需要 owner}"
REPO="${2:?需要 repo}"
PROJECT_ID="${3:?需要 GitHub Projects v2 node id}"
# 多組 SDD 全同步:glob 掃所有 tasks.md(新 folder 自動成組,免手動登記)。
GLOB="system-dev/docs/3-specs/*/tasks.md"
# ── 1. 分類增量(git diff 知道改了哪幾行 → 四動作)──────────────
# 這裡是「本地一半」的核心。實作策略(待端到端驗證後落地):
# - 拿 push 前後的 git diff,限定 $GLOB 範圍,只看動到的行。
# - 每行 task 解析:有無 `<!-- gh:N -->` id、checkbox 是否 [ ]→[x]、文字/負責人/日期變動。
# - 行不見(diff 的刪除行)且原本有 id → archive。
# - 子系統 = 該 tasks.md 的 folder 名(當 label 分組)。
# - 產出 tasks_json 陣列(格式見 yaml 註解)。
#
# ⚠️ 刻意不在這支用 TS/Python 刻複雜 parser(守薄殼)。複雜分類交給 CC 在 push 後
# 讀 diff 直接產 tasks_json;這支保持「薄殼 + 交棒 acr run」。若未來證明需要可重用的
# parser 零件,那屬於 arcrun 零件缺口 → 回報 issue #16 由 arcrun 端補,不在此自刻。
#
# 佔位:實際 tasks_json 由 CC 依上述策略產生後填入。
TASKS_JSON="${TASKS_JSON:-[]}"
if [ "$TASKS_JSON" = "[]" ]; then
echo "(無增量 → 本次 no-op,不呼叫 GitHub"
exit 0
fi
# ── 2. 交給遠端 workflow 投影 ───────────────────────────────────
acr run tasks_project_sync \
-i owner="$OWNER" \
-i repo="$REPO" \
-i project_id="$PROJECT_ID" \
-i tasks_json="$TASKS_JSON"
# ── 3. 回寫新 task 的 id(唯一對 md 的寫入,只在 create 時做一次)──
# create 動作的回傳 issue number → append `<!-- gh:N -->` 到該行末。
# ⚠️ 防迴圈:回寫造成 working tree 變動,這次回寫「不得」再觸發一輪投影
# (否則 push→觸發→回寫→又一個 diff→又觸發…)。實作時觸發端要排除
# 「只動到 <!-- gh:N --> 註解」的 diff,或回寫走 [skip-sync] 標記。
# 具體機制待端到端驗證定稿。
echo "(新 task 的 id 回寫:待端到端驗證後接上 acr run 的回傳 → append <!-- gh:N -->"
@@ -0,0 +1,152 @@
# tasks-project-sync — tasks.md ⇄ GitHub Project 單向投影
#
# 來源:issue #16;設計:system-dev/docs/3-specs/tasks-project-projection/design.md
#
# ── 這份 workflow 的職責邊界(很重要,別搞混)──────────────────
# arcrun workflow 在 Cloudflare Workers / WASM 上「遠端」執行,沒有本地檔案系統、
# 沒有 git、沒有 shell。所以「讀 tasks.md / 跑 git diff / 把 <!-- gh:id --> 回寫 md」
# 這三件事 **不是、也不該是 workflow 的步驟**——它們由本地觸發端(CC / push 後本機腳本)
# 先做完,把「分類好的 task 增量」當 input 餵進來(acr run -i tasks_json=...)。
#
# 本地端(住 templateCC/shell 跑):讀 tasks.md → git diff → 分類四動作 → 回寫 id
# │ acr run tasks-project-sync -i ...(把增量餵進來)
# ▼
# 遠端 workflow(這份 yaml):foreach 增量 → switch 動作 → github API 投影
#
# → 因此本 workflow 只負責「拿到分類好的增量後,打 GitHub API 投影成 issue/Project」。
# 單向:只寫 GitHub,永不回改 tasks.md(回寫 id 是本地端的事,且只在新 task 做一次)。
#
# ── 輸入(由本地觸發端用 acr run -i 餵)──────────────────────────
# owner GitHub repo owner(例:uncle6me-web
# repo GitHub repo 名
# project_id GitHub Projects v2 的 node id(投影目標,唯讀看板)
# tasks_json 本地分類好的增量陣列,每筆形如:
# { action: "create|close|edit|archive",
# gh: 42, # 已有 id 的帶上(create 無)
# title: "...", body: "...",
# subsystem: "wiki-architecture", # = SDD folder 名,當 label 分組
# assignee: "...", due: "..." }
#
# ── credential ─────────────────────────────────────────────────
# github_tokenacr auth-recipe scaffold github → 填 credentials.yaml → acr creds push
#
# ⚠️ 端到端(acr push 真部署 + acr run 真投影)尚未經 leo21c 驗證。
# 本檔為 code-done 骨架;真部署時 GitHub API 的欄位細節(Projects v2 GraphQL
# 可能要按實測微調,屆時於 issue #16 回報。
name: tasks_project_sync
description: >
把 SDD tasks.md 的待辦增量單向投影成唯讀 GitHub Projectissue CRUD + 加進 Project)。
本地端先讀檔/git diff/分類/回寫 id,這份只負責拿增量打 GitHub API。md 當家、單向、不反向同步。
# ── flow(三元組:A >> 關係詞 >> B)─────────────────────────────
# 對每筆增量 → 依 action 路由到四種 GitHub 動作。
flow:
- "input >> 完成後 >> each_task"
- "each_task >> 對每個 task >> route_action"
# 四種動作(issue 定案):新增/關閉/編輯/封存
- "route_action >> 完成後 >> gh_create"
- "route_action >> 完成後 >> gh_close"
- "route_action >> 完成後 >> gh_edit"
- "route_action >> 完成後 >> gh_archive"
# 新建的 issue 投影進 Project(唯讀看板)
- "gh_create >> 完成後 >> add_to_project"
config:
# 逐筆迭代本地餵進來的分類增量
each_task:
component: foreach_control
iterator: task
# 依 action 欄位分流到四種 GitHub 動作
route_action:
component: switch
key: "{{task.action}}"
cases:
create: gh_create # 有文字、無 id → 建 issue(id 由本地端回寫 md)
close: gh_close # 有 id 且 [ ]→[x] → 關 issue
edit: gh_edit # 有 id 且 文字/負責人/日期改 → 編輯 issue
archive: gh_archive # id 在但整行不見 → 關閉/封存
# ── 動作 1:建 issueREST POST /repos/:owner/:repo/issues)──
# 回傳的 issue number 由本地觸發端接住、回寫 <!-- gh:number --> 到那一行。
# ⚠️ 用 http_requestarcrun registry 沒有 github 零件,21 內建確認過);
# auth 走 credential{{creds.github_token}}acr creds push 上傳,不寫死 token)。
gh_create:
component: http_request
url: "https://api.github.com/repos/{{owner}}/{{repo}}/issues"
method: POST
headers:
Accept: "application/vnd.github+json"
Authorization: "Bearer {{creds.github_token}}"
User-Agent: "arcrun-tasks-project-sync"
body:
title: "{{task.title}}"
body: "{{task.body}}"
labels:
- "{{task.subsystem}}" # 子系統 label 分組(= SDD folder 名)
assignees:
- "{{task.assignee}}"
# ── 動作 2:關 issuestate=closed)──
gh_close:
component: http_request
url: "https://api.github.com/repos/{{owner}}/{{repo}}/issues/{{task.gh}}"
method: PATCH
headers:
Accept: "application/vnd.github+json"
Authorization: "Bearer {{creds.github_token}}"
User-Agent: "arcrun-tasks-project-sync"
body:
state: closed
# ── 動作 3:編輯 issue(標題/內文/負責人)──
gh_edit:
component: http_request
url: "https://api.github.com/repos/{{owner}}/{{repo}}/issues/{{task.gh}}"
method: PATCH
headers:
Accept: "application/vnd.github+json"
Authorization: "Bearer {{creds.github_token}}"
User-Agent: "arcrun-tasks-project-sync"
body:
title: "{{task.title}}"
body: "{{task.body}}"
assignees:
- "{{task.assignee}}"
# ── 動作 4:封存(行不見 = 關閉,標 not_planned 表示非完成而是移除)──
gh_archive:
component: http_request
url: "https://api.github.com/repos/{{owner}}/{{repo}}/issues/{{task.gh}}"
method: PATCH
headers:
Accept: "application/vnd.github+json"
Authorization: "Bearer {{creds.github_token}}"
User-Agent: "arcrun-tasks-project-sync"
body:
state: closed
state_reason: not_planned
# ── 投影進 GitHub Projects v2GraphQL)──
# Projects v2 只有 GraphQLREST 沒有。用 http_request 打 /graphql。
# ⚠️ 待 leo21c 端到端驗:addProjectV2ItemById 需要 content node idissue 的 GraphQL id
# 非 issue number),實測時可能要多一步「先查 issue node id」。屆時於 #16 回報。
add_to_project:
component: http_request
url: "https://api.github.com/graphql"
method: POST
headers:
Accept: "application/vnd.github+json"
Authorization: "Bearer {{creds.github_token}}"
User-Agent: "arcrun-tasks-project-sync"
body:
query: >
mutation($project: ID!, $content: ID!) {
addProjectV2ItemById(input: {projectId: $project, contentId: $content}) {
item { id }
}
}
variables:
project: "{{project_id}}"
content: "{{gh_create.node_id}}"