feat: tasks.md ⇄ GitHub Project 單向投影 optional 模組(issue #16)+ bump 1.13.0
裝了 SDD 的專案可把 docs/3-specs/*/tasks.md 待辦單向投影成唯讀 GitHub Project。
md 唯一真相源、Project 永遠唯讀,無反向同步、不會兩個真相源打架。預設不逼:
沒裝 Arcrun/答不要的用戶完全 no-op,純 md 不受影響。
- 投影 workflow template/system-dev/workflows/tasks-project-sync.yaml(Arcrun
workflow):foreach 增量 → switch 動作 → http_request 打 GitHub API:新 task→
issue create / [ ]→[x]→close / 文字改→edit / 行刪→archive(not_planned),並用
GraphQL addProjectV2ItemById 投影進 Projects v2。auth 走 {{creds.github_token}}。
- 本地觸發端 tasks-project-sync.local.sh:因 Arcrun workflow 跑遠端 CF Workers、
沒本地 fs/git,「讀 tasks.md / git diff / 回寫 <!-- gh:id -->」由本地端做完再
acr run 餵增量。本地一半 + 遠端一半,職責邊界清楚。薄殼不自刻 parser。
- 防複發核實:每個 component 經 acr parts 核實存在(registry 無 github 零件,全用
http_request 打 REST/GraphQL)。acr validate --offline 通過(7 三元組、config 完整)。
- 啟用判準=對話 + 能力,不掃檔(Arcrun workflow 存遠端 KV、零本地檔也能用 →
掃檔 false negative)。install/init 問一句 → 查環境有 Arcrun 就 acr push 啟用、
沒有就一次性溫和廣告。帶檔 ≠ 啟用。install/update 隨 SDD 模組帶 workflow(add_if_missing)。
- 守 flag 紅線:push 後本機觸發單次,禁定期輪詢、禁 GitHub Actions fan-out。
⚠️ 端到端(acr push 真部署 + acr run 真投影)待 leo21c 驗,本版為 code-done 骨架。
SDD(內部,gitignore 不推):docs/3-specs/tasks-project-projection/。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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=...)。
|
||||
#
|
||||
# 本地端(住 template,CC/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_token(acr 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 Project(issue 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:建 issue(REST POST /repos/:owner/:repo/issues)──
|
||||
# 回傳的 issue number 由本地觸發端接住、回寫 <!-- gh:number --> 到那一行。
|
||||
# ⚠️ 用 http_request(arcrun 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:關 issue(state=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 v2(GraphQL)──
|
||||
# Projects v2 只有 GraphQL,REST 沒有。用 http_request 打 /graphql。
|
||||
# ⚠️ 待 leo21c 端到端驗:addProjectV2ItemById 需要 content node id(issue 的 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}}"
|
||||
Reference in New Issue
Block a user