diff --git a/references/behaviors.md b/references/behaviors.md index 3e3db9a..a60ba42 100644 --- a/references/behaviors.md +++ b/references/behaviors.md @@ -7,7 +7,7 @@ | 項目 | 內容 | | --- | --- | | 觸發時機 | 使用者要把一個專案的所有外部套件更新到最新穩定版本時使用。jsc-sdlc 維護階段也把它當成維護動作之一。專案必須是 nodejs、python 或 dotnet,而且工作區必須乾淨。不要用它新增套件、移除套件,也不要用它把單一套件改成指定版本。 | -| 關鍵步驟 | 先跑 `tools/git-guard.sh check` 當硬閘門,證明目標是 git 工作樹而且 `git status --porcelain` 沒有輸出、跑 `tools/list-packages.sh` 列出每個套件的 ecosystem、名稱與目前版本、對每個 ecosystem 跑 `tools/build-test.sh --detect` 取得建置與測試指令,推不出來就依 jsc-ask:ask 問使用者、每個 ecosystem 開一個 sub agent 並行更新,同一個 ecosystem 內先併發跑 `tools/latest-version.sh` 查版本,再逐一跑 `tools/apply-version.sh` 改寫版本來源檔案、每個 ecosystem 的套件都套用完才跑一次 `tools/install-deps.sh`、跑 `tools/build-test.sh` 建置與測試、真的失敗就從錯誤輸出點出嫌疑套件,用 `tools/apply-version.sh` 把嫌疑套件釘回舊版並寫下原因註解,重新安裝後只重試一次、重試仍失敗才跑 `tools/git-guard.sh revert --confirm-destructive` 還原、成功就報出更新、釘回舊版與跳過三份清單,交給 jsc-git:commit。 | +| 關鍵步驟 | 先跑 `tools/git-guard.sh check` 當硬閘門,證明目標是 git 工作樹而且 `git status --porcelain` 沒有輸出、跑 `tools/list-packages.sh` 列出每個套件的 ecosystem、名稱與目前版本、對每個 ecosystem 跑 `tools/build-test.sh --detect` 取得建置與測試指令,推不出來就依 jsc-ask:ask 問使用者、每個 ecosystem 開一個 sub agent 並行更新,同一個 ecosystem 內先併發跑 `tools/latest-version.sh` 查版本,再逐一跑 `tools/apply-version.sh` 改寫版本來源檔案、每個 ecosystem 的套件都套用完才跑一次 `tools/install-deps.sh`、跑 `tools/build-test.sh` 建置與測試、真的失敗就從錯誤輸出點出嫌疑套件,用 `tools/apply-version.sh` 把嫌疑套件釘回舊版並寫下原因註解,重新安裝後只重試一次、重試仍失敗才跑 `tools/git-guard.sh revert --confirm-destructive` 還原、成功就報出更新、釘回舊版與跳過三份清單,交給 jsc-git:commit、最後不論成功、還原或停手,一律呼叫 `jsc-hooks/tools/report-status.sh skill-end jsc-pkg:pkg-update {status} {結束碼} {detail}` 記下這一輪怎麼結束。那支腳本在別的 plugin,路徑一定要帶 `jsc-hooks/` 前綴,寫成本技能自己的 `tools/` 會指到不存在的檔案;檔案不在就安靜跳過,回報失敗不得變成套件更新失敗。 | | 外部呼叫 | 腳本 `tools/git-guard.sh`、`tools/list-packages.sh`、`tools/latest-version.sh`、`tools/apply-version.sh`、`tools/install-deps.sh`、`tools/build-test.sh`。技能 jsc-ask:ask(問建置與測試指令)、jsc-git:commit(成功後提交)。外部服務為 npm、PyPI、NuGet 三個註冊處,由 `latest-version.sh` 以 curl 查詢。不呼叫 Gitea API。 | -| 完成條件 | 三種結局各自有完成條件。成功時,步驟 1 列出的每個套件都恰好出現在更新、釘回舊版、跳過其中一份清單,而且已經交給 jsc-git:commit。走還原路線時,`git-guard.sh revert` 回 0,接著報出失敗套件與錯誤摘要。走停手路線時,報出失敗的工具與結束碼、已經套用的套件、已經改寫的版本來源檔案,而且不進入還原步驟。 | -| 可驗證跡象 | 專案目錄的版本來源檔案被改寫,`git diff` 看得到 package.json、requirements.txt、pyproject.toml 或 `*.csproj` 的版本字串變動。`install-deps.sh` 跑過會留下鎖定檔與安裝產物,例如 package-lock.json、node_modules、bin、obj。釘回舊版的套件旁邊留有一行說明原因的註解,必要時附第三方 issue 連結。成功路線由 jsc-git:commit 產生 commit,`git log` 查得到。走還原路線後,`git status --porcelain` 不印任何內容,被 gitignore 的安裝產物留在原地。這支技能不寫任何 wiki 頁,也不開 PR。 | +| 完成條件 | 三種結局各自有完成條件。成功時,步驟 1 列出的每個套件都恰好出現在更新、釘回舊版、跳過其中一份清單,而且已經交給 jsc-git:commit。走還原路線時,`git-guard.sh revert` 回 0,接著報出失敗套件與錯誤摘要。走停手路線時,報出失敗的工具與結束碼、已經套用的套件、已經改寫的版本來源檔案,而且不進入還原步驟。三種結局都要再走完最後一步:呼叫 `report-status.sh skill-end`,狀態五選一——每個套件都升到新版而且建置與測試通過是 `ok`;有套件被釘回舊版或被跳過、但重試那一輪建置與測試過了而且交給 jsc-git:commit 是 `degraded`;真的安裝、建置或測試失敗而走還原是 `failed`;`git-guard.sh check` 因為工作區不乾淨或不是 git 工作樹擋下、什麼都還沒寫是 `blocked`;盤點列不出任何套件,或使用者講不出建置與測試指令而主動停手是 `aborted`。腳本不在磁碟上就跳過,這一步照樣算走完。 | +| 可驗證跡象 | 專案目錄的版本來源檔案被改寫,`git diff` 看得到 package.json、requirements.txt、pyproject.toml 或 `*.csproj` 的版本字串變動。`install-deps.sh` 跑過會留下鎖定檔與安裝產物,例如 package-lock.json、node_modules、bin、obj。釘回舊版的套件旁邊留有一行說明原因的註解,必要時附第三方 issue 連結。成功路線由 jsc-git:commit 產生 commit,`git log` 查得到。走還原路線後,`git status --porcelain` 不印任何內容,被 gitignore 的安裝產物留在原地。三種結局都會讓 `$JSC_HOME/usage/events.jsonl` 尾端多一筆 `{kind:skill,phase:end}` 事件,`name` 是 `jsc-pkg:pkg-update`,`status` 與那次結局相符,`exit` 是決定結局的工具的結束碼;`report-status.sh` 不在那台機器上就沒有這一筆,而更新結果與工作區狀態一字不變。這支技能不寫任何 wiki 頁,也不開 PR。 | diff --git a/skills/pkg-update/SKILL.md b/skills/pkg-update/SKILL.md index 1e34834..bcf048e 100644 --- a/skills/pkg-update/SKILL.md +++ b/skills/pkg-update/SKILL.md @@ -64,3 +64,7 @@ Exit 4 and exit 5 are the two codes whose meaning depends on the tool, so read t Step 3 is done only when every ecosystem landed in exactly one of three end states: exit 0 on the first run; exit 0 on the single retry, with every suspect of that ecosystem carrying a written hold plus a comment naming its reason; or entered step 4. 4. A real install, build or test failure reached this step → revert. Run `tools/git-guard.sh revert {project dir} --confirm-destructive`. The script re-proves the target is a git work tree before it runs anything destructive, restores every tracked file with `git checkout -- .`, removes this run's untracked files with `git clean -fd`, and verifies `git status --porcelain` prints nothing. Gitignored paths survive `-fd` on purpose: `node_modules`, `__pycache__`, `bin/` and `obj/` stay behind, so restoring install output is out of scope for this skill. Route the exit code per the table above. Step 4 is done only when the script exited 0; report the failing packages with an error summary after that. 5. Success → report the update list (package, old version, new version), every package held at its old version by step 3 with the reason, and every skip with its reason, then hand off to `jsc-git:commit`. Step 5 is done when the hand-off is made and every package from step 1 appears exactly once, as an update, a hold or a skip. +6. **Record how the run ended.** This is the closing step and it runs on all three endings — the success of step 5, the revert of step 4, and every stop route. Run `jsc-hooks/tools/report-status.sh skill-end jsc-pkg:pkg-update {status} {exit} "{detail}"`; the leading `jsc-hooks/` is the whole point — every other script this skill runs is its own `tools/{name}.sh`, and this one lives in a sibling plugin, so a bare `tools/` path would resolve to a file that is not there. What records a skill's start cannot see how it ended, so an ending that is never written reads afterwards as a run that was abandoned mid-way, and a reverted run looks the same as a clean one. + - `{status}` is one of five. `ok`: every package landed on its new version and build and test passed, with no hold and no skip. `degraded`: the run finished and part of it did not land — a package held at its old version by step 3 while build and test passed on the retry, or a package skipped for a missing registry entry or a missing version source file. That is `degraded` and never `failed`, because step 5 was reached and the hand-off to `jsc-git:commit` was made. `failed`: a real install, build or test failure that step 3's single retry did not clear, so step 4 reverted the working tree. `blocked`: `git-guard.sh check` refused the run because the tree was not clean or was not a git work tree, so nothing was ever written. `aborted`: the run stopped before it could do its work on a precondition that does not hold — the inventory listed zero packages, or the user could not name a build and test command at substep 1.3. + - Take `{exit}` from the tool that decided the ending — the `build-test.sh`, `install-deps.sh` or `git-guard.sh` code that routed the run — and otherwise use 0 for `ok` and 1 for every other status. `{detail}` is optional, one line, at most 200 characters: the counts of updated, held and skipped packages, or the failing tool with its code. Never put a build log or a package list of unbounded length there; the full lists belong in the report of step 5. + - Reporting never changes the run. A machine without `report-status.sh` skips this step in silence and keeps its step 5 result exactly as it stands; the script swallows its own write failures and always exits 0, so nothing here is worth routing on. Step 6 is done when the call was made, or the script was absent and the step was skipped without a word.