Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82e3233b4c | ||
|
|
156f2be2d9 | ||
|
|
67fa5c5f57 | ||
|
|
da31b890ed | ||
|
|
b3d2c5dc6d | ||
|
|
0dccd19061 | ||
|
|
49fef7069e | ||
|
|
756d1199d2 | ||
|
|
c9e1f52709 | ||
|
|
b76f1ab80f | ||
|
|
ab5494a128 | ||
|
|
1b223bd350 | ||
|
|
338c2da89c | ||
|
|
928f7ca808 | ||
|
|
bdacce2f8f | ||
|
|
db2605f36c | ||
|
|
88e67e5ebc | ||
|
|
9c33aae25f | ||
|
|
ce7d6d48a5 | ||
|
|
913a51796d | ||
|
|
992d9c759c | ||
|
|
4bf662ce0b | ||
|
|
35d4de6522 | ||
|
|
bb2e6821c1 | ||
|
|
ea11ca0590 | ||
|
|
85b89e4cdc | ||
|
|
f1fa06ac32 | ||
|
|
bf50821925 | ||
|
|
3724952cda | ||
|
|
ae287e054c | ||
|
|
ef91ebbff4 | ||
|
|
5ae3611960 | ||
|
|
3e3e1e588f | ||
|
|
d72db8a607 | ||
|
|
7b446fa28e | ||
|
|
6f263ef355 | ||
|
|
a35e122cfe | ||
|
|
3ce5ce989b |
@@ -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 追蹤)"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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 追蹤)"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "jsc-pkg",
|
||||
"version": "0.0.1",
|
||||
"description": "套件批次更新(nodejs/python/dotnet),失敗還原",
|
||||
"version": "0.0.9",
|
||||
"description": "套件批次更新(nodejs/python/dotnet),先把嫌疑套件釘回舊版重試一次,真失敗才還原",
|
||||
"skills": "./skills",
|
||||
"author": {
|
||||
"name": "JSC"
|
||||
@@ -13,5 +13,13 @@
|
||||
"pkg",
|
||||
"skills",
|
||||
"cross-tool"
|
||||
]
|
||||
],
|
||||
"jsc": {
|
||||
"requires": {
|
||||
"jsc-ask": ">=0.0.6",
|
||||
"jsc-git": ">=0.0.9",
|
||||
"jsc-hooks": ">=0.2.8",
|
||||
"jsc-review": ">=0.0.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
{
|
||||
"name": "jsc-pkg",
|
||||
"version": "0.0.1",
|
||||
"description": "套件批次更新(nodejs/python/dotnet),失敗還原",
|
||||
"skills": "./skills"
|
||||
"version": "0.0.9",
|
||||
"description": "套件批次更新(nodejs/python/dotnet),先把嫌疑套件釘回舊版重試一次,真失敗才還原",
|
||||
"skills": "./skills",
|
||||
"jsc": {
|
||||
"requires": {
|
||||
"jsc-ask": ">=0.0.6",
|
||||
"jsc-git": ">=0.0.9",
|
||||
"jsc-hooks": ">=0.2.8",
|
||||
"jsc-review": ">=0.0.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
## 規則
|
||||
|
||||
1. 所有交談與輸出內容基於 STE100 使用繁體中文:短句、一句一指令、主動語態、術語一致、UTF-8 無亂碼。
|
||||
1. 所有交談與輸出內容使用 STE100 繁體中文,帶擬人台灣感:短句、一句一指令、台灣用語、全形標點、去 AI 味、直接講重點。完整規則的唯一來源:`plugins/meta` 的 `references/ste100.md`。
|
||||
2. 技能位於 `skills/{name}/SKILL.md`;處理任務前先比對需求與各技能的 `description`,相符就載入並依其步驟執行。
|
||||
3. 技能準則的唯一來源:`plugins/meta` 存取庫的 `references/guidelines.md`。
|
||||
4. 所有 hook 只放在 `jsc-hooks`;gitea 操作一律經由 `jsc-gitea` 的 `tools/gitea.sh`;問使用者一律依 `jsc-ask:ask` 的決策樹規則。
|
||||
|
||||
@@ -1,27 +1,62 @@
|
||||
# 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 | 安裝 | 更新 | 移除 |
|
||||
| --- | --- | --- | --- |
|
||||
| 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` |
|
||||
| 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` |
|
||||
| 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` |
|
||||
| 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/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/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` |
|
||||
| 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`。
|
||||
|
||||
> 舊入口 `plugins/jsc` 已移除,marketplace 正本移到 `plugins/meta`。marketplace 名稱仍是 `jsc`(取自 marketplace.json 的 `name` 欄位,與存取庫名無關),安裝 token 不變;已從舊入口安裝過的人先執行 `claude plugin marketplace remove jsc`,再依上表重新 add。
|
||||
|
||||
## 工具
|
||||
|
||||
| 工具 | 用途 |
|
||||
| --- | --- |
|
||||
| `tools/list-packages.sh` | 偵測專案類型並列出所有外部套件與目前版本(TSV:ecosystem / name / current) |
|
||||
| `tools/latest-version.sh` | `latest-version.sh <ecosystem> <name>` 查最新穩定版(npm / pypi / nuget),查無 exit 4 |
|
||||
`list-packages.sh`、`latest-version.sh`、`apply-version.sh`、`build-test.sh` 靠 python3 解析或改寫檔案。少了 python3,這幾支都回報「需要的指令不存在」,不會裝成查無套件。`install-deps.sh` 不碰 python3,它把安裝交給各生態系自己的安裝器;`git-guard.sh` 只用 git。每支工具實際需要哪些指令,看下表最右欄。
|
||||
|
||||
| 工具 | 用途 | 需要的指令 |
|
||||
| --- | --- | --- |
|
||||
| `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 目錄
|
||||
|
||||
@@ -31,11 +66,13 @@ Marketplace 統一為 `jsc`(https://gitea.jsc.idv.tw/plugins/jsc.git),安
|
||||
|
||||
### `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 -->
|
||||
|
||||
## 相關 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):維護階段的建議維護方法之一
|
||||
|
||||
+11
-3
@@ -1,6 +1,14 @@
|
||||
{
|
||||
"name": "jsc-pkg",
|
||||
"version": "0.0.1",
|
||||
"description": "套件批次更新(nodejs/python/dotnet),失敗還原",
|
||||
"skills": "./skills/"
|
||||
"version": "0.0.9",
|
||||
"description": "套件批次更新(nodejs/python/dotnet),先把嫌疑套件釘回舊版重試一次,真失敗才還原",
|
||||
"skills": "./skills/",
|
||||
"jsc": {
|
||||
"requires": {
|
||||
"jsc-ask": ">=0.0.6",
|
||||
"jsc-git": ">=0.0.9",
|
||||
"jsc-hooks": ">=0.2.8",
|
||||
"jsc-review": ">=0.0.8"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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。 |
|
||||
| 外部呼叫 | 腳本 `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。 |
|
||||
+60
-16
@@ -1,22 +1,66 @@
|
||||
---
|
||||
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)。
|
||||
2. 確認工作樹乾淨:有未認可變更就先停止並回報(避免還原時誤傷)。
|
||||
3. 逐 ecosystem 更新,此步驟**必須以 sub agent 執行**(每個 ecosystem 一個 sub agent):
|
||||
1. 對每個套件 `tools/latest-version.sh {ecosystem} {name}` 查最新穩定版。
|
||||
2. 改寫版本來源檔(package.json / requirements.txt / pyproject.toml / *.csproj)。
|
||||
3. 重新解析安裝(`npm install` / `pip install -r` / `dotnet restore`)。
|
||||
4. 執行建置與測試:
|
||||
- nodejs:`npm run build`(如有)+ `npm test`(如有)。
|
||||
- python:`pytest`(如有測試)。
|
||||
- dotnet:`dotnet build` + `dotnet test`。
|
||||
- 推斷不出指令時依 `jsc-ask:ask` 規則詢問使用者。
|
||||
5. 建置或測試失敗 → 還原全部變更(`git checkout -- .` 與清除未追蹤的 lock 變更),回報失敗套件與錯誤摘要。
|
||||
6. 成功 → 回報更新清單(套件 / 舊版 / 新版),交由 `jsc-git:commit` 認可。
|
||||
Every tool exit code below has exactly one route. Three routes exist:
|
||||
|
||||
- **skip** — record the reason, keep going.
|
||||
- **stop** — report and end the run. Never enter step 4, so nothing is reverted and the working tree is left exactly as it stands.
|
||||
- **revert** — go to step 4, which reverts the working tree.
|
||||
|
||||
**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.
|
||||
|
||||
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.
|
||||
|
||||
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".
|
||||
|
||||
| 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.
|
||||
|
||||
Executable
+149
@@ -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
|
||||
Executable
+120
@@ -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
|
||||
Executable
+97
@@ -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
|
||||
Executable
+73
@@ -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
@@ -1,11 +1,29 @@
|
||||
#!/usr/bin/env sh
|
||||
# latest-version.sh — 查詢套件最新且穩定的版本號。
|
||||
# 用法: latest-version.sh <ecosystem> <name>
|
||||
# ecosystem: nodejs | python | dotnet
|
||||
# 輸出: 版本號一行;查不到 exit 4
|
||||
# 用法:latest-version.sh <ecosystem> <name>
|
||||
# ecosystem:nodejs | python | dotnet
|
||||
# 輸出:版本號一行。
|
||||
# 結束碼(慣例見 README「工具」章的結束碼總表):
|
||||
# 0 查到版本號
|
||||
# 1 參數個數不對
|
||||
# 2 ecosystem 不認識
|
||||
# 4 登錄站上查不到這個套件——呼叫方跳過這個套件
|
||||
# 5 需要的指令不存在(curl、python3)
|
||||
# 本工具不收專案目錄,所以沒有 6。
|
||||
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
|
||||
nodejs)
|
||||
|
||||
+20
-3
@@ -1,11 +1,28 @@
|
||||
#!/usr/bin/env sh
|
||||
# list-packages.sh — 列出專案的所有外部套件與目前版本。
|
||||
# 用法: list-packages.sh [專案目錄](預設目前目錄)
|
||||
# 輸出(TSV): ecosystem<TAB>name<TAB>current
|
||||
# 支援: nodejs(package.json)、python(requirements.txt / pyproject.toml)、dotnet(*.csproj)
|
||||
# 用法:list-packages.sh [專案目錄](預設目前目錄)
|
||||
# 輸出(TSV):ecosystem<TAB>name<TAB>current
|
||||
# 支援:nodejs(package.json)、python(requirements.txt / pyproject.toml)、dotnet(*.csproj)
|
||||
# 結束碼(慣例見 README「工具」章的結束碼總表):
|
||||
# 0 掃描完成(沒有套件時輸出零行)
|
||||
# 1 參數個數不對
|
||||
# 4 需要的指令不存在(python3)
|
||||
# 6 找不到專案目錄——呼叫方停手
|
||||
set -u
|
||||
|
||||
if [ "$#" -gt 1 ]; then
|
||||
echo "用法:list-packages.sh [專案目錄]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
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
|
||||
if [ -f "$DIR/package.json" ]; then
|
||||
python3 - "$DIR/package.json" <<'EOF'
|
||||
|
||||
Reference in New Issue
Block a user