Files

343 lines
19 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: target
description: 用專案根目錄的 `TARGET.md` 當每日待辦清單,並以系統排程(LinuxWSLmacOS 用 crontab、Windows 用 schtasks)在每天 00:00 自動跑完它。`--schedule on` 建立或更新排程、`--schedule off` 移除排程、`--run` 立即(或由排程)執行一次:先同步 git 並切到 `develop`(遠端沒有就從預設分支建立),逐項實作 `TARGET.md` 中未勾選的項目、完成一項就勾選並補完成時間,接著把工作區變更依 conventional commit 類型分類提交、push `develop`,最後透過 Gitea API 對**遠端預設分支**發 PR(已有同 head→base 的 open PR 就沿用不重開)。當使用者說把 TARGET.md 排成每天執行、每天半夜自動跑待辦、開啟/關閉每日自動任務、建立每日排程做完 TARGET、移除 TARGET 排程、跑一次 TARGET.md 的項目、讓 AI 每天自動做完待辦並發 PR,或提到 TARGET.md、每日排程、cron 每天 00:00、schtasks 每日任務時觸發。不適用於:解決 AI review findings(用 review-resolve)、實作 Gitea 議題的 TODO(用 issues)、角色記憶的睡眠排程(用 role)。
argument-hint: "[--schedule on|off] [--run] [--project-dir <專案根目錄>] [--cli <claude|codex|agy|opencode|copilot>] [--base <PR 目標分支>] [--no-pr] [--yes]"
---
# target — TARGET.md 每日排程執行、提交、push develop 並發 PR
兩個用途合在一個 skill:**管理排程**(開/關每天 00:00 的系統排程)與**執行一次**(把 `TARGET.md` 的待辦做完並走完 git 流程)。排程觸發時,執行的就是本 skill 的 `--run`
| 模式 | 觸發 | 動作 |
| --- | --- | --- |
| `--schedule on` | 使用者 | 依 OS 建立/更新「每天 00:00 執行本 skill `--run`」的系統排程(同一專案只會有一筆) |
| `--schedule off` | 使用者 | 移除該專案的排程條目(只動自己那筆,不碰使用者其他排程) |
| `--run` | 排程/使用者 | A Git 同步(切到 develop,沒有就建立)→ B 讀 `TARGET.md` → C 逐項實作並勾選 → D 分類提交 → E push develop → F 對預設分支發 PR |
| 不帶參數 | 使用者 | 只回報現況:排程裝了沒、下次執行時間、`TARGET.md` 還剩幾項未完成 |
`--schedule``--run` 可同時帶(`--schedule on --run` = 裝好排程並立刻先跑一次)。
---
## 共用規範(shared plugin,必要前置)
執行本 skill 前,先以 Skill 工具載入下列共用規範並全程遵守;**任一載入不到(shared plugin 未安裝)時,先詢問使用者是否安裝 shared plugin`https://gitea.jsc.idv.tw/plugins/shared.git`),使用者不安裝則直接中斷本 skill**,不得只憑下方一行摘要繼續執行:
- `/jsc-shared:spec-output`:繁體中文(台灣用語)、UTF-8(不含 BOM)無亂碼(含 `TARGET.md` 回寫、commit 訊息、PR 標題/描述)、表格呈現。
- `/jsc-shared:spec-execution`:自動執行原則(必要決策才中斷)、不臆測/需人工確認。
- `/jsc-shared:spec-git-safety`:不破壞既有工作(絕不 `reset --hard``clean`)、`pull --ff-only`、保守解衝突、新分支不覆蓋既有分支。
- `/jsc-shared:spec-gitea``GITEA_TOKEN` 機密保護(不 echo、遮蔽 `***`)、API body 以 UTF-8 JSON 檔帶入。
- `/jsc-shared:spec-time-log`Asia/Taipei `yyyy/MM/dd HH:mm:ss` 時間格式、`[時間][階段][等級]: 訊息` log 格式。
本 skill 特有補充:
- **排程只動自己那一筆**:安裝/移除都以固定標記比對,**絕不覆寫整份 crontab、不刪別人的排程任務**。
- **排程情境沒有人在旁邊**`--run` 遇到必要決策時**停下來並寫進 log**,不得臆測後硬做;下次執行會再遇到同一個決策點,由使用者處理。
- **必要決策**(會中斷詢問):工作區有未提交變更、`origin` 無法解析、找不到遠端預設分支、`TARGET.md` 項目語意不明無法安全實作、push 憑證皆失敗、無法判斷目前是哪個助理(且未帶 `--cli`)。
---
## 參數
格式:`[--schedule on|off] [--run] [--project-dir <專案根目錄>] [--cli <claude|codex|agy|opencode|copilot>] [--base <PR 目標分支>] [--no-pr] [--yes]`
- `--schedule on|off``on` 建立/更新排程,`off` 移除排程。省略時不動排程。
- `--run`:立即執行一次 `TARGET.md`(排程條目帶的就是這個)。
- `--project-dir <專案根目錄>`:目標專案根目錄,**省略時取目前工作目錄**(會先 `git rev-parse --show-toplevel` 正規化成 repo 根)。`TARGET.md` 固定讀**專案根目錄**的那一份。
- `--cli <claude|codex|agy|opencode|copilot>`:排程條目要用哪個助理的 headless 指令。省略時用**目前正在執行本 skill 的助理**;判斷不出來且未帶此參數 → 詢問,不猜測。
- `--base <PR 目標分支>`:PR 的目標分支。**省略時自動取遠端預設分支**(見 F1),取不到才詢問。
- `--no-pr`:執行到 push 為止,不開 PR。
- `--yes`:全自動,不做確認式詢問(必要決策仍會中斷)。
執行時間固定為**每天 00:00**(本機時區),不提供時間參數;要改時間請直接編輯排程條目。
---
## `TARGET.md` 格式
專案根目錄的 `TARGET.md`,用 Markdown checklist 表示待辦:
```markdown
# TARGET
- [ ] 補上使用者服務的單元測試
- [ ] 把 README 的安裝章節更新成新的 CLI 指令
- [ ] 順便修掉裡面過期的截圖連結
- [x] 移除已停用的舊設定檔(2026/08/02 23:41:07 完成)
```
| 規則 | 說明 |
| --- | --- |
| 待辦 | `- [ ]` 未勾選的項目,**由上而下**依序處理 |
| 已完成 | `- [x]` 直接略過,不重做 |
| 子項目 | 縮排的 `- [ ]` 視為上層項目的子步驟,隨上層一起處理;子項目全部完成才勾選上層 |
| 標題/說明文字 | 原樣保留,只當作項目的背景脈絡,不改寫 |
| 檔案不存在 | `--run` 視為「無待辦」正常結束(不建檔、不報錯);`--schedule on` 則提醒尚無 `TARGET.md`,非 `--yes` 時詢問是否仍要建立排程 |
完成一項後就地改成 `- [x]`,並在該行**行尾**補上 `<yyyy/MM/dd HH:mm:ss> 完成)`Asia/Taipei)。無法安全完成的項目**保持未勾選**,並在該行下方以縮排補一行 `> ⏭️ 待人工處理:<原因>`(已有同樣註記則更新而非重複新增)。
---
## 階段 S:管理排程(`--schedule on|off`
### S1. 判定 OS 與排程機制
```bash
uname -s # Linux / Darwin / MINGW*|MSYS*|CYGWIN*(後者代表在 Windows 的 git bash
```
| 環境 | 機制 | 備註 |
| --- | --- | --- |
| Linux、WSL | `crontab` | WSL 的 cron **預設不會自動啟動**,見 S5 |
| macOS | `crontab` | 可用;若使用者偏好 `launchd` 需自行改寫,本 skill 不代為產生 plist |
| WindowsPowerShellcmd | `schtasks` | 每日任務 |
判斷不出環境時停止並回報,不臆測。
### S2. 組出排程要執行的指令
```bash
proj="$(cd "<專案根目錄>" && git rev-parse --show-toplevel)" # 絕對路徑
log="$HOME/.jsc/target-$(basename "$proj").log"
```
log **刻意放在專案外面**:放進 repo 會讓每次執行都多出未追蹤檔,污染 D 階段的變更盤點。
各助理的 headless 指令(依 `--cli`):
| `--cli` | 指令 |
| --- | --- |
| `claude` | `claude -p "/jsc-code:target --run --yes"` |
| `codex` | `codex exec '$target --run --yes'`(單引號,避免 `$` 被 shell 展開) |
| `agy` | `agy -p "/jsc-code:target --run --yes"` |
| `opencode` | `opencode run "執行專案根目錄 TARGET.md 的未完成項目,完成後分類提交、push develop 並對預設分支發 PR"` |
| `copilot` | `copilot -p "執行專案根目錄 TARGET.md 的未完成項目,完成後分類提交、push develop 並對預設分支發 PR"` |
> **排程是非互動環境**:助理若跳出工具權限詢問會直接卡住,排程等於沒跑。建立排程前要提醒使用者**先在該專案預先授權**(例如 Claude Code 於專案設定的 `permissions.allow` 列出所需工具,或在指令上加該助理的非互動旗標)。這牽涉安全取捨,**由使用者決定,不代為加上略過權限的旗標**。
### S3. 建立/更新排程(`--schedule on`
**crontab**(標記寫在行尾,一筆條目就是一行,安裝與移除都靠它比對):
```bash
marker="# jsc-code:target ${proj}"
entry="0 0 * * * cd '${proj}' && <headless 指令> >> '${log}' 2>&1 ${marker}"
mkdir -p "$(dirname "${log}")"
tmp="$(mktemp)"
crontab -l 2>/dev/null | grep -vF "${marker}" > "${tmp}" # 先移除同專案的舊條目(更新用)
printf '%s\n' "${entry}" >> "${tmp}"
crontab "${tmp}"
rm -f "${tmp}"
crontab -l | grep -F "${marker}" # 驗證確實寫進去了
```
- `crontab -l` 在「完全沒有排程」時會回非 0 並印錯誤,所以要 `2>/dev/null`;**不可因此把整份 crontab 當成空的覆寫**——先確認 `crontab -l` 的失敗是「沒有排程」而不是權限問題。
- 指令中若出現 `%`,crontab 會把它當換行,必須寫成 `\%`
- 條目一律用**絕對路徑**(cron 的 `PATH` 很精簡);若 `command -v <cli>` 得到的不是系統路徑,把 CLI 也寫成絕對路徑。
**schtasks**Windows):
```powershell
$task = "jsc-code-target-<專案名>" # 專案名重複時附加路徑短碼,確保唯一
schtasks /Create /TN "$task" /SC DAILY /ST 00:00 /F `
/TR "cmd /c cd /d ""<專案絕對路徑>"" && <headless 指令> >> ""<log 絕對路徑>"" 2>&1"
schtasks /Query /TN "$task" # 驗證
```
`/F` 只覆蓋**同名**任務(即本 skill 自己那筆),不影響其他排程。
### S4. 移除排程(`--schedule off`
```bash
tmp="$(mktemp)"
crontab -l 2>/dev/null | grep -vF "# jsc-code:target ${proj}" > "${tmp}"
crontab "${tmp}"
rm -f "${tmp}"
crontab -l 2>/dev/null | grep -F "# jsc-code:target ${proj}" || echo "已移除"
```
```powershell
schtasks /Delete /TN "jsc-code-target-<專案名>" /F
```
- 找不到條目 → 回報「本來就沒有排程」,視為成功,不報錯。
- **只刪自己標記的那一筆**;其餘 crontab 內容原樣寫回。`TARGET.md`、log 檔、git 分支都不刪。
### S5. 確認排程服務真的在跑(`--schedule on` 後必做)
排程條目寫進去 ≠ 會被執行。安裝後檢查:
```bash
pgrep -x cron >/dev/null || pgrep -x crond >/dev/null || echo "cron 服務未執行"
```
- **WSL 特別注意**WSL 預設不啟動 cron,需要 `sudo service cron start`,且**重開 WSL 後要再啟動一次**(可寫進 `/etc/wsl.conf``boot.command` 或啟動腳本)。偵測到未執行時**明確告知使用者這件事**,不要只回報「排程已建立」。
- Windows`schtasks /Query /TN "<任務名>"` 能查到即可。
---
## 階段 AGit 同步(`--run`
### A1. 盤點狀態
```bash
cd "<專案根目錄>"
git rev-parse --show-toplevel
git status --porcelain
git rev-parse --abbrev-ref HEAD
```
- **工作區有未提交變更** → **停止**並回報(排程情境寫入 log)。理由:本 skill 結尾會分類提交所有變更,混入使用者未完成的工作會把它一起提交上去。依 `/jsc-shared:spec-git-safety`**不得** stash、`reset --hard``clean` 來清場。
- **detached HEAD** → 停止並回報。
### A2. Fetch 與判定遠端預設分支
```bash
git fetch --all --prune
git symbolic-ref --quiet refs/remotes/origin/HEAD # → refs/remotes/origin/<預設分支>
```
取不到時退而用 `git remote show origin`(找 `HEAD branch:` 那行);`--base` 有帶則直接採用。兩者都不成立 → 停止並詢問,不臆測 `master``main`
### A3. 切到 `develop`(不存在就從預設分支建立)
```bash
if git rev-parse --verify --quiet origin/develop >/dev/null; then
git switch develop 2>/dev/null || git switch -c develop --track origin/develop
git pull --ff-only
else
git switch -c develop "origin/<預設分支>" # 遠端沒有 develop → 從預設分支開一條新的
fi
```
- `pull --ff-only` 失敗(分岔)→ 停止並回報,交由使用者處理,不做 merge/rebase 猜測。
- 本地已有 `develop` 但遠端沒有 → 沿用本地那條,**不覆蓋、不重建**;並在回報中說明它還沒推上遠端。
- 新建 develop 屬不可忽略的狀態變更,必須在回報與 log 中明講「遠端沒有 develop,已從 `<預設分支>` 建立」。
---
## 階段 B:讀 `TARGET.md` 並盤點待辦
1.`<專案根目錄>/TARGET.md`(UTF-8)。檔案不存在 → 回報「無 TARGET.md,無待辦」,**正常結束**(不進入 C~F、不 commit、不發 PR)。
2. 解析出所有 `- [ ]` 未勾選項目(含子項目的從屬關係),依**檔案由上而下**的順序排定執行順序。
3. 全部都是 `- [x]` → 回報「TARGET.md 已全部完成」,正常結束。
4. 輸出待辦清單表,除非使用者要求確認,否則直接進入階段 C:
| # | 項目 | 子項目數 | 備註 |
| --- | --- | --- | --- |
---
## 階段 C:逐項實作並回寫 `TARGET.md`
依 B 的順序,**一次做一項,做完一項就回寫 `TARGET.md`**(中途失敗時已完成的進度不會遺失):
1. 讀懂該項目要什麼;必要時讀相關程式碼與文件確認脈絡。
2. **語意不明、範圍不清、或牽涉設計取捨無法安全決定** → 不硬做。保持未勾選、補上 `> ⏭️ 待人工處理:<原因>`,繼續下一項。
3. 實作最小且合理的變更;能驗證的就驗證(該專案既有的 build/test 指令)。驗證失敗且無法安全修正 → 比照第 2 點標記待人工處理,並**還原**該項目造成的變更,不留半成品。
4. 完成 → 把該行改成 `- [x]` 並於行尾補 `<yyyy/MM/dd HH:mm:ss> 完成)`;有子項目則子項目全部完成後才勾選上層。
5. 每項結束記錄結果(✅ 完成 / ⏭️ 待人工處理+原因),供階段 G 總結。
`TARGET.md` 一律以 UTF-8(不含 BOM)寫回,結尾保留一個換行;**只改動勾選狀態與完成時間/待人工註記**,其餘內容原樣保留。
---
## 階段 D:分類提交
1. 盤點**所有**變更(含未追蹤檔):
```bash
git status --porcelain=v1 -uall
```
2. 依實際異動內容歸入 conventional commit 類型(`feat``fix``docs``style``refactor``perf``test``chore``revert`),**每個 type 各一個 commit**。
3. commit 訊息格式 `type(範圍): 一句總結`,**括號內是實際異動的功能/模組名**,不是重述 type:
- ✅ `feat(使用者服務): 補上帳號建立與停用的單元測試`、`docs(README): 更新安裝章節的 CLI 指令`
- ❌ `feat(新增功能)`、`docs(文件)`
- `TARGET.md` 本身的勾選更新歸 `chore`,訊息用 `chore(TARGET): 更新每日待辦完成狀態`。
4. 逐組精準 `git add -- <該組檔案...>` 後 commit,不用 `git add -A` 一次混提。
5. 無任何變更(例如所有項目都待人工處理,只有 `TARGET.md` 的註記異動)→ 仍照常提交 `TARGET.md` 的異動;連 `TARGET.md` 都沒變則跳過 D、E、F,直接進入總結。
> 分類與訊息規則與 `/jsc-code:review-resolve` 階段 C 一致,細節可參照該 skill。
---
## 階段 EPush `develop`
```bash
git log --oneline "origin/develop..develop" 2>/dev/null # 領先遠端的 commit;遠端無 develop 時視為全部要推
```
無 commit 可推 → 跳過 push,直接進入階段 F(既有遠端分支仍可開 PR)。否則依序嘗試,前者失敗才換下一個:
1. **認證管理器**`git push -u origin develop`
2. **token**:從環境變數帶入,指令與輸出**全程遮蔽 token、不寫進 remote 設定**
```bash
git push "https://oauth2:${GITEA_TOKEN}@<host>/<owner>/<repo>.git" develop
```
3. **都失敗** → 停止並回報失敗原因(先遮蔽 token)。排程情境寫入 log,等使用者處理,不猜測其他憑證。
---
## 階段 F:對預設分支發 PR`--no-pr` 時略過)
### F1. 目標分支
`--base` 有帶則用它,否則用 A2 判定出的**遠端預設分支**。若預設分支就是 `develop`(等於 head=base)→ 不開 PR,回報原因即可。
### F2. 先查有沒有現成的 open PR(每天跑,不可重複開)
```bash
curl -sS -H "Authorization: token ${GITEA_TOKEN}" \
"https://<host>/api/v1/repos/<owner>/<repo>/pulls?state=open&base=<預設分支>"
```
已存在 `head=develop` → `base=<預設分支>` 的 open PR → **沿用它**(今天新推的 commit 會自動出現在該 PR),回報既有 PR 連結,並視情況在該 PR 補一則今日進度留言;**不重複建立**。
### F3. 建立 PR
repo 座標由 `git remote get-url origin` 解析(hostownerrepo)。body 以 UTF-8 JSON 檔帶入,換行用實際換行、不可送出字面 `\n`
```bash
curl -sS -X POST \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
"https://<host>/api/v1/repos/<owner>/<repo>/pulls" \
--data @body.json
```
- **標題**`chore(TARGET): <yyyy/MM/dd> 每日待辦`(有明確主軸時可改成該主軸的一句總結)。
- **描述**:本次完成的項目清單(✅/⏭️ 各自列出+待人工處理原因)、變更摘要與影響範圍、本次的 commit 一覽。
- 失敗 → 回報 API 錯誤訊息(先遮蔽 token)。常見原因:目標分支不存在、已有相同 head→base 的 PR(回到 F2 沿用)、token 權限不足。
### F4. 收尾
pushAPI 過程可能讓 token 殘留在對話內文;**互動情境**完成後提醒使用者清除對話(Claude Code 用 `/clear`)。排程情境只寫 log,不輸出 token。
---
## 總結
執行後輸出(排程情境同時以 `[時間][階段][等級]: 訊息` 寫入 log):
| 模式 | 要回報的內容 |
| --- | --- |
| `--schedule on` | 用了哪種機制(crontabschtasks)、條目內容、log 路徑、排程服務是否在跑(WSL 未啟動 cron 要明講)、下次執行時間 |
| `--schedule off` | 移除了哪一筆、其餘排程未受影響 |
| `--run` | 待辦總數/✅ 完成 N 項/⏭️ 待人工處理 K 項(逐項原因)、develop 是新建還是既有、建立了哪幾個 commit、push 結果與方式、PR 連結(新建或沿用既有)、或為何略過 |
| 無參數 | 排程狀態+下次執行時間、`TARGET.md` 是否存在與剩餘未完成項目數 |
---
## 呼叫方式
| 助理 | 呼叫 |
| --- | --- |
| Claude Code / Antigravity | `/jsc-code:target --schedule on`、`/jsc-code:target --schedule off`、`/jsc-code:target --run --yes`、`/jsc-code:target --schedule on --run --project-dir ~/work/app` |
| Codex | `$target --schedule on`、`$target --run --yes`,或用 `/skills` 選單 |
| OpenCode | 描述需求(如「幫我把這個專案的 TARGET.md 排成每天 00:00 自動執行,做完分類提交、push develop 並對預設分支發 PR」)自動觸發 |