Author SHA1 Message Date
admin fa15b57adc Merge pull request '釋出:收尾寫一筆執行狀態事件' (#23) from develop into master
Reviewed-on: #23
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-09-04 05:02:20 +00:00
admin 19a8443d4e Merge pull request '收尾寫一筆 skill-end 事件,執行狀態才回報得到助理' (#22) from feat/status-report into develop
Reviewed-on: #22
2026-09-02 08:04:51 +00:00
jiantw83 58cbab4be5 chore(plugin 版本): 三份 manifest 升版至 0.1.0 2026-09-02 16:01:17 +08:00
jiantw83 1cbe7aac3c feat(狀態回報): 收尾寫一筆 skill-end 事件
現行紀錄只記「被叫用」,沒有成敗也沒有結束碼。跑完整輪的技能與開場就
中止的技能,在紀錄裡長得一模一樣。

start 由技能用量 hook 順手發,不必改技能文件。end 只能由技能自己在收尾
步驟寫——hook 接在技能工具呼叫上,而實際工作發生在之後的模型輪次,它在
原理上看不到成敗。有 start 沒有配對的 end,就是那一輪中止了。

status 五選一,每支技能各自寫明什麼情況選哪一個。找不到回報腳本就安靜
跳過,回報失敗一律不改變技能自己的結論。
2026-09-02 16:01:17 +08:00
admin 38afebcc0d Merge pull request '釋出 jsc-assist 的 marketplace 條目' (#21) from develop into master
Reviewed-on: #21
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-09-01 04:57:52 +00:00
admin 3de1dcbabe Merge pull request '放行 jsc-assist 的 marketplace 條目到預設分支' (#20) from chore/marketplace-assist-registry/main into develop
Reviewed-on: #20
2026-09-01 04:55:31 +00:00
admin a4f42c91d1 Merge pull request 'chore/marketplace-assist-registry/sync-copies' (#19) from chore/marketplace-assist-registry/sync-copies into chore/marketplace-assist-registry/main
Reviewed-on: #19
2026-09-01 04:53:47 +00:00
jiantw83 a68e8fcfbb chore(marketplace): 把 jsc-assist 登錄進統一 marketplace
What:
- 兩份 marketplace 檔各加一個 jsc-assist 條目,來源網址指向 assist 存放庫。

Why:
- 準則要求每個 domain 存放庫都帶同一份 marketplace 檔,任何一個存放庫都能當註冊入口。副本之間只要有一份沒跟上,稽核就會報出不一致。
- 正本少了這個條目,各 CLI 的安裝指令就找不到 jsc-assist,這個 domain 等於發佈不出去。

How:
- 條目由 meta 的 sync-marketplace.sh 產生,同時寫進正本與每個 domain 存放庫的副本,寫完逐檔比對位元組。這一支存放庫的兩份副本就是那一輪的產物。
- 條目依名稱排序,縮排與非 ASCII 描述的處理都交給同一支腳本,不手改 JSON。
- 這一批是從最新的預設分支重新產生的。前一輪的分支基底早於監控頁型別那批改動,直接合併會把那些改動回退掉,所以整批重做而不是解衝突。

Who:
助理 domain 落地的註冊步驟在這個存放庫的同步。
2026-09-01 12:50:04 +08:00
admin 67fa5c5f57 Merge pull request '釋出 jsc-pkg 0.0.9:新增技能行為清單' (#17) from develop into master
Reviewed-on: #17
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-08-31 08:19:10 +00:00
jiantw83 da31b890ed Merge pull request '收攏 pkg-update 的行為清單,功能主幹併回 develop' (#16) from feat/skill-behaviors-and-version-block/main into develop 2026-08-31 08:10:38 +00:00
jiantw83 b3d2c5dc6d Merge pull request '寫下 pkg-update 的行為基準,新增行為清單' (#15) from feat/skill-behaviors-and-version-block/behavior-list into feat/skill-behaviors-and-version-block/main 2026-08-31 08:09:25 +00:00
jiantw83 0dccd19061 chore(manifest): 三份 manifest 升版到 0.0.9
What:
- .claude-plugin/plugin.json 版本改成 0.0.9。
- .codex-plugin/plugin.json 版本改成 0.0.9。
- plugin.json 版本改成 0.0.9。

Why:
- 這次新增行為清單,屬於外顯內容變更,要跟著升版。
- 版本守門會比對三份 manifest,版本不一致就報錯。

How:
- 由 0.0.8 遞增到 0.0.9,只改版本欄位。
- 三份檔案改成同一個版本字串。

Who:
- 版本守門讀這三份 manifest 判斷是否需要更新。
- 使用者安裝或更新外掛時,看到的是這個版本。
2026-08-31 13:43:34 +08:00
jiantw83 49fef7069e docs(behaviors): 新增 jsc-pkg 技能行為清單
What:
- 新建 references/ 目錄。
- 新增 references/behaviors.md,收錄 pkg-update 一支技能。
- 每支技能一節,節內五列表:觸發時機、關鍵步驟、外部呼叫、完成條件、可驗證跡象。

Why:
- 技能驗證需要一份行為基準來比對。
- 清單放在技能自己的 repo,技能改動與清單就能進同一個 PR。
- 同一個 PR 才不會漂移,也不用跨 repo 開兩條 PR 互卡。

How:
- 盤點 skills/ 下的技能,逐支寫出五個欄位的內容。
- 關鍵步驟寫到工具腳本層級,外部呼叫列出腳本、技能與外部服務。
- 格式交給 meta/tools/check-behaviors.sh 在程式層檢查。

Who:
- 技能驗證流程讀這份清單當基準。
- 日後任何技能異動,都要在同一個 PR 內同步更新這一頁。
2026-08-31 13:43:34 +08:00
admin 756d1199d2 Merge pull request 'chore(release): 放行技能組稽核修正到預設分支' (#14) from develop into master
Reviewed-on: #14
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-08-31 03:54:46 +00:00
admin c9e1f52709 Merge pull request 'fix/skill-check-compliance-and-flow' (#13) from fix/skill-check-compliance-and-flow into develop
Reviewed-on: #13
2026-08-31 03:16:17 +00:00
jiantw83 b76f1ab80f chore(plugin): 同步三份外掛描述與相依宣告
What:三份外掛資訊檔的描述改成「先把嫌疑套件釘回舊版重試一次,真失敗才還原」,補上 jsc-hooks 相依,版本號往前推一版。

Why:描述停在「失敗還原」,跟現在的行為不一樣,使用者從外掛清單看到的是錯的說明。技能靠 jsc-hooks 的註解掃描在程式層把關註解內容,相依沒宣告的話,環境缺了它也沒人擋得住。

How:claude、codex 與根目錄三份資訊檔一起改,欄位內容保持一致。

Who:jsc-pkg 外掛的中繼資料。
2026-08-31 11:09:01 +08:00
jiantw83 ab5494a128 docs(readme): 更新工具表與還原前提說明
What:工具表補上還原閘門腳本一列與 `--detect` 用法,新增「還原的破壞性前提」一節,改寫結束碼總表與技能摘要。

Why:新腳本沒進工具表,讀 README 的人找不到它,也不知道還原多了一個必要旗標。原本寫「五支工具都靠 python3」也不對——新的閘門腳本只用 git,安裝那支根本不碰 python3,照著讀會誤判環境需求。還原策略已經改成先釘回舊版再重試,摘要卻還停在舊寫法。

How:工具表逐支列出真正需要的指令。新增一節寫清楚 `git clean -fd` 的風險與那五道前提,並說明判定從嚴、動手從窄。結束碼總表改成「一個數字一個類別」,同一個碼在各工具指的對象寫在該列裡。技能摘要改成先把嫌疑套件釘回舊版重試一次,真失敗才還原。

Who:pkg domain 的說明文件。
2026-08-31 11:09:01 +08:00
jiantw83 1b223bd350 fix(pkg-update): 對齊 stop 的定義與行為,補上釘回舊版的產生路徑
What:改寫 stop 路由的定義,新增「把嫌疑套件釘回舊版重試一次」的路由,把 git 閘門提到最前面,流程由七步併成五步。

Why:原本寫 stop「不改任何檔案」,但流程走到後段才 stop 時,版本來源檔案已經被改寫過。定義跟行為對不上,使用者會以為工作區沒動過,實際上留了一地改過的檔案沒人回報。另外技能要求「套件停留在舊版時要寫註解說明原因」,卻沒有任何一條路由會產生停留在舊版的結果,這個要求等於永遠踩不到。閘門排在套件盤點後面也不划算,工作區髒的時候,整輪全專案掃描白做。

How:stop 照實描述——第一步之前確實沒動過檔案;第二步起的 stop 要一併報出失敗工具與結束碼、已套用的套件、已改寫的版本來源檔案。建置或測試真的失敗時,先從失敗輸出點名嫌疑套件,把它們釘回舊版,重裝後只重試一次;重試過了就算通過,並標記成停留在舊版,仍失敗才走還原。git 閘門移到第一步的第一個動作,建置與測試指令也在動任何檔案之前先問清楚。

Who:pkg-update 的路由表與流程步驟。
2026-08-31 11:09:01 +08:00
jiantw83 338c2da89c feat(tools): 新增 git 還原閘門腳本,建置測試加上唯讀偵測模式
What:新增 `tools/git-guard.sh`,把工作區乾淨檢查與還原序列從技能內文搬進腳本。`tools/build-test.sh` 加上 `--detect` 唯讀模式,只偵測建置與測試指令,不執行。

Why:還原會執行 `git clean -fd`。這個指令刪掉未追蹤的檔案,沒有 reflog 可救,也沒有任何救回的路徑。前提原本只寫在技能內文,讀的人漏掉一行,就可能對著一個根本不該還原的目錄動手,把整個工作區的未追蹤檔案清光。前提要擋得住,就得寫成程式碼。另一件事是「推不出建置與測試指令」,原本要等所有版本來源檔案都改寫完才發現,使用者又答不出指令時,前面全部作廢,還得走一次還原。

How:`revert` 在跑第一個破壞性指令之前擋五道——目錄存在、git 指令存在、確實是 git 工作樹(不是裸存取庫)、呼叫端明確帶 `--confirm-destructive`、目標不是檔案系統根目錄。任何一道不過就直接結束,工作區一個位元組都不動。`check` 的乾淨判定看整個存取庫,`revert` 只作用在專案目錄以下,判定從嚴、動手從窄。`clean` 用 `-fd` 不用 `-fdx`,被 gitignore 的安裝產物要留著。`--detect` 只做偵測與前置檢查,一個檔案都不寫,結束碼與路由跟執行模式完全一致。

Who:套件批次更新的前置檢查與還原路線。
2026-08-31 11:09:01 +08:00
admin 928f7ca808 Merge pull request 'release: 發布 jsc-pkg 相依版本宣告' (#12) from develop into master
Reviewed-on: #12
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-08-28 06:52:18 +00:00
admin bdacce2f8f Merge pull request 'feat/plugin-dependencies/main' (#11) from feat/plugin-dependencies/main into develop
Reviewed-on: #11
2026-08-28 04:05:11 +00:00
admin db2605f36c Merge pull request 'feat/plugin-dependencies/declare-requires' (#10) from feat/plugin-dependencies/declare-requires into feat/plugin-dependencies/main
Reviewed-on: #10
2026-08-28 04:03:14 +00:00
jiantw83 88e67e5ebc feat(manifest): 宣告 pkg 技能相依版本 2026-08-28 11:59:16 +08:00
admin 9c33aae25f Merge pull request 'release: v0.0.6 develop 到 master' (#9) from develop into master
Reviewed-on: #9
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-08-27 02:08:02 +00:00
admin ce7d6d48a5 Merge pull request 'feat/comment-scope-rule' (#8) from feat/comment-scope-rule into develop
Reviewed-on: #8
2026-08-27 00:56:54 +00:00
jiantw83 913a51796d feat(manifest): 三份 manifest 版本升至 0.0.6
What:把 plugin.json、.claude-plugin/plugin.json、.codex-plugin/plugin.json 的 version 由 0.0.5 升為 0.0.6。

Why:pkg-update 技能新增註解界線規則,屬於行為變更,必須發版才會被各 CLI 的外掛版本守衛辨識並更新。

How:三份 manifest 同步改動同一個 version 欄位,其餘欄位不動,維持三份內容一致。

Who:jsc-pkg 外掛的三份 manifest。
2026-08-26 19:00:37 +08:00
jiantw83 992d9c759c feat(pkg-update): 加入註解界線規則與 README 同步說明
What:在 pkg-update 技能的步驟 3.5 新增註解界線規則,並在 README.md 的 pkg-update 段落同步同一份指引。

Why:套件更新途中常會為了鎖版本或繞道不相容而留註解,過去沒有界線,容易把文件相關資訊寫進程式碼註解,造成註解與文件重複、且內容很快過期。

How:規則正文留在 jsc-review 的 references/comment-scope.md,這裡只放指引與連結、不重複清單。註解只寫鎖版本或繞道的原因;第三方套件的 issue 連結屬白名單,用來說明繞道成因與解除條件;內部議題編號、工作包編號、人名與 @ 提及、產生來源署名一律不得寫。步驟 3.5 另附可檢核的完成條件,並指向 jsc-hooks 的 comment-scope.sh 即時告警與修正回檢流程。

Who:jsc-pkg 的 pkg-update 技能,以及 README.md 的技能說明區塊。
2026-08-26 19:00:37 +08:00
admin 4bf662ce0b Merge pull request 'fix/skillset-audit-compliance-and-guard-fixes' (#7) from fix/skillset-audit-compliance-and-guard-fixes into develop
Reviewed-on: #7
2026-08-25 07:15:17 +00:00
jiantw83andClaude Opus 5 35d4de6522 chore(pkg): 三份 manifest 同步升版並同步 marketplace 正本
What:三份 plugin manifest 版本同步 bump,兩份 marketplace 檔與 plugins/meta 正本對齊。

Why:準則要求技能異動必須同步升版;marketplace 副本必須與正本完全一致。

How:以 jsc-meta 的 tools/sync-skill-manifest.sh 升版,marketplace 檔由正本複製。

Who:jsc-meta:skill-check 例行稽核(2026-08-25)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 14:58:54 +08:00
jiantw83andClaude Opus 5 bb2e6821c1 docs(pkg): 同步文件與參考資料
What:更新 README、AGENTS.md、templates 與 references,讓文件敘述與實際行為一致。

Why:稽核發現多處文件與程式行為分歧,違反「每個意義只有單一真實來源」。

How:以實際程式行為為準改寫敘述,重複的規則收成單一來源並以一行指引指過去。

Who:jsc-meta:skill-check 例行稽核(2026-08-25)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 14:58:54 +08:00
jiantw83andClaude Opus 5 ea11ca0590 fix(pkg): 補齊稽核缺失並修掉護欄失效
What:依 jsc-meta:skill-check 的稽核結果修正技能與工具——補上每個步驟的可檢核完成條件、
把留在內文的標準輸入輸出流程下放 tools/、修正查表與退碼路由造成的誤判。

Why:稽核發現這些缺失會讓技能在實際執行時走錯分支或靜默通過。
完成條件缺漏是最常被違反的一項;退碼誤判與查表錯誤則會讓良性狀況被當成失敗。

How:逐項對照 references/guidelines.md 的審核檢查清單修正,新增的工具都有
documented exit codes,並以真實執行驗證每條路徑。

Who:jsc-meta:skill-check 例行稽核(2026-08-25)。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-25 14:58:54 +08:00
admin 85b89e4cdc Merge pull request '發佈 jsc-pkg 0.0.3:marketplace 正本移至 plugins/meta' (#6) from develop into master
Reviewed-on: #6
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-08-24 10:19:29 +00:00
admin f1fa06ac32 Merge pull request 'chore(marketplace 正本): 正本移至 plugins/meta(升版 0.0.3)' (#5) from chore/marketplace-canon-to-meta into develop
Reviewed-on: #5
2026-08-24 10:13:59 +00:00
jiantw83andClaude Opus 5 bf50821925 chore(plugin 版本): 三份 manifest 升版至 0.0.3
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 18:00:35 +08:00
jiantw83andClaude Opus 5 3724952cda docs(README): 安裝入口改為 plugins/meta 並補上遷移說明
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-24 18:00:35 +08:00
admin ae287e054c Merge pull request '發佈 jsc-pkg 0.0.2:新增套件版本改寫工具' (#4) from develop into master
Reviewed-on: #4
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-08-24 08:27:56 +00:00
admin ef91ebbff4 Merge pull request 'fix/sync-marketplace-and-pkg-version-tool' (#3) from fix/sync-marketplace-and-pkg-version-tool into develop
Reviewed-on: #3
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-08-24 07:59:16 +00:00
jiantw83 5ae3611960 feat(pkg-update): 新增版本改寫工具並改用工具呼叫
What: 新增 tools/apply-version.sh,依 ecosystem 改寫專案的版本來源檔(package.json / requirements.txt / pyproject.toml / *.csproj),把指定套件釘選到指定版本;skills/pkg-update/SKILL.md 的改寫步驟改成呼叫這支工具;README.md 補上工具說明;plugin.json、.claude-plugin/plugin.json、.codex-plugin/plugin.json 版本號由 0.0.1 升到 0.0.2。
Why: jsc-meta:skill-check 稽核發現 SKILL.md 用文字描述改寫邏輯,不符合「邏輯放工具、SKILL.md 只放步驟」的準則,需要把改寫邏輯抽成獨立工具。
How: 依 tools/list-packages.sh 已支援的四種生態(nodejs 的 package.json、python 的 requirements.txt 或 pyproject.toml、dotnet 的 *.csproj)撰寫對應的 parse 與 rewrite 邏輯,找不到檔案時 exit 3、找不到套件時 exit 4;SKILL.md 步驟改成呼叫 `tools/apply-version.sh {ecosystem} {project dir} {name} {version}`;三份 manifest 版本號同步升級。
Who: jsc-pkg 套件更新流程,影響 pkg-update 技能與所有安裝 jsc-pkg 的使用者。
2026-08-24 14:48:30 +08:00
jiantw83 3e3e1e588f fix(marketplace): 補齊 canonical marketplace 缺少的欄位
What: 同步 .agents/plugins/marketplace.json 與 .claude-plugin/marketplace.json 至 plugins/jsc 的 canonical marketplace,補上每個套件缺少的 description 與頂層 owner 欄位。
Why: 兩份 marketplace.json 與 canonical 版本不同步,缺少的欄位會讓套件說明無法顯示,也不符合治理格式。
How: 逐一比對 canonical marketplace.json,把每個套件的 description 補齊,並在頂層加上 owner 欄位,內容與 canonical 一致。
Who: 全庫共用的 infra 修正,影響所有安裝這份 marketplace 的 CLI。
2026-08-24 14:48:15 +08:00
admin d72db8a607 Merge pull request 'feat(pkg): 匯入 jsc-pkg 技能組並統一 marketplace 為 jsc' (#2) from develop into master
Reviewed-on: #2
Reviewed-by: 系統管理員 <1+admin@noreply.localhost>
2026-08-21 06:42:09 +00:00
jiantw83andClaude Fable 5 7b446fa28e chore(pkg): 加入統一 jsc marketplace 副本(與正本一致,任一 repo 可作註冊入口)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 14:37:50 +08:00
jiantw83andClaude Fable 5 6f263ef355 style(pkg): 中文並列改頓號
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 14:29:45 +08:00
jiantw83andClaude Fable 5 a35e122cfe docs(pkg): translate SKILL.md into English per guidelines
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 14:29:45 +08:00
jiantw83andClaude Fable 5 3ce5ce989b docs(pkg): AGENTS 語言規則改指向 jsc-meta 的 references/ste100.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 14:13:27 +08:00
15 changed files with 837 additions and 47 deletions
+97
View File
@@ -0,0 +1,97 @@
{
"name": "jsc",
"description": "jsc 跨 AI 助理技能組的統一 marketplace(claude / codex / copilot / antigravity / kiro)。",
"owner": {
"name": "JSC"
},
"plugins": [
{
"name": "jsc-ask",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/ask.git"
},
"description": "決策樹問詢與問詢紀錄(QUESTION_* wiki 頁)"
},
{
"name": "jsc-assist",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/assist.git"
},
"description": "助理:事件收攏、健康巡檢與待辦簿(MONITOR_* wiki 頁)"
},
{
"name": "jsc-cli",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/cli.git"
},
"description": "CLI 偵測、模型能力標籤與技能庫批次部署"
},
{
"name": "jsc-git",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/git.git"
},
"description": "Commit 分組認可與 Push Request 建立"
},
{
"name": "jsc-gitea",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/gitea.git"
},
"description": "Gitea API 工具、Wiki 讀寫與存取庫批次同步"
},
{
"name": "jsc-hooks",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/hooks.git"
},
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查"
},
{
"name": "jsc-log",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/log.git"
},
"description": "工作日誌(LOG_* wiki 頁)與技能使用統計"
},
{
"name": "jsc-meta",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/meta.git"
},
"description": "技能組自我管理:新建、更新、刪除技能與技能準則"
},
{
"name": "jsc-pkg",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/pkg.git"
},
"description": "套件批次更新(nodejs/python/dotnet),失敗還原"
},
{
"name": "jsc-review",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/review.git"
},
"description": "程式碼審查:Refactoring 壞味道六組、註解規範、淺模組"
},
{
"name": "jsc-sdlc",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/sdlc.git"
},
"description": "開發生命週期:規劃、分析、實作、維護(wiki 追蹤)"
}
]
}
+97
View File
@@ -0,0 +1,97 @@
{
"name": "jsc",
"description": "jsc 跨 AI 助理技能組的統一 marketplace(claude / codex / copilot / antigravity / kiro)。",
"owner": {
"name": "JSC"
},
"plugins": [
{
"name": "jsc-ask",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/ask.git"
},
"description": "決策樹問詢與問詢紀錄(QUESTION_* wiki 頁)"
},
{
"name": "jsc-assist",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/assist.git"
},
"description": "助理:事件收攏、健康巡檢與待辦簿(MONITOR_* wiki 頁)"
},
{
"name": "jsc-cli",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/cli.git"
},
"description": "CLI 偵測、模型能力標籤與技能庫批次部署"
},
{
"name": "jsc-git",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/git.git"
},
"description": "Commit 分組認可與 Push Request 建立"
},
{
"name": "jsc-gitea",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/gitea.git"
},
"description": "Gitea API 工具、Wiki 讀寫與存取庫批次同步"
},
{
"name": "jsc-hooks",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/hooks.git"
},
"description": "跨 CLI hooks:STE100 語言強制、工時計時、技能用量記錄、SDLC 模型鎖、版本前置檢查"
},
{
"name": "jsc-log",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/log.git"
},
"description": "工作日誌(LOG_* wiki 頁)與技能使用統計"
},
{
"name": "jsc-meta",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/meta.git"
},
"description": "技能組自我管理:新建、更新、刪除技能與技能準則"
},
{
"name": "jsc-pkg",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/pkg.git"
},
"description": "套件批次更新(nodejs/python/dotnet),失敗還原"
},
{
"name": "jsc-review",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/review.git"
},
"description": "程式碼審查:Refactoring 壞味道六組、註解規範、淺模組"
},
{
"name": "jsc-sdlc",
"source": {
"source": "url",
"url": "https://gitea.jsc.idv.tw/plugins/sdlc.git"
},
"description": "開發生命週期:規劃、分析、實作、維護(wiki 追蹤)"
}
]
}
+11 -3
View File
@@ -1,7 +1,7 @@
{ {
"name": "jsc-pkg", "name": "jsc-pkg",
"version": "0.0.1", "version": "0.1.0",
"description": "套件批次更新(nodejs/python/dotnet),失敗還原", "description": "套件批次更新(nodejs/python/dotnet),先把嫌疑套件釘回舊版重試一次,真失敗才還原",
"skills": "./skills", "skills": "./skills",
"author": { "author": {
"name": "JSC" "name": "JSC"
@@ -13,5 +13,13 @@
"pkg", "pkg",
"skills", "skills",
"cross-tool" "cross-tool"
] ],
"jsc": {
"requires": {
"jsc-ask": ">=0.0.6",
"jsc-git": ">=0.0.9",
"jsc-hooks": ">=0.2.8",
"jsc-review": ">=0.0.8"
}
}
} }
+11 -3
View File
@@ -1,6 +1,14 @@
{ {
"name": "jsc-pkg", "name": "jsc-pkg",
"version": "0.0.1", "version": "0.1.0",
"description": "套件批次更新(nodejs/python/dotnet),失敗還原", "description": "套件批次更新(nodejs/python/dotnet),先把嫌疑套件釘回舊版重試一次,真失敗才還原",
"skills": "./skills" "skills": "./skills",
"jsc": {
"requires": {
"jsc-ask": ">=0.0.6",
"jsc-git": ">=0.0.9",
"jsc-hooks": ">=0.2.8",
"jsc-review": ">=0.0.8"
}
}
} }
+1 -1
View File
@@ -4,7 +4,7 @@
## 規則 ## 規則
1. 所有交談與輸出內容基於 STE100 使用繁體中文:短句、一句一指令、主動語態、術語一致、UTF-8 無亂碼。 1. 所有交談與輸出內容使用 STE100 繁體中文,帶擬人台灣感:短句、一句一指令、台灣用語、全形標點、去 AI 味、直接講重點。完整規則的唯一來源:`plugins/meta` 的 `references/ste100.md`。
2. 技能位於 `skills/{name}/SKILL.md`;處理任務前先比對需求與各技能的 `description`,相符就載入並依其步驟執行。 2. 技能位於 `skills/{name}/SKILL.md`;處理任務前先比對需求與各技能的 `description`,相符就載入並依其步驟執行。
3. 技能準則的唯一來源:`plugins/meta` 存取庫的 `references/guidelines.md`。 3. 技能準則的唯一來源:`plugins/meta` 存取庫的 `references/guidelines.md`。
4. 所有 hook 只放在 `jsc-hooks`;gitea 操作一律經由 `jsc-gitea` 的 `tools/gitea.sh`;問使用者一律依 `jsc-ask:ask` 的決策樹規則。 4. 所有 hook 只放在 `jsc-hooks`;gitea 操作一律經由 `jsc-gitea` 的 `tools/gitea.sh`;問使用者一律依 `jsc-ask:ask` 的決策樹規則。
+50 -13
View File
@@ -1,27 +1,62 @@
# jsc-pkg — 套件批次更新 # jsc-pkg — 套件批次更新
jsc 技能組的 pkg domain:把專案所有外部套件更新到最新穩定版本,完成後執行建置與測試,失敗則還原變更。支援 nodejs / python / dotnet。 jsc 技能組的 pkg domain:把專案所有外部套件更新到最新穩定版本,完成後執行建置與測試。過不了先把嫌疑套件釘回舊版重試一次,真的裝不起來或建置測試仍過不了才還原變更。支援 nodejs / python / dotnet。
## 安裝 / 更新 / 移除 ## 安裝、更新、移除
Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/jsc.git),安裝 token 為 `jsc-pkg@jsc`。每個指令一行: Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/meta.git),安裝 token 為 `jsc-pkg@jsc`。每個指令一行:
| CLI | 安裝 | 更新 | 移除 | | CLI | 安裝 | 更新 | 移除 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| claude | `claude plugin marketplace add https://gitea.jsc.idv.tw/plugins/jsc.git && claude plugin install jsc-pkg@jsc` | `claude plugin marketplace update jsc && claude plugin update jsc-pkg@jsc` | `claude plugin uninstall jsc-pkg@jsc` | | claude | `claude plugin marketplace add https://gitea.jsc.idv.tw/plugins/meta.git && claude plugin install jsc-pkg@jsc` | `claude plugin marketplace update jsc && claude plugin update jsc-pkg@jsc` | `claude plugin uninstall jsc-pkg@jsc` |
| codex | `codex plugin marketplace add https://gitea.jsc.idv.tw/plugins/jsc.git && codex plugin add jsc-pkg@jsc` | `codex plugin marketplace upgrade jsc` | `codex plugin remove jsc-pkg@jsc` | | codex | `codex plugin marketplace add https://gitea.jsc.idv.tw/plugins/meta.git && codex plugin add jsc-pkg@jsc` | `codex plugin marketplace upgrade jsc` | `codex plugin remove jsc-pkg@jsc` |
| copilot | `copilot plugin marketplace add https://gitea.jsc.idv.tw/plugins/jsc.git && copilot plugin install jsc-pkg@jsc` | `copilot plugin marketplace update jsc && copilot plugin update jsc-pkg@jsc` | `copilot plugin uninstall jsc-pkg@jsc` | | copilot | `copilot plugin marketplace add https://gitea.jsc.idv.tw/plugins/meta.git && copilot plugin install jsc-pkg@jsc` | `copilot plugin marketplace update jsc && copilot plugin update jsc-pkg@jsc` | `copilot plugin uninstall jsc-pkg@jsc` |
| antigravity | `git clone https://gitea.jsc.idv.tw/plugins/pkg.git ~/plugins/pkg && agy plugin install ~/plugins/pkg` | `git -C ~/plugins/pkg pull && agy plugin uninstall jsc-pkg && agy plugin install ~/plugins/pkg` | `agy plugin uninstall jsc-pkg` | | antigravity | `git clone https://gitea.jsc.idv.tw/plugins/pkg.git ~/plugins/pkg && agy plugin install ~/plugins/pkg` | `git -C ~/plugins/pkg pull && agy plugin uninstall jsc-pkg && agy plugin install ~/plugins/pkg` | `agy plugin uninstall jsc-pkg` |
| kiro | `kiro-cli plugin marketplace add https://gitea.jsc.idv.tw/plugins/jsc.git && kiro-cli plugin install jsc-pkg@jsc` | `kiro-cli plugin marketplace update jsc && kiro-cli plugin update jsc-pkg@jsc` | `kiro-cli plugin uninstall jsc-pkg@jsc` | | kiro | `kiro-cli plugin marketplace add https://gitea.jsc.idv.tw/plugins/meta.git && kiro-cli plugin install jsc-pkg@jsc` | `kiro-cli plugin marketplace update jsc && kiro-cli plugin update jsc-pkg@jsc` | `kiro-cli plugin uninstall jsc-pkg@jsc` |
> antigravity 不支援 gitea URL 安裝,改用本地 clone 路徑。批次操作五個 CLI:使用 `/jsc-cli:deploy`。 > antigravity 不支援 gitea URL 安裝,改用本地 clone 路徑。批次操作五個 CLI:使用 `/jsc-cli:deploy`。
> 舊入口 `plugins/jsc` 已移除,marketplace 正本移到 `plugins/meta`。marketplace 名稱仍是 `jsc`(取自 marketplace.json 的 `name` 欄位,與存取庫名無關),安裝 token 不變;已從舊入口安裝過的人先執行 `claude plugin marketplace remove jsc`,再依上表重新 add。
## 工具 ## 工具
| 工具 | 用途 | `list-packages.sh`、`latest-version.sh`、`apply-version.sh`、`build-test.sh` 靠 python3 解析或改寫檔案。少了 python3,這幾支都回報「需要的指令不存在」,不會裝成查無套件。`install-deps.sh` 不碰 python3,它把安裝交給各生態系自己的安裝器;`git-guard.sh` 只用 git。每支工具實際需要哪些指令,看下表最右欄。
| --- | --- |
| `tools/list-packages.sh` | 偵測專案類型並列出所有外部套件與目前版本(TSV:ecosystem / name / current) | | 工具 | 用途 | 需要的指令 |
| `tools/latest-version.sh` | `latest-version.sh <ecosystem> <name>` 查最新穩定版(npm / pypi / nuget),查無 exit 4 | | --- | --- | --- |
| `tools/git-guard.sh` | `git-guard.sh check <project dir>` 確認是 git 工作樹且工作區乾淨;`git-guard.sh revert <project dir> --confirm-destructive` 還原工作區 | git |
| `tools/list-packages.sh` | `list-packages.sh [專案目錄]` 偵測專案類型並列出所有外部套件與目前版本(TSV:ecosystem / name / current) | python3 |
| `tools/latest-version.sh` | `latest-version.sh <ecosystem> <name>` 查最新穩定版(npm / pypi / nuget) | curl、python3 |
| `tools/apply-version.sh` | `apply-version.sh <ecosystem> <project dir> <name> <version>` 改寫版本來源檔案,把套件釘選到指定版本 | python3 |
| `tools/install-deps.sh` | `install-deps.sh <ecosystem> <project dir>` 重新解析並安裝相依套件(npm install、pip install、dotnet restore) | npm、pip、dotnet |
| `tools/build-test.sh` | `build-test.sh [--detect] <ecosystem> <project dir>` 偵測並執行建置與測試;`--detect` 只偵測不執行,用來在動任何檔案之前先問出推不出指令的情況 | npm、python3、pytest、dotnet |
### 還原的破壞性前提
`git-guard.sh revert` 會執行 `git clean -fd`,未追蹤檔案刪掉沒有 reflog 可救。所以前提判定寫在腳本裡,不寫在技能內文:目錄存在、git 指令存在、確實是 git 工作樹(不是裸存取庫)、呼叫端明確傳入 `--confirm-destructive`、目標不是檔案系統根目錄,五條全過才會跑第一個破壞性指令。任何一條不過就回 1、4、5 或 6,工作區一個位元組都不動。
`check` 的乾淨判定看整個存取庫,`revert` 的還原只作用在專案目錄以下:判定從嚴,動手從窄。
### 結束碼總表
一個數字一個類別,六支工具共用;同一個類別在各工具指的對象可以不同,差別寫在該列裡。新增工具請沿用這張表,不要自己編號。
| 碼 | 意思 | 呼叫方的動作 |
| --- | --- | --- |
| 0 | 成功 | 繼續 |
| 1 | 參數個數不對 | 停手 |
| 2 | 不認識的輸入:多數工具是 ecosystem 不認識,`git-guard.sh` 是子指令不認識 | 停手 |
| 3 | 該 ecosystem 沒有來源檔案 | 跳過這個 ecosystem 或套件 |
| 4 | 依工具而定:`list-packages.sh`、`build-test.sh`、`install-deps.sh` 是指令不存在(停手);`latest-version.sh`、`apply-version.sh` 是查不到該套件(跳過) | 見左欄 |
| 5 | 依工具而定:`latest-version.sh`、`apply-version.sh` 是指令不存在(停手);`build-test.sh` 是推不出建置或測試指令(改問使用者);`git-guard.sh` 是前提不成立,指不是 git 工作樹、工作區不乾淨、還原後仍不乾淨(停手) | 見左欄 |
| 6 | 找不到專案目錄 | 停手 |
| 其他 | 底層指令的結束碼 | 只有 `install-deps.sh`、`build-test.sh` 會走還原 |
6 不與 3 合併:找不到專案目錄是輸入壞了,併進 3 會被當成跳過藏起來。`latest-version.sh` 不收專案目錄,所以沒有 6。
4 與 5 在各工具間意思不同,是為了保住既有的跳過路由;新增工具請優先用 4 表示指令不存在。
回 1、回 2、回 6 與回「指令不存在」時,技能只回報並停手,不還原工作樹——工具鏈壞了不是套件壞了,還原只會白白刪掉檔案。停手不等於沒動過檔案:更新步驟跑到一半才停手時,已改寫的版本來源檔案留在原地,技能要一併報出改了哪些檔案與哪些套件。
## Skills 目錄 ## Skills 目錄
@@ -31,11 +66,13 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/jsc.git),安
### `pkg-update` ### `pkg-update`
套件批次更新:列套件 → 查最新穩定版 → 逐 ecosystem 以 sub agent 更新 → 建置與測試(指令不明時決策樹詢問)→ 失敗還原全部變更。 套件批次更新:先驗 git 工作區乾淨當硬閘門,過了才列套件並問出建置與測試指令 → 各 ecosystem 以 sub agent 並行更新 → 建置與測試,失敗先把兇手釘回舊版重試一次 → 仍失敗才還原全部變更。
更新途中要在版本來源檔案或程式碼留註解時,只寫繞道或釘回舊版的原因;第三方套件的 issue 連結可以寫,用來說明成因與解除條件。禁止清單的正本只有一份,在 [`jsc-review`](https://gitea.jsc.idv.tw/plugins/review) 的 `references/comment-scope.md`,由 `jsc-hooks` 的 `hooks/comment-scope.sh` 在程式層強制。
<!-- JSC-SKILLS:END --> <!-- JSC-SKILLS:END -->
## 相關 domain ## 相關 domain
- [`jsc-ask`](https://gitea.jsc.idv.tw/plugins/ask):建置 / 測試指令不明時的決策樹詢問 - [`jsc-ask`](https://gitea.jsc.idv.tw/plugins/ask):建置與測試指令不明時的決策樹詢問
- [`jsc-sdlc`](https://gitea.jsc.idv.tw/plugins/sdlc):維護階段的建議維護方法之一 - [`jsc-sdlc`](https://gitea.jsc.idv.tw/plugins/sdlc):維護階段的建議維護方法之一
+11 -3
View File
@@ -1,6 +1,14 @@
{ {
"name": "jsc-pkg", "name": "jsc-pkg",
"version": "0.0.1", "version": "0.1.0",
"description": "套件批次更新(nodejs/python/dotnet),失敗還原", "description": "套件批次更新(nodejs/python/dotnet),先把嫌疑套件釘回舊版重試一次,真失敗才還原",
"skills": "./skills/" "skills": "./skills/",
"jsc": {
"requires": {
"jsc-ask": ">=0.0.6",
"jsc-git": ">=0.0.9",
"jsc-hooks": ">=0.2.8",
"jsc-review": ">=0.0.8"
}
}
} }
+13
View File
@@ -0,0 +1,13 @@
# jsc-pkg 技能行為清單
本頁記錄 jsc-pkg 每支技能的行為基準,供技能驗證比對。技能異動時,在同一個 PR 內一起更新這一頁。
## pkg-update
| 項目 | 內容 |
| --- | --- |
| 觸發時機 | 使用者要把一個專案的所有外部套件更新到最新穩定版本時使用。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、最後不論成功、還原或停手,一律呼叫 `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,接著報出失敗套件與錯誤摘要。走停手路線時,報出失敗的工具與結束碼、已經套用的套件、已經改寫的版本來源檔案,而且不進入還原步驟。三種結局都要再走完最後一步:呼叫 `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。 |
+64 -16
View File
@@ -1,22 +1,70 @@
--- ---
name: pkg-update name: pkg-update
description: Update every external package of a project to its latest stable version using list-packages.sh and latest-version.sh, then run build and tests. Revert all changes on failure. Supports nodejs, python, and dotnet projects. Use for dependency refresh; not for adding or removing packages. description: Update every external package of a project to its latest stable version using list-packages.sh and latest-version.sh, then run build and tests. Hold suspects at their old version and retry once, and revert only on a real install, build or test failure. Supports nodejs, python, and dotnet projects. Use for dependency refresh; not for adding or removing packages.
--- ---
# pkg-update — 套件批次更新 # pkg-update — batch-update packages
## 步驟 ## Exit-code routes
1. `tools/list-packages.sh {專案目錄}` 取得所有外部套件(ecosystem / name / current)。 Every tool exit code below has exactly one route. Three routes exist:
2. 確認工作樹乾淨:有未認可變更就先停止並回報(避免還原時誤傷)。
3. 逐 ecosystem 更新,此步驟**必須以 sub agent 執行**(每個 ecosystem 一個 sub agent): - **skip** — record the reason, keep going.
1. 對每個套件 `tools/latest-version.sh {ecosystem} {name}` 查最新穩定版。 - **stop** — report and end the run. Never enter step 4, so nothing is reverted and the working tree is left exactly as it stands.
2. 改寫版本來源檔(package.json / requirements.txt / pyproject.toml / *.csproj)。 - **revert** — go to step 4, which reverts the working tree.
3. 重新解析安裝(`npm install` / `pip install -r` / `dotnet restore`)。
4. 執行建置與測試: **What a stop leaves behind.** Step 1 writes nothing, so a stop there leaves every file untouched. From step 2 on, a stop can land after `apply-version.sh` has already rewritten one or more version source files, and the stop route still reverts nothing: a broken toolchain is not a broken package, and step 1 already proved the tree was clean, so those rewrites are recoverable by hand. Every stop from step 2 on therefore reports three things — the failing tool with its exit code, every package already applied, and every version source file already rewritten. Report "no file was changed" only for a stop that happened before step 2 started.
- nodejs:`npm run build`(如有)+ `npm test`(如有)。
- python:`pytest`(如有測試)。 Only a real install, build or test failure takes the revert route. A missing input, a bad argument, an unknown ecosystem or a missing command is a broken call or a broken toolchain, so it takes the stop route.
- dotnet:`dotnet build` + `dotnet test`。
- 推斷不出指令時依 `jsc-ask:ask` 規則詢問使用者。 One number carries one category across all six tools, and the table below names the object that category points at for each tool. Exit 6 is always a missing project directory and always stops the run — never fold it into the exit 3 skip, or a bad path hides as "nothing to do".
5. 建置或測試失敗 → 還原全部變更(`git checkout -- .` 與清除未追蹤的 lock 變更),回報失敗套件與錯誤摘要。
6. 成功 → 回報更新清單(套件 / 舊版 / 新版),交由 `jsc-git:commit` 認可。 | Tool | 0 | 1 (bad argument count) | 2 (unknown input) | 3 (no source file) | 4 | 5 | 6 (project dir not found) | other |
| --- | --- | --- | --- | --- | --- | --- | --- | --- |
| `git-guard.sh` | continue | stop (also `revert` without `--confirm-destructive`) | stop (unknown subcommand) | — | stop (git missing) | stop (not a git work tree, tree not clean, or still dirty after a revert) | stop | stop |
| `list-packages.sh` | continue | stop | — | — | stop (python3 missing) | — | stop | stop |
| `latest-version.sh` | continue | stop | stop (unknown ecosystem) | — | skip (not in registry) | stop (curl or python3 missing) | — (takes no project dir) | stop |
| `apply-version.sh` | continue | stop | stop (unknown ecosystem) | skip | skip (package absent from the file) | stop (python3 missing) | stop | stop |
| `install-deps.sh` | continue | stop | stop (unknown ecosystem) | skip | stop (npm, pip or dotnet missing) | — | stop | **revert** |
| `build-test.sh` | continue | stop | stop (unknown ecosystem) | — (uses 5 instead) | stop (npm, python3, pytest or dotnet missing) | ask the user | stop | **revert** |
Exit 4 and exit 5 are the two codes whose meaning depends on the tool, so read them off this table rather than from memory.
`build-test.sh --detect` returns the same codes with the same routes. It writes nothing, so every stop route it takes leaves the working tree untouched.
## Steps
1. **Preflight: gate on a clean tree, then take inventory and prove a build and test command exists.** Nothing in this step writes a file. Run the three substeps in this order — each one only earns its cost once the one before it passed:
1. **The gate.** Run `tools/git-guard.sh check {project dir}` first, before any other tool. It proves the directory is a git work tree and that `git status --porcelain` prints zero bytes, untracked `??` lines included. Step 4 runs `git clean -fd` through the same script, which deletes untracked files with no reflog and no way back, so a clean tree is the precondition for the whole run. A non-zero exit ends the run here: report the guard's stderr verbatim, including every `??` line, and run no further tool. Order the gate first on purpose — a dirty tree then costs one guard call instead of a full package scan that gets thrown away.
2. **Inventory.** The gate exited 0 → run `tools/list-packages.sh {project dir}` for every external package (ecosystem, name, current version). Route the exit code per the table above. Zero rows → stop and report the project directory you scanned plus the supported ecosystems (nodejs / python / dotnet). Keep every row's current version: step 3 pins packages back to it.
3. **Build and test commands.** For every distinct ecosystem the inventory listed, run `tools/build-test.sh --detect {ecosystem} {project dir}`. This mode only detects, so the working tree stays untouched here too.
- 0 → the commands are inferable. Record them against this ecosystem.
- 5 → no build or test command could be inferred. Ask the user for the build command and the test command per the `jsc-ask:ask` rules, and record both against this ecosystem. A user who cannot name them takes the stop route right here, where no file has been touched and there is nothing to revert.
- 1, 2, 4 or 6 → stop route. Report the code together with the bad argument, unknown ecosystem, missing command or missing directory.
Asking this question here is the whole point: found out in step 3 instead, it wastes every rewrite and forces a revert.
4. Step 1 is done only when all three hold: `git-guard.sh check` exited 0, the inventory holds at least one row with an ecosystem, a name and a current version, and every ecosystem in that inventory carries either a detected command set or a user-supplied build command plus test command, written down. Report all three results, then enter step 2.
2. Update ecosystem by ecosystem. This step **MUST run as a sub agent — one sub agent per ecosystem, and the ecosystems run in parallel**, because no ecosystem touches another's version source files:
1. Query first, write second. Run `tools/latest-version.sh {ecosystem} {name}` for **every package of the ecosystem as one concurrent batch**; the queries are independent registry reads. Then run `tools/apply-version.sh {ecosystem} {project dir} {name} {version}` **one package at a time, sequentially**: several packages of one ecosystem share one version source file (package.json / requirements.txt / pyproject.toml / *.csproj), and concurrent rewrites of one file lose edits. This substep is done when every package of the ecosystem carries either an applied version or a skip reason from substep 2.
2. Route every exit code per the table above. The skip route covers `latest-version.sh` exit 4 (package not found in the registry), `apply-version.sh` exit 3 (no version source file) and `apply-version.sh` exit 4 (package absent from the file): record the package and the reason, move on to the next package, and never abort the whole ecosystem. The stop route covers exit 1, exit 2, `latest-version.sh` or `apply-version.sh` exit 5 (a required command is missing) and `apply-version.sh` exit 6 (project directory not found): report the code, list every package already applied and every file already rewritten, and end the run without entering step 4. This substep is done when every exit code seen has taken exactly one route.
3. Run `tools/install-deps.sh {ecosystem} {project dir}` once, after every package of that ecosystem is applied. Exit 0 means the install finished. Exit 3 means this ecosystem has no dependency source file, which is the same documented skip as substep 2: record the ecosystem and the reason, and install nothing. Exit 1, exit 2, exit 4 and exit 6 take the stop route — exit 6 is a missing project directory, so report it instead of skipping it. Only another non-zero code is a real install failure: hand that ecosystem to step 3's hold-and-retry route at substep 3.2. This substep is done when the exit code is recorded together with the route it took.
4. The sub agent returns one row per package from substep 1: name, old version, new version — or `skipped` plus the reason. It also returns the `install-deps.sh` exit code and its route. The ecosystem is done only when every package appears in exactly one row.
5. **Comments this step writes.** Coding around an incompatibility needs a comment in the version source file or in the code, and so does every hold written in step 3. Write **why** the workaround or the hold exists. The one this skill trips over most sits on the allow list, not the ban list: **a third-party package's issue link belongs in the comment** — it is what states the cause and the condition for removing the workaround, as in `// works around github.com/foo/bar/issues/88; drop this hold once that ships`. The ban list itself has one home, `jsc-review/references/comment-scope.md`, and `jsc-hooks/hooks/comment-scope.sh` enforces it in code, so do not re-audit it line by line here. Know when that enforcement actually fires: **only claude scans per file at write time** (PostToolUse). codex scans when a turn ends, kiro only when the next prompt is submitted, copilot and antigravity only through a session-level wrapper — so on those four the `comment-scope.sh sweep` that `jsc-git:commit` runs in step 5 is the only pass that lands before the comment reaches a commit. Fix any warning that does arrive on the spot. This substep is done when every comment this step wrote names the reason for its workaround or hold.
3. **Build and test, and on a real failure hold the culprits and retry once.** Holding is the only route that leaves a package on an older version, and it runs at most one extra build and test round per failing ecosystem:
1. Run the build and test for every ecosystem, using what step 1 recorded: `tools/build-test.sh {ecosystem} {project dir}` where substep 1.3 detected the commands, or the user's build command followed by the user's test command where substep 1.3 collected them. Route on the exit code:
- 0 → this ecosystem passed.
- 1, 2, 4 or 6 → stop route. Report the code and the missing argument, ecosystem, command or directory, and enter no further step. Exit 4 means the toolchain is broken, not that the packages are broken, so reverting would destroy files for nothing.
- 5 → substep 1.3 already proved a command existed, so this code means the project changed underneath the run. Stop route: report the contradiction against what substep 1.3 recorded, and do not ask the same question twice.
- any other code → a real build or test failure. Go to substep 2. An ecosystem handed over by substep 2.3 for a real `install-deps.sh` failure enters at substep 2 as well.
2. Name the suspects: every package this run updated whose name appears in the install, build or test failure output. No named suspect → go straight to step 4.
3. Run `tools/apply-version.sh {ecosystem} {project dir} {name} {old version}` for each suspect, sequentially, with the old version substep 1.2 recorded. Route per the table, with one change: `apply-version.sh` exit 3 and exit 4 mean the hold cannot be written at all, so that ecosystem goes to step 4 instead of skipping the package.
4. Write the comment for every hold per substep 2.5 — the reason for the hold and, where a third-party issue drives it, that issue's link.
5. Run `tools/install-deps.sh {ecosystem} {project dir}` once, then this ecosystem's build and test command once, routing both per substep 2.3 and substep 3.1. Retry exactly once: exit 0 → this ecosystem passed with those packages held, and step 5 reports them as held rather than updated. Any non-zero on the retry → step 4.
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.
+149
View File
@@ -0,0 +1,149 @@
#!/usr/bin/env sh
# apply-version.sh — 改寫版本來源檔案,把套件釘選到指定版本。
# 用法:apply-version.sh <ecosystem> <project-dir> <name> <version>
# ecosystem:nodejs | python | dotnet
# 對應檔案(與 list-packages.sh 相同的解析邏輯):
# nodejs → package.json(dependencies / devDependencies)
# python → requirements.txt(優先)或 pyproject.toml 的 [project] dependencies
# dotnet → *.csproj 的 PackageReference
# 結束碼(慣例見 README「工具」章的結束碼總表):
# 0 改寫成功
# 1 參數個數不對
# 2 ecosystem 不認識
# 3 找不到對應的版本來源檔案——呼叫方跳過這個套件
# 4 檔案中找不到該套件——呼叫方跳過這個套件
# 5 需要的指令不存在(python3)
# 6 找不到專案目錄——呼叫方停手,不要當成跳過
set -u
if [ "$#" -ne 4 ]; then
echo "用法:apply-version.sh <ecosystem> <project-dir> <name> <version>" >&2
exit 1
fi
eco="$1"
DIR="$2"
name="$3"
version="$4"
# 先擋找不到專案目錄。少了這道,壞路徑會被報成「沒有版本來源檔案」,然後被當成跳過藏起來。
[ -d "$DIR" ] || { echo "project dir not found: $DIR" >&2; exit 6; }
# 三種來源檔案都靠 python3 改寫。少了 python3 會被誤判成檔案中找不到該套件,所以先擋。
command -v python3 >/dev/null 2>&1 || { echo "python3 not found" >&2; exit 5; }
case "$eco" in
nodejs)
[ -f "$DIR/package.json" ] || { echo "package.json not found in $DIR" >&2; exit 3; }
python3 - "$DIR/package.json" "$name" "$version" <<'EOF'
import json,sys
path,name,version=sys.argv[1],sys.argv[2],sys.argv[3]
d=json.load(open(path,encoding="utf-8"))
found=False
for k in ("dependencies","devDependencies"):
if name in (d.get(k) or {}):
d[k][name]=version
found=True
if not found:
sys.exit(4)
with open(path,"w",encoding="utf-8") as f:
json.dump(d,f,indent=2,ensure_ascii=False)
f.write("\n")
EOF
rc=$?
[ "$rc" -eq 0 ] || { echo "$name not found in $DIR/package.json" >&2; exit "$rc"; }
;;
python)
if [ -f "$DIR/requirements.txt" ]; then
python3 - "$DIR/requirements.txt" "$name" "$version" <<'EOF'
import re,sys
path,name,version=sys.argv[1],sys.argv[2],sys.argv[3]
pat=re.compile(r"([A-Za-z0-9_.\-\[\]]+)\s*(?:(?:==|>=|<=|~=|!=|>|<)\s*([^,;\s]+))?")
lname=name.split("[")[0].lower()
lines=open(path,encoding="utf-8").readlines()
out=[]
found=False
for line in lines:
body=line.split("#")[0].strip()
m=pat.match(body)
if m and m.group(1).split("[")[0].lower()==lname:
out.append(f"{m.group(1)}=={version}\n")
found=True
else:
out.append(line)
if not found:
sys.exit(4)
open(path,"w",encoding="utf-8").writelines(out)
EOF
rc=$?
[ "$rc" -eq 0 ] || { echo "$name not found in $DIR/requirements.txt" >&2; exit "$rc"; }
elif [ -f "$DIR/pyproject.toml" ]; then
python3 - "$DIR/pyproject.toml" "$name" "$version" <<'EOF'
import re,sys
path,name,version=sys.argv[1],sys.argv[2],sys.argv[3]
pat=re.compile(r"([A-Za-z0-9_.\-\[\]]+)\s*(?:(?:==|>=|<=|~=|!=|>|<)\s*([^,;\s\"']+))?")
lname=name.split("[")[0].lower()
lines=open(path,encoding="utf-8").readlines()
out=[]
found=False
for line in lines:
m=re.search(r'"([^"]+)"', line)
if m:
dep=m.group(1)
dm=pat.match(dep)
if dm and dm.group(1).split("[")[0].lower()==lname:
newdep=f"{dm.group(1)}=={version}"
line=line.replace(dep,newdep,1)
found=True
out.append(line)
if not found:
sys.exit(4)
open(path,"w",encoding="utf-8").writelines(out)
EOF
rc=$?
[ "$rc" -eq 0 ] || { echo "$name not found in $DIR/pyproject.toml" >&2; exit "$rc"; }
else
echo "requirements.txt or pyproject.toml not found in $DIR" >&2
exit 3
fi
;;
dotnet)
projs=$(find "$DIR" -maxdepth 3 -name '*.csproj' 2>/dev/null)
[ -n "$projs" ] || { echo "*.csproj not found in $DIR" >&2; exit 3; }
found=0
for proj in $projs; do
python3 - "$proj" "$name" "$version" <<'EOF'
import re,sys
path,name,version=sys.argv[1],sys.argv[2],sys.argv[3]
text=open(path,encoding="utf-8").read()
hit=[False]
def repl(m):
tag=m.group(0)
inc=re.search(r'Include="([^"]+)"',tag)
if inc and inc.group(1)==name:
hit[0]=True
if re.search(r'Version="[^"]*"',tag):
tag=re.sub(r'Version="[^"]*"', 'Version="%s"'%version, tag)
elif tag.endswith("/>"):
tag=tag[:-2]+' Version="%s" />'%version
else:
tag=tag[:-1]+' Version="%s">'%version
return tag
new_text=re.sub(r"<PackageReference\b[^>]*>", repl, text)
if not hit[0]:
sys.exit(4)
open(path,"w",encoding="utf-8").write(new_text)
EOF
if [ $? -eq 0 ]; then
found=1
break
fi
done
[ "$found" -eq 1 ] || { echo "PackageReference $name not found under $DIR" >&2; exit 4; }
;;
*) echo "unknown ecosystem: $eco" >&2; exit 2 ;;
esac
exit 0
+120
View File
@@ -0,0 +1,120 @@
#!/usr/bin/env sh
# build-test.sh — 偵測並執行專案的建置與測試,先建置再測試。
# 用法:
# build-test.sh <ecosystem> <project-dir> 偵測後實際執行
# build-test.sh --detect <ecosystem> <project-dir> 只偵測,唯讀,不執行任何建置或測試
# ecosystem:nodejs | python | dotnet
# 偵測方式:
# nodejs → package.json 的 scripts.build 與 scripts.test(npm init 產生的佔位 test 視為沒有)
# python → pytest 設定(pytest.ini、setup.cfg、tox.ini、pyproject.toml)或 tests 目錄與 test_*.py
# dotnet → *.sln 或 *.csproj,建置與測試都用 dotnet
# 為什麼要有 --detect:推不出指令(5)本來要等所有版本都改寫完才發現,使用者又答不出指令時,
# 前面全數作廢還要走還原。--detect 讓呼叫方在動任何檔案之前先問出結果,工作區完全沒被動過。
# --detect 只做偵測與前置檢查,一個檔案都不寫。
# 輸出:底層指令的原始輸出;--detect 模式輸出偵測到的指令名稱,一行一個(nodejs 是 build、test,
# python 是 pytest,dotnet 是 dotnet build、dotnet test)。
# 結束碼(慣例見 README「工具」章的結束碼總表):
# 0 建置與測試都通過;--detect 模式是推得出指令
# 1 參數個數不對
# 2 ecosystem 不認識
# 4 需要的指令不存在(npm、python3、pytest、dotnet)
# 5 推不出任何建置或測試指令——呼叫方要改問使用者
# 6 找不到專案目錄——呼叫方停手
# 其他 建置或測試失敗,帶回失敗指令的結束碼;--detect 模式不會回這一類
# 本工具不用 3:3 保留給「該 ecosystem 沒有來源檔案」,這支改用 5 表達推不出指令。
set -u
DETECT=0
if [ "${1:-}" = "--detect" ]; then
DETECT=1
shift
fi
if [ "$#" -ne 2 ]; then
echo "用法:build-test.sh [--detect] <ecosystem> <project-dir>" >&2
exit 1
fi
eco="$1"
DIR="$2"
NO_COMMAND=5
[ -d "$DIR" ] || { echo "project dir not found: $DIR" >&2; exit 6; }
case "$eco" in
nodejs)
[ -f "$DIR/package.json" ] || { echo "no build or test command inferred for $DIR" >&2; exit "$NO_COMMAND"; }
# package.json 靠 python3 解析。少了 python3 會解出空字串,被誤判成推不出指令,所以先擋。
command -v python3 >/dev/null 2>&1 || { echo "python3 not found" >&2; exit 4; }
scripts=$(python3 - "$DIR/package.json" <<'EOF'
import json,sys
try:
d=json.load(open(sys.argv[1],encoding="utf-8"))
except Exception:
sys.exit(0)
s=(d.get("scripts") or {})
if s.get("build"): print("build")
t=s.get("test") or ""
if t and "no test specified" not in t: print("test")
EOF
)
[ -n "$scripts" ] || { echo "no build or test command inferred for $DIR" >&2; exit "$NO_COMMAND"; }
command -v npm >/dev/null 2>&1 || { echo "npm not found" >&2; exit 4; }
if [ "$DETECT" -eq 1 ]; then
printf '%s\n' "$scripts"
exit 0
fi
for s in $scripts; do
( cd "$DIR" && npm run --if-present "$s" ) || exit $?
done
exit 0
;;
python)
has_tests=0
for f in pytest.ini setup.cfg tox.ini pyproject.toml; do
if [ -f "$DIR/$f" ] && grep -q 'pytest' "$DIR/$f" 2>/dev/null; then has_tests=1; fi
done
[ -d "$DIR/tests" ] && has_tests=1
if [ "$has_tests" -eq 0 ]; then
found=$(find "$DIR" -maxdepth 3 -name 'test_*.py' 2>/dev/null | head -n 1)
[ -n "$found" ] && has_tests=1
fi
[ "$has_tests" -eq 1 ] || { echo "no build or test command inferred for $DIR" >&2; exit "$NO_COMMAND"; }
if command -v pytest >/dev/null 2>&1; then
if [ "$DETECT" -eq 1 ]; then
echo "pytest"
exit 0
fi
( cd "$DIR" && pytest )
exit $?
elif command -v python3 >/dev/null 2>&1 && python3 -m pytest --version >/dev/null 2>&1; then
if [ "$DETECT" -eq 1 ]; then
echo "python3 -m pytest"
exit 0
fi
( cd "$DIR" && python3 -m pytest )
exit $?
else
echo "pytest not found" >&2
exit 4
fi
;;
dotnet)
projs=$(find "$DIR" -maxdepth 3 \( -name '*.sln' -o -name '*.csproj' \) 2>/dev/null)
[ -n "$projs" ] || { echo "no build or test command inferred for $DIR" >&2; exit "$NO_COMMAND"; }
command -v dotnet >/dev/null 2>&1 || { echo "dotnet not found" >&2; exit 4; }
if [ "$DETECT" -eq 1 ]; then
echo "dotnet build"
echo "dotnet test"
exit 0
fi
( cd "$DIR" && dotnet build ) || exit $?
( cd "$DIR" && dotnet test ) || exit $?
exit 0
;;
*) echo "unknown ecosystem: $eco" >&2; exit 2 ;;
esac
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env sh
# git-guard.sh — 還原路線的前提把關與還原本身,兩個子指令一支腳本。
# 用法:
# git-guard.sh check <project-dir>
# 確認目標是 git 工作樹,而且工作區乾淨。任何一步改檔案之前先跑這支。
# git-guard.sh revert <project-dir> --confirm-destructive
# 還原工作區:git checkout -- . 之後 git clean -fd,最後複驗乾淨。
#
# 為什麼還原要多一個 --confirm-destructive:
# revert 會跑 git clean -fd,未追蹤檔案刪掉就沒有 reflog 可救。把「確實是 git 工作樹」與
# 「呼叫端真的要還原」兩個前提寫進腳本,誤觸就退回 exit 1 或 exit 5,工作區一個位元組都不動。
# 前提逐條擋在動作之前,順序是:目錄存在 → git 指令存在 → 確實是 git 工作樹(不是裸存取庫)
# → 旗標給了 → 目標不是檔案系統根目錄。五條全過才會執行第一個破壞性指令。
#
# 範圍:check 的 status --porcelain 是整個存取庫(比較嚴,髒在別的目錄也擋得住);
# revert 的 checkout 與 clean 只作用在 <project-dir> 以下(比較窄,不會波及上層目錄)。
# clean 用 -fd 不用 -fdx:被 gitignore 的 node_modules、__pycache__、bin、obj 要留著。
#
# 輸出:
# 成功 → stdout 最後一行是 `check ok <project-dir>` 或 `revert ok <project-dir>`;
# revert 會先帶出 git clean 自己的 Removing 清單,那是刪掉哪些檔案的紀錄,要留給呼叫方回報。
# 前提不成立 → stderr 第一行是原因(not a git repository、working tree not clean、
# still dirty after revert),工作區不乾淨時後面接 git status --porcelain 的原始輸出。
#
# 結束碼(慣例見 README「工具」章的結束碼總表):
# 0 check:是 git 工作樹而且乾淨/revert:還原完成而且乾淨
# 1 參數個數不對,或 revert 少了 --confirm-destructive——本工具不動任何檔案
# 2 子指令不認識(只收 check、revert)
# 4 需要的指令不存在(git)
# 5 前提不成立。check 是「不是 git 工作樹」或「工作區不乾淨」;revert 是「不是 git 工作樹」
# 或「還原後仍不乾淨」。呼叫方停手,stderr 第一行講明是哪一種
# 6 找不到專案目錄——呼叫方停手
# 其他 底層 git 指令的結束碼
set -u
usage() {
echo "用法:git-guard.sh check <project-dir>" >&2
echo " git-guard.sh revert <project-dir> --confirm-destructive" >&2
}
[ "$#" -ge 1 ] || { usage; exit 1; }
cmd="$1"
case "$cmd" in
check) [ "$#" -eq 2 ] || { usage; exit 1; } ;;
revert) [ "$#" -eq 3 ] || { usage; exit 1; } ;;
*) echo "unknown subcommand: $cmd" >&2; usage; exit 2 ;;
esac
DIR="$2"
[ -d "$DIR" ] || { echo "project dir not found: $DIR" >&2; exit 6; }
command -v git >/dev/null 2>&1 || { echo "git not found" >&2; exit 4; }
# 確實是 git 工作樹才有還原路線。裸存取庫沒有工作樹,checkout 與 clean 都無從跑起。
inside=$(git -C "$DIR" rev-parse --is-inside-work-tree 2>/dev/null || true)
[ "$inside" = "true" ] || { echo "not a git repository: $DIR" >&2; exit 5; }
if [ "$cmd" = "check" ]; then
dirty=$(git -C "$DIR" status --porcelain 2>/dev/null)
rc=$?
[ "$rc" -eq 0 ] || { echo "git status failed: $DIR" >&2; exit "$rc"; }
if [ -n "$dirty" ]; then
# 未追蹤的 ?? 行也算髒:還原會跑 git clean -fd,那些檔案刪掉沒有還原路徑。
echo "working tree not clean: $DIR" >&2
printf '%s\n' "$dirty" >&2
exit 5
fi
echo "check ok $DIR"
exit 0
fi
# 以下只有 revert 走得到。破壞性指令的最後兩道前提。
[ "$3" = "--confirm-destructive" ] || {
echo "revert 會執行 git clean -fd,必須明確傳入 --confirm-destructive" >&2
usage
exit 1
}
# 目標是檔案系統根目錄時直接拒絕:路徑組錯的時候,這是唯一擋得住的地方。
abs=$(CDPATH= cd -- "$DIR" 2>/dev/null && pwd) || { echo "project dir not found: $DIR" >&2; exit 6; }
[ "$abs" != "/" ] || { echo "refusing to revert the filesystem root" >&2; exit 5; }
git -C "$DIR" checkout -- . || exit $?
git -C "$DIR" clean -fd || exit $?
left=$(git -C "$DIR" status --porcelain 2>/dev/null)
rc=$?
[ "$rc" -eq 0 ] || { echo "git status failed: $DIR" >&2; exit "$rc"; }
if [ -n "$left" ]; then
echo "still dirty after revert: $DIR" >&2
printf '%s\n' "$left" >&2
exit 5
fi
echo "revert ok $DIR"
exit 0
+73
View File
@@ -0,0 +1,73 @@
#!/usr/bin/env sh
# install-deps.sh — 重新解析並安裝專案的相依套件。
# 用法:install-deps.sh <ecosystem> <project-dir>
# ecosystem:nodejs | python | dotnet
# 對應動作:
# nodejs → npm install
# python → pip install -r requirements.txt(優先)或 pip install -e .(pyproject.toml)
# dotnet → dotnet restore
# 輸出:底層指令的原始輸出。
# 檢查順序:先確認相依來源檔案存在,才檢查指令存在。順序顛倒會把「沒有來源檔案」報成「指令不存在」。
# 結束碼(慣例見 README「工具」章的結束碼總表):
# 0 安裝成功
# 1 參數個數不對
# 2 ecosystem 不認識
# 3 該 ecosystem 沒有相依來源檔案——呼叫方跳過這個 ecosystem
# 4 需要的指令不存在(npm、pip、dotnet)
# 6 找不到專案目錄——呼叫方停手,不要當成跳過
# 其他 底層指令的結束碼,代表安裝失敗
set -u
if [ "$#" -ne 2 ]; then
echo "用法:install-deps.sh <ecosystem> <project-dir>" >&2
exit 1
fi
eco="$1"
DIR="$2"
[ -d "$DIR" ] || { echo "project dir not found: $DIR" >&2; exit 6; }
case "$eco" in
nodejs)
[ -f "$DIR/package.json" ] || { echo "package.json not found in $DIR" >&2; exit 3; }
command -v npm >/dev/null 2>&1 || { echo "npm not found" >&2; exit 4; }
( cd "$DIR" && npm install )
exit $?
;;
python)
if [ -f "$DIR/requirements.txt" ]; then
target="-r requirements.txt"
elif [ -f "$DIR/pyproject.toml" ]; then
target="-e ."
else
echo "requirements.txt or pyproject.toml not found in $DIR" >&2
exit 3
fi
pip=""
for c in pip3 pip; do
if command -v "$c" >/dev/null 2>&1; then pip="$c"; break; fi
done
if [ -z "$pip" ]; then
if command -v python3 >/dev/null 2>&1 && python3 -m pip --version >/dev/null 2>&1; then
pip="python3 -m pip"
else
echo "pip not found" >&2
exit 4
fi
fi
( cd "$DIR" && $pip install $target )
exit $?
;;
dotnet)
projs=$(find "$DIR" -maxdepth 3 \( -name '*.csproj' -o -name '*.sln' \) 2>/dev/null)
[ -n "$projs" ] || { echo "*.csproj or *.sln not found in $DIR" >&2; exit 3; }
command -v dotnet >/dev/null 2>&1 || { echo "dotnet not found" >&2; exit 4; }
( cd "$DIR" && dotnet restore )
exit $?
;;
*) echo "unknown ecosystem: $eco" >&2; exit 2 ;;
esac
+23 -5
View File
@@ -1,11 +1,29 @@
#!/usr/bin/env sh #!/usr/bin/env sh
# latest-version.sh — 查詢套件最新且穩定的版本號。 # latest-version.sh — 查詢套件最新且穩定的版本號。
# 用法: latest-version.sh <ecosystem> <name> # 用法:latest-version.sh <ecosystem> <name>
# ecosystem: nodejs | python | dotnet # ecosystem:nodejs | python | dotnet
# 輸出: 版本號一行;查不到 exit 4 # 輸出:版本號一行。
# 結束碼(慣例見 README「工具」章的結束碼總表):
# 0 查到版本號
# 1 參數個數不對
# 2 ecosystem 不認識
# 4 登錄站上查不到這個套件——呼叫方跳過這個套件
# 5 需要的指令不存在(curl、python3)
# 本工具不收專案目錄,所以沒有 6。
set -u set -u
eco="${1:?ecosystem required (nodejs|python|dotnet)}"
name="${2:?package name required}" if [ "$#" -ne 2 ]; then
echo "用法:latest-version.sh <ecosystem> <name>" >&2
exit 1
fi
eco="$1"
name="$2"
# 回應靠 curl 取得、靠 python3 解析。少了任一支會解出空字串,被誤判成查不到套件,所以先擋。
for c in curl python3; do
command -v "$c" >/dev/null 2>&1 || { echo "$c not found" >&2; exit 5; }
done
case "$eco" in case "$eco" in
nodejs) nodejs)
+20 -3
View File
@@ -1,11 +1,28 @@
#!/usr/bin/env sh #!/usr/bin/env sh
# list-packages.sh — 列出專案的所有外部套件與目前版本。 # list-packages.sh — 列出專案的所有外部套件與目前版本。
# 用法: list-packages.sh [專案目錄](預設目前目錄) # 用法:list-packages.sh [專案目錄](預設目前目錄)
# 輸出(TSV): ecosystem<TAB>name<TAB>current # 輸出(TSV):ecosystem<TAB>name<TAB>current
# 支援: nodejs(package.json)、python(requirements.txt / pyproject.toml)、dotnet(*.csproj) # 支援:nodejs(package.json)、python(requirements.txt / pyproject.toml)、dotnet(*.csproj)
# 結束碼(慣例見 README「工具」章的結束碼總表):
# 0 掃描完成(沒有套件時輸出零行)
# 1 參數個數不對
# 4 需要的指令不存在(python3)
# 6 找不到專案目錄——呼叫方停手
set -u set -u
if [ "$#" -gt 1 ]; then
echo "用法:list-packages.sh [專案目錄]" >&2
exit 1
fi
DIR="${1:-.}" DIR="${1:-.}"
# 先擋找不到專案目錄。少了這道,壞路徑會輸出零行,被誤判成專案沒有套件。
[ -d "$DIR" ] || { echo "project dir not found: $DIR" >&2; exit 6; }
# 四種來源檔案都靠 python3 解析。少了 python3 會輸出零行,被誤判成專案沒有套件,所以先擋。
command -v python3 >/dev/null 2>&1 || { echo "python3 not found" >&2; exit 4; }
# nodejs: package.json 的 dependencies / devDependencies # nodejs: package.json 的 dependencies / devDependencies
if [ -f "$DIR/package.json" ]; then if [ -f "$DIR/package.json" ]; then
python3 - "$DIR/package.json" <<'EOF' python3 - "$DIR/package.json" <<'EOF'